diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 0d07bcf37f4d06fa5dbc795512c8f7bd44c9ea6d..e1e8c291849d63c7c0512f4aa1bdd1d6dc39eb8a 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -1713,7 +1713,6 @@ $files_list - 'Share is read-only' $freeSpace @@ -2476,18 +2475,6 @@ Resource - - - OCP\LDAP\ILDAPProvider - - - - OCP\LDAP\ILDAPProvider - - - OCP\LDAP\ILDAPProvider - - bool @@ -3213,11 +3200,6 @@ $default - - - $closure - - false @@ -3342,21 +3324,12 @@ Color - - $avatar - - - string|boolean - $finalPalette[$this->hashToInt($hash, $steps * 3)] Color - - $image->data() - @@ -5747,14 +5720,6 @@ $this->resources - - - Closure - - - Closure - - $jobList diff --git a/lib/composer/amphp/amp/LICENSE b/lib/composer/amphp/amp/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2b431ba021f8801a385a43f5511d930ce0690754 --- /dev/null +++ b/lib/composer/amphp/amp/LICENSE @@ -0,0 +1,23 @@ + +The MIT License (MIT) + +Copyright (c) 2015-2019 amphp +Copyright (c) 2016 PHP Asynchronous Interoperability Group + +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/lib/composer/amphp/amp/composer.json b/lib/composer/amphp/amp/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..624e0172b63a2420631b2b854fd5f2a0d9d13bba --- /dev/null +++ b/lib/composer/amphp/amp/composer.json @@ -0,0 +1,74 @@ +{ + "name": "amphp/amp", + "homepage": "http://amphp.org/amp", + "description": "A non-blocking concurrency framework for PHP applications.", + "keywords": [ + "async", + "asynchronous", + "concurrency", + "promise", + "awaitable", + "future", + "non-blocking", + "event", + "event-loop" + ], + "license": "MIT", + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "require": { + "php": ">=7" + }, + "require-dev": { + "ext-json": "*", + "amphp/phpunit-util": "^1", + "amphp/php-cs-fixer-config": "dev-master", + "react/promise": "^2", + "phpunit/phpunit": "^6.0.9 | ^7", + "psalm/phar": "^3.11@dev", + "jetbrains/phpstorm-stubs": "^2019.3" + }, + "autoload": { + "psr-4": { + "Amp\\": "lib" + }, + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Amp\\Test\\": "test" + } + }, + "support": { + "issues": "https://github.com/amphp/amp/issues", + "irc": "irc://irc.freenode.org/amphp" + }, + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "scripts": { + "test": "@php -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit", + "code-style": "@php ./vendor/bin/php-cs-fixer fix" + } +} diff --git a/lib/composer/amphp/amp/lib/CallableMaker.php b/lib/composer/amphp/amp/lib/CallableMaker.php new file mode 100644 index 0000000000000000000000000000000000000000..8fa8cff76e0abcfa96453b80528876b0642658c1 --- /dev/null +++ b/lib/composer/amphp/amp/lib/CallableMaker.php @@ -0,0 +1,80 @@ +getMethod($method); + } + + return self::$__reflectionMethods[$method]->getClosure($this); + } + + /** + * Creates a callable from a protected or private static method that may be invoked by methods requiring a + * publicly invokable callback. + * + * @param string $method Static method name. + * + * @return callable + * + * @psalm-suppress MixedInferredReturnType + */ + private static function callableFromStaticMethod(string $method): callable + { + if (!isset(self::$__reflectionMethods[$method])) { + if (self::$__reflectionClass === null) { + self::$__reflectionClass = new \ReflectionClass(self::class); + } + self::$__reflectionMethods[$method] = self::$__reflectionClass->getMethod($method); + } + + return self::$__reflectionMethods[$method]->getClosure(); + } + } +} else { + /** @psalm-suppress DuplicateClass */ + trait CallableMaker + { + /** + * @deprecated Use \Closure::fromCallable() instead of this method in PHP 7.1. + */ + private function callableFromInstanceMethod(string $method): callable + { + return \Closure::fromCallable([$this, $method]); + } + + /** + * @deprecated Use \Closure::fromCallable() instead of this method in PHP 7.1. + */ + private static function callableFromStaticMethod(string $method): callable + { + return \Closure::fromCallable([self::class, $method]); + } + } +} // @codeCoverageIgnoreEnd diff --git a/lib/composer/amphp/amp/lib/CancellationToken.php b/lib/composer/amphp/amp/lib/CancellationToken.php new file mode 100644 index 0000000000000000000000000000000000000000..b802994dfcea12d957569d61c54332d4e62ae14b --- /dev/null +++ b/lib/composer/amphp/amp/lib/CancellationToken.php @@ -0,0 +1,49 @@ +getToken(); + * + * $response = yield $httpClient->request("https://example.com/stream", $token); + * $responseBody = $response->getBody(); + * + * while (($chunk = yield $response->read()) !== null) { + * // consume $chunk + * + * if ($noLongerInterested) { + * $cancellationTokenSource->cancel(); + * break; + * } + * } + * ``` + * + * @see CancellationToken + * @see CancelledException + */ +final class CancellationTokenSource +{ + /** @var CancellationToken */ + private $token; + + /** @var callable|null */ + private $onCancel; + + public function __construct() + { + $onCancel = null; + + $this->token = new class($onCancel) implements CancellationToken { + /** @var string */ + private $nextId = "a"; + + /** @var callable[] */ + private $callbacks = []; + + /** @var \Throwable|null */ + private $exception; + + /** + * @param mixed $onCancel + * @param-out callable $onCancel + */ + public function __construct(&$onCancel) + { + /** @psalm-suppress MissingClosureReturnType We still support PHP 7.0 */ + $onCancel = function (\Throwable $exception) { + $this->exception = $exception; + + $callbacks = $this->callbacks; + $this->callbacks = []; + + foreach ($callbacks as $callback) { + $this->invokeCallback($callback); + } + }; + } + + /** + * @param callable $callback + * + * @return void + */ + private function invokeCallback(callable $callback) + { + // No type declaration to prevent exception outside the try! + try { + /** @var mixed $result */ + $result = $callback($this->exception); + + if ($result instanceof \Generator) { + /** @psalm-var \Generator $result */ + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + Loop::defer(static function () use ($exception) { + throw $exception; + }); + } + } + + public function subscribe(callable $callback): string + { + $id = $this->nextId++; + + if ($this->exception) { + $this->invokeCallback($callback); + } else { + $this->callbacks[$id] = $callback; + } + + return $id; + } + + public function unsubscribe(string $id) + { + unset($this->callbacks[$id]); + } + + public function isRequested(): bool + { + return isset($this->exception); + } + + public function throwIfRequested() + { + if (isset($this->exception)) { + throw $this->exception; + } + } + }; + + $this->onCancel = $onCancel; + } + + public function getToken(): CancellationToken + { + return $this->token; + } + + /** + * @param \Throwable|null $previous Exception to be used as the previous exception to CancelledException. + * + * @return void + */ + public function cancel(\Throwable $previous = null) + { + if ($this->onCancel === null) { + return; + } + + $onCancel = $this->onCancel; + $this->onCancel = null; + $onCancel(new CancelledException($previous)); + } +} diff --git a/lib/composer/amphp/amp/lib/CancelledException.php b/lib/composer/amphp/amp/lib/CancelledException.php new file mode 100644 index 0000000000000000000000000000000000000000..25f8c4002cb8377651e8350914e60e9b0cc00818 --- /dev/null +++ b/lib/composer/amphp/amp/lib/CancelledException.php @@ -0,0 +1,17 @@ +subscribe(function (CancelledException $exception) { + $this->exception = $exception; + + $callbacks = $this->callbacks; + $this->callbacks = []; + + foreach ($callbacks as $callback) { + asyncCall($callback, $this->exception); + } + }); + + $this->tokens[] = [$token, $id]; + } + } + + public function __destruct() + { + foreach ($this->tokens as list($token, $id)) { + /** @var CancellationToken $token */ + $token->unsubscribe($id); + } + } + + /** @inheritdoc */ + public function subscribe(callable $callback): string + { + $id = $this->nextId++; + + if ($this->exception) { + asyncCall($callback, $this->exception); + } else { + $this->callbacks[$id] = $callback; + } + + return $id; + } + + /** @inheritdoc */ + public function unsubscribe(string $id) + { + unset($this->callbacks[$id]); + } + + /** @inheritdoc */ + public function isRequested(): bool + { + foreach ($this->tokens as list($token)) { + if ($token->isRequested()) { + return true; + } + } + + return false; + } + + /** @inheritdoc */ + public function throwIfRequested() + { + foreach ($this->tokens as list($token)) { + $token->throwIfRequested(); + } + } +} diff --git a/lib/composer/amphp/amp/lib/Coroutine.php b/lib/composer/amphp/amp/lib/Coroutine.php new file mode 100644 index 0000000000000000000000000000000000000000..5a3b4aa848bbe992e7d591cf6bf4992b02193a13 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Coroutine.php @@ -0,0 +1,160 @@ + + */ +final class Coroutine implements Promise +{ + use Internal\Placeholder; + + /** + * Attempts to transform the non-promise yielded from the generator into a promise, otherwise returns an instance + * `Amp\Failure` failed with an instance of `Amp\InvalidYieldError`. + * + * @param mixed $yielded Non-promise yielded from generator. + * @param \Generator $generator No type for performance, we already know the type. + * + * @return Promise + */ + private static function transform($yielded, $generator): Promise + { + $exception = null; // initialize here, see https://github.com/vimeo/psalm/issues/2951 + + try { + if (\is_array($yielded)) { + return Promise\all($yielded); + } + + if ($yielded instanceof ReactPromise) { + return Promise\adapt($yielded); + } + + // No match, continue to returning Failure below. + } catch (\Throwable $exception) { + // Conversion to promise failed, fall-through to returning Failure below. + } + + return new Failure(new InvalidYieldError( + $generator, + \sprintf( + "Unexpected yield; Expected an instance of %s or %s or an array of such instances", + Promise::class, + ReactPromise::class + ), + $exception + )); + } + + /** + * @param \Generator $generator + * @psalm-param \Generator,mixed,Promise|ReactPromise|TReturn> $generator + */ + public function __construct(\Generator $generator) + { + try { + $yielded = $generator->current(); + + if (!$yielded instanceof Promise) { + if (!$generator->valid()) { + $this->resolve($generator->getReturn()); + return; + } + + $yielded = self::transform($yielded, $generator); + } + } catch (\Throwable $exception) { + $this->fail($exception); + return; + } + + /** + * @param \Throwable|null $e Exception to be thrown into the generator. + * @param mixed $v Value to be sent into the generator. + * + * @return void + * + * @psalm-suppress MissingClosureParamType + * @psalm-suppress MissingClosureReturnType + */ + $onResolve = function (\Throwable $e = null, $v) use ($generator, &$onResolve) { + /** @var bool $immediate Used to control iterative coroutine continuation. */ + static $immediate = true; + + /** @var \Throwable|null $exception Promise failure reason when executing next coroutine step, null at all other times. */ + static $exception; + + /** @var mixed $value Promise success value when executing next coroutine step, null at all other times. */ + static $value; + + $exception = $e; + /** @psalm-suppress MixedAssignment */ + $value = $v; + + if (!$immediate) { + $immediate = true; + return; + } + + try { + try { + do { + if ($exception) { + // Throw exception at current execution point. + $yielded = $generator->throw($exception); + } else { + // Send the new value and execute to next yield statement. + $yielded = $generator->send($value); + } + + if (!$yielded instanceof Promise) { + if (!$generator->valid()) { + $this->resolve($generator->getReturn()); + $onResolve = null; + return; + } + + $yielded = self::transform($yielded, $generator); + } + + $immediate = false; + $yielded->onResolve($onResolve); + } while ($immediate); + + $immediate = true; + } catch (\Throwable $exception) { + $this->fail($exception); + $onResolve = null; + } finally { + $exception = null; + $value = null; + } + } catch (\Throwable $e) { + Loop::defer(static function () use ($e) { + throw $e; + }); + } + }; + + try { + $yielded->onResolve($onResolve); + + unset($generator, $yielded, $onResolve); + } catch (\Throwable $e) { + Loop::defer(static function () use ($e) { + throw $e; + }); + } + } +} diff --git a/lib/composer/amphp/amp/lib/Deferred.php b/lib/composer/amphp/amp/lib/Deferred.php new file mode 100644 index 0000000000000000000000000000000000000000..39e47accf19ee54bb8977eadedca745783f26b4a --- /dev/null +++ b/lib/composer/amphp/amp/lib/Deferred.php @@ -0,0 +1,67 @@ + Has public resolve and fail methods. */ + private $resolver; + + /** @var Promise Hides placeholder methods */ + private $promise; + + public function __construct() + { + $this->resolver = new class implements Promise { + use Internal\Placeholder { + resolve as public; + fail as public; + } + }; + + $this->promise = new Internal\PrivatePromise($this->resolver); + } + + /** + * @return Promise + */ + public function promise(): Promise + { + return $this->promise; + } + + /** + * Fulfill the promise with the given value. + * + * @param mixed $value + * + * @psalm-param TValue|Promise $value + * + * @return void + */ + public function resolve($value = null) + { + /** @psalm-suppress UndefinedInterfaceMethod */ + $this->resolver->resolve($value); + } + + /** + * Fails the promise the the given reason. + * + * @param \Throwable $reason + * + * @return void + */ + public function fail(\Throwable $reason) + { + /** @psalm-suppress UndefinedInterfaceMethod */ + $this->resolver->fail($reason); + } +} diff --git a/lib/composer/amphp/amp/lib/Delayed.php b/lib/composer/amphp/amp/lib/Delayed.php new file mode 100644 index 0000000000000000000000000000000000000000..ec9b42027aa6668f2a73d5463478cc8ef866869f --- /dev/null +++ b/lib/composer/amphp/amp/lib/Delayed.php @@ -0,0 +1,58 @@ + + */ +final class Delayed implements Promise +{ + use Internal\Placeholder; + + /** @var string|null Event loop watcher identifier. */ + private $watcher; + + /** + * @param int $time Milliseconds before succeeding the promise. + * @param TReturn $value Succeed the promise with this value. + */ + public function __construct(int $time, $value = null) + { + $this->watcher = Loop::delay($time, function () use ($value) { + $this->watcher = null; + $this->resolve($value); + }); + } + + /** + * References the internal watcher in the event loop, keeping the loop running while this promise is pending. + * + * @return self + */ + public function reference(): self + { + if ($this->watcher !== null) { + Loop::reference($this->watcher); + } + + return $this; + } + + /** + * Unreferences the internal watcher in the event loop, allowing the loop to stop while this promise is pending if + * no other events are pending in the loop. + * + * @return self + */ + public function unreference(): self + { + if ($this->watcher !== null) { + Loop::unreference($this->watcher); + } + + return $this; + } +} diff --git a/lib/composer/amphp/amp/lib/Emitter.php b/lib/composer/amphp/amp/lib/Emitter.php new file mode 100644 index 0000000000000000000000000000000000000000..cd6b7e5430dc659c885f981d7772409bde5ee053 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Emitter.php @@ -0,0 +1,84 @@ + Has public emit, complete, and fail methods. */ + private $emitter; + + /** @var Iterator Hides producer methods. */ + private $iterator; + + public function __construct() + { + $this->emitter = new class implements Iterator { + use Internal\Producer { + emit as public; + complete as public; + fail as public; + } + }; + + $this->iterator = new Internal\PrivateIterator($this->emitter); + } + + /** + * @return Iterator + * @psalm-return Iterator + */ + public function iterate(): Iterator + { + return $this->iterator; + } + + /** + * Emits a value to the iterator. + * + * @param mixed $value + * + * @psalm-param TValue $value + * + * @return Promise + * @psalm-return Promise + * @psalm-suppress MixedInferredReturnType + * @psalm-suppress MixedReturnStatement + */ + public function emit($value): Promise + { + /** @psalm-suppress UndefinedInterfaceMethod */ + return $this->emitter->emit($value); + } + + /** + * Completes the iterator. + * + * @return void + */ + public function complete() + { + /** @psalm-suppress UndefinedInterfaceMethod */ + $this->emitter->complete(); + } + + /** + * Fails the iterator with the given reason. + * + * @param \Throwable $reason + * + * @return void + */ + public function fail(\Throwable $reason) + { + /** @psalm-suppress UndefinedInterfaceMethod */ + $this->emitter->fail($reason); + } +} diff --git a/lib/composer/amphp/amp/lib/Failure.php b/lib/composer/amphp/amp/lib/Failure.php new file mode 100644 index 0000000000000000000000000000000000000000..4c1ea62d2de7d5857438f217318e3c7072202ca4 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Failure.php @@ -0,0 +1,52 @@ + + */ +final class Failure implements Promise +{ + /** @var \Throwable $exception */ + private $exception; + + /** + * @param \Throwable $exception Rejection reason. + */ + public function __construct(\Throwable $exception) + { + $this->exception = $exception; + } + + /** + * {@inheritdoc} + */ + public function onResolve(callable $onResolved) + { + try { + /** @var mixed $result */ + $result = $onResolved($this->exception, null); + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + Promise\rethrow($result); + } + } catch (\Throwable $exception) { + Loop::defer(static function () use ($exception) { + throw $exception; + }); + } + } +} diff --git a/lib/composer/amphp/amp/lib/Internal/Placeholder.php b/lib/composer/amphp/amp/lib/Internal/Placeholder.php new file mode 100644 index 0000000000000000000000000000000000000000..b84aa704b4e7dfa60dbe544cff679ad5a68b5eff --- /dev/null +++ b/lib/composer/amphp/amp/lib/Internal/Placeholder.php @@ -0,0 +1,179 @@ +, mixed, + * mixed>|null)|callable(\Throwable|null, mixed): void */ + private $onResolved; + + /** @var null|array */ + private $resolutionTrace; + + /** + * @inheritdoc + */ + public function onResolve(callable $onResolved) + { + if ($this->resolved) { + if ($this->result instanceof Promise) { + $this->result->onResolve($onResolved); + return; + } + + try { + /** @var mixed $result */ + $result = $onResolved(null, $this->result); + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + Promise\rethrow($result); + } + } catch (\Throwable $exception) { + Loop::defer(static function () use ($exception) { + throw $exception; + }); + } + return; + } + + if (null === $this->onResolved) { + $this->onResolved = $onResolved; + return; + } + + if (!$this->onResolved instanceof ResolutionQueue) { + /** @psalm-suppress InternalClass */ + $this->onResolved = new ResolutionQueue($this->onResolved); + } + + /** @psalm-suppress InternalMethod */ + $this->onResolved->push($onResolved); + } + + public function __destruct() + { + try { + $this->result = null; + } catch (\Throwable $e) { + Loop::defer(static function () use ($e) { + throw $e; + }); + } + } + + /** + * @param mixed $value + * + * @return void + * + * @throws \Error Thrown if the promise has already been resolved. + */ + private function resolve($value = null) + { + if ($this->resolved) { + $message = "Promise has already been resolved"; + + if (isset($this->resolutionTrace)) { + $trace = formatStacktrace($this->resolutionTrace); + $message .= ". Previous resolution trace:\n\n{$trace}\n\n"; + } else { + // @codeCoverageIgnoreStart + $message .= ", define environment variable AMP_DEBUG or const AMP_DEBUG = true and enable assertions " + . "for a stacktrace of the previous resolution."; + // @codeCoverageIgnoreEnd + } + + throw new \Error($message); + } + + \assert((function () { + $env = \getenv("AMP_DEBUG") ?: "0"; + if (($env !== "0" && $env !== "false") || (\defined("AMP_DEBUG") && \AMP_DEBUG)) { + $trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); + \array_shift($trace); // remove current closure + $this->resolutionTrace = $trace; + } + + return true; + })()); + + if ($value instanceof ReactPromise) { + $value = Promise\adapt($value); + } + + $this->resolved = true; + $this->result = $value; + + if ($this->onResolved === null) { + return; + } + + $onResolved = $this->onResolved; + $this->onResolved = null; + + if ($this->result instanceof Promise) { + $this->result->onResolve($onResolved); + return; + } + + try { + /** @var mixed $result */ + $result = $onResolved(null, $this->result); + $onResolved = null; // allow garbage collection of $onResolved, to catch any exceptions from destructors + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + Promise\rethrow($result); + } + } catch (\Throwable $exception) { + Loop::defer(static function () use ($exception) { + throw $exception; + }); + } + } + + /** + * @param \Throwable $reason Failure reason. + * + * @return void + */ + private function fail(\Throwable $reason) + { + $this->resolve(new Failure($reason)); + } +} diff --git a/lib/composer/amphp/amp/lib/Internal/PrivateIterator.php b/lib/composer/amphp/amp/lib/Internal/PrivateIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..7a14b2438936b282bb6a92ae76f8edb4ce6ab000 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Internal/PrivateIterator.php @@ -0,0 +1,45 @@ + + */ +final class PrivateIterator implements Iterator +{ + /** @var Iterator */ + private $iterator; + + /** + * @param Iterator $iterator + * + * @psalm-param Iterator $iterator + */ + public function __construct(Iterator $iterator) + { + $this->iterator = $iterator; + } + + /** + * @return Promise + */ + public function advance(): Promise + { + return $this->iterator->advance(); + } + + /** + * @psalm-return TValue + */ + public function getCurrent() + { + return $this->iterator->getCurrent(); + } +} diff --git a/lib/composer/amphp/amp/lib/Internal/PrivatePromise.php b/lib/composer/amphp/amp/lib/Internal/PrivatePromise.php new file mode 100644 index 0000000000000000000000000000000000000000..3bd568a822e7527ab2fd1c85d267b2a7e6d7ad08 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Internal/PrivatePromise.php @@ -0,0 +1,25 @@ +promise = $promise; + } + + public function onResolve(callable $onResolved) + { + $this->promise->onResolve($onResolved); + } +} diff --git a/lib/composer/amphp/amp/lib/Internal/Producer.php b/lib/composer/amphp/amp/lib/Internal/Producer.php new file mode 100644 index 0000000000000000000000000000000000000000..c955755a39aa391154eb034388ee86dc903ca555 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Internal/Producer.php @@ -0,0 +1,212 @@ + + */ + public function advance(): Promise + { + if ($this->waiting !== null) { + throw new \Error("The prior promise returned must resolve before invoking this method again"); + } + + unset($this->values[$this->consumePosition]); + + $position = ++$this->consumePosition; + + if (\array_key_exists($position, $this->values)) { + \assert(isset($this->backPressure[$position])); + $deferred = $this->backPressure[$position]; + unset($this->backPressure[$position]); + $deferred->resolve(); + + return new Success(true); + } + + if ($this->complete) { + return $this->complete; + } + + $this->waiting = new Deferred; + + return $this->waiting->promise(); + } + + /** + * {@inheritdoc} + * + * @return TValue + */ + public function getCurrent() + { + if (empty($this->values) && $this->complete) { + throw new \Error("The iterator has completed"); + } + + if (!\array_key_exists($this->consumePosition, $this->values)) { + throw new \Error("Promise returned from advance() must resolve before calling this method"); + } + + return $this->values[$this->consumePosition]; + } + + /** + * Emits a value from the iterator. The returned promise is resolved once the emitted value has been consumed. + * + * @param mixed $value + * + * @return Promise + * @psalm-return Promise + * + * @throws \Error If the iterator has completed. + */ + private function emit($value): Promise + { + if ($this->complete) { + throw new \Error("Iterators cannot emit values after calling complete"); + } + + if ($value instanceof ReactPromise) { + $value = Promise\adapt($value); + } + + if ($value instanceof Promise) { + $deferred = new Deferred; + $value->onResolve(function ($e, $v) use ($deferred) { + if ($this->complete) { + $deferred->fail( + new \Error("The iterator was completed before the promise result could be emitted") + ); + return; + } + + if ($e) { + $this->fail($e); + $deferred->fail($e); + return; + } + + $deferred->resolve($this->emit($v)); + }); + + return $deferred->promise(); + } + + $position = ++$this->emitPosition; + + $this->values[$position] = $value; + + if ($this->waiting !== null) { + $waiting = $this->waiting; + $this->waiting = null; + $waiting->resolve(true); + return new Success; // Consumer was already waiting for a new value, so back-pressure is unnecessary. + } + + $this->backPressure[$position] = $pressure = new Deferred; + + return $pressure->promise(); + } + + /** + * Completes the iterator. + * + * @return void + * + * @throws \Error If the iterator has already been completed. + */ + private function complete() + { + if ($this->complete) { + $message = "Iterator has already been completed"; + + if (isset($this->resolutionTrace)) { + $trace = formatStacktrace($this->resolutionTrace); + $message .= ". Previous completion trace:\n\n{$trace}\n\n"; + } else { + // @codeCoverageIgnoreStart + $message .= ", define environment variable AMP_DEBUG or const AMP_DEBUG = true and enable assertions " + . "for a stacktrace of the previous resolution."; + // @codeCoverageIgnoreEnd + } + + throw new \Error($message); + } + + \assert((function () { + $env = \getenv("AMP_DEBUG") ?: "0"; + if (($env !== "0" && $env !== "false") || (\defined("AMP_DEBUG") && \AMP_DEBUG)) { + $trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); + \array_shift($trace); // remove current closure + $this->resolutionTrace = $trace; + } + + return true; + })()); + + $this->complete = new Success(false); + + if ($this->waiting !== null) { + $waiting = $this->waiting; + $this->waiting = null; + $waiting->resolve($this->complete); + } + } + + /** + * @param \Throwable $exception + * + * @return void + */ + private function fail(\Throwable $exception) + { + $this->complete = new Failure($exception); + + if ($this->waiting !== null) { + $waiting = $this->waiting; + $this->waiting = null; + $waiting->resolve($this->complete); + } + } +} diff --git a/lib/composer/amphp/amp/lib/Internal/ResolutionQueue.php b/lib/composer/amphp/amp/lib/Internal/ResolutionQueue.php new file mode 100644 index 0000000000000000000000000000000000000000..353a8b9390296a338e612195c1e9d1cd8d019533 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Internal/ResolutionQueue.php @@ -0,0 +1,90 @@ +, mixed, + * mixed>|null) | callable(\Throwable|null, mixed): void> */ + private $queue = []; + + /** + * @param callable|null $callback Initial callback to add to queue. + * + * @psalm-param null|callable(\Throwable|null, mixed): (Promise|\React\Promise\PromiseInterface|\Generator, mixed, + * mixed>|null) | callable(\Throwable|null, mixed): void $callback + */ + public function __construct(callable $callback = null) + { + if ($callback !== null) { + $this->push($callback); + } + } + + /** + * Unrolls instances of self to avoid blowing up the call stack on resolution. + * + * @param callable $callback + * + * @psalm-param callable(\Throwable|null, mixed): (Promise|\React\Promise\PromiseInterface|\Generator, mixed, + * mixed>|null) | callable(\Throwable|null, mixed): void $callback + * + * @return void + */ + public function push(callable $callback) + { + if ($callback instanceof self) { + $this->queue = \array_merge($this->queue, $callback->queue); + return; + } + + $this->queue[] = $callback; + } + + /** + * Calls each callback in the queue, passing the provided values to the function. + * + * @param \Throwable|null $exception + * @param mixed $value + * + * @return void + */ + public function __invoke($exception, $value) + { + foreach ($this->queue as $callback) { + try { + $result = $callback($exception, $value); + + if ($result === null) { + continue; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + Promise\rethrow($result); + } + } catch (\Throwable $exception) { + Loop::defer(static function () use ($exception) { + throw $exception; + }); + } + } + } +} diff --git a/lib/composer/amphp/amp/lib/Internal/functions.php b/lib/composer/amphp/amp/lib/Internal/functions.php new file mode 100644 index 0000000000000000000000000000000000000000..eee9af200ccbad9634abaa938cf036b37b0d9cb0 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Internal/functions.php @@ -0,0 +1,117 @@ + $trace Output of + * `debug_backtrace()`. + * + * @return string Formatted stacktrace. + * + * @codeCoverageIgnore + * @internal + */ +function formatStacktrace(array $trace): string +{ + return \implode("\n", \array_map(static function ($e, $i) { + $line = "#{$i} "; + + if (isset($e["file"])) { + $line .= "{$e['file']}:{$e['line']} "; + } + + if (isset($e["type"])) { + $line .= $e["class"] . $e["type"]; + } + + return $line . $e["function"] . "()"; + }, $trace, \array_keys($trace))); +} + +/** + * Creates a `TypeError` with a standardized error message. + * + * @param string[] $expected Expected types. + * @param mixed $given Given value. + * + * @return \TypeError + * + * @internal + */ +function createTypeError(array $expected, $given): \TypeError +{ + $givenType = \is_object($given) ? \sprintf("instance of %s", \get_class($given)) : \gettype($given); + + if (\count($expected) === 1) { + $expectedType = "Expected the following type: " . \array_pop($expected); + } else { + $expectedType = "Expected one of the following types: " . \implode(", ", $expected); + } + + return new \TypeError("{$expectedType}; {$givenType} given"); +} + +/** + * Returns the current time relative to an arbitrary point in time. + * + * @return int Time in milliseconds. + */ +function getCurrentTime(): int +{ + /** @var int|null $startTime */ + static $startTime; + /** @var int|null $nextWarning */ + static $nextWarning; + + if (\PHP_INT_SIZE === 4) { + // @codeCoverageIgnoreStart + if ($startTime === null) { + $startTime = \PHP_VERSION_ID >= 70300 ? \hrtime(false)[0] : \time(); + $nextWarning = \PHP_INT_MAX - 86400 * 7; + } + + if (\PHP_VERSION_ID >= 70300) { + list($seconds, $nanoseconds) = \hrtime(false); + $seconds -= $startTime; + + if ($seconds >= $nextWarning) { + $timeToOverflow = (\PHP_INT_MAX - $seconds * 1000) / 1000; + \trigger_error( + "getCurrentTime() will overflow in $timeToOverflow seconds, please restart the process before that. " . + "You're using a 32 bit version of PHP, so time will overflow about every 24 days. Regular restarts are required.", + \E_USER_WARNING + ); + + /** @psalm-suppress PossiblyNullOperand */ + $nextWarning += 600; // every 10 minutes + } + + return (int) ($seconds * 1000 + $nanoseconds / 1000000); + } + + $seconds = \microtime(true) - $startTime; + if ($seconds >= $nextWarning) { + $timeToOverflow = (\PHP_INT_MAX - $seconds * 1000) / 1000; + \trigger_error( + "getCurrentTime() will overflow in $timeToOverflow seconds, please restart the process before that. " . + "You're using a 32 bit version of PHP, so time will overflow about every 24 days. Regular restarts are required.", + \E_USER_WARNING + ); + + /** @psalm-suppress PossiblyNullOperand */ + $nextWarning += 600; // every 10 minutes + } + + return (int) ($seconds * 1000); + // @codeCoverageIgnoreEnd + } + + if (\PHP_VERSION_ID >= 70300) { + list($seconds, $nanoseconds) = \hrtime(false); + return (int) ($seconds * 1000 + $nanoseconds / 1000000); + } + + return (int) (\microtime(true) * 1000); +} diff --git a/lib/composer/amphp/amp/lib/InvalidYieldError.php b/lib/composer/amphp/amp/lib/InvalidYieldError.php new file mode 100644 index 0000000000000000000000000000000000000000..8785708e3bb3ecc504df320c84f4eae7e9756cd5 --- /dev/null +++ b/lib/composer/amphp/amp/lib/InvalidYieldError.php @@ -0,0 +1,39 @@ +current(); + $prefix .= \sprintf( + "; %s yielded at key %s", + \is_object($yielded) ? \get_class($yielded) : \gettype($yielded), + \var_export($generator->key(), true) + ); + + if (!$generator->valid()) { + parent::__construct($prefix, 0, $previous); + return; + } + + $reflGen = new \ReflectionGenerator($generator); + $exeGen = $reflGen->getExecutingGenerator(); + if ($isSubgenerator = ($exeGen !== $generator)) { + $reflGen = new \ReflectionGenerator($exeGen); + } + + parent::__construct(\sprintf( + "%s on line %s in %s", + $prefix, + $reflGen->getExecutingLine(), + $reflGen->getExecutingFile() + ), 0, $previous); + } +} diff --git a/lib/composer/amphp/amp/lib/Iterator.php b/lib/composer/amphp/amp/lib/Iterator.php new file mode 100644 index 0000000000000000000000000000000000000000..fee9b76b6451d1affcda4d8bcfcbeaf0f98e82ea --- /dev/null +++ b/lib/composer/amphp/amp/lib/Iterator.php @@ -0,0 +1,34 @@ + + * + * @throws \Error If the prior promise returned from this method has not resolved. + * @throws \Throwable The exception used to fail the iterator. + */ + public function advance(): Promise; + + /** + * Gets the last emitted value or throws an exception if the iterator has completed. + * + * @return mixed Value emitted from the iterator. + * @psalm-return TValue + * + * @throws \Error If the iterator has resolved or advance() was not called before calling this method. + * @throws \Throwable The exception used to fail the iterator. + */ + public function getCurrent(); +} diff --git a/lib/composer/amphp/amp/lib/LazyPromise.php b/lib/composer/amphp/amp/lib/LazyPromise.php new file mode 100644 index 0000000000000000000000000000000000000000..96be33f5fff74a69078ae39605c2504ce785e711 --- /dev/null +++ b/lib/composer/amphp/amp/lib/LazyPromise.php @@ -0,0 +1,44 @@ +promisor = $promisor; + } + + /** + * {@inheritdoc} + */ + public function onResolve(callable $onResolved) + { + if ($this->promise === null) { + \assert($this->promisor !== null); + + $provider = $this->promisor; + $this->promisor = null; + $this->promise = call($provider); + } + + \assert($this->promise !== null); + + $this->promise->onResolve($onResolved); + } +} diff --git a/lib/composer/amphp/amp/lib/Loop.php b/lib/composer/amphp/amp/lib/Loop.php new file mode 100644 index 0000000000000000000000000000000000000000..d89d2101a60ce02eb12a82a916cf9cbccb793a40 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop.php @@ -0,0 +1,456 @@ +defer($callback); + } + + self::$driver->run(); + } + + /** + * Stop the event loop. + * + * When an event loop is stopped, it continues with its current tick and exits the loop afterwards. Multiple calls + * to stop MUST be ignored and MUST NOT raise an exception. + * + * @return void + */ + public static function stop() + { + self::$driver->stop(); + } + + /** + * Defer the execution of a callback. + * + * The deferred callable MUST be executed before any other type of watcher in a tick. Order of enabling MUST be + * preserved when executing the callbacks. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param callable(string $watcherId, mixed $data) $callback The callback to defer. The `$watcherId` will be + * invalidated before the callback call. + * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + */ + public static function defer(callable $callback, $data = null): string + { + return self::$driver->defer($callback, $data); + } + + /** + * Delay the execution of a callback. + * + * The delay is a minimum and approximate, accuracy is not guaranteed. Order of calls MUST be determined by which + * timers expire first, but timers with the same expiration time MAY be executed in any order. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param int $delay The amount of time, in milliseconds, to delay the execution for. + * @param callable(string $watcherId, mixed $data) $callback The callback to delay. The `$watcherId` will be + * invalidated before the callback call. + * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + */ + public static function delay(int $delay, callable $callback, $data = null): string + { + return self::$driver->delay($delay, $callback, $data); + } + + /** + * Repeatedly execute a callback. + * + * The interval between executions is a minimum and approximate, accuracy is not guaranteed. Order of calls MUST be + * determined by which timers expire first, but timers with the same expiration time MAY be executed in any order. + * The first execution is scheduled after the first interval period. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param int $interval The time interval, in milliseconds, to wait between executions. + * @param callable(string $watcherId, mixed $data) $callback The callback to repeat. + * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + */ + public static function repeat(int $interval, callable $callback, $data = null): string + { + return self::$driver->repeat($interval, $callback, $data); + } + + /** + * Execute a callback when a stream resource becomes readable or is closed for reading. + * + * Warning: Closing resources locally, e.g. with `fclose`, might not invoke the callback. Be sure to `cancel` the + * watcher when closing the resource locally. Drivers MAY choose to notify the user if there are watchers on invalid + * resources, but are not required to, due to the high performance impact. Watchers on closed resources are + * therefore undefined behavior. + * + * Multiple watchers on the same stream MAY be executed in any order. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param resource $stream The stream to monitor. + * @param callable(string $watcherId, resource $stream, mixed $data) $callback The callback to execute. + * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + */ + public static function onReadable($stream, callable $callback, $data = null): string + { + return self::$driver->onReadable($stream, $callback, $data); + } + + /** + * Execute a callback when a stream resource becomes writable or is closed for writing. + * + * Warning: Closing resources locally, e.g. with `fclose`, might not invoke the callback. Be sure to `cancel` the + * watcher when closing the resource locally. Drivers MAY choose to notify the user if there are watchers on invalid + * resources, but are not required to, due to the high performance impact. Watchers on closed resources are + * therefore undefined behavior. + * + * Multiple watchers on the same stream MAY be executed in any order. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param resource $stream The stream to monitor. + * @param callable(string $watcherId, resource $stream, mixed $data) $callback The callback to execute. + * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + */ + public static function onWritable($stream, callable $callback, $data = null): string + { + return self::$driver->onWritable($stream, $callback, $data); + } + + /** + * Execute a callback when a signal is received. + * + * Warning: Installing the same signal on different instances of this interface is deemed undefined behavior. + * Implementations MAY try to detect this, if possible, but are not required to. This is due to technical + * limitations of the signals being registered globally per process. + * + * Multiple watchers on the same signal MAY be executed in any order. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param int $signo The signal number to monitor. + * @param callable(string $watcherId, int $signo, mixed $data) $callback The callback to execute. + * @param mixed $data Arbitrary data given to the callback function as the $data parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + * + * @throws UnsupportedFeatureException If signal handling is not supported. + */ + public static function onSignal(int $signo, callable $callback, $data = null): string + { + return self::$driver->onSignal($signo, $callback, $data); + } + + /** + * Enable a watcher to be active starting in the next tick. + * + * Watchers MUST immediately be marked as enabled, but only be activated (i.e. callbacks can be called) right before + * the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param string $watcherId The watcher identifier. + * + * @return void + * + * @throws InvalidWatcherError If the watcher identifier is invalid. + */ + public static function enable(string $watcherId) + { + self::$driver->enable($watcherId); + } + + /** + * Disable a watcher immediately. + * + * A watcher MUST be disabled immediately, e.g. if a defer watcher disables a later defer watcher, the second defer + * watcher isn't executed in this tick. + * + * Disabling a watcher MUST NOT invalidate the watcher. Calling this function MUST NOT fail, even if passed an + * invalid watcher. + * + * @param string $watcherId The watcher identifier. + * + * @return void + */ + public static function disable(string $watcherId) + { + if (\PHP_VERSION_ID < 70200 && !isset(self::$driver)) { + // Prior to PHP 7.2, self::$driver may be unset during destruct. + // See https://github.com/amphp/amp/issues/212. + return; + } + + self::$driver->disable($watcherId); + } + + /** + * Cancel a watcher. + * + * This will detatch the event loop from all resources that are associated to the watcher. After this operation the + * watcher is permanently invalid. Calling this function MUST NOT fail, even if passed an invalid watcher. + * + * @param string $watcherId The watcher identifier. + * + * @return void + */ + public static function cancel(string $watcherId) + { + if (\PHP_VERSION_ID < 70200 && !isset(self::$driver)) { + // Prior to PHP 7.2, self::$driver may be unset during destruct. + // See https://github.com/amphp/amp/issues/212. + return; + } + + self::$driver->cancel($watcherId); + } + + /** + * Reference a watcher. + * + * This will keep the event loop alive whilst the watcher is still being monitored. Watchers have this state by + * default. + * + * @param string $watcherId The watcher identifier. + * + * @return void + * + * @throws InvalidWatcherError If the watcher identifier is invalid. + */ + public static function reference(string $watcherId) + { + self::$driver->reference($watcherId); + } + + /** + * Unreference a watcher. + * + * The event loop should exit the run method when only unreferenced watchers are still being monitored. Watchers + * are all referenced by default. + * + * @param string $watcherId The watcher identifier. + * + * @return void + */ + public static function unreference(string $watcherId) + { + if (\PHP_VERSION_ID < 70200 && !isset(self::$driver)) { + // Prior to PHP 7.2, self::$driver may be unset during destruct. + // See https://github.com/amphp/amp/issues/212. + return; + } + + self::$driver->unreference($watcherId); + } + + /** + * Returns the current loop time in millisecond increments. Note this value does not necessarily correlate to + * wall-clock time, rather the value returned is meant to be used in relative comparisons to prior values returned + * by this method (intervals, expiration calculations, etc.) and is only updated once per loop tick. + * + * @return int + */ + public static function now(): int + { + return self::$driver->now(); + } + + /** + * Stores information in the loop bound registry. + * + * Stored information is package private. Packages MUST NOT retrieve the stored state of other packages. Packages + * MUST use their namespace as prefix for keys. They may do so by using `SomeClass::class` as key. + * + * If packages want to expose loop bound state to consumers other than the package, they SHOULD provide a dedicated + * interface for that purpose instead of sharing the storage key. + * + * @param string $key The namespaced storage key. + * @param mixed $value The value to be stored. + * + * @return void + */ + public static function setState(string $key, $value) + { + self::$driver->setState($key, $value); + } + + /** + * Gets information stored bound to the loop. + * + * Stored information is package private. Packages MUST NOT retrieve the stored state of other packages. Packages + * MUST use their namespace as prefix for keys. They may do so by using `SomeClass::class` as key. + * + * If packages want to expose loop bound state to consumers other than the package, they SHOULD provide a dedicated + * interface for that purpose instead of sharing the storage key. + * + * @param string $key The namespaced storage key. + * + * @return mixed The previously stored value or `null` if it doesn't exist. + */ + public static function getState(string $key) + { + return self::$driver->getState($key); + } + + /** + * Set a callback to be executed when an error occurs. + * + * The callback receives the error as the first and only parameter. The return value of the callback gets ignored. + * If it can't handle the error, it MUST throw the error. Errors thrown by the callback or during its invocation + * MUST be thrown into the `run` loop and stop the driver. + * + * Subsequent calls to this method will overwrite the previous handler. + * + * @param callable(\Throwable $error)|null $callback The callback to execute. `null` will clear the + * current handler. + * + * @return callable(\Throwable $error)|null The previous handler, `null` if there was none. + */ + public static function setErrorHandler(callable $callback = null) + { + return self::$driver->setErrorHandler($callback); + } + + /** + * Retrieve an associative array of information about the event loop driver. + * + * The returned array MUST contain the following data describing the driver's currently registered watchers: + * + * [ + * "defer" => ["enabled" => int, "disabled" => int], + * "delay" => ["enabled" => int, "disabled" => int], + * "repeat" => ["enabled" => int, "disabled" => int], + * "on_readable" => ["enabled" => int, "disabled" => int], + * "on_writable" => ["enabled" => int, "disabled" => int], + * "on_signal" => ["enabled" => int, "disabled" => int], + * "enabled_watchers" => ["referenced" => int, "unreferenced" => int], + * "running" => bool + * ]; + * + * Implementations MAY optionally add more information in the array but at minimum the above `key => value` format + * MUST always be provided. + * + * @return array Statistics about the loop in the described format. + */ + public static function getInfo(): array + { + return self::$driver->getInfo(); + } + + /** + * Retrieve the event loop driver that is in scope. + * + * @return Driver + */ + public static function get(): Driver + { + return self::$driver; + } +} + +// Default factory, don't move this to a file loaded by the composer "files" autoload mechanism, otherwise custom +// implementations might have issues setting a default loop, because it's overridden by us then. + +// @codeCoverageIgnoreStart +Loop::set((new DriverFactory)->create()); +// @codeCoverageIgnoreEnd diff --git a/lib/composer/amphp/amp/lib/Loop/Driver.php b/lib/composer/amphp/amp/lib/Loop/Driver.php new file mode 100644 index 0000000000000000000000000000000000000000..448bab62d08dda9223ed8275a71252c4ac8a67be --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop/Driver.php @@ -0,0 +1,742 @@ +running = true; + + try { + while ($this->running) { + if ($this->isEmpty()) { + return; + } + $this->tick(); + } + } finally { + $this->stop(); + } + } + + /** + * @return bool True if no enabled and referenced watchers remain in the loop. + */ + private function isEmpty(): bool + { + foreach ($this->watchers as $watcher) { + if ($watcher->enabled && $watcher->referenced) { + return false; + } + } + + return true; + } + + /** + * Executes a single tick of the event loop. + * + * @return void + */ + private function tick() + { + if (empty($this->deferQueue)) { + $this->deferQueue = $this->nextTickQueue; + } else { + $this->deferQueue = \array_merge($this->deferQueue, $this->nextTickQueue); + } + $this->nextTickQueue = []; + + $this->activate($this->enableQueue); + $this->enableQueue = []; + + foreach ($this->deferQueue as $watcher) { + if (!isset($this->deferQueue[$watcher->id])) { + continue; // Watcher disabled by another defer watcher. + } + + unset($this->watchers[$watcher->id], $this->deferQueue[$watcher->id]); + + try { + /** @var mixed $result */ + $result = ($watcher->callback)($watcher->id, $watcher->data); + + if ($result === null) { + continue; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + } + + /** @psalm-suppress RedundantCondition */ + $this->dispatch(empty($this->nextTickQueue) && empty($this->enableQueue) && $this->running && !$this->isEmpty()); + } + + /** + * Activates (enables) all the given watchers. + * + * @param Watcher[] $watchers + * + * @return void + */ + abstract protected function activate(array $watchers); + + /** + * Dispatches any pending read/write, timer, and signal events. + * + * @param bool $blocking + * + * @return void + */ + abstract protected function dispatch(bool $blocking); + + /** + * Stop the event loop. + * + * When an event loop is stopped, it continues with its current tick and exits the loop afterwards. Multiple calls + * to stop MUST be ignored and MUST NOT raise an exception. + * + * @return void + */ + public function stop() + { + $this->running = false; + } + + /** + * Defer the execution of a callback. + * + * The deferred callable MUST be executed before any other type of watcher in a tick. Order of enabling MUST be + * preserved when executing the callbacks. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param callable (string $watcherId, mixed $data) $callback The callback to defer. The `$watcherId` will be + * invalidated before the callback call. + * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + */ + public function defer(callable $callback, $data = null): string + { + /** @psalm-var Watcher $watcher */ + $watcher = new Watcher; + $watcher->type = Watcher::DEFER; + $watcher->id = $this->nextId++; + $watcher->callback = $callback; + $watcher->data = $data; + + $this->watchers[$watcher->id] = $watcher; + $this->nextTickQueue[$watcher->id] = $watcher; + + return $watcher->id; + } + + /** + * Delay the execution of a callback. + * + * The delay is a minimum and approximate, accuracy is not guaranteed. Order of calls MUST be determined by which + * timers expire first, but timers with the same expiration time MAY be executed in any order. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param int $delay The amount of time, in milliseconds, to delay the execution for. + * @param callable (string $watcherId, mixed $data) $callback The callback to delay. The `$watcherId` will be + * invalidated before the callback call. + * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + */ + public function delay(int $delay, callable $callback, $data = null): string + { + if ($delay < 0) { + throw new \Error("Delay must be greater than or equal to zero"); + } + + /** @psalm-var Watcher $watcher */ + $watcher = new Watcher; + $watcher->type = Watcher::DELAY; + $watcher->id = $this->nextId++; + $watcher->callback = $callback; + $watcher->value = $delay; + $watcher->expiration = $this->now() + $delay; + $watcher->data = $data; + + $this->watchers[$watcher->id] = $watcher; + $this->enableQueue[$watcher->id] = $watcher; + + return $watcher->id; + } + + /** + * Repeatedly execute a callback. + * + * The interval between executions is a minimum and approximate, accuracy is not guaranteed. Order of calls MUST be + * determined by which timers expire first, but timers with the same expiration time MAY be executed in any order. + * The first execution is scheduled after the first interval period. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param int $interval The time interval, in milliseconds, to wait between executions. + * @param callable (string $watcherId, mixed $data) $callback The callback to repeat. + * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + */ + public function repeat(int $interval, callable $callback, $data = null): string + { + if ($interval < 0) { + throw new \Error("Interval must be greater than or equal to zero"); + } + + /** @psalm-var Watcher $watcher */ + $watcher = new Watcher; + $watcher->type = Watcher::REPEAT; + $watcher->id = $this->nextId++; + $watcher->callback = $callback; + $watcher->value = $interval; + $watcher->expiration = $this->now() + $interval; + $watcher->data = $data; + + $this->watchers[$watcher->id] = $watcher; + $this->enableQueue[$watcher->id] = $watcher; + + return $watcher->id; + } + + /** + * Execute a callback when a stream resource becomes readable or is closed for reading. + * + * Warning: Closing resources locally, e.g. with `fclose`, might not invoke the callback. Be sure to `cancel` the + * watcher when closing the resource locally. Drivers MAY choose to notify the user if there are watchers on invalid + * resources, but are not required to, due to the high performance impact. Watchers on closed resources are + * therefore undefined behavior. + * + * Multiple watchers on the same stream MAY be executed in any order. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param resource $stream The stream to monitor. + * @param callable (string $watcherId, resource $stream, mixed $data) $callback The callback to execute. + * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + */ + public function onReadable($stream, callable $callback, $data = null): string + { + /** @psalm-var Watcher $watcher */ + $watcher = new Watcher; + $watcher->type = Watcher::READABLE; + $watcher->id = $this->nextId++; + $watcher->callback = $callback; + $watcher->value = $stream; + $watcher->data = $data; + + $this->watchers[$watcher->id] = $watcher; + $this->enableQueue[$watcher->id] = $watcher; + + return $watcher->id; + } + + /** + * Execute a callback when a stream resource becomes writable or is closed for writing. + * + * Warning: Closing resources locally, e.g. with `fclose`, might not invoke the callback. Be sure to `cancel` the + * watcher when closing the resource locally. Drivers MAY choose to notify the user if there are watchers on invalid + * resources, but are not required to, due to the high performance impact. Watchers on closed resources are + * therefore undefined behavior. + * + * Multiple watchers on the same stream MAY be executed in any order. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param resource $stream The stream to monitor. + * @param callable (string $watcherId, resource $stream, mixed $data) $callback The callback to execute. + * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + */ + public function onWritable($stream, callable $callback, $data = null): string + { + /** @psalm-var Watcher $watcher */ + $watcher = new Watcher; + $watcher->type = Watcher::WRITABLE; + $watcher->id = $this->nextId++; + $watcher->callback = $callback; + $watcher->value = $stream; + $watcher->data = $data; + + $this->watchers[$watcher->id] = $watcher; + $this->enableQueue[$watcher->id] = $watcher; + + return $watcher->id; + } + + /** + * Execute a callback when a signal is received. + * + * Warning: Installing the same signal on different instances of this interface is deemed undefined behavior. + * Implementations MAY try to detect this, if possible, but are not required to. This is due to technical + * limitations of the signals being registered globally per process. + * + * Multiple watchers on the same signal MAY be executed in any order. + * + * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) + * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param int $signo The signal number to monitor. + * @param callable (string $watcherId, int $signo, mixed $data) $callback The callback to execute. + * @param mixed $data Arbitrary data given to the callback function as the $data parameter. + * + * @return string An unique identifier that can be used to cancel, enable or disable the watcher. + * + * @throws UnsupportedFeatureException If signal handling is not supported. + */ + public function onSignal(int $signo, callable $callback, $data = null): string + { + /** @psalm-var Watcher $watcher */ + $watcher = new Watcher; + $watcher->type = Watcher::SIGNAL; + $watcher->id = $this->nextId++; + $watcher->callback = $callback; + $watcher->value = $signo; + $watcher->data = $data; + + $this->watchers[$watcher->id] = $watcher; + $this->enableQueue[$watcher->id] = $watcher; + + return $watcher->id; + } + + /** + * Enable a watcher to be active starting in the next tick. + * + * Watchers MUST immediately be marked as enabled, but only be activated (i.e. callbacks can be called) right before + * the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. + * + * @param string $watcherId The watcher identifier. + * + * @return void + * + * @throws InvalidWatcherError If the watcher identifier is invalid. + */ + public function enable(string $watcherId) + { + if (!isset($this->watchers[$watcherId])) { + throw new InvalidWatcherError($watcherId, "Cannot enable an invalid watcher identifier: '{$watcherId}'"); + } + + $watcher = $this->watchers[$watcherId]; + + if ($watcher->enabled) { + return; // Watcher already enabled. + } + + $watcher->enabled = true; + + switch ($watcher->type) { + case Watcher::DEFER: + $this->nextTickQueue[$watcher->id] = $watcher; + break; + + case Watcher::REPEAT: + case Watcher::DELAY: + \assert(\is_int($watcher->value)); + + $watcher->expiration = $this->now() + $watcher->value; + $this->enableQueue[$watcher->id] = $watcher; + break; + + default: + $this->enableQueue[$watcher->id] = $watcher; + break; + } + } + + /** + * Cancel a watcher. + * + * This will detach the event loop from all resources that are associated to the watcher. After this operation the + * watcher is permanently invalid. Calling this function MUST NOT fail, even if passed an invalid watcher. + * + * @param string $watcherId The watcher identifier. + * + * @return void + */ + public function cancel(string $watcherId) + { + $this->disable($watcherId); + unset($this->watchers[$watcherId]); + } + + /** + * Disable a watcher immediately. + * + * A watcher MUST be disabled immediately, e.g. if a defer watcher disables a later defer watcher, the second defer + * watcher isn't executed in this tick. + * + * Disabling a watcher MUST NOT invalidate the watcher. Calling this function MUST NOT fail, even if passed an + * invalid watcher. + * + * @param string $watcherId The watcher identifier. + * + * @return void + */ + public function disable(string $watcherId) + { + if (!isset($this->watchers[$watcherId])) { + return; + } + + $watcher = $this->watchers[$watcherId]; + + if (!$watcher->enabled) { + return; // Watcher already disabled. + } + + $watcher->enabled = false; + $id = $watcher->id; + + switch ($watcher->type) { + case Watcher::DEFER: + if (isset($this->nextTickQueue[$id])) { + // Watcher was only queued to be enabled. + unset($this->nextTickQueue[$id]); + } else { + unset($this->deferQueue[$id]); + } + break; + + default: + if (isset($this->enableQueue[$id])) { + // Watcher was only queued to be enabled. + unset($this->enableQueue[$id]); + } else { + $this->deactivate($watcher); + } + break; + } + } + + /** + * Deactivates (disables) the given watcher. + * + * @param Watcher $watcher + * + * @return void + */ + abstract protected function deactivate(Watcher $watcher); + + /** + * Reference a watcher. + * + * This will keep the event loop alive whilst the watcher is still being monitored. Watchers have this state by + * default. + * + * @param string $watcherId The watcher identifier. + * + * @return void + * + * @throws InvalidWatcherError If the watcher identifier is invalid. + */ + public function reference(string $watcherId) + { + if (!isset($this->watchers[$watcherId])) { + throw new InvalidWatcherError($watcherId, "Cannot reference an invalid watcher identifier: '{$watcherId}'"); + } + + $this->watchers[$watcherId]->referenced = true; + } + + /** + * Unreference a watcher. + * + * The event loop should exit the run method when only unreferenced watchers are still being monitored. Watchers + * are all referenced by default. + * + * @param string $watcherId The watcher identifier. + * + * @return void + */ + public function unreference(string $watcherId) + { + if (!isset($this->watchers[$watcherId])) { + return; + } + + $this->watchers[$watcherId]->referenced = false; + } + + /** + * Stores information in the loop bound registry. + * + * Stored information is package private. Packages MUST NOT retrieve the stored state of other packages. Packages + * MUST use their namespace as prefix for keys. They may do so by using `SomeClass::class` as key. + * + * If packages want to expose loop bound state to consumers other than the package, they SHOULD provide a dedicated + * interface for that purpose instead of sharing the storage key. + * + * @param string $key The namespaced storage key. + * @param mixed $value The value to be stored. + * + * @return void + */ + final public function setState(string $key, $value) + { + if ($value === null) { + unset($this->registry[$key]); + } else { + $this->registry[$key] = $value; + } + } + + /** + * Gets information stored bound to the loop. + * + * Stored information is package private. Packages MUST NOT retrieve the stored state of other packages. Packages + * MUST use their namespace as prefix for keys. They may do so by using `SomeClass::class` as key. + * + * If packages want to expose loop bound state to consumers other than the package, they SHOULD provide a dedicated + * interface for that purpose instead of sharing the storage key. + * + * @param string $key The namespaced storage key. + * + * @return mixed The previously stored value or `null` if it doesn't exist. + */ + final public function getState(string $key) + { + return isset($this->registry[$key]) ? $this->registry[$key] : null; + } + + /** + * Set a callback to be executed when an error occurs. + * + * The callback receives the error as the first and only parameter. The return value of the callback gets ignored. + * If it can't handle the error, it MUST throw the error. Errors thrown by the callback or during its invocation + * MUST be thrown into the `run` loop and stop the driver. + * + * Subsequent calls to this method will overwrite the previous handler. + * + * @param callable(\Throwable $error):void|null $callback The callback to execute. `null` will clear the + * current handler. + * + * @return callable(\Throwable $error):void|null The previous handler, `null` if there was none. + */ + public function setErrorHandler(callable $callback = null) + { + $previous = $this->errorHandler; + $this->errorHandler = $callback; + return $previous; + } + + /** + * Invokes the error handler with the given exception. + * + * @param \Throwable $exception The exception thrown from a watcher callback. + * + * @return void + * @throws \Throwable If no error handler has been set. + */ + protected function error(\Throwable $exception) + { + if ($this->errorHandler === null) { + throw $exception; + } + + ($this->errorHandler)($exception); + } + + /** + * Returns the current loop time in millisecond increments. Note this value does not necessarily correlate to + * wall-clock time, rather the value returned is meant to be used in relative comparisons to prior values returned + * by this method (intervals, expiration calculations, etc.) and is only updated once per loop tick. + * + * Extending classes should override this function to return a value cached once per loop tick. + * + * @return int + */ + public function now(): int + { + return (int) (\microtime(true) * self::MILLISEC_PER_SEC); + } + + /** + * Get the underlying loop handle. + * + * Example: the `uv_loop` resource for `libuv` or the `EvLoop` object for `libev` or `null` for a native driver. + * + * Note: This function is *not* exposed in the `Loop` class. Users shall access it directly on the respective loop + * instance. + * + * @return null|object|resource The loop handle the event loop operates on. `null` if there is none. + */ + abstract public function getHandle(); + + /** + * Returns the same array of data as getInfo(). + * + * @return array + */ + public function __debugInfo() + { + // @codeCoverageIgnoreStart + return $this->getInfo(); + // @codeCoverageIgnoreEnd + } + + /** + * Retrieve an associative array of information about the event loop driver. + * + * The returned array MUST contain the following data describing the driver's currently registered watchers: + * + * [ + * "defer" => ["enabled" => int, "disabled" => int], + * "delay" => ["enabled" => int, "disabled" => int], + * "repeat" => ["enabled" => int, "disabled" => int], + * "on_readable" => ["enabled" => int, "disabled" => int], + * "on_writable" => ["enabled" => int, "disabled" => int], + * "on_signal" => ["enabled" => int, "disabled" => int], + * "enabled_watchers" => ["referenced" => int, "unreferenced" => int], + * "running" => bool + * ]; + * + * Implementations MAY optionally add more information in the array but at minimum the above `key => value` format + * MUST always be provided. + * + * @return array Statistics about the loop in the described format. + */ + public function getInfo(): array + { + $watchers = [ + "referenced" => 0, + "unreferenced" => 0, + ]; + + $defer = $delay = $repeat = $onReadable = $onWritable = $onSignal = [ + "enabled" => 0, + "disabled" => 0, + ]; + + foreach ($this->watchers as $watcher) { + switch ($watcher->type) { + case Watcher::READABLE: + $array = &$onReadable; + break; + case Watcher::WRITABLE: + $array = &$onWritable; + break; + case Watcher::SIGNAL: + $array = &$onSignal; + break; + case Watcher::DEFER: + $array = &$defer; + break; + case Watcher::DELAY: + $array = &$delay; + break; + case Watcher::REPEAT: + $array = &$repeat; + break; + + default: + // @codeCoverageIgnoreStart + throw new \Error("Unknown watcher type"); + // @codeCoverageIgnoreEnd + } + + if ($watcher->enabled) { + ++$array["enabled"]; + + if ($watcher->referenced) { + ++$watchers["referenced"]; + } else { + ++$watchers["unreferenced"]; + } + } else { + ++$array["disabled"]; + } + } + + return [ + "enabled_watchers" => $watchers, + "defer" => $defer, + "delay" => $delay, + "repeat" => $repeat, + "on_readable" => $onReadable, + "on_writable" => $onWritable, + "on_signal" => $onSignal, + "running" => (bool) $this->running, + ]; + } +} diff --git a/lib/composer/amphp/amp/lib/Loop/DriverFactory.php b/lib/composer/amphp/amp/lib/Loop/DriverFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..0ce1652421a108ffef65383983f0a7809920e06a --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop/DriverFactory.php @@ -0,0 +1,73 @@ +createDriverFromEnv()) { + return $driver; + } + + if (UvDriver::isSupported()) { + return new UvDriver; + } + + if (EvDriver::isSupported()) { + return new EvDriver; + } + + if (EventDriver::isSupported()) { + return new EventDriver; + } + + return new NativeDriver; + })(); + + if (\getenv("AMP_DEBUG_TRACE_WATCHERS")) { + return new TracingDriver($driver); + } + + return $driver; + } + + /** + * @return Driver|null + */ + private function createDriverFromEnv() + { + $driver = \getenv("AMP_LOOP_DRIVER"); + + if (!$driver) { + return null; + } + + if (!\class_exists($driver)) { + throw new \Error(\sprintf( + "Driver '%s' does not exist.", + $driver + )); + } + + if (!\is_subclass_of($driver, Driver::class)) { + throw new \Error(\sprintf( + "Driver '%s' is not a subclass of '%s'.", + $driver, + Driver::class + )); + } + + return new $driver; + } +} +// @codeCoverageIgnoreEnd diff --git a/lib/composer/amphp/amp/lib/Loop/EvDriver.php b/lib/composer/amphp/amp/lib/Loop/EvDriver.php new file mode 100644 index 0000000000000000000000000000000000000000..401bb3bae0b9d22ed661ddca2cb126af652be5ba --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop/EvDriver.php @@ -0,0 +1,317 @@ +handle = new \EvLoop; + $this->nowOffset = getCurrentTime(); + $this->now = \random_int(0, $this->nowOffset); + $this->nowOffset -= $this->now; + + if (self::$activeSignals === null) { + self::$activeSignals = &$this->signals; + } + + /** + * @param \EvIO $event + * + * @return void + */ + $this->ioCallback = function (\EvIO $event) { + /** @var Watcher $watcher */ + $watcher = $event->data; + + try { + $result = ($watcher->callback)($watcher->id, $watcher->value, $watcher->data); + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + }; + + /** + * @param \EvTimer $event + * + * @return void + */ + $this->timerCallback = function (\EvTimer $event) { + /** @var Watcher $watcher */ + $watcher = $event->data; + + if ($watcher->type & Watcher::DELAY) { + $this->cancel($watcher->id); + } elseif ($watcher->value === 0) { + // Disable and re-enable so it's not executed repeatedly in the same tick + // See https://github.com/amphp/amp/issues/131 + $this->disable($watcher->id); + $this->enable($watcher->id); + } + + try { + $result = ($watcher->callback)($watcher->id, $watcher->data); + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + }; + + /** + * @param \EvSignal $event + * + * @return void + */ + $this->signalCallback = function (\EvSignal $event) { + /** @var Watcher $watcher */ + $watcher = $event->data; + + try { + $result = ($watcher->callback)($watcher->id, $watcher->value, $watcher->data); + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + }; + } + + /** + * {@inheritdoc} + */ + public function cancel(string $watcherId) + { + parent::cancel($watcherId); + unset($this->events[$watcherId]); + } + + public function __destruct() + { + foreach ($this->events as $event) { + /** @psalm-suppress all */ + if ($event !== null) { // Events may have been nulled in extension depending on destruct order. + $event->stop(); + } + } + + // We need to clear all references to events manually, see + // https://bitbucket.org/osmanov/pecl-ev/issues/31/segfault-in-ev_timer_stop + $this->events = []; + } + + /** + * {@inheritdoc} + */ + public function run() + { + $active = self::$activeSignals; + + \assert($active !== null); + + foreach ($active as $event) { + $event->stop(); + } + + self::$activeSignals = &$this->signals; + + foreach ($this->signals as $event) { + $event->start(); + } + + try { + parent::run(); + } finally { + foreach ($this->signals as $event) { + $event->stop(); + } + + self::$activeSignals = &$active; + + foreach ($active as $event) { + $event->start(); + } + } + } + + /** + * {@inheritdoc} + */ + public function stop() + { + $this->handle->stop(); + parent::stop(); + } + + /** + * {@inheritdoc} + */ + public function now(): int + { + $this->now = getCurrentTime() - $this->nowOffset; + + return $this->now; + } + + /** + * {@inheritdoc} + */ + public function getHandle(): \EvLoop + { + return $this->handle; + } + + /** + * {@inheritdoc} + * + * @return void + */ + protected function dispatch(bool $blocking) + { + $this->handle->run($blocking ? \Ev::RUN_ONCE : \Ev::RUN_ONCE | \Ev::RUN_NOWAIT); + } + + /** + * {@inheritdoc} + * + * @return void + */ + protected function activate(array $watchers) + { + $this->handle->nowUpdate(); + $now = $this->now(); + + foreach ($watchers as $watcher) { + if (!isset($this->events[$id = $watcher->id])) { + switch ($watcher->type) { + case Watcher::READABLE: + \assert(\is_resource($watcher->value)); + + $this->events[$id] = $this->handle->io($watcher->value, \Ev::READ, $this->ioCallback, $watcher); + break; + + case Watcher::WRITABLE: + \assert(\is_resource($watcher->value)); + + $this->events[$id] = $this->handle->io( + $watcher->value, + \Ev::WRITE, + $this->ioCallback, + $watcher + ); + break; + + case Watcher::DELAY: + case Watcher::REPEAT: + \assert(\is_int($watcher->value)); + + $interval = $watcher->value / self::MILLISEC_PER_SEC; + $this->events[$id] = $this->handle->timer( + \max(0, ($watcher->expiration - $now) / self::MILLISEC_PER_SEC), + ($watcher->type & Watcher::REPEAT) ? $interval : 0, + $this->timerCallback, + $watcher + ); + break; + + case Watcher::SIGNAL: + \assert(\is_int($watcher->value)); + + $this->events[$id] = $this->handle->signal($watcher->value, $this->signalCallback, $watcher); + break; + + default: + // @codeCoverageIgnoreStart + throw new \Error("Unknown watcher type"); + // @codeCoverageIgnoreEnd + } + } else { + $this->events[$id]->start(); + } + + if ($watcher->type === Watcher::SIGNAL) { + /** @psalm-suppress PropertyTypeCoercion */ + $this->signals[$id] = $this->events[$id]; + } + } + } + + /** + * {@inheritdoc} + * + * @return void + */ + protected function deactivate(Watcher $watcher) + { + if (isset($this->events[$id = $watcher->id])) { + $this->events[$id]->stop(); + + if ($watcher->type === Watcher::SIGNAL) { + unset($this->signals[$id]); + } + } + } +} diff --git a/lib/composer/amphp/amp/lib/Loop/EventDriver.php b/lib/composer/amphp/amp/lib/Loop/EventDriver.php new file mode 100644 index 0000000000000000000000000000000000000000..676ba7d10f405f6ac8a4f099ec23185938e3548a --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop/EventDriver.php @@ -0,0 +1,362 @@ +handle = new \EventBase; + $this->nowOffset = getCurrentTime(); + $this->now = \random_int(0, $this->nowOffset); + $this->nowOffset -= $this->now; + + if (self::$activeSignals === null) { + self::$activeSignals = &$this->signals; + } + + /** + * @param $resource + * @param $what + * @param Watcher $watcher + * + * @return void + */ + $this->ioCallback = function ($resource, $what, Watcher $watcher) { + \assert(\is_resource($watcher->value)); + + try { + $result = ($watcher->callback)($watcher->id, $watcher->value, $watcher->data); + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + }; + + /** + * @param $resource + * @param $what + * @param Watcher $watcher + * + * @return void + */ + $this->timerCallback = function ($resource, $what, Watcher $watcher) { + \assert(\is_int($watcher->value)); + + if ($watcher->type & Watcher::DELAY) { + $this->cancel($watcher->id); + } else { + $this->events[$watcher->id]->add($watcher->value / self::MILLISEC_PER_SEC); + } + + try { + $result = ($watcher->callback)($watcher->id, $watcher->data); + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + }; + + /** + * @param $signum + * @param $what + * @param Watcher $watcher + * + * @return void + */ + $this->signalCallback = function ($signum, $what, Watcher $watcher) { + try { + $result = ($watcher->callback)($watcher->id, $watcher->value, $watcher->data); + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + }; + } + + /** + * {@inheritdoc} + */ + public function cancel(string $watcherId) + { + parent::cancel($watcherId); + + if (isset($this->events[$watcherId])) { + $this->events[$watcherId]->free(); + unset($this->events[$watcherId]); + } + } + + public static function isSupported(): bool + { + return \extension_loaded("event"); + } + + /** + * @codeCoverageIgnore + */ + public function __destruct() + { + foreach ($this->events as $event) { + if ($event !== null) { // Events may have been nulled in extension depending on destruct order. + $event->free(); + } + } + + // Unset here, otherwise $event->del() fails with a warning, because __destruct order isn't defined. + // See https://github.com/amphp/amp/issues/159. + $this->events = []; + + // Manually free the loop handle to fully release loop resources. + // See https://github.com/amphp/amp/issues/177. + if ($this->handle !== null) { + $this->handle->free(); + $this->handle = null; + } + } + + /** + * {@inheritdoc} + */ + public function run() + { + $active = self::$activeSignals; + + \assert($active !== null); + + foreach ($active as $event) { + $event->del(); + } + + self::$activeSignals = &$this->signals; + + foreach ($this->signals as $event) { + /** @psalm-suppress TooFewArguments https://github.com/JetBrains/phpstorm-stubs/pull/763 */ + $event->add(); + } + + try { + parent::run(); + } finally { + foreach ($this->signals as $event) { + $event->del(); + } + + self::$activeSignals = &$active; + + foreach ($active as $event) { + /** @psalm-suppress TooFewArguments https://github.com/JetBrains/phpstorm-stubs/pull/763 */ + $event->add(); + } + } + } + + /** + * {@inheritdoc} + */ + public function stop() + { + $this->handle->stop(); + parent::stop(); + } + + /** + * {@inheritdoc} + */ + public function now(): int + { + $this->now = getCurrentTime() - $this->nowOffset; + + return $this->now; + } + + /** + * {@inheritdoc} + */ + public function getHandle(): \EventBase + { + return $this->handle; + } + + /** + * {@inheritdoc} + * + * @return void + */ + protected function dispatch(bool $blocking) + { + $this->handle->loop($blocking ? \EventBase::LOOP_ONCE : \EventBase::LOOP_ONCE | \EventBase::LOOP_NONBLOCK); + } + + /** + * {@inheritdoc} + * + * @return void + */ + protected function activate(array $watchers) + { + $now = $this->now(); + + foreach ($watchers as $watcher) { + if (!isset($this->events[$id = $watcher->id])) { + switch ($watcher->type) { + case Watcher::READABLE: + \assert(\is_resource($watcher->value)); + + $this->events[$id] = new \Event( + $this->handle, + $watcher->value, + \Event::READ | \Event::PERSIST, + $this->ioCallback, + $watcher + ); + break; + + case Watcher::WRITABLE: + \assert(\is_resource($watcher->value)); + + $this->events[$id] = new \Event( + $this->handle, + $watcher->value, + \Event::WRITE | \Event::PERSIST, + $this->ioCallback, + $watcher + ); + break; + + case Watcher::DELAY: + case Watcher::REPEAT: + \assert(\is_int($watcher->value)); + + $this->events[$id] = new \Event( + $this->handle, + -1, + \Event::TIMEOUT, + $this->timerCallback, + $watcher + ); + break; + + case Watcher::SIGNAL: + \assert(\is_int($watcher->value)); + + $this->events[$id] = new \Event( + $this->handle, + $watcher->value, + \Event::SIGNAL | \Event::PERSIST, + $this->signalCallback, + $watcher + ); + break; + + default: + // @codeCoverageIgnoreStart + throw new \Error("Unknown watcher type"); + // @codeCoverageIgnoreEnd + } + } + + switch ($watcher->type) { + case Watcher::DELAY: + case Watcher::REPEAT: + \assert(\is_int($watcher->value)); + + $interval = \max(0, $watcher->expiration - $now); + $this->events[$id]->add($interval > 0 ? $interval / self::MILLISEC_PER_SEC : 0); + break; + + case Watcher::SIGNAL: + $this->signals[$id] = $this->events[$id]; + // no break + + default: + /** @psalm-suppress TooFewArguments https://github.com/JetBrains/phpstorm-stubs/pull/763 */ + $this->events[$id]->add(); + break; + } + } + } + + /** + * {@inheritdoc} + * + * @return void + */ + protected function deactivate(Watcher $watcher) + { + if (isset($this->events[$id = $watcher->id])) { + $this->events[$id]->del(); + + if ($watcher->type === Watcher::SIGNAL) { + unset($this->signals[$id]); + } + } + } +} diff --git a/lib/composer/amphp/amp/lib/Loop/Internal/TimerQueue.php b/lib/composer/amphp/amp/lib/Loop/Internal/TimerQueue.php new file mode 100644 index 0000000000000000000000000000000000000000..1e51de8ff3e14e9e84ebf8c68cd74b5934070987 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop/Internal/TimerQueue.php @@ -0,0 +1,145 @@ + $watcher + * + * @return void + */ + public function insert(Watcher $watcher) + { + \assert($watcher->expiration !== null); + \assert(!isset($this->pointers[$watcher->id])); + + $entry = new TimerQueueEntry($watcher, $watcher->expiration); + + $node = \count($this->data); + $this->data[$node] = $entry; + $this->pointers[$watcher->id] = $node; + + while ($node !== 0 && $entry->expiration < $this->data[$parent = ($node - 1) >> 1]->expiration) { + $temp = $this->data[$parent]; + $this->data[$node] = $temp; + $this->pointers[$temp->watcher->id] = $node; + + $this->data[$parent] = $entry; + $this->pointers[$watcher->id] = $parent; + + $node = $parent; + } + } + + /** + * Removes the given watcher from the queue. Time complexity: O(log(n)). + * + * @param Watcher $watcher + * + * @psalm-param Watcher $watcher + * + * @return void + */ + public function remove(Watcher $watcher) + { + $id = $watcher->id; + + if (!isset($this->pointers[$id])) { + return; + } + + $this->removeAndRebuild($this->pointers[$id]); + } + + /** + * Deletes and returns the Watcher on top of the heap if it has expired, otherwise null is returned. + * Time complexity: O(log(n)). + * + * @param int $now Current loop time. + * + * @return Watcher|null Expired watcher at the top of the heap or null if the watcher has not expired. + * + * @psalm-return Watcher|null + */ + public function extract(int $now) + { + if (empty($this->data)) { + return null; + } + + $data = $this->data[0]; + + if ($data->expiration > $now) { + return null; + } + + $this->removeAndRebuild(0); + + return $data->watcher; + } + + /** + * Returns the expiration time value at the top of the heap. Time complexity: O(1). + * + * @return int|null Expiration time of the watcher at the top of the heap or null if the heap is empty. + */ + public function peek() + { + return isset($this->data[0]) ? $this->data[0]->expiration : null; + } + + /** + * @param int $node Remove the given node and then rebuild the data array from that node downward. + * + * @return void + */ + private function removeAndRebuild(int $node) + { + $length = \count($this->data) - 1; + $id = $this->data[$node]->watcher->id; + $left = $this->data[$node] = $this->data[$length]; + $this->pointers[$left->watcher->id] = $node; + unset($this->data[$length], $this->pointers[$id]); + + while (($child = ($node << 1) + 1) < $length) { + if ($this->data[$child]->expiration < $this->data[$node]->expiration + && ($child + 1 >= $length || $this->data[$child]->expiration < $this->data[$child + 1]->expiration) + ) { + // Left child is less than parent and right child. + $swap = $child; + } elseif ($child + 1 < $length && $this->data[$child + 1]->expiration < $this->data[$node]->expiration) { + // Right child is less than parent and left child. + $swap = $child + 1; + } else { // Left and right child are greater than parent. + break; + } + + $left = $this->data[$node]; + $right = $this->data[$swap]; + + $this->data[$node] = $right; + $this->pointers[$right->watcher->id] = $node; + + $this->data[$swap] = $left; + $this->pointers[$left->watcher->id] = $swap; + + $node = $swap; + } + } +} diff --git a/lib/composer/amphp/amp/lib/Loop/Internal/TimerQueueEntry.php b/lib/composer/amphp/amp/lib/Loop/Internal/TimerQueueEntry.php new file mode 100644 index 0000000000000000000000000000000000000000..35f5a3d6fa34d82711c202df615024e9e198eeea --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop/Internal/TimerQueueEntry.php @@ -0,0 +1,30 @@ +watcher = $watcher; + $this->expiration = $expiration; + } +} diff --git a/lib/composer/amphp/amp/lib/Loop/InvalidWatcherError.php b/lib/composer/amphp/amp/lib/Loop/InvalidWatcherError.php new file mode 100644 index 0000000000000000000000000000000000000000..4842ecf57de8996f5581672b7bd336fe649b18a4 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop/InvalidWatcherError.php @@ -0,0 +1,32 @@ +watcherId = $watcherId; + parent::__construct($message); + } + + /** + * @return string The watcher identifier. + */ + public function getWatcherId() + { + return $this->watcherId; + } +} diff --git a/lib/composer/amphp/amp/lib/Loop/NativeDriver.php b/lib/composer/amphp/amp/lib/Loop/NativeDriver.php new file mode 100644 index 0000000000000000000000000000000000000000..c5f880f31cdb79926ea058d8ea7d6cb47392cbb5 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop/NativeDriver.php @@ -0,0 +1,394 @@ +timerQueue = new Internal\TimerQueue; + $this->signalHandling = \extension_loaded("pcntl"); + $this->nowOffset = getCurrentTime(); + $this->now = \random_int(0, $this->nowOffset); + $this->nowOffset -= $this->now; + } + + /** + * {@inheritdoc} + * + * @throws \Amp\Loop\UnsupportedFeatureException If the pcntl extension is not available. + */ + public function onSignal(int $signo, callable $callback, $data = null): string + { + if (!$this->signalHandling) { + throw new UnsupportedFeatureException("Signal handling requires the pcntl extension"); + } + + return parent::onSignal($signo, $callback, $data); + } + + /** + * {@inheritdoc} + */ + public function now(): int + { + $this->now = getCurrentTime() - $this->nowOffset; + + return $this->now; + } + + /** + * {@inheritdoc} + */ + public function getHandle() + { + return null; + } + + /** + * @param bool $blocking + * + * @return void + * + * @throws \Throwable + */ + protected function dispatch(bool $blocking) + { + $this->selectStreams( + $this->readStreams, + $this->writeStreams, + $blocking ? $this->getTimeout() : 0 + ); + + $now = $this->now(); + + while ($watcher = $this->timerQueue->extract($now)) { + if ($watcher->type & Watcher::REPEAT) { + $watcher->enabled = false; // Trick base class into adding to enable queue when calling enable() + $this->enable($watcher->id); + } else { + $this->cancel($watcher->id); + } + + try { + // Execute the timer. + $result = ($watcher->callback)($watcher->id, $watcher->data); + + if ($result === null) { + continue; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + } + + if ($this->signalHandling) { + \pcntl_signal_dispatch(); + } + } + + /** + * @param resource[] $read + * @param resource[] $write + * @param int $timeout + * + * @return void + */ + private function selectStreams(array $read, array $write, int $timeout) + { + $timeout /= self::MILLISEC_PER_SEC; + + if (!empty($read) || !empty($write)) { // Use stream_select() if there are any streams in the loop. + if ($timeout >= 0) { + $seconds = (int) $timeout; + $microseconds = (int) (($timeout - $seconds) * self::MICROSEC_PER_SEC); + } else { + $seconds = null; + $microseconds = null; + } + + $except = null; + + // Error reporting suppressed since stream_select() emits an E_WARNING if it is interrupted by a signal. + if (!($result = @\stream_select($read, $write, $except, $seconds, $microseconds))) { + if ($result === 0) { + return; + } + + $error = \error_get_last(); + + if (\strpos($error["message"] ?? '', "unable to select") !== 0) { + return; + } + + $this->error(new \Exception($error["message"] ?? 'Unknown error during stream_select')); + } + + foreach ($read as $stream) { + $streamId = (int) $stream; + if (!isset($this->readWatchers[$streamId])) { + continue; // All read watchers disabled. + } + + foreach ($this->readWatchers[$streamId] as $watcher) { + if (!isset($this->readWatchers[$streamId][$watcher->id])) { + continue; // Watcher disabled by another IO watcher. + } + + try { + $result = ($watcher->callback)($watcher->id, $stream, $watcher->data); + + if ($result === null) { + continue; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + } + } + + \assert(\is_array($write)); // See https://github.com/vimeo/psalm/issues/3036 + + foreach ($write as $stream) { + $streamId = (int) $stream; + if (!isset($this->writeWatchers[$streamId])) { + continue; // All write watchers disabled. + } + + foreach ($this->writeWatchers[$streamId] as $watcher) { + if (!isset($this->writeWatchers[$streamId][$watcher->id])) { + continue; // Watcher disabled by another IO watcher. + } + + try { + $result = ($watcher->callback)($watcher->id, $stream, $watcher->data); + + if ($result === null) { + continue; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + } + } + + return; + } + + if ($timeout > 0) { // Otherwise sleep with usleep() if $timeout > 0. + \usleep((int) ($timeout * self::MICROSEC_PER_SEC)); + } + } + + /** + * @return int Milliseconds until next timer expires or -1 if there are no pending times. + */ + private function getTimeout(): int + { + $expiration = $this->timerQueue->peek(); + + if ($expiration === null) { + return -1; + } + + $expiration -= getCurrentTime() - $this->nowOffset; + + return $expiration > 0 ? $expiration : 0; + } + + /** + * {@inheritdoc} + * + * @return void + */ + protected function activate(array $watchers) + { + foreach ($watchers as $watcher) { + switch ($watcher->type) { + case Watcher::READABLE: + \assert(\is_resource($watcher->value)); + + $streamId = (int) $watcher->value; + $this->readWatchers[$streamId][$watcher->id] = $watcher; + $this->readStreams[$streamId] = $watcher->value; + break; + + case Watcher::WRITABLE: + \assert(\is_resource($watcher->value)); + + $streamId = (int) $watcher->value; + $this->writeWatchers[$streamId][$watcher->id] = $watcher; + $this->writeStreams[$streamId] = $watcher->value; + break; + + case Watcher::DELAY: + case Watcher::REPEAT: + \assert(\is_int($watcher->value)); + $this->timerQueue->insert($watcher); + break; + + case Watcher::SIGNAL: + \assert(\is_int($watcher->value)); + + if (!isset($this->signalWatchers[$watcher->value])) { + if (!@\pcntl_signal($watcher->value, $this->callableFromInstanceMethod('handleSignal'))) { + $message = "Failed to register signal handler"; + if ($error = \error_get_last()) { + $message .= \sprintf("; Errno: %d; %s", $error["type"], $error["message"]); + } + throw new \Error($message); + } + } + + $this->signalWatchers[$watcher->value][$watcher->id] = $watcher; + break; + + default: + // @codeCoverageIgnoreStart + throw new \Error("Unknown watcher type"); + // @codeCoverageIgnoreEnd + } + } + } + + /** + * {@inheritdoc} + * + * @return void + */ + protected function deactivate(Watcher $watcher) + { + switch ($watcher->type) { + case Watcher::READABLE: + $streamId = (int) $watcher->value; + unset($this->readWatchers[$streamId][$watcher->id]); + if (empty($this->readWatchers[$streamId])) { + unset($this->readWatchers[$streamId], $this->readStreams[$streamId]); + } + break; + + case Watcher::WRITABLE: + $streamId = (int) $watcher->value; + unset($this->writeWatchers[$streamId][$watcher->id]); + if (empty($this->writeWatchers[$streamId])) { + unset($this->writeWatchers[$streamId], $this->writeStreams[$streamId]); + } + break; + + case Watcher::DELAY: + case Watcher::REPEAT: + $this->timerQueue->remove($watcher); + break; + + case Watcher::SIGNAL: + \assert(\is_int($watcher->value)); + + if (isset($this->signalWatchers[$watcher->value])) { + unset($this->signalWatchers[$watcher->value][$watcher->id]); + + if (empty($this->signalWatchers[$watcher->value])) { + unset($this->signalWatchers[$watcher->value]); + @\pcntl_signal($watcher->value, \SIG_DFL); + } + } + break; + + default: + // @codeCoverageIgnoreStart + throw new \Error("Unknown watcher type"); + // @codeCoverageIgnoreEnd + } + } + + /** + * @param int $signo + * + * @return void + */ + private function handleSignal(int $signo) + { + foreach ($this->signalWatchers[$signo] as $watcher) { + if (!isset($this->signalWatchers[$signo][$watcher->id])) { + continue; + } + + try { + $result = ($watcher->callback)($watcher->id, $signo, $watcher->data); + + if ($result === null) { + continue; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + } + } +} diff --git a/lib/composer/amphp/amp/lib/Loop/TracingDriver.php b/lib/composer/amphp/amp/lib/Loop/TracingDriver.php new file mode 100644 index 0000000000000000000000000000000000000000..7b787548dd16ea9df156369ff59e325cae116f32 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop/TracingDriver.php @@ -0,0 +1,251 @@ +driver = $driver; + } + + public function run() + { + $this->driver->run(); + } + + public function stop() + { + $this->driver->stop(); + } + + public function defer(callable $callback, $data = null): string + { + $id = $this->driver->defer(function (...$args) use ($callback) { + $this->cancel($args[0]); + return $callback(...$args); + }, $data); + + $this->creationTraces[$id] = formatStacktrace(\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)); + $this->enabledWatchers[$id] = true; + + return $id; + } + + public function delay(int $delay, callable $callback, $data = null): string + { + $id = $this->driver->delay($delay, function (...$args) use ($callback) { + $this->cancel($args[0]); + return $callback(...$args); + }, $data); + + $this->creationTraces[$id] = formatStacktrace(\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)); + $this->enabledWatchers[$id] = true; + + return $id; + } + + public function repeat(int $interval, callable $callback, $data = null): string + { + $id = $this->driver->repeat($interval, $callback, $data); + + $this->creationTraces[$id] = formatStacktrace(\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)); + $this->enabledWatchers[$id] = true; + + return $id; + } + + public function onReadable($stream, callable $callback, $data = null): string + { + $id = $this->driver->onReadable($stream, $callback, $data); + + $this->creationTraces[$id] = formatStacktrace(\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)); + $this->enabledWatchers[$id] = true; + + return $id; + } + + public function onWritable($stream, callable $callback, $data = null): string + { + $id = $this->driver->onWritable($stream, $callback, $data); + + $this->creationTraces[$id] = formatStacktrace(\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)); + $this->enabledWatchers[$id] = true; + + return $id; + } + + public function onSignal(int $signo, callable $callback, $data = null): string + { + $id = $this->driver->onSignal($signo, $callback, $data); + + $this->creationTraces[$id] = formatStacktrace(\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)); + $this->enabledWatchers[$id] = true; + + return $id; + } + + public function enable(string $watcherId) + { + try { + $this->driver->enable($watcherId); + $this->enabledWatchers[$watcherId] = true; + } catch (InvalidWatcherError $e) { + throw new InvalidWatcherError( + $watcherId, + $e->getMessage() . "\r\n\r\n" . $this->getTraces($watcherId) + ); + } + } + + public function cancel(string $watcherId) + { + $this->driver->cancel($watcherId); + + if (!isset($this->cancelTraces[$watcherId])) { + $this->cancelTraces[$watcherId] = formatStacktrace(\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)); + } + + unset($this->enabledWatchers[$watcherId], $this->unreferencedWatchers[$watcherId]); + } + + public function disable(string $watcherId) + { + $this->driver->disable($watcherId); + unset($this->enabledWatchers[$watcherId]); + } + + public function reference(string $watcherId) + { + try { + $this->driver->reference($watcherId); + unset($this->unreferencedWatchers[$watcherId]); + } catch (InvalidWatcherError $e) { + throw new InvalidWatcherError( + $watcherId, + $e->getMessage() . "\r\n\r\n" . $this->getTraces($watcherId) + ); + } + } + + public function unreference(string $watcherId) + { + $this->driver->unreference($watcherId); + $this->unreferencedWatchers[$watcherId] = true; + } + + public function setErrorHandler(callable $callback = null) + { + return $this->driver->setErrorHandler($callback); + } + + /** @inheritdoc */ + public function getHandle() + { + $this->driver->getHandle(); + } + + public function dump(): string + { + $dump = "Enabled, referenced watchers keeping the loop running: "; + + foreach ($this->enabledWatchers as $watcher => $_) { + if (isset($this->unreferencedWatchers[$watcher])) { + continue; + } + + $dump .= "Watcher ID: " . $watcher . "\r\n"; + $dump .= $this->getCreationTrace($watcher); + $dump .= "\r\n\r\n"; + } + + return \rtrim($dump); + } + + public function getInfo(): array + { + return $this->driver->getInfo(); + } + + public function __debugInfo() + { + return $this->driver->__debugInfo(); + } + + public function now(): int + { + return $this->driver->now(); + } + + protected function error(\Throwable $exception) + { + $this->driver->error($exception); + } + + /** + * @inheritdoc + * + * @return void + */ + protected function activate(array $watchers) + { + // nothing to do in a decorator + } + + /** + * @inheritdoc + * + * @return void + */ + protected function dispatch(bool $blocking) + { + // nothing to do in a decorator + } + + /** + * @inheritdoc + * + * @return void + */ + protected function deactivate(Watcher $watcher) + { + // nothing to do in a decorator + } + + private function getTraces(string $watcherId): string + { + return "Creation Trace:\r\n" . $this->getCreationTrace($watcherId) . "\r\n\r\n" . + "Cancellation Trace:\r\n" . $this->getCancelTrace($watcherId); + } + + private function getCreationTrace(string $watcher): string + { + if (!isset($this->creationTraces[$watcher])) { + return 'No creation trace, yet.'; + } + + return $this->creationTraces[$watcher]; + } + + private function getCancelTrace(string $watcher): string + { + if (!isset($this->cancelTraces[$watcher])) { + return 'No cancellation trace, yet.'; + } + + return $this->cancelTraces[$watcher]; + } +} diff --git a/lib/composer/amphp/amp/lib/Loop/UnsupportedFeatureException.php b/lib/composer/amphp/amp/lib/Loop/UnsupportedFeatureException.php new file mode 100644 index 0000000000000000000000000000000000000000..e767cbe176a9c4719eeb58be5a6d214b825488da --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop/UnsupportedFeatureException.php @@ -0,0 +1,12 @@ +handle = \uv_loop_new(); + + /** + * @param $event + * @param $status + * @param $events + * @param $resource + * + * @return void + */ + $this->ioCallback = function ($event, $status, $events, $resource) { + $watchers = $this->watchers[(int) $event]; + + switch ($status) { + case 0: // OK + break; + + default: // Invoke the callback on errors, as this matches behavior with other loop back-ends. + // Re-enable watcher as libuv disables the watcher on non-zero status. + $flags = 0; + foreach ($watchers as $watcher) { + $flags |= $watcher->enabled ? $watcher->type : 0; + } + \uv_poll_start($event, $flags, $this->ioCallback); + break; + } + + foreach ($watchers as $watcher) { + // $events is OR'ed with 4 to trigger watcher if no events are indicated (0) or on UV_DISCONNECT (4). + // http://docs.libuv.org/en/v1.x/poll.html + if (!($watcher->enabled && ($watcher->type & $events || ($events | 4) === 4))) { + continue; + } + + try { + $result = ($watcher->callback)($watcher->id, $resource, $watcher->data); + + if ($result === null) { + continue; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + } + }; + + /** + * @param $event + * + * @return void + */ + $this->timerCallback = function ($event) { + $watcher = $this->watchers[(int) $event][0]; + + if ($watcher->type & Watcher::DELAY) { + unset($this->events[$watcher->id], $this->watchers[(int) $event]); // Avoid call to uv_is_active(). + $this->cancel($watcher->id); // Remove reference to watcher in parent. + } elseif ($watcher->value === 0) { + // Disable and re-enable so it's not executed repeatedly in the same tick + // See https://github.com/amphp/amp/issues/131 + $this->disable($watcher->id); + $this->enable($watcher->id); + } + + try { + $result = ($watcher->callback)($watcher->id, $watcher->data); + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + }; + + /** + * @param $event + * @param $signo + * + * @return void + */ + $this->signalCallback = function ($event, $signo) { + $watcher = $this->watchers[(int) $event][0]; + + try { + $result = ($watcher->callback)($watcher->id, $signo, $watcher->data); + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + rethrow($result); + } + } catch (\Throwable $exception) { + $this->error($exception); + } + }; + } + + /** + * {@inheritdoc} + */ + public function cancel(string $watcherId) + { + parent::cancel($watcherId); + + if (!isset($this->events[$watcherId])) { + return; + } + + $event = $this->events[$watcherId]; + $eventId = (int) $event; + + if (isset($this->watchers[$eventId][0])) { // All except IO watchers. + unset($this->watchers[$eventId]); + } elseif (isset($this->watchers[$eventId][$watcherId])) { + $watcher = $this->watchers[$eventId][$watcherId]; + unset($this->watchers[$eventId][$watcherId]); + + if (empty($this->watchers[$eventId])) { + unset($this->watchers[$eventId], $this->streams[(int) $watcher->value]); + } + } + + unset($this->events[$watcherId]); + } + + public static function isSupported(): bool + { + return \extension_loaded("uv"); + } + + /** + * {@inheritdoc} + */ + public function now(): int + { + \uv_update_time($this->handle); + + /** @psalm-suppress TooManyArguments */ + return \uv_now($this->handle); + } + + /** + * {@inheritdoc} + */ + public function getHandle() + { + return $this->handle; + } + + /** + * {@inheritdoc} + * + * @return void + */ + protected function dispatch(bool $blocking) + { + /** @psalm-suppress TooManyArguments */ + \uv_run($this->handle, $blocking ? \UV::RUN_ONCE : \UV::RUN_NOWAIT); + } + + /** + * {@inheritdoc} + * + * @return void + */ + protected function activate(array $watchers) + { + $now = $this->now(); + + foreach ($watchers as $watcher) { + $id = $watcher->id; + + switch ($watcher->type) { + case Watcher::READABLE: + case Watcher::WRITABLE: + \assert(\is_resource($watcher->value)); + + $streamId = (int) $watcher->value; + + if (isset($this->streams[$streamId])) { + $event = $this->streams[$streamId]; + } elseif (isset($this->events[$id])) { + $event = $this->streams[$streamId] = $this->events[$id]; + } else { + /** @psalm-suppress UndefinedFunction */ + $event = $this->streams[$streamId] = \uv_poll_init_socket($this->handle, $watcher->value); + } + + $eventId = (int) $event; + $this->events[$id] = $event; + $this->watchers[$eventId][$id] = $watcher; + + $flags = 0; + foreach ($this->watchers[$eventId] as $w) { + $flags |= $w->enabled ? $w->type : 0; + } + \uv_poll_start($event, $flags, $this->ioCallback); + break; + + case Watcher::DELAY: + case Watcher::REPEAT: + \assert(\is_int($watcher->value)); + + if (isset($this->events[$id])) { + $event = $this->events[$id]; + } else { + $event = $this->events[$id] = \uv_timer_init($this->handle); + } + + $this->watchers[(int) $event] = [$watcher]; + + \uv_timer_start( + $event, + \max(0, $watcher->expiration - $now), + ($watcher->type & Watcher::REPEAT) ? $watcher->value : 0, + $this->timerCallback + ); + break; + + case Watcher::SIGNAL: + \assert(\is_int($watcher->value)); + + if (isset($this->events[$id])) { + $event = $this->events[$id]; + } else { + /** @psalm-suppress UndefinedFunction */ + $event = $this->events[$id] = \uv_signal_init($this->handle); + } + + $this->watchers[(int) $event] = [$watcher]; + + /** @psalm-suppress UndefinedFunction */ + \uv_signal_start($event, $this->signalCallback, $watcher->value); + break; + + default: + // @codeCoverageIgnoreStart + throw new \Error("Unknown watcher type"); + // @codeCoverageIgnoreEnd + } + } + } + + /** + * {@inheritdoc} + * + * @return void + */ + protected function deactivate(Watcher $watcher) + { + $id = $watcher->id; + + if (!isset($this->events[$id])) { + return; + } + + $event = $this->events[$id]; + + if (!\uv_is_active($event)) { + return; + } + + switch ($watcher->type) { + case Watcher::READABLE: + case Watcher::WRITABLE: + $flags = 0; + foreach ($this->watchers[(int) $event] as $w) { + $flags |= $w->enabled ? $w->type : 0; + } + + if ($flags) { + \uv_poll_start($event, $flags, $this->ioCallback); + } else { + \uv_poll_stop($event); + } + break; + + case Watcher::DELAY: + case Watcher::REPEAT: + \uv_timer_stop($event); + break; + + case Watcher::SIGNAL: + \uv_signal_stop($event); + break; + + default: + // @codeCoverageIgnoreStart + throw new \Error("Unknown watcher type"); + // @codeCoverageIgnoreEnd + } + } +} diff --git a/lib/composer/amphp/amp/lib/Loop/Watcher.php b/lib/composer/amphp/amp/lib/Loop/Watcher.php new file mode 100644 index 0000000000000000000000000000000000000000..4d16f9b9c098f077c24ba284e6013c2bd7314ea8 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Loop/Watcher.php @@ -0,0 +1,57 @@ +reasons = $reasons; + } + + /** + * @return \Throwable[] + */ + public function getReasons(): array + { + return $this->reasons; + } +} diff --git a/lib/composer/amphp/amp/lib/NullCancellationToken.php b/lib/composer/amphp/amp/lib/NullCancellationToken.php new file mode 100644 index 0000000000000000000000000000000000000000..66faeba13a6ea98cfc6fd8dda8cf0d55b3292def --- /dev/null +++ b/lib/composer/amphp/amp/lib/NullCancellationToken.php @@ -0,0 +1,53 @@ +throwIfRequested(); + * } + * ``` + * + * potentially multiple times, it allows writing + * + * ```php + * $token = $token ?? new NullCancellationToken; + * + * // ... + * + * $token->throwIfRequested(); + * ``` + * + * instead. + */ +final class NullCancellationToken implements CancellationToken +{ + /** @inheritdoc */ + public function subscribe(callable $callback): string + { + return "null-token"; + } + + /** @inheritdoc */ + public function unsubscribe(string $id) + { + // nothing to do + } + + /** @inheritdoc */ + public function isRequested(): bool + { + return false; + } + + /** @inheritdoc */ + public function throwIfRequested() + { + // nothing to do + } +} diff --git a/lib/composer/amphp/amp/lib/Producer.php b/lib/composer/amphp/amp/lib/Producer.php new file mode 100644 index 0000000000000000000000000000000000000000..35b88c912fc4aa50e03210e435453e677b044036 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Producer.php @@ -0,0 +1,43 @@ + + */ +final class Producer implements Iterator +{ + /** + * @use Internal\Producer + */ + use CallableMaker, Internal\Producer; + + /** + * @param callable(callable(TValue):Promise):\Generator $producer + * + * @throws \Error Thrown if the callable does not return a Generator. + */ + public function __construct(callable $producer) + { + $result = $producer($this->callableFromInstanceMethod("emit")); + + if (!$result instanceof \Generator) { + throw new \Error("The callable did not return a Generator"); + } + + $coroutine = new Coroutine($result); + $coroutine->onResolve(function ($exception) { + if ($this->complete) { + return; + } + + if ($exception) { + $this->fail($exception); + return; + } + + $this->complete(); + }); + } +} diff --git a/lib/composer/amphp/amp/lib/Promise.php b/lib/composer/amphp/amp/lib/Promise.php new file mode 100644 index 0000000000000000000000000000000000000000..2f7e824e984721aefca12a04e48b73e431540517 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Promise.php @@ -0,0 +1,37 @@ +, mixed, + * mixed>|null) | callable(\Throwable|null, mixed): void $onResolved + * + * @return void + */ + public function onResolve(callable $onResolved); +} diff --git a/lib/composer/amphp/amp/lib/Struct.php b/lib/composer/amphp/amp/lib/Struct.php new file mode 100644 index 0000000000000000000000000000000000000000..0cb2563c6acdf9dcd3dcaa569ecd7b10e984b5bc --- /dev/null +++ b/lib/composer/amphp/amp/lib/Struct.php @@ -0,0 +1,78 @@ +generateStructPropertyError($property) + ); + } + + /** + * @param string $property + * @param mixed $value + * + * @psalm-return no-return + */ + public function __set(string $property, $value) + { + throw new \Error( + $this->generateStructPropertyError($property) + ); + } + + private function generateStructPropertyError(string $property): string + { + $suggestion = $this->suggestPropertyName($property); + $suggestStr = ($suggestion == "") ? "" : " ... did you mean \"{$suggestion}?\""; + + return \sprintf( + "%s property \"%s\" does not exist%s", + \str_replace("\0", "@", \get_class($this)), // Handle anonymous class names. + $property, + $suggestStr + ); + } + + private function suggestPropertyName(string $badProperty): string + { + $badProperty = \strtolower($badProperty); + $bestMatch = ""; + $bestMatchPercentage = 0; + + /** @psalm-suppress RawObjectIteration */ + foreach ($this as $property => $value) { + // Never suggest properties that begin with an underscore + if ($property[0] === "_") { + continue; + } + \similar_text($badProperty, \strtolower($property), $byRefPercentage); + if ($byRefPercentage > $bestMatchPercentage) { + $bestMatchPercentage = $byRefPercentage; + $bestMatch = $property; + } + } + + return ($bestMatchPercentage >= $this->__propertySuggestThreshold) ? $bestMatch : ""; + } +} diff --git a/lib/composer/amphp/amp/lib/Success.php b/lib/composer/amphp/amp/lib/Success.php new file mode 100644 index 0000000000000000000000000000000000000000..1817c5a2c52ba29cfdac6f1b1ce76e424a290052 --- /dev/null +++ b/lib/composer/amphp/amp/lib/Success.php @@ -0,0 +1,60 @@ + + */ +final class Success implements Promise +{ + /** @var mixed */ + private $value; + + /** + * @param mixed $value Anything other than a Promise object. + * + * @psalm-param TValue $value + * + * @throws \Error If a promise is given as the value. + */ + public function __construct($value = null) + { + if ($value instanceof Promise || $value instanceof ReactPromise) { + throw new \Error("Cannot use a promise as success value"); + } + + $this->value = $value; + } + + /** + * {@inheritdoc} + */ + public function onResolve(callable $onResolved) + { + try { + $result = $onResolved(null, $this->value); + + if ($result === null) { + return; + } + + if ($result instanceof \Generator) { + $result = new Coroutine($result); + } + + if ($result instanceof Promise || $result instanceof ReactPromise) { + Promise\rethrow($result); + } + } catch (\Throwable $exception) { + Loop::defer(static function () use ($exception) { + throw $exception; + }); + } + } +} diff --git a/lib/composer/amphp/amp/lib/TimeoutCancellationToken.php b/lib/composer/amphp/amp/lib/TimeoutCancellationToken.php new file mode 100644 index 0000000000000000000000000000000000000000..4c46ceb0693d371ca00ba59a5200f1f719c4ae92 --- /dev/null +++ b/lib/composer/amphp/amp/lib/TimeoutCancellationToken.php @@ -0,0 +1,75 @@ +token = $source->getToken(); + + $trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); + $this->watcher = Loop::delay($timeout, static function () use ($source, $message, $trace) { + $trace = formatStacktrace($trace); + $source->cancel(new TimeoutException("$message\r\nTimeoutCancellationToken was created here:\r\n$trace")); + }); + + Loop::unreference($this->watcher); + } + + /** + * Cancels the delay watcher. + */ + public function __destruct() + { + Loop::cancel($this->watcher); + } + + /** + * {@inheritdoc} + */ + public function subscribe(callable $callback): string + { + return $this->token->subscribe($callback); + } + + /** + * {@inheritdoc} + */ + public function unsubscribe(string $id) + { + $this->token->unsubscribe($id); + } + + /** + * {@inheritdoc} + */ + public function isRequested(): bool + { + return $this->token->isRequested(); + } + + /** + * {@inheritdoc} + */ + public function throwIfRequested() + { + $this->token->throwIfRequested(); + } +} diff --git a/lib/composer/amphp/amp/lib/TimeoutException.php b/lib/composer/amphp/amp/lib/TimeoutException.php new file mode 100644 index 0000000000000000000000000000000000000000..dc7fba9ed816fdf79b98aeacf1a5a444fcf876d0 --- /dev/null +++ b/lib/composer/amphp/amp/lib/TimeoutException.php @@ -0,0 +1,19 @@ + + * @template T as TReturn|Promise|\Generator + * + * @formatter:off + * + * @param callable(...mixed): T $callback + * + * @return callable + * @psalm-return (T is Promise ? (callable(mixed...): Promise) : (T is \Generator ? (TGenerator is Promise ? (callable(mixed...): Promise) : (callable(mixed...): Promise)) : (callable(mixed...): Promise))) + * + * @formatter:on + * + * @see asyncCoroutine() + * + * @psalm-suppress InvalidReturnType + */ + function coroutine(callable $callback): callable + { + /** @psalm-suppress InvalidReturnStatement */ + return static function (...$args) use ($callback): Promise { + return call($callback, ...$args); + }; + } + + /** + * Returns a new function that wraps $callback in a promise/coroutine-aware function that automatically runs + * Generators as coroutines. The returned function always returns void when invoked. Errors are forwarded to the + * loop's error handler using `Amp\Promise\rethrow()`. + * + * Use this function to create a coroutine-aware callable for a non-promise-aware callback caller. + * + * @param callable(...mixed): mixed $callback + * + * @return callable + * @psalm-return callable(mixed...): void + * + * @see coroutine() + */ + function asyncCoroutine(callable $callback): callable + { + return static function (...$args) use ($callback) { + Promise\rethrow(call($callback, ...$args)); + }; + } + + /** + * Calls the given function, always returning a promise. If the function returns a Generator, it will be run as a + * coroutine. If the function throws, a failed promise will be returned. + * + * @template TReturn + * @template TPromise + * @template TGeneratorReturn + * @template TGeneratorPromise + * + * @template TGenerator as TGeneratorReturn|Promise + * @template T as TReturn|Promise|\Generator + * + * @formatter:off + * + * @param callable(...mixed): T $callback + * @param mixed ...$args Arguments to pass to the function. + * + * @return Promise + * @psalm-return (T is Promise ? Promise : (T is \Generator ? (TGenerator is Promise ? Promise : Promise) : Promise)) + * + * @formatter:on + */ + function call(callable $callback, ...$args): Promise + { + try { + $result = $callback(...$args); + } catch (\Throwable $exception) { + return new Failure($exception); + } + + if ($result instanceof \Generator) { + return new Coroutine($result); + } + + if ($result instanceof Promise) { + return $result; + } + + if ($result instanceof ReactPromise) { + return Promise\adapt($result); + } + + return new Success($result); + } + + /** + * Calls the given function. If the function returns a Generator, it will be run as a coroutine. If the function + * throws or returns a failing promise, the failure is forwarded to the loop error handler. + * + * @param callable(...mixed): mixed $callback + * @param mixed ...$args Arguments to pass to the function. + * + * @return void + */ + function asyncCall(callable $callback, ...$args) + { + Promise\rethrow(call($callback, ...$args)); + } + + /** + * Sleeps for the specified number of milliseconds. + * + * @param int $milliseconds + * + * @return Delayed + */ + function delay(int $milliseconds): Delayed + { + return new Delayed($milliseconds); + } + + /** + * Returns the current time relative to an arbitrary point in time. + * + * @return int Time in milliseconds. + */ + function getCurrentTime(): int + { + return Internal\getCurrentTime(); + } +} + +namespace Amp\Promise +{ + + use Amp\Deferred; + use Amp\Loop; + use Amp\MultiReasonException; + use Amp\Promise; + use Amp\Success; + use Amp\TimeoutException; + use React\Promise\PromiseInterface as ReactPromise; + use function Amp\call; + use function Amp\Internal\createTypeError; + + /** + * Registers a callback that will forward the failure reason to the event loop's error handler if the promise fails. + * + * Use this function if you neither return the promise nor handle a possible error yourself to prevent errors from + * going entirely unnoticed. + * + * @param Promise|ReactPromise $promise Promise to register the handler on. + * + * @return void + * @throws \TypeError If $promise is not an instance of \Amp\Promise or \React\Promise\PromiseInterface. + * + */ + function rethrow($promise) + { + if (!$promise instanceof Promise) { + if ($promise instanceof ReactPromise) { + $promise = adapt($promise); + } else { + throw createTypeError([Promise::class, ReactPromise::class], $promise); + } + } + + $promise->onResolve(static function ($exception) { + if ($exception) { + throw $exception; + } + }); + } + + /** + * Runs the event loop until the promise is resolved. Should not be called within a running event loop. + * + * Use this function only in synchronous contexts to wait for an asynchronous operation. Use coroutines and yield to + * await promise resolution in a fully asynchronous application instead. + * + * @template TPromise + * @template T as Promise|ReactPromise + * + * @param Promise|ReactPromise $promise Promise to wait for. + * + * @return mixed Promise success value. + * + * @psalm-param T $promise + * @psalm-return (T is Promise ? TPromise : mixed) + * + * @throws \TypeError If $promise is not an instance of \Amp\Promise or \React\Promise\PromiseInterface. + * @throws \Error If the event loop stopped without the $promise being resolved. + * @throws \Throwable Promise failure reason. + */ + function wait($promise) + { + if (!$promise instanceof Promise) { + if ($promise instanceof ReactPromise) { + $promise = adapt($promise); + } else { + throw createTypeError([Promise::class, ReactPromise::class], $promise); + } + } + + $resolved = false; + + try { + Loop::run(function () use (&$resolved, &$value, &$exception, $promise) { + $promise->onResolve(function ($e, $v) use (&$resolved, &$value, &$exception) { + Loop::stop(); + $resolved = true; + $exception = $e; + $value = $v; + }); + }); + } catch (\Throwable $throwable) { + throw new \Error("Loop exceptionally stopped without resolving the promise", 0, $throwable); + } + + if (!$resolved) { + throw new \Error("Loop stopped without resolving the promise"); + } + + if ($exception) { + throw $exception; + } + + return $value; + } + + /** + * Creates an artificial timeout for any `Promise`. + * + * If the timeout expires before the promise is resolved, the returned promise fails with an instance of + * `Amp\TimeoutException`. + * + * @template TReturn + * + * @param Promise|ReactPromise $promise Promise to which the timeout is applied. + * @param int $timeout Timeout in milliseconds. + * + * @return Promise + * + * @throws \TypeError If $promise is not an instance of \Amp\Promise or \React\Promise\PromiseInterface. + */ + function timeout($promise, int $timeout): Promise + { + if (!$promise instanceof Promise) { + if ($promise instanceof ReactPromise) { + $promise = adapt($promise); + } else { + throw createTypeError([Promise::class, ReactPromise::class], $promise); + } + } + + $deferred = new Deferred; + + $watcher = Loop::delay($timeout, static function () use (&$deferred) { + $temp = $deferred; // prevent double resolve + $deferred = null; + $temp->fail(new TimeoutException); + }); + Loop::unreference($watcher); + + $promise->onResolve(function () use (&$deferred, $promise, $watcher) { + if ($deferred !== null) { + Loop::cancel($watcher); + $deferred->resolve($promise); + } + }); + + return $deferred->promise(); + } + + /** + * Creates an artificial timeout for any `Promise`. + * + * If the promise is resolved before the timeout expires, the result is returned + * + * If the timeout expires before the promise is resolved, a default value is returned + * + * @template TReturn + * + * @param Promise|ReactPromise $promise Promise to which the timeout is applied. + * @param int $timeout Timeout in milliseconds. + * @param TReturn $default + * + * @return Promise + * + * @throws \TypeError If $promise is not an instance of \Amp\Promise or \React\Promise\PromiseInterface. + */ + function timeoutWithDefault($promise, int $timeout, $default = null): Promise + { + $promise = timeout($promise, $timeout); + + return call(static function () use ($promise, $default) { + try { + return yield $promise; + } catch (TimeoutException $exception) { + return $default; + } + }); + } + + /** + * Adapts any object with a done(callable $onFulfilled, callable $onRejected) or then(callable $onFulfilled, + * callable $onRejected) method to a promise usable by components depending on placeholders implementing + * \AsyncInterop\Promise. + * + * @param object $promise Object with a done() or then() method. + * + * @return Promise Promise resolved by the $thenable object. + * + * @throws \Error If the provided object does not have a then() method. + */ + function adapt($promise): Promise + { + $deferred = new Deferred; + + if (\method_exists($promise, 'done')) { + $promise->done([$deferred, 'resolve'], [$deferred, 'fail']); + } elseif (\method_exists($promise, 'then')) { + $promise->then([$deferred, 'resolve'], [$deferred, 'fail']); + } else { + throw new \Error("Object must have a 'then' or 'done' method"); + } + + return $deferred->promise(); + } + + /** + * Returns a promise that is resolved when all promises are resolved. The returned promise will not fail. + * Returned promise succeeds with a two-item array delineating successful and failed promise results, + * with keys identical and corresponding to the original given array. + * + * This function is the same as some() with the notable exception that it will never fail even + * if all promises in the array resolve unsuccessfully. + * + * @param Promise[]|ReactPromise[] $promises + * + * @return Promise + * + * @throws \Error If a non-Promise is in the array. + */ + function any(array $promises): Promise + { + return some($promises, 0); + } + + /** + * Returns a promise that succeeds when all promises succeed, and fails if any promise fails. Returned + * promise succeeds with an array of values used to succeed each contained promise, with keys corresponding to + * the array of promises. + * + * @param Promise[]|ReactPromise[] $promises Array of only promises. + * + * @return Promise + * + * @throws \Error If a non-Promise is in the array. + * + * @template TValue + * + * @psalm-param array|ReactPromise> $promises + * @psalm-assert array|ReactPromise> $promises $promises + * @psalm-return Promise> + */ + function all(array $promises): Promise + { + if (empty($promises)) { + return new Success([]); + } + + $deferred = new Deferred; + $result = $deferred->promise(); + + $pending = \count($promises); + $values = []; + + foreach ($promises as $key => $promise) { + if ($promise instanceof ReactPromise) { + $promise = adapt($promise); + } elseif (!$promise instanceof Promise) { + throw createTypeError([Promise::class, ReactPromise::class], $promise); + } + + $values[$key] = null; // add entry to array to preserve order + $promise->onResolve(function ($exception, $value) use (&$deferred, &$values, &$pending, $key) { + if ($pending === 0) { + return; + } + + if ($exception) { + $pending = 0; + $deferred->fail($exception); + $deferred = null; + return; + } + + $values[$key] = $value; + if (0 === --$pending) { + $deferred->resolve($values); + } + }); + } + + return $result; + } + + /** + * Returns a promise that succeeds when the first promise succeeds, and fails only if all promises fail. + * + * @param Promise[]|ReactPromise[] $promises Array of only promises. + * + * @return Promise + * + * @throws \Error If the array is empty or a non-Promise is in the array. + */ + function first(array $promises): Promise + { + if (empty($promises)) { + throw new \Error("No promises provided"); + } + + $deferred = new Deferred; + $result = $deferred->promise(); + + $pending = \count($promises); + $exceptions = []; + + foreach ($promises as $key => $promise) { + if ($promise instanceof ReactPromise) { + $promise = adapt($promise); + } elseif (!$promise instanceof Promise) { + throw createTypeError([Promise::class, ReactPromise::class], $promise); + } + + $exceptions[$key] = null; // add entry to array to preserve order + $promise->onResolve(function ($error, $value) use (&$deferred, &$exceptions, &$pending, $key) { + if ($pending === 0) { + return; + } + + if (!$error) { + $pending = 0; + $deferred->resolve($value); + $deferred = null; + return; + } + + $exceptions[$key] = $error; + if (0 === --$pending) { + $deferred->fail(new MultiReasonException($exceptions)); + } + }); + } + + return $result; + } + + /** + * Resolves with a two-item array delineating successful and failed Promise results. + * + * The returned promise will only fail if the given number of required promises fail. + * + * @param Promise[]|ReactPromise[] $promises Array of only promises. + * @param int $required Number of promises that must succeed for the + * returned promise to succeed. + * + * @return Promise + * + * @throws \Error If a non-Promise is in the array. + */ + function some(array $promises, int $required = 1): Promise + { + if ($required < 0) { + throw new \Error("Number of promises required must be non-negative"); + } + + $pending = \count($promises); + + if ($required > $pending) { + throw new \Error("Too few promises provided"); + } + + if (empty($promises)) { + return new Success([[], []]); + } + + $deferred = new Deferred; + $result = $deferred->promise(); + $values = []; + $exceptions = []; + + foreach ($promises as $key => $promise) { + if ($promise instanceof ReactPromise) { + $promise = adapt($promise); + } elseif (!$promise instanceof Promise) { + throw createTypeError([Promise::class, ReactPromise::class], $promise); + } + + $values[$key] = $exceptions[$key] = null; // add entry to arrays to preserve order + $promise->onResolve(static function ($exception, $value) use ( + &$values, + &$exceptions, + &$pending, + $key, + $required, + $deferred + ) { + if ($exception) { + $exceptions[$key] = $exception; + unset($values[$key]); + } else { + $values[$key] = $value; + unset($exceptions[$key]); + } + + if (0 === --$pending) { + if (\count($values) < $required) { + $deferred->fail(new MultiReasonException($exceptions)); + } else { + $deferred->resolve([$exceptions, $values]); + } + } + }); + } + + return $result; + } + + /** + * Wraps a promise into another promise, altering the exception or result. + * + * @param Promise|ReactPromise $promise + * @param callable $callback + * + * @return Promise + */ + function wrap($promise, callable $callback): Promise + { + if ($promise instanceof ReactPromise) { + $promise = adapt($promise); + } elseif (!$promise instanceof Promise) { + throw createTypeError([Promise::class, ReactPromise::class], $promise); + } + + $deferred = new Deferred(); + + $promise->onResolve(static function (\Throwable $exception = null, $result) use ($deferred, $callback) { + try { + $result = $callback($exception, $result); + } catch (\Throwable $exception) { + $deferred->fail($exception); + + return; + } + + $deferred->resolve($result); + }); + + return $deferred->promise(); + } +} + +namespace Amp\Iterator +{ + + use Amp\Delayed; + use Amp\Emitter; + use Amp\Iterator; + use Amp\Producer; + use Amp\Promise; + use function Amp\call; + use function Amp\coroutine; + use function Amp\Internal\createTypeError; + + /** + * Creates an iterator from the given iterable, emitting the each value. The iterable may contain promises. If any + * promise fails, the iterator will fail with the same reason. + * + * @param array|\Traversable $iterable Elements to emit. + * @param int $delay Delay between element emissions in milliseconds. + * + * @return Iterator + * + * @throws \TypeError If the argument is not an array or instance of \Traversable. + */ + function fromIterable(/* iterable */ + $iterable, + int $delay = 0 + ): Iterator { + if (!$iterable instanceof \Traversable && !\is_array($iterable)) { + throw createTypeError(["array", "Traversable"], $iterable); + } + + if ($delay) { + return new Producer(static function (callable $emit) use ($iterable, $delay) { + foreach ($iterable as $value) { + yield new Delayed($delay); + yield $emit($value); + } + }); + } + + return new Producer(static function (callable $emit) use ($iterable) { + foreach ($iterable as $value) { + yield $emit($value); + } + }); + } + + /** + * @template TValue + * @template TReturn + * + * @param Iterator $iterator + * @param callable (TValue $value): TReturn $onEmit + * + * @return Iterator + */ + function map(Iterator $iterator, callable $onEmit): Iterator + { + return new Producer(static function (callable $emit) use ($iterator, $onEmit) { + while (yield $iterator->advance()) { + yield $emit($onEmit($iterator->getCurrent())); + } + }); + } + + /** + * @template TValue + * + * @param Iterator $iterator + * @param callable(TValue $value):bool $filter + * + * @return Iterator + */ + function filter(Iterator $iterator, callable $filter): Iterator + { + return new Producer(static function (callable $emit) use ($iterator, $filter) { + while (yield $iterator->advance()) { + if ($filter($iterator->getCurrent())) { + yield $emit($iterator->getCurrent()); + } + } + }); + } + + /** + * Creates an iterator that emits values emitted from any iterator in the array of iterators. + * + * @param Iterator[] $iterators + * + * @return Iterator + */ + function merge(array $iterators): Iterator + { + $emitter = new Emitter; + $result = $emitter->iterate(); + + $coroutine = coroutine(static function (Iterator $iterator) use (&$emitter) { + while ((yield $iterator->advance()) && $emitter !== null) { + yield $emitter->emit($iterator->getCurrent()); + } + }); + + $coroutines = []; + foreach ($iterators as $iterator) { + if (!$iterator instanceof Iterator) { + throw createTypeError([Iterator::class], $iterator); + } + + $coroutines[] = $coroutine($iterator); + } + + Promise\all($coroutines)->onResolve(static function ($exception) use (&$emitter) { + if ($exception) { + $emitter->fail($exception); + $emitter = null; + } else { + $emitter->complete(); + } + }); + + return $result; + } + + /** + * Concatenates the given iterators into a single iterator, emitting values from a single iterator at a time. The + * prior iterator must complete before values are emitted from any subsequent iterators. Iterators are concatenated + * in the order given (iteration order of the array). + * + * @param Iterator[] $iterators + * + * @return Iterator + */ + function concat(array $iterators): Iterator + { + foreach ($iterators as $iterator) { + if (!$iterator instanceof Iterator) { + throw createTypeError([Iterator::class], $iterator); + } + } + + $emitter = new Emitter; + $previous = []; + $promise = Promise\all($previous); + + $coroutine = coroutine(static function (Iterator $iterator, callable $emit) { + while (yield $iterator->advance()) { + yield $emit($iterator->getCurrent()); + } + }); + + foreach ($iterators as $iterator) { + $emit = coroutine(static function ($value) use ($emitter, $promise) { + static $pending = true, $failed = false; + + if ($failed) { + return; + } + + if ($pending) { + try { + yield $promise; + $pending = false; + } catch (\Throwable $exception) { + $failed = true; + return; // Prior iterator failed. + } + } + + yield $emitter->emit($value); + }); + $previous[] = $coroutine($iterator, $emit); + $promise = Promise\all($previous); + } + + $promise->onResolve(static function ($exception) use ($emitter) { + if ($exception) { + $emitter->fail($exception); + return; + } + + $emitter->complete(); + }); + + return $emitter->iterate(); + } + + /** + * Discards all remaining items and returns the number of discarded items. + * + * @template TValue + * + * @param Iterator $iterator + * + * @return Promise + * + * @psalm-param Iterator $iterator + * @psalm-return Promise + */ + function discard(Iterator $iterator): Promise + { + return call(static function () use ($iterator): \Generator { + $count = 0; + + while (yield $iterator->advance()) { + $count++; + } + + return $count; + }); + } + + /** + * Collects all items from an iterator into an array. + * + * @template TValue + * + * @param Iterator $iterator + * + * @psalm-param Iterator $iterator + * + * @return Promise + * @psalm-return Promise> + */ + function toArray(Iterator $iterator): Promise + { + return call(static function () use ($iterator) { + /** @psalm-var list $array */ + $array = []; + + while (yield $iterator->advance()) { + $array[] = $iterator->getCurrent(); + } + + return $array; + }); + } +} diff --git a/lib/composer/amphp/amp/psalm.examples.xml b/lib/composer/amphp/amp/psalm.examples.xml new file mode 100644 index 0000000000000000000000000000000000000000..2ac9d74911774cb7d37358b7627067fa290cebe9 --- /dev/null +++ b/lib/composer/amphp/amp/psalm.examples.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/lib/composer/amphp/amp/psalm.xml b/lib/composer/amphp/amp/psalm.xml new file mode 100644 index 0000000000000000000000000000000000000000..ad5dd774f144f05ba885c41947d490f0dca288b2 --- /dev/null +++ b/lib/composer/amphp/amp/psalm.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/composer/amphp/byte-stream/LICENSE b/lib/composer/amphp/byte-stream/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..b2f91e7cff7dd47bb7c6ae5986a40cf61d151556 --- /dev/null +++ b/lib/composer/amphp/byte-stream/LICENSE @@ -0,0 +1,22 @@ + +The MIT License (MIT) + +Copyright (c) 2016-2020 amphp + +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/lib/composer/amphp/byte-stream/composer.json b/lib/composer/amphp/byte-stream/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..0315674b9d6b11e7d277fc3e2fd6d1513dabcf93 --- /dev/null +++ b/lib/composer/amphp/byte-stream/composer.json @@ -0,0 +1,58 @@ +{ + "name": "amphp/byte-stream", + "homepage": "http://amphp.org/byte-stream", + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "support": { + "issues": "https://github.com/amphp/byte-stream/issues", + "irc": "irc://irc.freenode.org/amphp" + }, + "keywords": [ + "stream", + "async", + "non-blocking", + "amp", + "amphp", + "io" + ], + "license": "MIT", + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "require": { + "php": ">=7.1", + "amphp/amp": "^2" + }, + "require-dev": { + "amphp/phpunit-util": "^1.4", + "phpunit/phpunit": "^6 || ^7 || ^8", + "friendsofphp/php-cs-fixer": "^2.3", + "amphp/php-cs-fixer-config": "dev-master", + "psalm/phar": "^3.11.4", + "jetbrains/phpstorm-stubs": "^2019.3" + }, + "autoload": { + "psr-4": { + "Amp\\ByteStream\\": "lib" + }, + "files": [ + "lib/functions.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Amp\\ByteStream\\Test\\": "test" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + } +} diff --git a/lib/composer/amphp/byte-stream/lib/Base64/Base64DecodingInputStream.php b/lib/composer/amphp/byte-stream/lib/Base64/Base64DecodingInputStream.php new file mode 100644 index 0000000000000000000000000000000000000000..294287d2447fccd350dde0b851e5db23c420835d --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/Base64/Base64DecodingInputStream.php @@ -0,0 +1,65 @@ +source = $source; + } + + public function read(): Promise + { + return call(function () { + if ($this->source === null) { + throw new StreamException('Failed to read stream chunk due to invalid base64 data'); + } + + $chunk = yield $this->source->read(); + if ($chunk === null) { + if ($this->buffer === null) { + return null; + } + + $chunk = \base64_decode($this->buffer, true); + if ($chunk === false) { + $this->source = null; + $this->buffer = null; + + throw new StreamException('Failed to read stream chunk due to invalid base64 data'); + } + + $this->buffer = null; + + return $chunk; + } + + $this->buffer .= $chunk; + + $length = \strlen($this->buffer); + $chunk = \base64_decode(\substr($this->buffer, 0, $length - $length % 4), true); + if ($chunk === false) { + $this->source = null; + $this->buffer = null; + + throw new StreamException('Failed to read stream chunk due to invalid base64 data'); + } + + $this->buffer = \substr($this->buffer, $length - $length % 4); + + return $chunk; + }); + } +} diff --git a/lib/composer/amphp/byte-stream/lib/Base64/Base64DecodingOutputStream.php b/lib/composer/amphp/byte-stream/lib/Base64/Base64DecodingOutputStream.php new file mode 100644 index 0000000000000000000000000000000000000000..664d99d793855ae0baa316054667a9b97f5cd0e0 --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/Base64/Base64DecodingOutputStream.php @@ -0,0 +1,55 @@ +destination = $destination; + } + + public function write(string $data): Promise + { + $this->buffer .= $data; + + $length = \strlen($this->buffer); + $chunk = \base64_decode(\substr($this->buffer, 0, $length - $length % 4), true); + if ($chunk === false) { + return new Failure(new StreamException('Invalid base64 near offset ' . $this->offset)); + } + + $this->offset += $length - $length % 4; + $this->buffer = \substr($this->buffer, $length - $length % 4); + + return $this->destination->write($chunk); + } + + public function end(string $finalData = ""): Promise + { + $this->offset += \strlen($this->buffer); + + $chunk = \base64_decode($this->buffer . $finalData, true); + if ($chunk === false) { + return new Failure(new StreamException('Invalid base64 near offset ' . $this->offset)); + } + + $this->buffer = ''; + + return $this->destination->end($chunk); + } +} diff --git a/lib/composer/amphp/byte-stream/lib/Base64/Base64EncodingInputStream.php b/lib/composer/amphp/byte-stream/lib/Base64/Base64EncodingInputStream.php new file mode 100644 index 0000000000000000000000000000000000000000..523af9d28bcc6c2eb608d9032a25d5eb6dc75274 --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/Base64/Base64EncodingInputStream.php @@ -0,0 +1,46 @@ +source = $source; + } + + public function read(): Promise + { + return call(function () { + $chunk = yield $this->source->read(); + if ($chunk === null) { + if ($this->buffer === null) { + return null; + } + + $chunk = \base64_encode($this->buffer); + $this->buffer = null; + + return $chunk; + } + + $this->buffer .= $chunk; + + $length = \strlen($this->buffer); + $chunk = \base64_encode(\substr($this->buffer, 0, $length - $length % 3)); + $this->buffer = \substr($this->buffer, $length - $length % 3); + + return $chunk; + }); + } +} diff --git a/lib/composer/amphp/byte-stream/lib/Base64/Base64EncodingOutputStream.php b/lib/composer/amphp/byte-stream/lib/Base64/Base64EncodingOutputStream.php new file mode 100644 index 0000000000000000000000000000000000000000..e23d6f53b4af270dec30ae80fd13f33d4827a6f3 --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/Base64/Base64EncodingOutputStream.php @@ -0,0 +1,39 @@ +destination = $destination; + } + + public function write(string $data): Promise + { + $this->buffer .= $data; + + $length = \strlen($this->buffer); + $chunk = \base64_encode(\substr($this->buffer, 0, $length - $length % 3)); + $this->buffer = \substr($this->buffer, $length - $length % 3); + + return $this->destination->write($chunk); + } + + public function end(string $finalData = ""): Promise + { + $chunk = \base64_encode($this->buffer . $finalData); + $this->buffer = ''; + + return $this->destination->end($chunk); + } +} diff --git a/lib/composer/amphp/byte-stream/lib/ClosedException.php b/lib/composer/amphp/byte-stream/lib/ClosedException.php new file mode 100644 index 0000000000000000000000000000000000000000..17957a866e3f2276c512ac8c24f16c2b54e1bc7c --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/ClosedException.php @@ -0,0 +1,7 @@ +contents = $contents; + } + + /** + * Reads data from the stream. + * + * @return Promise Resolves with the full contents or `null` if the stream has closed / already been consumed. + */ + public function read(): Promise + { + if ($this->contents === null) { + return new Success; + } + + $promise = new Success($this->contents); + $this->contents = null; + + return $promise; + } +} diff --git a/lib/composer/amphp/byte-stream/lib/InputStream.php b/lib/composer/amphp/byte-stream/lib/InputStream.php new file mode 100644 index 0000000000000000000000000000000000000000..4c0b9e8f5986f9dc44c4ac94c0ca362f948232c3 --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/InputStream.php @@ -0,0 +1,38 @@ +read()) !== null) { + * $buffer .= $chunk; + * } + * + * return $buffer; + * }); + * } + * ``` + */ +interface InputStream +{ + /** + * Reads data from the stream. + * + * @return Promise Resolves with a string when new data is available or `null` if the stream has closed. + * + * @psalm-return Promise + * + * @throws PendingReadError Thrown if another read operation is still pending. + */ + public function read(): Promise; +} diff --git a/lib/composer/amphp/byte-stream/lib/InputStreamChain.php b/lib/composer/amphp/byte-stream/lib/InputStreamChain.php new file mode 100644 index 0000000000000000000000000000000000000000..d952a852656592da77163d6447cef00d4ef05af1 --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/InputStreamChain.php @@ -0,0 +1,52 @@ +streams = $streams; + } + + /** @inheritDoc */ + public function read(): Promise + { + if ($this->reading) { + throw new PendingReadError; + } + + if (!$this->streams) { + return new Success(null); + } + + return call(function () { + $this->reading = true; + + try { + while ($this->streams) { + $chunk = yield $this->streams[0]->read(); + if ($chunk === null) { + \array_shift($this->streams); + continue; + } + + return $chunk; + } + + return null; + } finally { + $this->reading = false; + } + }); + } +} diff --git a/lib/composer/amphp/byte-stream/lib/IteratorStream.php b/lib/composer/amphp/byte-stream/lib/IteratorStream.php new file mode 100644 index 0000000000000000000000000000000000000000..6fbe3912ac815da7d4b71480b604c2c7136b7a6e --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/IteratorStream.php @@ -0,0 +1,70 @@ + */ + private $iterator; + /** @var \Throwable|null */ + private $exception; + /** @var bool */ + private $pending = false; + + /** + * @psam-param Iterator $iterator + */ + public function __construct(Iterator $iterator) + { + $this->iterator = $iterator; + } + + /** @inheritdoc */ + public function read(): Promise + { + if ($this->exception) { + return new Failure($this->exception); + } + + if ($this->pending) { + throw new PendingReadError; + } + + $this->pending = true; + /** @var Deferred $deferred */ + $deferred = new Deferred; + + $this->iterator->advance()->onResolve(function ($error, $hasNextElement) use ($deferred) { + $this->pending = false; + + if ($error) { + $this->exception = $error; + $deferred->fail($error); + } elseif ($hasNextElement) { + $chunk = $this->iterator->getCurrent(); + + if (!\is_string($chunk)) { + $this->exception = new StreamException(\sprintf( + "Unexpected iterator value of type '%s', expected string", + \is_object($chunk) ? \get_class($chunk) : \gettype($chunk) + )); + + $deferred->fail($this->exception); + + return; + } + + $deferred->resolve($chunk); + } else { + $deferred->resolve(); + } + }); + + return $deferred->promise(); + } +} diff --git a/lib/composer/amphp/byte-stream/lib/LineReader.php b/lib/composer/amphp/byte-stream/lib/LineReader.php new file mode 100644 index 0000000000000000000000000000000000000000..ed6b0a2af311f4f0cea0a964248b390895fd0c11 --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/LineReader.php @@ -0,0 +1,71 @@ +source = $inputStream; + $this->delimiter = $delimiter === null ? "\n" : $delimiter; + $this->lineMode = $delimiter === null; + } + + /** + * @return Promise + */ + public function readLine(): Promise + { + return call(function () { + if (false !== \strpos($this->buffer, $this->delimiter)) { + list($line, $this->buffer) = \explode($this->delimiter, $this->buffer, 2); + return $this->lineMode ? \rtrim($line, "\r") : $line; + } + + while (null !== $chunk = yield $this->source->read()) { + $this->buffer .= $chunk; + + if (false !== \strpos($this->buffer, $this->delimiter)) { + list($line, $this->buffer) = \explode($this->delimiter, $this->buffer, 2); + return $this->lineMode ? \rtrim($line, "\r") : $line; + } + } + + if ($this->buffer === "") { + return null; + } + + $line = $this->buffer; + $this->buffer = ""; + return $this->lineMode ? \rtrim($line, "\r") : $line; + }); + } + + public function getBuffer(): string + { + return $this->buffer; + } + + /** + * @return void + */ + public function clearBuffer() + { + $this->buffer = ""; + } +} diff --git a/lib/composer/amphp/byte-stream/lib/Message.php b/lib/composer/amphp/byte-stream/lib/Message.php new file mode 100644 index 0000000000000000000000000000000000000000..33046233c06f1453a95136e417f84ec95bdfc4bb --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/Message.php @@ -0,0 +1,176 @@ +read()) !== null) { + * // Immediately use $chunk, reducing memory consumption since the entire message is never buffered. + * } + * + * @deprecated Use Amp\ByteStream\Payload instead. + */ +class Message implements InputStream, Promise +{ + /** @var InputStream */ + private $source; + + /** @var string */ + private $buffer = ""; + + /** @var Deferred|null */ + private $pendingRead; + + /** @var Coroutine|null */ + private $coroutine; + + /** @var bool True if onResolve() has been called. */ + private $buffering = false; + + /** @var Deferred|null */ + private $backpressure; + + /** @var bool True if the iterator has completed. */ + private $complete = false; + + /** @var \Throwable|null Used to fail future reads on failure. */ + private $error; + + /** + * @param InputStream $source An iterator that only emits strings. + */ + public function __construct(InputStream $source) + { + $this->source = $source; + } + + private function consume(): \Generator + { + while (($chunk = yield $this->source->read()) !== null) { + $buffer = $this->buffer .= $chunk; + + if ($buffer === "") { + continue; // Do not succeed reads with empty string. + } elseif ($this->pendingRead) { + $deferred = $this->pendingRead; + $this->pendingRead = null; + $this->buffer = ""; + $deferred->resolve($buffer); + $buffer = ""; // Destroy last emitted chunk to free memory. + } elseif (!$this->buffering) { + $buffer = ""; // Destroy last emitted chunk to free memory. + $this->backpressure = new Deferred; + yield $this->backpressure->promise(); + } + } + + $this->complete = true; + + if ($this->pendingRead) { + $deferred = $this->pendingRead; + $this->pendingRead = null; + $deferred->resolve($this->buffer !== "" ? $this->buffer : null); + $this->buffer = ""; + } + + return $this->buffer; + } + + /** @inheritdoc */ + final public function read(): Promise + { + if ($this->pendingRead) { + throw new PendingReadError; + } + + if ($this->coroutine === null) { + $this->coroutine = new Coroutine($this->consume()); + $this->coroutine->onResolve(function ($error) { + if ($error) { + $this->error = $error; + } + + if ($this->pendingRead) { + $deferred = $this->pendingRead; + $this->pendingRead = null; + $deferred->fail($error); + } + }); + } + + if ($this->error) { + return new Failure($this->error); + } + + if ($this->buffer !== "") { + $buffer = $this->buffer; + $this->buffer = ""; + + if ($this->backpressure) { + $backpressure = $this->backpressure; + $this->backpressure = null; + $backpressure->resolve(); + } + + return new Success($buffer); + } + + if ($this->complete) { + return new Success; + } + + $this->pendingRead = new Deferred; + return $this->pendingRead->promise(); + } + + /** @inheritdoc */ + final public function onResolve(callable $onResolved) + { + $this->buffering = true; + + if ($this->coroutine === null) { + $this->coroutine = new Coroutine($this->consume()); + } + + if ($this->backpressure) { + $backpressure = $this->backpressure; + $this->backpressure = null; + $backpressure->resolve(); + } + + $this->coroutine->onResolve($onResolved); + } + + /** + * Exposes the source input stream. + * + * This might be required to resolve a promise with an InputStream, because promises in Amp can't be resolved with + * other promises. + * + * @return InputStream + */ + final public function getInputStream(): InputStream + { + return $this->source; + } +} diff --git a/lib/composer/amphp/byte-stream/lib/OutputBuffer.php b/lib/composer/amphp/byte-stream/lib/OutputBuffer.php new file mode 100644 index 0000000000000000000000000000000000000000..832dc71b7a40387f29e61968b5ec7c159d3cc628 --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/OutputBuffer.php @@ -0,0 +1,55 @@ +deferred = new Deferred; + } + + public function write(string $data): Promise + { + if ($this->closed) { + throw new ClosedException("The stream has already been closed."); + } + + $this->contents .= $data; + + return new Success(\strlen($data)); + } + + public function end(string $finalData = ""): Promise + { + if ($this->closed) { + throw new ClosedException("The stream has already been closed."); + } + + $this->contents .= $finalData; + $this->closed = true; + + $this->deferred->resolve($this->contents); + $this->contents = ""; + + return new Success(\strlen($finalData)); + } + + public function onResolve(callable $onResolved) + { + $this->deferred->promise()->onResolve($onResolved); + } +} diff --git a/lib/composer/amphp/byte-stream/lib/OutputStream.php b/lib/composer/amphp/byte-stream/lib/OutputStream.php new file mode 100644 index 0000000000000000000000000000000000000000..68f51fc1a323644011b1769b4f2dc647df7a2e89 --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/OutputStream.php @@ -0,0 +1,37 @@ +stream = $stream; + } + + public function __destruct() + { + if (!$this->promise) { + Promise\rethrow(new Coroutine($this->consume())); + } + } + + private function consume(): \Generator + { + try { + if ($this->lastRead && null === yield $this->lastRead) { + return; + } + + while (null !== yield $this->stream->read()) { + // Discard unread bytes from message. + } + } catch (\Throwable $exception) { + // If exception is thrown here the connection closed anyway. + } + } + + /** + * @inheritdoc + * + * @throws \Error If a buffered message was requested by calling buffer(). + */ + final public function read(): Promise + { + if ($this->promise) { + throw new \Error("Cannot stream message data once a buffered message has been requested"); + } + + return $this->lastRead = $this->stream->read(); + } + + /** + * Buffers the entire message and resolves the returned promise then. + * + * @return Promise Resolves with the entire message contents. + */ + final public function buffer(): Promise + { + if ($this->promise) { + return $this->promise; + } + + return $this->promise = call(function () { + $buffer = ''; + if ($this->lastRead && null === yield $this->lastRead) { + return $buffer; + } + + while (null !== $chunk = yield $this->stream->read()) { + $buffer .= $chunk; + } + return $buffer; + }); + } +} diff --git a/lib/composer/amphp/byte-stream/lib/PendingReadError.php b/lib/composer/amphp/byte-stream/lib/PendingReadError.php new file mode 100644 index 0000000000000000000000000000000000000000..66dc1fbd0b0fb8b0ef0c1bdf98609f7aa0dfa060 --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/PendingReadError.php @@ -0,0 +1,17 @@ +useSingleRead = $useSingleRead; + + if (\strpos($meta["mode"], "r") === false && \strpos($meta["mode"], "+") === false) { + throw new \Error("Expected a readable stream"); + } + + \stream_set_blocking($stream, false); + \stream_set_read_buffer($stream, 0); + + $this->resource = &$stream; + $this->chunkSize = &$chunkSize; + + $deferred = &$this->deferred; + $readable = &$this->readable; + + $this->watcher = Loop::onReadable($this->resource, static function ($watcher) use ( + &$deferred, + &$readable, + &$stream, + &$chunkSize, + $useSingleRead + ) { + if ($useSingleRead) { + $data = @\fread($stream, $chunkSize); + } else { + $data = @\stream_get_contents($stream, $chunkSize); + } + + \assert($data !== false, "Trying to read from a previously fclose()'d resource. Do NOT manually fclose() resources the loop still has a reference to."); + + // Error suppression, because pthreads does crazy things with resources, + // which might be closed during two operations. + // See https://github.com/amphp/byte-stream/issues/32 + if ($data === '' && @\feof($stream)) { + $readable = false; + $stream = null; + $data = null; // Stream closed, resolve read with null. + Loop::cancel($watcher); + } else { + Loop::disable($watcher); + } + + $temp = $deferred; + $deferred = null; + + \assert($temp instanceof Deferred); + $temp->resolve($data); + }); + + $this->immediateCallable = static function ($watcherId, $data) use (&$deferred) { + $temp = $deferred; + $deferred = null; + + \assert($temp instanceof Deferred); + $temp->resolve($data); + }; + + Loop::disable($this->watcher); + } + + /** @inheritdoc */ + public function read(): Promise + { + if ($this->deferred !== null) { + throw new PendingReadError; + } + + if (!$this->readable) { + return new Success; // Resolve with null on closed stream. + } + + \assert($this->resource !== null); + + // Attempt a direct read, because Windows suffers from slow I/O on STDIN otherwise. + if ($this->useSingleRead) { + $data = @\fread($this->resource, $this->chunkSize); + } else { + $data = @\stream_get_contents($this->resource, $this->chunkSize); + } + + \assert($data !== false, "Trying to read from a previously fclose()'d resource. Do NOT manually fclose() resources the loop still has a reference to."); + + if ($data === '') { + // Error suppression, because pthreads does crazy things with resources, + // which might be closed during two operations. + // See https://github.com/amphp/byte-stream/issues/32 + if (@\feof($this->resource)) { + $this->readable = false; + $this->resource = null; + Loop::cancel($this->watcher); + + return new Success; // Stream closed, resolve read with null. + } + + $this->deferred = new Deferred; + Loop::enable($this->watcher); + + return $this->deferred->promise(); + } + + // Prevent an immediate read → write loop from blocking everything + // See e.g. examples/benchmark-throughput.php + $this->deferred = new Deferred; + $this->immediateWatcher = Loop::defer($this->immediateCallable, $data); + + return $this->deferred->promise(); + } + + /** + * Closes the stream forcefully. Multiple `close()` calls are ignored. + * + * @return void + */ + public function close() + { + if ($this->resource) { + // Error suppression, as resource might already be closed + $meta = @\stream_get_meta_data($this->resource); + + if ($meta && \strpos($meta["mode"], "+") !== false) { + @\stream_socket_shutdown($this->resource, \STREAM_SHUT_RD); + } else { + /** @psalm-suppress InvalidPropertyAssignmentValue */ + @\fclose($this->resource); + } + } + + $this->free(); + } + + /** + * Nulls reference to resource, marks stream unreadable, and succeeds any pending read with null. + * + * @return void + */ + private function free() + { + $this->readable = false; + $this->resource = null; + + if ($this->deferred !== null) { + $deferred = $this->deferred; + $this->deferred = null; + $deferred->resolve(); + } + + Loop::cancel($this->watcher); + + if ($this->immediateWatcher !== null) { + Loop::cancel($this->immediateWatcher); + } + } + + /** + * @return resource|null The stream resource or null if the stream has closed. + */ + public function getResource() + { + return $this->resource; + } + + /** + * @return void + */ + public function setChunkSize(int $chunkSize) + { + $this->chunkSize = $chunkSize; + } + + /** + * References the read watcher, so the loop keeps running in case there's an active read. + * + * @return void + * + * @see Loop::reference() + */ + public function reference() + { + if (!$this->resource) { + throw new \Error("Resource has already been freed"); + } + + Loop::reference($this->watcher); + } + + /** + * Unreferences the read watcher, so the loop doesn't keep running even if there are active reads. + * + * @return void + * + * @see Loop::unreference() + */ + public function unreference() + { + if (!$this->resource) { + throw new \Error("Resource has already been freed"); + } + + Loop::unreference($this->watcher); + } + + public function __destruct() + { + if ($this->resource !== null) { + $this->free(); + } + } +} diff --git a/lib/composer/amphp/byte-stream/lib/ResourceOutputStream.php b/lib/composer/amphp/byte-stream/lib/ResourceOutputStream.php new file mode 100644 index 0000000000000000000000000000000000000000..8bf3fdc6cdf204ee6bf43de52f87449c7c30da6a --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/ResourceOutputStream.php @@ -0,0 +1,321 @@ + */ + private $writes; + + /** @var bool */ + private $writable = true; + + /** @var int|null */ + private $chunkSize; + + /** + * @param resource $stream Stream resource. + * @param int|null $chunkSize Chunk size per `fwrite()` operation. + */ + public function __construct($stream, int $chunkSize = null) + { + if (!\is_resource($stream) || \get_resource_type($stream) !== 'stream') { + throw new \Error("Expected a valid stream"); + } + + $meta = \stream_get_meta_data($stream); + + if (\strpos($meta["mode"], "r") !== false && \strpos($meta["mode"], "+") === false) { + throw new \Error("Expected a writable stream"); + } + + \stream_set_blocking($stream, false); + \stream_set_write_buffer($stream, 0); + + $this->resource = $stream; + $this->chunkSize = &$chunkSize; + + $writes = $this->writes = new \SplQueue; + $writable = &$this->writable; + $resource = &$this->resource; + + $this->watcher = Loop::onWritable($stream, static function ($watcher, $stream) use ($writes, &$chunkSize, &$writable, &$resource) { + static $emptyWrites = 0; + + try { + while (!$writes->isEmpty()) { + /** @var Deferred $deferred */ + list($data, $previous, $deferred) = $writes->shift(); + $length = \strlen($data); + + if ($length === 0) { + $deferred->resolve(0); + continue; + } + + if (!\is_resource($stream) || (($metaData = @\stream_get_meta_data($stream)) && $metaData['eof'])) { + throw new ClosedException("The stream was closed by the peer"); + } + + // Error reporting suppressed since fwrite() emits E_WARNING if the pipe is broken or the buffer is full. + // Use conditional, because PHP doesn't like getting null passed + if ($chunkSize) { + $written = @\fwrite($stream, $data, $chunkSize); + } else { + $written = @\fwrite($stream, $data); + } + + \assert( + $written !== false || \PHP_VERSION_ID >= 70400, // PHP 7.4+ returns false on EPIPE. + "Trying to write on a previously fclose()'d resource. Do NOT manually fclose() resources the still referenced in the loop." + ); + + // PHP 7.4.0 and 7.4.1 may return false on EAGAIN. + if ($written === false && \PHP_VERSION_ID >= 70402) { + $message = "Failed to write to stream"; + if ($error = \error_get_last()) { + $message .= \sprintf("; %s", $error["message"]); + } + throw new StreamException($message); + } + + // Broken pipes between processes on macOS/FreeBSD do not detect EOF properly. + if ($written === 0 || $written === false) { + if ($emptyWrites++ > self::MAX_CONSECUTIVE_EMPTY_WRITES) { + $message = "Failed to write to stream after multiple attempts"; + if ($error = \error_get_last()) { + $message .= \sprintf("; %s", $error["message"]); + } + throw new StreamException($message); + } + + $writes->unshift([$data, $previous, $deferred]); + return; + } + + $emptyWrites = 0; + + if ($length > $written) { + $data = \substr($data, $written); + $writes->unshift([$data, $written + $previous, $deferred]); + return; + } + + $deferred->resolve($written + $previous); + } + } catch (\Throwable $exception) { + $resource = null; + $writable = false; + + /** @psalm-suppress PossiblyUndefinedVariable */ + $deferred->fail($exception); + while (!$writes->isEmpty()) { + list(, , $deferred) = $writes->shift(); + $deferred->fail($exception); + } + + Loop::cancel($watcher); + } finally { + if ($writes->isEmpty()) { + Loop::disable($watcher); + } + } + }); + + Loop::disable($this->watcher); + } + + /** + * Writes data to the stream. + * + * @param string $data Bytes to write. + * + * @return Promise Succeeds once the data has been successfully written to the stream. + * + * @throws ClosedException If the stream has already been closed. + */ + public function write(string $data): Promise + { + return $this->send($data, false); + } + + /** + * Closes the stream after all pending writes have been completed. Optionally writes a final data chunk before. + * + * @param string $finalData Bytes to write. + * + * @return Promise Succeeds once the data has been successfully written to the stream. + * + * @throws ClosedException If the stream has already been closed. + */ + public function end(string $finalData = ""): Promise + { + return $this->send($finalData, true); + } + + private function send(string $data, bool $end = false): Promise + { + if (!$this->writable) { + return new Failure(new ClosedException("The stream is not writable")); + } + + $length = \strlen($data); + $written = 0; + + if ($end) { + $this->writable = false; + } + + if ($this->writes->isEmpty()) { + if ($length === 0) { + if ($end) { + $this->close(); + } + return new Success(0); + } + + if (!\is_resource($this->resource) || (($metaData = @\stream_get_meta_data($this->resource)) && $metaData['eof'])) { + return new Failure(new ClosedException("The stream was closed by the peer")); + } + + // Error reporting suppressed since fwrite() emits E_WARNING if the pipe is broken or the buffer is full. + // Use conditional, because PHP doesn't like getting null passed. + if ($this->chunkSize) { + $written = @\fwrite($this->resource, $data, $this->chunkSize); + } else { + $written = @\fwrite($this->resource, $data); + } + + \assert( + $written !== false || \PHP_VERSION_ID >= 70400, // PHP 7.4+ returns false on EPIPE. + "Trying to write on a previously fclose()'d resource. Do NOT manually fclose() resources the still referenced in the loop." + ); + + // PHP 7.4.0 and 7.4.1 may return false on EAGAIN. + if ($written === false && \PHP_VERSION_ID >= 70402) { + $message = "Failed to write to stream"; + if ($error = \error_get_last()) { + $message .= \sprintf("; %s", $error["message"]); + } + return new Failure(new StreamException($message)); + } + + $written = (int) $written; // Cast potential false to 0. + + if ($length === $written) { + if ($end) { + $this->close(); + } + return new Success($written); + } + + $data = \substr($data, $written); + } + + $deferred = new Deferred; + + if ($length - $written > self::LARGE_CHUNK_SIZE) { + $chunks = \str_split($data, self::LARGE_CHUNK_SIZE); + $data = \array_pop($chunks); + foreach ($chunks as $chunk) { + $this->writes->push([$chunk, $written, new Deferred]); + $written += self::LARGE_CHUNK_SIZE; + } + } + + $this->writes->push([$data, $written, $deferred]); + Loop::enable($this->watcher); + $promise = $deferred->promise(); + + if ($end) { + $promise->onResolve([$this, "close"]); + } + + return $promise; + } + + /** + * Closes the stream forcefully. Multiple `close()` calls are ignored. + * + * @return void + */ + public function close() + { + if ($this->resource) { + // Error suppression, as resource might already be closed + $meta = @\stream_get_meta_data($this->resource); + + if ($meta && \strpos($meta["mode"], "+") !== false) { + @\stream_socket_shutdown($this->resource, \STREAM_SHUT_WR); + } else { + /** @psalm-suppress InvalidPropertyAssignmentValue psalm reports this as closed-resource */ + @\fclose($this->resource); + } + } + + $this->free(); + } + + /** + * Nulls reference to resource, marks stream unwritable, and fails any pending write. + * + * @return void + */ + private function free() + { + $this->resource = null; + $this->writable = false; + + if (!$this->writes->isEmpty()) { + $exception = new ClosedException("The socket was closed before writing completed"); + do { + /** @var Deferred $deferred */ + list(, , $deferred) = $this->writes->shift(); + $deferred->fail($exception); + } while (!$this->writes->isEmpty()); + } + + Loop::cancel($this->watcher); + } + + /** + * @return resource|null Stream resource or null if end() has been called or the stream closed. + */ + public function getResource() + { + return $this->resource; + } + + /** + * @return void + */ + public function setChunkSize(int $chunkSize) + { + $this->chunkSize = $chunkSize; + } + + public function __destruct() + { + if ($this->resource !== null) { + $this->free(); + } + } +} diff --git a/lib/composer/amphp/byte-stream/lib/StreamException.php b/lib/composer/amphp/byte-stream/lib/StreamException.php new file mode 100644 index 0000000000000000000000000000000000000000..b86ec7e0feeebbf76aac05af5e8d2bf537a6bf35 --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/StreamException.php @@ -0,0 +1,7 @@ +source = $source; + $this->encoding = $encoding; + $this->options = $options; + $this->resource = @\inflate_init($encoding, $options); + + if ($this->resource === false) { + throw new StreamException("Failed initializing deflate context"); + } + } + + /** @inheritdoc */ + public function read(): Promise + { + return call(function () { + if ($this->resource === null) { + return null; + } + + \assert($this->source !== null); + + $data = yield $this->source->read(); + + // Needs a double guard, as stream might have been closed while reading + /** @psalm-suppress ParadoxicalCondition */ + if ($this->resource === null) { + return null; + } + + if ($data === null) { + $decompressed = @\inflate_add($this->resource, "", \ZLIB_FINISH); + + if ($decompressed === false) { + throw new StreamException("Failed adding data to deflate context"); + } + + $this->close(); + + return $decompressed; + } + + $decompressed = @\inflate_add($this->resource, $data, \ZLIB_SYNC_FLUSH); + + if ($decompressed === false) { + throw new StreamException("Failed adding data to deflate context"); + } + + return $decompressed; + }); + } + + /** + * @internal + * @return void + */ + private function close() + { + $this->resource = null; + $this->source = null; + } + + /** + * Gets the used compression encoding. + * + * @return int Encoding specified on construction time. + */ + public function getEncoding(): int + { + return $this->encoding; + } + /** + * Gets the used compression options. + * + * @return array Options array passed on construction time. + */ + public function getOptions(): array + { + return $this->options; + } +} diff --git a/lib/composer/amphp/byte-stream/lib/ZlibOutputStream.php b/lib/composer/amphp/byte-stream/lib/ZlibOutputStream.php new file mode 100644 index 0000000000000000000000000000000000000000..542df1cee271b0d253da02d3b74e5d51f36c635a --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/ZlibOutputStream.php @@ -0,0 +1,119 @@ +destination = $destination; + $this->encoding = $encoding; + $this->options = $options; + $this->resource = @\deflate_init($encoding, $options); + + if ($this->resource === false) { + throw new StreamException("Failed initializing deflate context"); + } + } + + /** @inheritdoc */ + public function write(string $data): Promise + { + if ($this->resource === null) { + throw new ClosedException("The stream has already been closed"); + } + + \assert($this->destination !== null); + + $compressed = \deflate_add($this->resource, $data, \ZLIB_SYNC_FLUSH); + + if ($compressed === false) { + throw new StreamException("Failed adding data to deflate context"); + } + + $promise = $this->destination->write($compressed); + $promise->onResolve(function ($error) { + if ($error) { + $this->close(); + } + }); + + return $promise; + } + + /** @inheritdoc */ + public function end(string $finalData = ""): Promise + { + if ($this->resource === null) { + throw new ClosedException("The stream has already been closed"); + } + + \assert($this->destination !== null); + + $compressed = \deflate_add($this->resource, $finalData, \ZLIB_FINISH); + + if ($compressed === false) { + throw new StreamException("Failed adding data to deflate context"); + } + + $promise = $this->destination->end($compressed); + $promise->onResolve(function () { + $this->close(); + }); + + return $promise; + } + + /** + * @internal + * @return void + */ + private function close() + { + $this->resource = null; + $this->destination = null; + } + + /** + * Gets the used compression encoding. + * + * @return int Encoding specified on construction time. + */ + public function getEncoding(): int + { + return $this->encoding; + } + + /** + * Gets the used compression options. + * + * @return array Options array passed on construction time. + */ + public function getOptions(): array + { + return $this->options; + } +} diff --git a/lib/composer/amphp/byte-stream/lib/functions.php b/lib/composer/amphp/byte-stream/lib/functions.php new file mode 100644 index 0000000000000000000000000000000000000000..434aef323b738afbaf21d82f306b084c7a8c47c9 --- /dev/null +++ b/lib/composer/amphp/byte-stream/lib/functions.php @@ -0,0 +1,188 @@ +read()) !== null) { + $written += \strlen($chunk); + $writePromise = $destination->write($chunk); + $chunk = null; // free memory + yield $writePromise; + } + + return $written; + }); +} + +/** + * @param \Amp\ByteStream\InputStream $source + * + * @return \Amp\Promise + */ +function buffer(InputStream $source): Promise +{ + return call(function () use ($source): \Generator { + $buffer = ""; + + while (($chunk = yield $source->read()) !== null) { + $buffer .= $chunk; + $chunk = null; // free memory + } + + return $buffer; + }); +} + +/** + * The php://input input buffer stream for the process associated with the currently active event loop. + * + * @return ResourceInputStream + */ +function getInputBufferStream(): ResourceInputStream +{ + static $key = InputStream::class . '\\input'; + + $stream = Loop::getState($key); + + if (!$stream) { + $stream = new ResourceInputStream(\fopen('php://input', 'rb')); + Loop::setState($key, $stream); + } + + return $stream; +} + +/** + * The php://output output buffer stream for the process associated with the currently active event loop. + * + * @return ResourceOutputStream + */ +function getOutputBufferStream(): ResourceOutputStream +{ + static $key = OutputStream::class . '\\output'; + + $stream = Loop::getState($key); + + if (!$stream) { + $stream = new ResourceOutputStream(\fopen('php://output', 'wb')); + Loop::setState($key, $stream); + } + + return $stream; +} + +/** + * The STDIN stream for the process associated with the currently active event loop. + * + * @return ResourceInputStream + */ +function getStdin(): ResourceInputStream +{ + static $key = InputStream::class . '\\stdin'; + + $stream = Loop::getState($key); + + if (!$stream) { + $stream = new ResourceInputStream(\STDIN); + Loop::setState($key, $stream); + } + + return $stream; +} + +/** + * The STDOUT stream for the process associated with the currently active event loop. + * + * @return ResourceOutputStream + */ +function getStdout(): ResourceOutputStream +{ + static $key = OutputStream::class . '\\stdout'; + + $stream = Loop::getState($key); + + if (!$stream) { + $stream = new ResourceOutputStream(\STDOUT); + Loop::setState($key, $stream); + } + + return $stream; +} + +/** + * The STDERR stream for the process associated with the currently active event loop. + * + * @return ResourceOutputStream + */ +function getStderr(): ResourceOutputStream +{ + static $key = OutputStream::class . '\\stderr'; + + $stream = Loop::getState($key); + + if (!$stream) { + $stream = new ResourceOutputStream(\STDERR); + Loop::setState($key, $stream); + } + + return $stream; +} + +function parseLineDelimitedJson(InputStream $stream, bool $assoc = false, int $depth = 512, int $options = 0): Iterator +{ + return new Producer(static function (callable $emit) use ($stream, $assoc, $depth, $options) { + $reader = new LineReader($stream); + + while (null !== $line = yield $reader->readLine()) { + $line = \trim($line); + + if ($line === '') { + continue; + } + + /** @noinspection PhpComposerExtensionStubsInspection */ + $data = \json_decode($line, $assoc, $depth, $options); + /** @noinspection PhpComposerExtensionStubsInspection */ + $error = \json_last_error(); + + /** @noinspection PhpComposerExtensionStubsInspection */ + if ($error !== \JSON_ERROR_NONE) { + /** @noinspection PhpComposerExtensionStubsInspection */ + throw new StreamException('Failed to parse JSON: ' . \json_last_error_msg(), $error); + } + + yield $emit($data); + } + }); +} diff --git a/lib/composer/amphp/byte-stream/psalm.xml b/lib/composer/amphp/byte-stream/psalm.xml new file mode 100644 index 0000000000000000000000000000000000000000..9684f55df70e35388b01b20d56b79a79920ad3dd --- /dev/null +++ b/lib/composer/amphp/byte-stream/psalm.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/composer/bin/php-cs-fixer b/lib/composer/bin/php-cs-fixer new file mode 120000 index 0000000000000000000000000000000000000000..7f39b6c51bcba590cb3537ec93efba2a9b07d93f --- /dev/null +++ b/lib/composer/bin/php-cs-fixer @@ -0,0 +1 @@ +../friendsofphp/php-cs-fixer/php-cs-fixer \ No newline at end of file diff --git a/lib/composer/bin/php-parse b/lib/composer/bin/php-parse new file mode 120000 index 0000000000000000000000000000000000000000..062d66a3ed39a3a63233fabdbdd918d3c625f613 --- /dev/null +++ b/lib/composer/bin/php-parse @@ -0,0 +1 @@ +../nikic/php-parser/bin/php-parse \ No newline at end of file diff --git a/lib/composer/bin/psalm b/lib/composer/bin/psalm new file mode 120000 index 0000000000000000000000000000000000000000..82484c004eeb5d60c20af69ce27c437b08463c44 --- /dev/null +++ b/lib/composer/bin/psalm @@ -0,0 +1 @@ +../vimeo/psalm/psalm \ No newline at end of file diff --git a/lib/composer/bin/psalm-language-server b/lib/composer/bin/psalm-language-server new file mode 120000 index 0000000000000000000000000000000000000000..71ca8029587711c8b571016806807b8ebe1f82ba --- /dev/null +++ b/lib/composer/bin/psalm-language-server @@ -0,0 +1 @@ +../vimeo/psalm/psalm-language-server \ No newline at end of file diff --git a/lib/composer/bin/psalm-plugin b/lib/composer/bin/psalm-plugin new file mode 120000 index 0000000000000000000000000000000000000000..625a931425cc655a769afec23e9a99f4558f00db --- /dev/null +++ b/lib/composer/bin/psalm-plugin @@ -0,0 +1 @@ +../vimeo/psalm/psalm-plugin \ No newline at end of file diff --git a/lib/composer/bin/psalm-refactor b/lib/composer/bin/psalm-refactor new file mode 120000 index 0000000000000000000000000000000000000000..a366509e9cec723497472ca30f5ff377aa8191d3 --- /dev/null +++ b/lib/composer/bin/psalm-refactor @@ -0,0 +1 @@ +../vimeo/psalm/psalm-refactor \ No newline at end of file diff --git a/lib/composer/bin/psalter b/lib/composer/bin/psalter new file mode 120000 index 0000000000000000000000000000000000000000..227df4b64cb46d573af87b40e1ac2aa3e8e01a90 --- /dev/null +++ b/lib/composer/bin/psalter @@ -0,0 +1 @@ +../vimeo/psalm/psalter \ No newline at end of file diff --git a/lib/composer/composer/ClassLoader.php b/lib/composer/composer/ClassLoader.php index 03b9bb9c40cb86c2c2bbec2ce6ff0ddce9ad586c..1a58957d25d4a5fd00be8d9f086a4ece3bf8371e 100644 --- a/lib/composer/composer/ClassLoader.php +++ b/lib/composer/composer/ClassLoader.php @@ -37,8 +37,8 @@ namespace Composer\Autoload; * * @author Fabien Potencier * @author Jordi Boggiano - * @see http://www.php-fig.org/psr/psr-0/ - * @see http://www.php-fig.org/psr/psr-4/ + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { diff --git a/lib/composer/composer/InstalledVersions.php b/lib/composer/composer/InstalledVersions.php new file mode 100644 index 0000000000000000000000000000000000000000..ea16c3f84f1e242b5ed83332769169c611142d24 --- /dev/null +++ b/lib/composer/composer/InstalledVersions.php @@ -0,0 +1,667 @@ + + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => '11fca45e4c9ed5bc53436b6232a656a51f4984fa', + 'name' => '__root__', + ), + 'versions' => + array ( + '__root__' => + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => '11fca45e4c9ed5bc53436b6232a656a51f4984fa', + ), + 'amphp/amp' => + array ( + 'pretty_version' => 'v2.5.0', + 'version' => '2.5.0.0', + 'aliases' => + array ( + ), + 'reference' => 'f220a51458bf4dd0dedebb171ac3457813c72bbc', + ), + 'amphp/byte-stream' => + array ( + 'pretty_version' => 'v1.8.0', + 'version' => '1.8.0.0', + 'aliases' => + array ( + ), + 'reference' => 'f0c20cf598a958ba2aa8c6e5a71c697d652c7088', + ), + 'composer/package-versions-deprecated' => + array ( + 'pretty_version' => '1.11.99', + 'version' => '1.11.99.0', + 'aliases' => + array ( + ), + 'reference' => 'c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855', + ), + 'composer/semver' => + array ( + 'pretty_version' => '1.7.1', + 'version' => '1.7.1.0', + 'aliases' => + array ( + ), + 'reference' => '38276325bd896f90dfcfe30029aa5db40df387a7', + ), + 'composer/xdebug-handler' => + array ( + 'pretty_version' => '1.4.3', + 'version' => '1.4.3.0', + 'aliases' => + array ( + ), + 'reference' => 'ebd27a9866ae8254e873866f795491f02418c5a5', + ), + 'dnoegel/php-xdg-base-dir' => + array ( + 'pretty_version' => 'v0.1.1', + 'version' => '0.1.1.0', + 'aliases' => + array ( + ), + 'reference' => '8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd', + ), + 'doctrine/annotations' => + array ( + 'pretty_version' => '1.10.3', + 'version' => '1.10.3.0', + 'aliases' => + array ( + ), + 'reference' => '5db60a4969eba0e0c197a19c077780aadbc43c5d', + ), + 'doctrine/lexer' => + array ( + 'pretty_version' => '1.2.1', + 'version' => '1.2.1.0', + 'aliases' => + array ( + ), + 'reference' => 'e864bbf5904cb8f5bb334f99209b48018522f042', + ), + 'felixfbecker/advanced-json-rpc' => + array ( + 'pretty_version' => 'v3.1.1', + 'version' => '3.1.1.0', + 'aliases' => + array ( + ), + 'reference' => '0ed363f8de17d284d479ec813c9ad3f6834b5c40', + ), + 'felixfbecker/language-server-protocol' => + array ( + 'pretty_version' => 'v1.4.0', + 'version' => '1.4.0.0', + 'aliases' => + array ( + ), + 'reference' => '378801f6139bb74ac215d81cca1272af61df9a9f', + ), + 'friendsofphp/php-cs-fixer' => + array ( + 'pretty_version' => 'v2.16.3', + 'version' => '2.16.3.0', + 'aliases' => + array ( + ), + 'reference' => '83baf823a33a1cbd5416c8626935cf3f843c10b0', + ), + 'netresearch/jsonmapper' => + array ( + 'pretty_version' => 'v2.1.0', + 'version' => '2.1.0.0', + 'aliases' => + array ( + ), + 'reference' => 'e0f1e33a71587aca81be5cffbb9746510e1fe04e', + ), + 'nextcloud/coding-standard' => + array ( + 'pretty_version' => 'v0.3.0', + 'version' => '0.3.0.0', + 'aliases' => + array ( + ), + 'reference' => '4f5cd012760f8293e19e602651a0ecaa265e4db9', + ), + 'nikic/php-parser' => + array ( + 'pretty_version' => 'v4.10.2', + 'version' => '4.10.2.0', + 'aliases' => + array ( + ), + 'reference' => '658f1be311a230e0907f5dfe0213742aff0596de', + ), + 'ocramius/package-versions' => + array ( + 'replaced' => + array ( + 0 => '1.11.99', + ), + ), + 'openlss/lib-array2xml' => + array ( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'a91f18a8dfc69ffabe5f9b068bc39bb202c81d90', + ), + 'paragonie/random_compat' => + array ( + 'pretty_version' => 'v9.99.99', + 'version' => '9.99.99.0', + 'aliases' => + array ( + ), + 'reference' => '84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95', + ), + 'php-cs-fixer/diff' => + array ( + 'pretty_version' => 'v1.3.0', + 'version' => '1.3.0.0', + 'aliases' => + array ( + ), + 'reference' => '78bb099e9c16361126c86ce82ec4405ebab8e756', + ), + 'phpdocumentor/reflection-common' => + array ( + 'pretty_version' => '2.2.0', + 'version' => '2.2.0.0', + 'aliases' => + array ( + ), + 'reference' => '1d01c49d4ed62f25aa84a747ad35d5a16924662b', + ), + 'phpdocumentor/reflection-docblock' => + array ( + 'pretty_version' => '5.2.2', + 'version' => '5.2.2.0', + 'aliases' => + array ( + ), + 'reference' => '069a785b2141f5bcf49f3e353548dc1cce6df556', + ), + 'phpdocumentor/type-resolver' => + array ( + 'pretty_version' => '1.4.0', + 'version' => '1.4.0.0', + 'aliases' => + array ( + ), + 'reference' => '6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0', + ), + 'psalm/psalm' => + array ( + 'provided' => + array ( + 0 => '4.0.1', + ), + ), + 'psr/container' => + array ( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f', + ), + 'psr/event-dispatcher' => + array ( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', + ), + 'psr/event-dispatcher-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'psr/log' => + array ( + 'pretty_version' => '1.1.3', + 'version' => '1.1.3.0', + 'aliases' => + array ( + ), + 'reference' => '0f73288fd15629204f9d42b7055f72dacbe811fc', + ), + 'psr/log-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'sebastian/diff' => + array ( + 'pretty_version' => '4.0.3', + 'version' => '4.0.3.0', + 'aliases' => + array ( + ), + 'reference' => 'ffc949a1a2aae270ea064453d7535b82e4c32092', + ), + 'symfony/console' => + array ( + 'pretty_version' => 'v5.1.7', + 'version' => '5.1.7.0', + 'aliases' => + array ( + ), + 'reference' => 'ae789a8a2ad189ce7e8216942cdb9b77319f5eb8', + ), + 'symfony/deprecation-contracts' => + array ( + 'pretty_version' => 'v2.1.2', + 'version' => '2.1.2.0', + 'aliases' => + array ( + ), + 'reference' => 'dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337', + ), + 'symfony/event-dispatcher' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => 'cc0d059e2e997e79ca34125a52f3e33de4424ac7', + ), + 'symfony/event-dispatcher-contracts' => + array ( + 'pretty_version' => 'v2.1.2', + 'version' => '2.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '405952c4e90941a17e52ef7489a2bd94870bb290', + ), + 'symfony/event-dispatcher-implementation' => + array ( + 'provided' => + array ( + 0 => '2.0', + ), + ), + 'symfony/filesystem' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '6e4320f06d5f2cce0d96530162491f4465179157', + ), + 'symfony/finder' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '4298870062bfc667cb78d2b379be4bf5dec5f187', + ), + 'symfony/options-resolver' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '663f5dd5e14057d1954fe721f9709d35837f2447', + ), + 'symfony/polyfill-ctype' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => '1c302646f6efc070cd46856e600e5e0684d6b454', + ), + 'symfony/polyfill-intl-grapheme' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => 'b740103edbdcc39602239ee8860f0f45a8eb9aa5', + ), + 'symfony/polyfill-intl-normalizer' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => '37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e', + ), + 'symfony/polyfill-mbstring' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => 'a6977d63bf9a0ad4c65cd352709e230876f9904a', + ), + 'symfony/polyfill-php70' => + array ( + 'pretty_version' => 'v1.17.1', + 'version' => '1.17.1.0', + 'aliases' => + array ( + ), + 'reference' => '471b096aede7025bace8eb356b9ac801aaba7e2d', + ), + 'symfony/polyfill-php72' => + array ( + 'pretty_version' => 'v1.17.0', + 'version' => '1.17.0.0', + 'aliases' => + array ( + ), + 'reference' => 'f048e612a3905f34931127360bdd2def19a5e582', + ), + 'symfony/polyfill-php73' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => 'fffa1a52a023e782cdcc221d781fe1ec8f87fcca', + ), + 'symfony/polyfill-php80' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => 'd87d5766cbf48d72388a9f6b85f280c8ad51f981', + ), + 'symfony/process' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1', + ), + 'symfony/service-contracts' => + array ( + 'pretty_version' => 'v2.2.0', + 'version' => '2.2.0.0', + 'aliases' => + array ( + ), + 'reference' => 'd15da7ba4957ffb8f1747218be9e1a121fd298a1', + ), + 'symfony/stopwatch' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '0f7c58cf81dbb5dd67d423a89d577524a2ec0323', + ), + 'symfony/string' => + array ( + 'pretty_version' => 'v5.1.7', + 'version' => '5.1.7.0', + 'aliases' => + array ( + ), + 'reference' => '4a9afe9d07bac506f75bcee8ed3ce76da5a9343e', + ), + 'vimeo/psalm' => + array ( + 'pretty_version' => '4.0.1', + 'version' => '4.0.1.0', + 'aliases' => + array ( + ), + 'reference' => 'b1e2e30026936ef8d5bf6a354d1c3959b6231f44', + ), + 'webmozart/assert' => + array ( + 'pretty_version' => '1.9.1', + 'version' => '1.9.1.0', + 'aliases' => + array ( + ), + 'reference' => 'bafc69caeb4d49c39fd0779086c03a3738cbb389', + ), + 'webmozart/glob' => + array ( + 'pretty_version' => '4.1.0', + 'version' => '4.1.0.0', + 'aliases' => + array ( + ), + 'reference' => '3cbf63d4973cf9d780b93d2da8eec7e4a9e63bbe', + ), + 'webmozart/path-util' => + array ( + 'pretty_version' => '2.3.0', + 'version' => '2.3.0.0', + 'aliases' => + array ( + ), + 'reference' => 'd939f7edc24c9a1bb9c0dee5cb05d8e859490725', + ), + ), +); + + + + + + + +public static function getInstalledPackages() +{ +return array_keys(self::$installed['versions']); +} + + + + + + + + + +public static function isInstalled($packageName) +{ +return isset(self::$installed['versions'][$packageName]); +} + + + + + + + + + + + + + + +public static function satisfies(VersionParser $parser, $packageName, $constraint) +{ +$constraint = $parser->parseConstraints($constraint); +$provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + +return $provided->matches($constraint); +} + + + + + + + + + + +public static function getVersionRanges($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +$ranges = array(); +if (isset(self::$installed['versions'][$packageName]['pretty_version'])) { +$ranges[] = self::$installed['versions'][$packageName]['pretty_version']; +} +if (array_key_exists('aliases', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']); +} +if (array_key_exists('replaced', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']); +} +if (array_key_exists('provided', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']); +} + +return implode(' || ', $ranges); +} + + + + + +public static function getVersion($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['version'])) { +return null; +} + +return self::$installed['versions'][$packageName]['version']; +} + + + + + +public static function getPrettyVersion($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['pretty_version'])) { +return null; +} + +return self::$installed['versions'][$packageName]['pretty_version']; +} + + + + + +public static function getReference($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['reference'])) { +return null; +} + +return self::$installed['versions'][$packageName]['reference']; +} + + + + + +public static function getRootPackage() +{ +return self::$installed['root']; +} + + + + + + + +public static function getRawData() +{ +return self::$installed; +} + + + + + + + + + + + + + + + + + + + +public static function reload($data) +{ +self::$installed = $data; +} +} diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index a966ad04f5ff49ea0beb57a03997498a313867be..c0b2ecaca1301c416338c9ff9cd1d363c2854141 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -6,6 +6,172 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname(dirname($vendorDir)); return array( + 'AdvancedJsonRpc\\Dispatcher' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Dispatcher.php', + 'AdvancedJsonRpc\\Error' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Error.php', + 'AdvancedJsonRpc\\ErrorCode' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/ErrorCode.php', + 'AdvancedJsonRpc\\ErrorResponse' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/ErrorResponse.php', + 'AdvancedJsonRpc\\Message' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Message.php', + 'AdvancedJsonRpc\\Notification' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Notification.php', + 'AdvancedJsonRpc\\Request' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Request.php', + 'AdvancedJsonRpc\\Response' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Response.php', + 'AdvancedJsonRpc\\SuccessResponse' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/SuccessResponse.php', + 'Amp\\ByteStream\\Base64\\Base64DecodingInputStream' => $vendorDir . '/amphp/byte-stream/lib/Base64/Base64DecodingInputStream.php', + 'Amp\\ByteStream\\Base64\\Base64DecodingOutputStream' => $vendorDir . '/amphp/byte-stream/lib/Base64/Base64DecodingOutputStream.php', + 'Amp\\ByteStream\\Base64\\Base64EncodingInputStream' => $vendorDir . '/amphp/byte-stream/lib/Base64/Base64EncodingInputStream.php', + 'Amp\\ByteStream\\Base64\\Base64EncodingOutputStream' => $vendorDir . '/amphp/byte-stream/lib/Base64/Base64EncodingOutputStream.php', + 'Amp\\ByteStream\\ClosedException' => $vendorDir . '/amphp/byte-stream/lib/ClosedException.php', + 'Amp\\ByteStream\\InMemoryStream' => $vendorDir . '/amphp/byte-stream/lib/InMemoryStream.php', + 'Amp\\ByteStream\\InputStream' => $vendorDir . '/amphp/byte-stream/lib/InputStream.php', + 'Amp\\ByteStream\\InputStreamChain' => $vendorDir . '/amphp/byte-stream/lib/InputStreamChain.php', + 'Amp\\ByteStream\\IteratorStream' => $vendorDir . '/amphp/byte-stream/lib/IteratorStream.php', + 'Amp\\ByteStream\\LineReader' => $vendorDir . '/amphp/byte-stream/lib/LineReader.php', + 'Amp\\ByteStream\\Message' => $vendorDir . '/amphp/byte-stream/lib/Message.php', + 'Amp\\ByteStream\\OutputBuffer' => $vendorDir . '/amphp/byte-stream/lib/OutputBuffer.php', + 'Amp\\ByteStream\\OutputStream' => $vendorDir . '/amphp/byte-stream/lib/OutputStream.php', + 'Amp\\ByteStream\\Payload' => $vendorDir . '/amphp/byte-stream/lib/Payload.php', + 'Amp\\ByteStream\\PendingReadError' => $vendorDir . '/amphp/byte-stream/lib/PendingReadError.php', + 'Amp\\ByteStream\\ResourceInputStream' => $vendorDir . '/amphp/byte-stream/lib/ResourceInputStream.php', + 'Amp\\ByteStream\\ResourceOutputStream' => $vendorDir . '/amphp/byte-stream/lib/ResourceOutputStream.php', + 'Amp\\ByteStream\\StreamException' => $vendorDir . '/amphp/byte-stream/lib/StreamException.php', + 'Amp\\ByteStream\\ZlibInputStream' => $vendorDir . '/amphp/byte-stream/lib/ZlibInputStream.php', + 'Amp\\ByteStream\\ZlibOutputStream' => $vendorDir . '/amphp/byte-stream/lib/ZlibOutputStream.php', + 'Amp\\CallableMaker' => $vendorDir . '/amphp/amp/lib/CallableMaker.php', + 'Amp\\CancellationToken' => $vendorDir . '/amphp/amp/lib/CancellationToken.php', + 'Amp\\CancellationTokenSource' => $vendorDir . '/amphp/amp/lib/CancellationTokenSource.php', + 'Amp\\CancelledException' => $vendorDir . '/amphp/amp/lib/CancelledException.php', + 'Amp\\CombinedCancellationToken' => $vendorDir . '/amphp/amp/lib/CombinedCancellationToken.php', + 'Amp\\Coroutine' => $vendorDir . '/amphp/amp/lib/Coroutine.php', + 'Amp\\Deferred' => $vendorDir . '/amphp/amp/lib/Deferred.php', + 'Amp\\Delayed' => $vendorDir . '/amphp/amp/lib/Delayed.php', + 'Amp\\Emitter' => $vendorDir . '/amphp/amp/lib/Emitter.php', + 'Amp\\Failure' => $vendorDir . '/amphp/amp/lib/Failure.php', + 'Amp\\Internal\\Placeholder' => $vendorDir . '/amphp/amp/lib/Internal/Placeholder.php', + 'Amp\\Internal\\PrivateIterator' => $vendorDir . '/amphp/amp/lib/Internal/PrivateIterator.php', + 'Amp\\Internal\\PrivatePromise' => $vendorDir . '/amphp/amp/lib/Internal/PrivatePromise.php', + 'Amp\\Internal\\Producer' => $vendorDir . '/amphp/amp/lib/Internal/Producer.php', + 'Amp\\Internal\\ResolutionQueue' => $vendorDir . '/amphp/amp/lib/Internal/ResolutionQueue.php', + 'Amp\\InvalidYieldError' => $vendorDir . '/amphp/amp/lib/InvalidYieldError.php', + 'Amp\\Iterator' => $vendorDir . '/amphp/amp/lib/Iterator.php', + 'Amp\\LazyPromise' => $vendorDir . '/amphp/amp/lib/LazyPromise.php', + 'Amp\\Loop' => $vendorDir . '/amphp/amp/lib/Loop.php', + 'Amp\\Loop\\Driver' => $vendorDir . '/amphp/amp/lib/Loop/Driver.php', + 'Amp\\Loop\\DriverFactory' => $vendorDir . '/amphp/amp/lib/Loop/DriverFactory.php', + 'Amp\\Loop\\EvDriver' => $vendorDir . '/amphp/amp/lib/Loop/EvDriver.php', + 'Amp\\Loop\\EventDriver' => $vendorDir . '/amphp/amp/lib/Loop/EventDriver.php', + 'Amp\\Loop\\Internal\\TimerQueue' => $vendorDir . '/amphp/amp/lib/Loop/Internal/TimerQueue.php', + 'Amp\\Loop\\Internal\\TimerQueueEntry' => $vendorDir . '/amphp/amp/lib/Loop/Internal/TimerQueueEntry.php', + 'Amp\\Loop\\InvalidWatcherError' => $vendorDir . '/amphp/amp/lib/Loop/InvalidWatcherError.php', + 'Amp\\Loop\\NativeDriver' => $vendorDir . '/amphp/amp/lib/Loop/NativeDriver.php', + 'Amp\\Loop\\TracingDriver' => $vendorDir . '/amphp/amp/lib/Loop/TracingDriver.php', + 'Amp\\Loop\\UnsupportedFeatureException' => $vendorDir . '/amphp/amp/lib/Loop/UnsupportedFeatureException.php', + 'Amp\\Loop\\UvDriver' => $vendorDir . '/amphp/amp/lib/Loop/UvDriver.php', + 'Amp\\Loop\\Watcher' => $vendorDir . '/amphp/amp/lib/Loop/Watcher.php', + 'Amp\\MultiReasonException' => $vendorDir . '/amphp/amp/lib/MultiReasonException.php', + 'Amp\\NullCancellationToken' => $vendorDir . '/amphp/amp/lib/NullCancellationToken.php', + 'Amp\\Producer' => $vendorDir . '/amphp/amp/lib/Producer.php', + 'Amp\\Promise' => $vendorDir . '/amphp/amp/lib/Promise.php', + 'Amp\\Struct' => $vendorDir . '/amphp/amp/lib/Struct.php', + 'Amp\\Success' => $vendorDir . '/amphp/amp/lib/Success.php', + 'Amp\\TimeoutCancellationToken' => $vendorDir . '/amphp/amp/lib/TimeoutCancellationToken.php', + 'Amp\\TimeoutException' => $vendorDir . '/amphp/amp/lib/TimeoutException.php', + 'ArithmeticError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php', + 'AssertionError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'Composer\\Semver\\Comparator' => $vendorDir . '/composer/semver/src/Comparator.php', + 'Composer\\Semver\\Constraint\\AbstractConstraint' => $vendorDir . '/composer/semver/src/Constraint/AbstractConstraint.php', + 'Composer\\Semver\\Constraint\\Constraint' => $vendorDir . '/composer/semver/src/Constraint/Constraint.php', + 'Composer\\Semver\\Constraint\\ConstraintInterface' => $vendorDir . '/composer/semver/src/Constraint/ConstraintInterface.php', + 'Composer\\Semver\\Constraint\\EmptyConstraint' => $vendorDir . '/composer/semver/src/Constraint/EmptyConstraint.php', + 'Composer\\Semver\\Constraint\\MultiConstraint' => $vendorDir . '/composer/semver/src/Constraint/MultiConstraint.php', + 'Composer\\Semver\\Semver' => $vendorDir . '/composer/semver/src/Semver.php', + 'Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php', + 'Composer\\XdebugHandler\\PhpConfig' => $vendorDir . '/composer/xdebug-handler/src/PhpConfig.php', + 'Composer\\XdebugHandler\\Process' => $vendorDir . '/composer/xdebug-handler/src/Process.php', + 'Composer\\XdebugHandler\\Status' => $vendorDir . '/composer/xdebug-handler/src/Status.php', + 'Composer\\XdebugHandler\\XdebugHandler' => $vendorDir . '/composer/xdebug-handler/src/XdebugHandler.php', + 'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', + 'Doctrine\\Common\\Annotations\\Annotation' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php', + 'Doctrine\\Common\\Annotations\\AnnotationException' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php', + 'Doctrine\\Common\\Annotations\\AnnotationReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php', + 'Doctrine\\Common\\Annotations\\AnnotationRegistry' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Attribute' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Attributes' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Enum' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php', + 'Doctrine\\Common\\Annotations\\Annotation\\IgnoreAnnotation' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Required' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Target' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php', + 'Doctrine\\Common\\Annotations\\CachedReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php', + 'Doctrine\\Common\\Annotations\\DocLexer' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php', + 'Doctrine\\Common\\Annotations\\DocParser' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php', + 'Doctrine\\Common\\Annotations\\FileCacheReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php', + 'Doctrine\\Common\\Annotations\\IndexedReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php', + 'Doctrine\\Common\\Annotations\\PhpParser' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php', + 'Doctrine\\Common\\Annotations\\Reader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php', + 'Doctrine\\Common\\Annotations\\SimpleAnnotationReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php', + 'Doctrine\\Common\\Annotations\\TokenParser' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php', + 'Doctrine\\Common\\Lexer\\AbstractLexer' => $vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php', + 'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php', + 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'JsonMapper' => $vendorDir . '/netresearch/jsonmapper/src/JsonMapper.php', + 'JsonMapper_Exception' => $vendorDir . '/netresearch/jsonmapper/src/JsonMapper/Exception.php', + 'LSS\\Array2XML' => $vendorDir . '/openlss/lib-array2xml/LSS/Array2XML.php', + 'LSS\\XML2Array' => $vendorDir . '/openlss/lib-array2xml/LSS/XML2Array.php', + 'LanguageServerProtocol\\ClientCapabilities' => $vendorDir . '/felixfbecker/language-server-protocol/src/ClientCapabilities.php', + 'LanguageServerProtocol\\CodeActionContext' => $vendorDir . '/felixfbecker/language-server-protocol/src/CodeActionContext.php', + 'LanguageServerProtocol\\CodeLens' => $vendorDir . '/felixfbecker/language-server-protocol/src/CodeLens.php', + 'LanguageServerProtocol\\CodeLensOptions' => $vendorDir . '/felixfbecker/language-server-protocol/src/CodeLensOptions.php', + 'LanguageServerProtocol\\Command' => $vendorDir . '/felixfbecker/language-server-protocol/src/Command.php', + 'LanguageServerProtocol\\CompletionContext' => $vendorDir . '/felixfbecker/language-server-protocol/src/CompletionContext.php', + 'LanguageServerProtocol\\CompletionItem' => $vendorDir . '/felixfbecker/language-server-protocol/src/CompletionItem.php', + 'LanguageServerProtocol\\CompletionItemKind' => $vendorDir . '/felixfbecker/language-server-protocol/src/CompletionItemKind.php', + 'LanguageServerProtocol\\CompletionList' => $vendorDir . '/felixfbecker/language-server-protocol/src/CompletionList.php', + 'LanguageServerProtocol\\CompletionOptions' => $vendorDir . '/felixfbecker/language-server-protocol/src/CompletionOptions.php', + 'LanguageServerProtocol\\CompletionTriggerKind' => $vendorDir . '/felixfbecker/language-server-protocol/src/CompletionTriggerKind.php', + 'LanguageServerProtocol\\ContentChangeEvent' => $vendorDir . '/felixfbecker/language-server-protocol/src/ContentChangeEvent.php', + 'LanguageServerProtocol\\DependencyReference' => $vendorDir . '/felixfbecker/language-server-protocol/src/DependencyReference.php', + 'LanguageServerProtocol\\Diagnostic' => $vendorDir . '/felixfbecker/language-server-protocol/src/Diagnostic.php', + 'LanguageServerProtocol\\DiagnosticSeverity' => $vendorDir . '/felixfbecker/language-server-protocol/src/DiagnosticSeverity.php', + 'LanguageServerProtocol\\DocumentHighlight' => $vendorDir . '/felixfbecker/language-server-protocol/src/DocumentHighlight.php', + 'LanguageServerProtocol\\DocumentHighlightKind' => $vendorDir . '/felixfbecker/language-server-protocol/src/DocumentHighlightKind.php', + 'LanguageServerProtocol\\DocumentOnTypeFormattingOptions' => $vendorDir . '/felixfbecker/language-server-protocol/src/DocumentOnTypeFormattingOptions.php', + 'LanguageServerProtocol\\ErrorCode' => $vendorDir . '/felixfbecker/language-server-protocol/src/ErrorCode.php', + 'LanguageServerProtocol\\FileChangeType' => $vendorDir . '/felixfbecker/language-server-protocol/src/FileChangeType.php', + 'LanguageServerProtocol\\FileEvent' => $vendorDir . '/felixfbecker/language-server-protocol/src/FileEvent.php', + 'LanguageServerProtocol\\FormattingOptions' => $vendorDir . '/felixfbecker/language-server-protocol/src/FormattingOptions.php', + 'LanguageServerProtocol\\Hover' => $vendorDir . '/felixfbecker/language-server-protocol/src/Hover.php', + 'LanguageServerProtocol\\InitializeResult' => $vendorDir . '/felixfbecker/language-server-protocol/src/InitializeResult.php', + 'LanguageServerProtocol\\InsertTextFormat' => $vendorDir . '/felixfbecker/language-server-protocol/src/InsertTextFormat.php', + 'LanguageServerProtocol\\Location' => $vendorDir . '/felixfbecker/language-server-protocol/src/Location.php', + 'LanguageServerProtocol\\MarkedString' => $vendorDir . '/felixfbecker/language-server-protocol/src/MarkedString.php', + 'LanguageServerProtocol\\MarkupContent' => $vendorDir . '/felixfbecker/language-server-protocol/src/MarkupContent.php', + 'LanguageServerProtocol\\MarkupKind' => $vendorDir . '/felixfbecker/language-server-protocol/src/MarkupKind.php', + 'LanguageServerProtocol\\MessageActionItem' => $vendorDir . '/felixfbecker/language-server-protocol/src/MessageActionItem.php', + 'LanguageServerProtocol\\MessageType' => $vendorDir . '/felixfbecker/language-server-protocol/src/MessageType.php', + 'LanguageServerProtocol\\PackageDescriptor' => $vendorDir . '/felixfbecker/language-server-protocol/src/PackageDescriptor.php', + 'LanguageServerProtocol\\ParameterInformation' => $vendorDir . '/felixfbecker/language-server-protocol/src/ParameterInformation.php', + 'LanguageServerProtocol\\Position' => $vendorDir . '/felixfbecker/language-server-protocol/src/Position.php', + 'LanguageServerProtocol\\Range' => $vendorDir . '/felixfbecker/language-server-protocol/src/Range.php', + 'LanguageServerProtocol\\ReferenceContext' => $vendorDir . '/felixfbecker/language-server-protocol/src/ReferenceContext.php', + 'LanguageServerProtocol\\ReferenceInformation' => $vendorDir . '/felixfbecker/language-server-protocol/src/ReferenceInformation.php', + 'LanguageServerProtocol\\SaveOptions' => $vendorDir . '/felixfbecker/language-server-protocol/src/SaveOptions.php', + 'LanguageServerProtocol\\ServerCapabilities' => $vendorDir . '/felixfbecker/language-server-protocol/src/ServerCapabilities.php', + 'LanguageServerProtocol\\SignatureHelp' => $vendorDir . '/felixfbecker/language-server-protocol/src/SignatureHelp.php', + 'LanguageServerProtocol\\SignatureHelpOptions' => $vendorDir . '/felixfbecker/language-server-protocol/src/SignatureHelpOptions.php', + 'LanguageServerProtocol\\SignatureInformation' => $vendorDir . '/felixfbecker/language-server-protocol/src/SignatureInformation.php', + 'LanguageServerProtocol\\SymbolDescriptor' => $vendorDir . '/felixfbecker/language-server-protocol/src/SymbolDescriptor.php', + 'LanguageServerProtocol\\SymbolInformation' => $vendorDir . '/felixfbecker/language-server-protocol/src/SymbolInformation.php', + 'LanguageServerProtocol\\SymbolKind' => $vendorDir . '/felixfbecker/language-server-protocol/src/SymbolKind.php', + 'LanguageServerProtocol\\SymbolLocationInformation' => $vendorDir . '/felixfbecker/language-server-protocol/src/SymbolLocationInformation.php', + 'LanguageServerProtocol\\TextDocumentContentChangeEvent' => $vendorDir . '/felixfbecker/language-server-protocol/src/TextDocumentContentChangeEvent.php', + 'LanguageServerProtocol\\TextDocumentIdentifier' => $vendorDir . '/felixfbecker/language-server-protocol/src/TextDocumentIdentifier.php', + 'LanguageServerProtocol\\TextDocumentItem' => $vendorDir . '/felixfbecker/language-server-protocol/src/TextDocumentItem.php', + 'LanguageServerProtocol\\TextDocumentSyncKind' => $vendorDir . '/felixfbecker/language-server-protocol/src/TextDocumentSyncKind.php', + 'LanguageServerProtocol\\TextDocumentSyncOptions' => $vendorDir . '/felixfbecker/language-server-protocol/src/TextDocumentSyncOptions.php', + 'LanguageServerProtocol\\TextEdit' => $vendorDir . '/felixfbecker/language-server-protocol/src/TextEdit.php', + 'LanguageServerProtocol\\VersionedTextDocumentIdentifier' => $vendorDir . '/felixfbecker/language-server-protocol/src/VersionedTextDocumentIdentifier.php', + 'LanguageServerProtocol\\WorkspaceEdit' => $vendorDir . '/felixfbecker/language-server-protocol/src/WorkspaceEdit.php', + 'Nextcloud\\CodingStandard\\Config' => $vendorDir . '/nextcloud/coding-standard/src/Config.php', + 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'OCP\\API' => $baseDir . '/lib/public/API.php', 'OCP\\Accounts\\IAccount' => $baseDir . '/lib/public/Accounts/IAccount.php', 'OCP\\Accounts\\IAccountManager' => $baseDir . '/lib/public/Accounts/IAccountManager.php', @@ -1388,4 +1554,1750 @@ return array( 'OC_Template' => $baseDir . '/lib/private/legacy/OC_Template.php', 'OC_User' => $baseDir . '/lib/private/legacy/OC_User.php', 'OC_Util' => $baseDir . '/lib/private/legacy/OC_Util.php', + 'PackageVersions\\FallbackVersions' => $vendorDir . '/composer/package-versions-deprecated/src/PackageVersions/FallbackVersions.php', + 'PackageVersions\\Installer' => $vendorDir . '/composer/package-versions-deprecated/src/PackageVersions/Installer.php', + 'PackageVersions\\Versions' => $vendorDir . '/composer/package-versions-deprecated/src/PackageVersions/Versions.php', + 'ParseError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ParseError.php', + 'PhpCsFixer\\AbstractAlignFixerHelper' => $vendorDir . '/friendsofphp/php-cs-fixer/src/AbstractAlignFixerHelper.php', + 'PhpCsFixer\\AbstractDoctrineAnnotationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.php', + 'PhpCsFixer\\AbstractFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/AbstractFixer.php', + 'PhpCsFixer\\AbstractFopenFlagFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.php', + 'PhpCsFixer\\AbstractFunctionReferenceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.php', + 'PhpCsFixer\\AbstractLinesBeforeNamespaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/AbstractLinesBeforeNamespaceFixer.php', + 'PhpCsFixer\\AbstractNoUselessElseFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.php', + 'PhpCsFixer\\AbstractPhpdocTypesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.php', + 'PhpCsFixer\\AbstractProxyFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.php', + 'PhpCsFixer\\AbstractPsrAutoloadingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/AbstractPsrAutoloadingFixer.php', + 'PhpCsFixer\\Cache\\Cache' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Cache/Cache.php', + 'PhpCsFixer\\Cache\\CacheInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.php', + 'PhpCsFixer\\Cache\\CacheManagerInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.php', + 'PhpCsFixer\\Cache\\Directory' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Cache/Directory.php', + 'PhpCsFixer\\Cache\\DirectoryInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Cache/DirectoryInterface.php', + 'PhpCsFixer\\Cache\\FileCacheManager' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.php', + 'PhpCsFixer\\Cache\\FileHandler' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Cache/FileHandler.php', + 'PhpCsFixer\\Cache\\FileHandlerInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Cache/FileHandlerInterface.php', + 'PhpCsFixer\\Cache\\NullCacheManager' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Cache/NullCacheManager.php', + 'PhpCsFixer\\Cache\\Signature' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Cache/Signature.php', + 'PhpCsFixer\\Cache\\SignatureInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.php', + 'PhpCsFixer\\Config' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Config.php', + 'PhpCsFixer\\ConfigInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/ConfigInterface.php', + 'PhpCsFixer\\ConfigurationException\\InvalidConfigurationException' => $vendorDir . '/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.php', + 'PhpCsFixer\\ConfigurationException\\InvalidFixerConfigurationException' => $vendorDir . '/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.php', + 'PhpCsFixer\\ConfigurationException\\InvalidForEnvFixerConfigurationException' => $vendorDir . '/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidForEnvFixerConfigurationException.php', + 'PhpCsFixer\\ConfigurationException\\RequiredFixerConfigurationException' => $vendorDir . '/friendsofphp/php-cs-fixer/src/ConfigurationException/RequiredFixerConfigurationException.php', + 'PhpCsFixer\\Console\\Application' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Application.php', + 'PhpCsFixer\\Console\\Command\\DescribeCommand' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php', + 'PhpCsFixer\\Console\\Command\\DescribeNameNotFoundException' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Command/DescribeNameNotFoundException.php', + 'PhpCsFixer\\Console\\Command\\FixCommand' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php', + 'PhpCsFixer\\Console\\Command\\FixCommandExitStatusCalculator' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.php', + 'PhpCsFixer\\Console\\Command\\HelpCommand' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php', + 'PhpCsFixer\\Console\\Command\\ReadmeCommand' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Command/ReadmeCommand.php', + 'PhpCsFixer\\Console\\Command\\SelfUpdateCommand' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Command/SelfUpdateCommand.php', + 'PhpCsFixer\\Console\\ConfigurationResolver' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php', + 'PhpCsFixer\\Console\\Output\\ErrorOutput' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php', + 'PhpCsFixer\\Console\\Output\\NullOutput' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Output/NullOutput.php', + 'PhpCsFixer\\Console\\Output\\ProcessOutput' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutput.php', + 'PhpCsFixer\\Console\\Output\\ProcessOutputInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutputInterface.php', + 'PhpCsFixer\\Console\\SelfUpdate\\GithubClient' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.php', + 'PhpCsFixer\\Console\\SelfUpdate\\GithubClientInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.php', + 'PhpCsFixer\\Console\\SelfUpdate\\NewVersionChecker' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php', + 'PhpCsFixer\\Console\\SelfUpdate\\NewVersionCheckerInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionCheckerInterface.php', + 'PhpCsFixer\\Console\\WarningsDetector' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.php', + 'PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\ConfigurationException' => $vendorDir . '/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/ConfigurationException.php', + 'PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\UnifiedDiffOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/UnifiedDiffOutputBuilder.php', + 'PhpCsFixer\\Diff\\v1_4\\Chunk' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/Chunk.php', + 'PhpCsFixer\\Diff\\v1_4\\Diff' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/Diff.php', + 'PhpCsFixer\\Diff\\v1_4\\Differ' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/Differ.php', + 'PhpCsFixer\\Diff\\v1_4\\LCS\\LongestCommonSubsequence' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/LCS/LongestCommonSubsequence.php', + 'PhpCsFixer\\Diff\\v1_4\\LCS\\MemoryEfficientImplementation' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php', + 'PhpCsFixer\\Diff\\v1_4\\LCS\\TimeEfficientImplementation' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php', + 'PhpCsFixer\\Diff\\v1_4\\Line' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/Line.php', + 'PhpCsFixer\\Diff\\v1_4\\Parser' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/Parser.php', + 'PhpCsFixer\\Diff\\v2_0\\Chunk' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Chunk.php', + 'PhpCsFixer\\Diff\\v2_0\\Diff' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Diff.php', + 'PhpCsFixer\\Diff\\v2_0\\Differ' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Differ.php', + 'PhpCsFixer\\Diff\\v2_0\\Exception' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Exception/Exception.php', + 'PhpCsFixer\\Diff\\v2_0\\InvalidArgumentException' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Exception/InvalidArgumentException.php', + 'PhpCsFixer\\Diff\\v2_0\\Line' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Line.php', + 'PhpCsFixer\\Diff\\v2_0\\LongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/LongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Diff\\v2_0\\MemoryEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/MemoryEfficientLongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Diff\\v2_0\\Output\\AbstractChunkOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Output/AbstractChunkOutputBuilder.php', + 'PhpCsFixer\\Diff\\v2_0\\Output\\DiffOnlyOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Output/DiffOnlyOutputBuilder.php', + 'PhpCsFixer\\Diff\\v2_0\\Output\\DiffOutputBuilderInterface' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Output/DiffOutputBuilderInterface.php', + 'PhpCsFixer\\Diff\\v2_0\\Output\\UnifiedDiffOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Output/UnifiedDiffOutputBuilder.php', + 'PhpCsFixer\\Diff\\v2_0\\Parser' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Parser.php', + 'PhpCsFixer\\Diff\\v2_0\\TimeEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/TimeEfficientLongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Diff\\v3_0\\Chunk' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Chunk.php', + 'PhpCsFixer\\Diff\\v3_0\\ConfigurationException' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Exception/ConfigurationException.php', + 'PhpCsFixer\\Diff\\v3_0\\Diff' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Diff.php', + 'PhpCsFixer\\Diff\\v3_0\\Differ' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Differ.php', + 'PhpCsFixer\\Diff\\v3_0\\Exception' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Exception/Exception.php', + 'PhpCsFixer\\Diff\\v3_0\\InvalidArgumentException' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Exception/InvalidArgumentException.php', + 'PhpCsFixer\\Diff\\v3_0\\Line' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Line.php', + 'PhpCsFixer\\Diff\\v3_0\\LongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/LongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Diff\\v3_0\\MemoryEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/MemoryEfficientLongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Diff\\v3_0\\Output\\AbstractChunkOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Output/AbstractChunkOutputBuilder.php', + 'PhpCsFixer\\Diff\\v3_0\\Output\\DiffOnlyOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Output/DiffOnlyOutputBuilder.php', + 'PhpCsFixer\\Diff\\v3_0\\Output\\DiffOutputBuilderInterface' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Output/DiffOutputBuilderInterface.php', + 'PhpCsFixer\\Diff\\v3_0\\Output\\StrictUnifiedDiffOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Output/StrictUnifiedDiffOutputBuilder.php', + 'PhpCsFixer\\Diff\\v3_0\\Output\\UnifiedDiffOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Output/UnifiedDiffOutputBuilder.php', + 'PhpCsFixer\\Diff\\v3_0\\Parser' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Parser.php', + 'PhpCsFixer\\Diff\\v3_0\\TimeEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/TimeEfficientLongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Differ\\DiffConsoleFormatter' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.php', + 'PhpCsFixer\\Differ\\DifferInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Differ/DifferInterface.php', + 'PhpCsFixer\\Differ\\FullDiffer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php', + 'PhpCsFixer\\Differ\\NullDiffer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.php', + 'PhpCsFixer\\Differ\\SebastianBergmannDiffer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Differ/SebastianBergmannDiffer.php', + 'PhpCsFixer\\Differ\\SebastianBergmannShortDiffer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Differ/SebastianBergmannShortDiffer.php', + 'PhpCsFixer\\Differ\\UnifiedDiffer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.php', + 'PhpCsFixer\\DocBlock\\Annotation' => $vendorDir . '/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php', + 'PhpCsFixer\\DocBlock\\DocBlock' => $vendorDir . '/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php', + 'PhpCsFixer\\DocBlock\\Line' => $vendorDir . '/friendsofphp/php-cs-fixer/src/DocBlock/Line.php', + 'PhpCsFixer\\DocBlock\\ShortDescription' => $vendorDir . '/friendsofphp/php-cs-fixer/src/DocBlock/ShortDescription.php', + 'PhpCsFixer\\DocBlock\\Tag' => $vendorDir . '/friendsofphp/php-cs-fixer/src/DocBlock/Tag.php', + 'PhpCsFixer\\DocBlock\\TagComparator' => $vendorDir . '/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.php', + 'PhpCsFixer\\Doctrine\\Annotation\\Token' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.php', + 'PhpCsFixer\\Doctrine\\Annotation\\Tokens' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php', + 'PhpCsFixer\\Error\\Error' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Error/Error.php', + 'PhpCsFixer\\Error\\ErrorsManager' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.php', + 'PhpCsFixer\\Event\\Event' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Event/Event.php', + 'PhpCsFixer\\FileReader' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FileReader.php', + 'PhpCsFixer\\FileRemoval' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FileRemoval.php', + 'PhpCsFixer\\Finder' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Finder.php', + 'PhpCsFixer\\FixerConfiguration\\AliasedFixerOption' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.php', + 'PhpCsFixer\\FixerConfiguration\\AliasedFixerOptionBuilder' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.php', + 'PhpCsFixer\\FixerConfiguration\\AllowedValueSubset' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.php', + 'PhpCsFixer\\FixerConfiguration\\DeprecatedFixerOption' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.php', + 'PhpCsFixer\\FixerConfiguration\\DeprecatedFixerOptionInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOptionInterface.php', + 'PhpCsFixer\\FixerConfiguration\\FixerConfigurationResolver' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.php', + 'PhpCsFixer\\FixerConfiguration\\FixerConfigurationResolverInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.php', + 'PhpCsFixer\\FixerConfiguration\\FixerConfigurationResolverRootless' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverRootless.php', + 'PhpCsFixer\\FixerConfiguration\\FixerOption' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.php', + 'PhpCsFixer\\FixerConfiguration\\FixerOptionBuilder' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php', + 'PhpCsFixer\\FixerConfiguration\\FixerOptionInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.php', + 'PhpCsFixer\\FixerConfiguration\\InvalidOptionsForEnvException' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/InvalidOptionsForEnvException.php', + 'PhpCsFixer\\FixerDefinition\\CodeSample' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.php', + 'PhpCsFixer\\FixerDefinition\\CodeSampleInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.php', + 'PhpCsFixer\\FixerDefinition\\FileSpecificCodeSample' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.php', + 'PhpCsFixer\\FixerDefinition\\FileSpecificCodeSampleInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSampleInterface.php', + 'PhpCsFixer\\FixerDefinition\\FixerDefinition' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.php', + 'PhpCsFixer\\FixerDefinition\\FixerDefinitionInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.php', + 'PhpCsFixer\\FixerDefinition\\VersionSpecificCodeSample' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.php', + 'PhpCsFixer\\FixerDefinition\\VersionSpecificCodeSampleInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSampleInterface.php', + 'PhpCsFixer\\FixerDefinition\\VersionSpecification' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.php', + 'PhpCsFixer\\FixerDefinition\\VersionSpecificationInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificationInterface.php', + 'PhpCsFixer\\FixerFactory' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerFactory.php', + 'PhpCsFixer\\FixerFileProcessedEvent' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.php', + 'PhpCsFixer\\FixerNameValidator' => $vendorDir . '/friendsofphp/php-cs-fixer/src/FixerNameValidator.php', + 'PhpCsFixer\\Fixer\\Alias\\BacktickToShellExecFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\EregToPregFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\MbStrFunctionsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\NoAliasFunctionsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\NoMixedEchoPrintFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\PowToExponentiationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\RandomApiMigrationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\SetTypeToCastFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\ArraySyntaxFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\NoMultilineWhitespaceAroundDoubleArrowFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\NoTrailingCommaInSinglelineArrayFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\NoWhitespaceBeforeCommaInArrayFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\NormalizeIndexBraceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\TrailingCommaInMultilineArrayFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrailingCommaInMultilineArrayFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\TrimArraySpacesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\WhitespaceAfterCommaInArrayFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php', + 'PhpCsFixer\\Fixer\\Basic\\BracesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php', + 'PhpCsFixer\\Fixer\\Basic\\EncodingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.php', + 'PhpCsFixer\\Fixer\\Basic\\NonPrintableCharacterFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php', + 'PhpCsFixer\\Fixer\\Basic\\Psr0Fixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Basic/Psr0Fixer.php', + 'PhpCsFixer\\Fixer\\Basic\\Psr4Fixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Basic/Psr4Fixer.php', + 'PhpCsFixer\\Fixer\\Casing\\ConstantCaseFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\LowercaseConstantsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseConstantsFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\LowercaseKeywordsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\LowercaseStaticReferenceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\MagicConstantCasingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\MagicMethodCasingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\NativeFunctionCasingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\NativeFunctionTypeDeclarationCasingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\CastSpacesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\LowercaseCastFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\ModernizeTypesCastingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\NoShortBoolCastFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\NoUnsetCastFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\ShortScalarCastFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\ClassAttributesSeparationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\ClassDefinitionFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\FinalClassFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\FinalInternalClassFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\FinalPublicMethodForAbstractClassFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\FinalStaticAccessFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalStaticAccessFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\MethodSeparationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/MethodSeparationFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\NoBlankLinesAfterClassOpeningFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\NoNullPropertyInitializationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\NoPhp4ConstructorFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\NoUnneededFinalMethodFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\OrderedClassElementsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\OrderedInterfacesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\ProtectedToPrivateFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\SelfAccessorFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\SelfStaticAccessorFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\SingleClassElementPerStatementFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\SingleTraitInsertPerStatementFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\VisibilityRequiredFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.php', + 'PhpCsFixer\\Fixer\\ClassUsage\\DateTimeImmutableFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\CommentToPhpdocFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\HashToSlashCommentFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/HashToSlashCommentFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\HeaderCommentFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\MultilineCommentOpeningClosingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\NoEmptyCommentFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\NoTrailingWhitespaceInCommentFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\SingleLineCommentStyleFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.php', + 'PhpCsFixer\\Fixer\\ConfigurableFixerInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.php', + 'PhpCsFixer\\Fixer\\ConfigurationDefinitionFixerInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ConfigurationDefinitionFixerInterface.php', + 'PhpCsFixer\\Fixer\\ConstantNotation\\NativeConstantInvocationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\ElseifFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\IncludeFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/IncludeFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoAlternativeSyntaxFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoBreakCommentFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoSuperfluousElseifFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoTrailingCommaInListCallFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoUnneededControlParenthesesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoUnneededCurlyBracesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoUselessElseFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\SwitchCaseSemicolonToColonFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\SwitchCaseSpaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\YodaStyleFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php', + 'PhpCsFixer\\Fixer\\DefinedFixerInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/DefinedFixerInterface.php', + 'PhpCsFixer\\Fixer\\DeprecatedFixerInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.php', + 'PhpCsFixer\\Fixer\\DoctrineAnnotation\\DoctrineAnnotationArrayAssignmentFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php', + 'PhpCsFixer\\Fixer\\DoctrineAnnotation\\DoctrineAnnotationBracesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php', + 'PhpCsFixer\\Fixer\\DoctrineAnnotation\\DoctrineAnnotationIndentationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php', + 'PhpCsFixer\\Fixer\\DoctrineAnnotation\\DoctrineAnnotationSpacesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php', + 'PhpCsFixer\\Fixer\\FixerInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FixerInterface.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\CombineNestedDirnameFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\FopenFlagOrderFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\FopenFlagsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\FunctionDeclarationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\FunctionTypehintSpaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\ImplodeCallFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\MethodArgumentSpaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\NativeFunctionInvocationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\NoSpacesAfterFunctionNameFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\NoUnreachableDefaultArgumentValueFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\NullableTypeDeclarationForDefaultNullValueFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\PhpdocToParamTypeFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\PhpdocToReturnTypeFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\ReturnTypeDeclarationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\SingleLineThrowFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\StaticLambdaFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\VoidReturnFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php', + 'PhpCsFixer\\Fixer\\Import\\FullyQualifiedStrictTypesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php', + 'PhpCsFixer\\Fixer\\Import\\GlobalNamespaceImportFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php', + 'PhpCsFixer\\Fixer\\Import\\NoLeadingImportSlashFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.php', + 'PhpCsFixer\\Fixer\\Import\\NoUnusedImportsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php', + 'PhpCsFixer\\Fixer\\Import\\OrderedImportsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.php', + 'PhpCsFixer\\Fixer\\Import\\SingleImportPerStatementFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.php', + 'PhpCsFixer\\Fixer\\Import\\SingleLineAfterImportsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\ClassKeywordRemoveFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\CombineConsecutiveIssetsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\CombineConsecutiveUnsetsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\DeclareEqualNormalizeFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\DirConstantFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\ErrorSuppressionFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\ExplicitIndirectVariableFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\FunctionToConstantFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\IsNullFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\NoUnsetOnPropertyFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\SilencedDeprecationErrorFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SilencedDeprecationErrorFixer.php', + 'PhpCsFixer\\Fixer\\ListNotation\\ListSyntaxFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.php', + 'PhpCsFixer\\Fixer\\NamespaceNotation\\BlankLineAfterNamespaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php', + 'PhpCsFixer\\Fixer\\NamespaceNotation\\NoBlankLinesBeforeNamespaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php', + 'PhpCsFixer\\Fixer\\NamespaceNotation\\NoLeadingNamespaceWhitespaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php', + 'PhpCsFixer\\Fixer\\NamespaceNotation\\SingleBlankLineBeforeNamespaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php', + 'PhpCsFixer\\Fixer\\Naming\\NoHomoglyphNamesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\AlignDoubleArrowFixerHelper' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/AlignDoubleArrowFixerHelper.php', + 'PhpCsFixer\\Fixer\\Operator\\AlignEqualsFixerHelper' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/AlignEqualsFixerHelper.php', + 'PhpCsFixer\\Fixer\\Operator\\BinaryOperatorSpacesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\ConcatSpaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\IncrementStyleFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\LogicalOperatorsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\NewWithBracesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\NotOperatorWithSpaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\NotOperatorWithSuccessorSpaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\ObjectOperatorWithoutWhitespaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\PreIncrementFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/PreIncrementFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\StandardizeIncrementFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\StandardizeNotEqualsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\TernaryOperatorSpacesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\TernaryToNullCoalescingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\UnaryOperatorSpacesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.php', + 'PhpCsFixer\\Fixer\\PhpTag\\BlankLineAfterOpeningTagFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php', + 'PhpCsFixer\\Fixer\\PhpTag\\FullOpeningTagFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.php', + 'PhpCsFixer\\Fixer\\PhpTag\\LinebreakAfterOpeningTagFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php', + 'PhpCsFixer\\Fixer\\PhpTag\\NoClosingTagFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.php', + 'PhpCsFixer\\Fixer\\PhpTag\\NoShortEchoTagFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoShortEchoTagFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitConstructFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitDedicateAssertFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitDedicateAssertInternalTypeFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitExpectationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitFqcnAnnotationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitInternalClassFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitMethodCasingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitMockFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitMockShortWillReturnFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitNamespacedFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitNoExpectationAnnotationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitOrderedCoversFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitSetUpTearDownVisibilityFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitSizeClassFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitStrictFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitTargetVersion' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitTestAnnotationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitTestCaseStaticMethodCallsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitTestClassRequiresCoversFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\AlignMultilineCommentFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\GeneralPhpdocAnnotationRemoveFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\NoBlankLinesAfterPhpdocFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\NoEmptyPhpdocFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\NoSuperfluousPhpdocTagsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocAddMissingParamAnnotationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocAlignFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocAnnotationWithoutDotFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocIndentFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocInlineTagFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocLineSpanFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocLineSpanFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocNoAccessFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocNoAliasTagFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocNoEmptyReturnFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocNoPackageFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocNoUselessInheritdocFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocOrderFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocReturnSelfReferenceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocScalarFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocSeparationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocSingleLineVarSpacingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocSummaryFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocToCommentFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocTrimConsecutiveBlankLineSeparationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocTrimFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocTypesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocTypesOrderFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocVarAnnotationCorrectOrderFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocVarWithoutNameFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php', + 'PhpCsFixer\\Fixer\\ReturnNotation\\BlankLineBeforeReturnFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/BlankLineBeforeReturnFixer.php', + 'PhpCsFixer\\Fixer\\ReturnNotation\\NoUselessReturnFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php', + 'PhpCsFixer\\Fixer\\ReturnNotation\\ReturnAssignmentFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php', + 'PhpCsFixer\\Fixer\\ReturnNotation\\SimplifiedNullReturnFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\MultilineWhitespaceBeforeSemicolonsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\NoEmptyStatementFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\NoMultilineWhitespaceBeforeSemicolonsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoMultilineWhitespaceBeforeSemicolonsFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\NoSinglelineWhitespaceBeforeSemicolonsFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\SemicolonAfterInstructionFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\SpaceAfterSemicolonFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php', + 'PhpCsFixer\\Fixer\\Strict\\DeclareStrictTypesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.php', + 'PhpCsFixer\\Fixer\\Strict\\StrictComparisonFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.php', + 'PhpCsFixer\\Fixer\\Strict\\StrictParamFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\EscapeImplicitBackslashesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\ExplicitStringVariableFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\HeredocToNowdocFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\NoBinaryStringFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\SimpleToComplexStringVariableFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\SingleQuoteFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\StringLineEndingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\ArrayIndentationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\BlankLineBeforeStatementFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\CompactNullableTypehintFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\HeredocIndentationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/HeredocIndentationFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\IndentationTypeFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\LineEndingFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\MethodChainingIndentationFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoExtraBlankLinesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoExtraConsecutiveBlankLinesFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraConsecutiveBlankLinesFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoSpacesAroundOffsetFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoSpacesInsideParenthesisFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoTrailingWhitespaceFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoWhitespaceInBlankLineFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\SingleBlankLineAtEofFixer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php', + 'PhpCsFixer\\Fixer\\WhitespacesAwareFixerInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Fixer/WhitespacesAwareFixerInterface.php', + 'PhpCsFixer\\Indicator\\PhpUnitTestCaseIndicator' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.php', + 'PhpCsFixer\\Linter\\CachingLinter' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.php', + 'PhpCsFixer\\Linter\\Linter' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Linter/Linter.php', + 'PhpCsFixer\\Linter\\LinterInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Linter/LinterInterface.php', + 'PhpCsFixer\\Linter\\LintingException' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Linter/LintingException.php', + 'PhpCsFixer\\Linter\\LintingResultInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Linter/LintingResultInterface.php', + 'PhpCsFixer\\Linter\\ProcessLinter' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.php', + 'PhpCsFixer\\Linter\\ProcessLinterProcessBuilder' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Linter/ProcessLinterProcessBuilder.php', + 'PhpCsFixer\\Linter\\ProcessLintingResult' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.php', + 'PhpCsFixer\\Linter\\TokenizerLinter' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.php', + 'PhpCsFixer\\Linter\\TokenizerLintingResult' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.php', + 'PhpCsFixer\\Linter\\UnavailableLinterException' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.php', + 'PhpCsFixer\\PharChecker' => $vendorDir . '/friendsofphp/php-cs-fixer/src/PharChecker.php', + 'PhpCsFixer\\PharCheckerInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/PharCheckerInterface.php', + 'PhpCsFixer\\Preg' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Preg.php', + 'PhpCsFixer\\PregException' => $vendorDir . '/friendsofphp/php-cs-fixer/src/PregException.php', + 'PhpCsFixer\\Report\\CheckstyleReporter' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Report/CheckstyleReporter.php', + 'PhpCsFixer\\Report\\GitlabReporter' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Report/GitlabReporter.php', + 'PhpCsFixer\\Report\\JsonReporter' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Report/JsonReporter.php', + 'PhpCsFixer\\Report\\JunitReporter' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Report/JunitReporter.php', + 'PhpCsFixer\\Report\\ReportSummary' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Report/ReportSummary.php', + 'PhpCsFixer\\Report\\ReporterFactory' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Report/ReporterFactory.php', + 'PhpCsFixer\\Report\\ReporterInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Report/ReporterInterface.php', + 'PhpCsFixer\\Report\\TextReporter' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Report/TextReporter.php', + 'PhpCsFixer\\Report\\XmlReporter' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Report/XmlReporter.php', + 'PhpCsFixer\\RuleSet' => $vendorDir . '/friendsofphp/php-cs-fixer/src/RuleSet.php', + 'PhpCsFixer\\RuleSetInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/RuleSetInterface.php', + 'PhpCsFixer\\Runner\\FileCachingLintingIterator' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingIterator.php', + 'PhpCsFixer\\Runner\\FileFilterIterator' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.php', + 'PhpCsFixer\\Runner\\FileLintingIterator' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Runner/FileLintingIterator.php', + 'PhpCsFixer\\Runner\\Runner' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Runner/Runner.php', + 'PhpCsFixer\\StdinFileInfo' => $vendorDir . '/friendsofphp/php-cs-fixer/src/StdinFileInfo.php', + 'PhpCsFixer\\Test\\AbstractFixerTestCase' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Test/AbstractFixerTestCase.php', + 'PhpCsFixer\\Test\\AbstractIntegrationTestCase' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Test/AbstractIntegrationTestCase.php', + 'PhpCsFixer\\Test\\AccessibleObject' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Test/AccessibleObject.php', + 'PhpCsFixer\\Test\\IntegrationCase' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Test/IntegrationCase.php', + 'PhpCsFixer\\Tests\\TestCase' => $vendorDir . '/friendsofphp/php-cs-fixer/tests/TestCase.php', + 'PhpCsFixer\\Tests\\Test\\AbstractFixerTestCase' => $vendorDir . '/friendsofphp/php-cs-fixer/tests/Test/AbstractFixerTestCase.php', + 'PhpCsFixer\\Tests\\Test\\AbstractIntegrationCaseFactory' => $vendorDir . '/friendsofphp/php-cs-fixer/tests/Test/AbstractIntegrationCaseFactory.php', + 'PhpCsFixer\\Tests\\Test\\AbstractIntegrationTestCase' => $vendorDir . '/friendsofphp/php-cs-fixer/tests/Test/AbstractIntegrationTestCase.php', + 'PhpCsFixer\\Tests\\Test\\Assert\\AssertTokensTrait' => $vendorDir . '/friendsofphp/php-cs-fixer/tests/Test/Assert/AssertTokensTrait.php', + 'PhpCsFixer\\Tests\\Test\\IntegrationCase' => $vendorDir . '/friendsofphp/php-cs-fixer/tests/Test/IntegrationCase.php', + 'PhpCsFixer\\Tests\\Test\\IntegrationCaseFactory' => $vendorDir . '/friendsofphp/php-cs-fixer/tests/Test/IntegrationCaseFactory.php', + 'PhpCsFixer\\Tests\\Test\\IntegrationCaseFactoryInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/tests/Test/IntegrationCaseFactoryInterface.php', + 'PhpCsFixer\\Tests\\Test\\InternalIntegrationCaseFactory' => $vendorDir . '/friendsofphp/php-cs-fixer/tests/Test/InternalIntegrationCaseFactory.php', + 'PhpCsFixer\\Tests\\Test\\IsIdenticalConstraint' => $vendorDir . '/friendsofphp/php-cs-fixer/tests/Test/IsIdenticalConstraint.php', + 'PhpCsFixer\\Tokenizer\\AbstractTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\Analysis\\ArgumentAnalysis' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\Analysis\\NamespaceAnalysis' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceAnalysis.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\Analysis\\NamespaceUseAnalysis' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\Analysis\\StartEndTokenAwareAnalysis' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/StartEndTokenAwareAnalysis.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\Analysis\\TypeAnalysis' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\ArgumentsAnalyzer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\BlocksAnalyzer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\ClassyAnalyzer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\CommentsAnalyzer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\FunctionsAnalyzer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\NamespaceUsesAnalyzer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\NamespacesAnalyzer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\CT' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/CT.php', + 'PhpCsFixer\\Tokenizer\\CodeHasher' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/CodeHasher.php', + 'PhpCsFixer\\Tokenizer\\Generator\\NamespacedStringTokenGenerator' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Generator/NamespacedStringTokenGenerator.php', + 'PhpCsFixer\\Tokenizer\\Resolver\\TypeShortNameResolver' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Resolver/TypeShortNameResolver.php', + 'PhpCsFixer\\Tokenizer\\Token' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php', + 'PhpCsFixer\\Tokenizer\\Tokens' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php', + 'PhpCsFixer\\Tokenizer\\TokensAnalyzer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\TransformerInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\ArrayTypehintTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\BraceClassInstantiationTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\ClassConstantTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\CurlyBraceTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/CurlyBraceTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\ImportTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\NamespaceOperatorTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\NullableTypeTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\ReturnRefTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\SquareBraceTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\TypeAlternationTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\TypeColonTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\UseTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\WhitespacyCommentTransformer' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformers' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformers.php', + 'PhpCsFixer\\ToolInfo' => $vendorDir . '/friendsofphp/php-cs-fixer/src/ToolInfo.php', + 'PhpCsFixer\\ToolInfoInterface' => $vendorDir . '/friendsofphp/php-cs-fixer/src/ToolInfoInterface.php', + 'PhpCsFixer\\Utils' => $vendorDir . '/friendsofphp/php-cs-fixer/src/Utils.php', + 'PhpCsFixer\\WhitespacesFixerConfig' => $vendorDir . '/friendsofphp/php-cs-fixer/src/WhitespacesFixerConfig.php', + 'PhpCsFixer\\WordMatcher' => $vendorDir . '/friendsofphp/php-cs-fixer/src/WordMatcher.php', + 'PhpParser\\Builder' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder.php', + 'PhpParser\\BuilderFactory' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php', + 'PhpParser\\BuilderHelpers' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderHelpers.php', + 'PhpParser\\Builder\\Class_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Class_.php', + 'PhpParser\\Builder\\Declaration' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Declaration.php', + 'PhpParser\\Builder\\FunctionLike' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php', + 'PhpParser\\Builder\\Function_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Function_.php', + 'PhpParser\\Builder\\Interface_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Interface_.php', + 'PhpParser\\Builder\\Method' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Method.php', + 'PhpParser\\Builder\\Namespace_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php', + 'PhpParser\\Builder\\Param' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Param.php', + 'PhpParser\\Builder\\Property' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Property.php', + 'PhpParser\\Builder\\TraitUse' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php', + 'PhpParser\\Builder\\TraitUseAdaptation' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php', + 'PhpParser\\Builder\\Trait_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Trait_.php', + 'PhpParser\\Builder\\Use_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Use_.php', + 'PhpParser\\Comment' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Comment.php', + 'PhpParser\\Comment\\Doc' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Comment/Doc.php', + 'PhpParser\\ConstExprEvaluationException' => $vendorDir . '/nikic/php-parser/lib/PhpParser/ConstExprEvaluationException.php', + 'PhpParser\\ConstExprEvaluator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php', + 'PhpParser\\Error' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Error.php', + 'PhpParser\\ErrorHandler' => $vendorDir . '/nikic/php-parser/lib/PhpParser/ErrorHandler.php', + 'PhpParser\\ErrorHandler\\Collecting' => $vendorDir . '/nikic/php-parser/lib/PhpParser/ErrorHandler/Collecting.php', + 'PhpParser\\ErrorHandler\\Throwing' => $vendorDir . '/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php', + 'PhpParser\\Internal\\DiffElem' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Internal/DiffElem.php', + 'PhpParser\\Internal\\Differ' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Internal/Differ.php', + 'PhpParser\\Internal\\PrintableNewAnonClassNode' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php', + 'PhpParser\\Internal\\TokenStream' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php', + 'PhpParser\\JsonDecoder' => $vendorDir . '/nikic/php-parser/lib/PhpParser/JsonDecoder.php', + 'PhpParser\\Lexer' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer.php', + 'PhpParser\\Lexer\\Emulative' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php', + 'PhpParser\\Lexer\\TokenEmulator\\AttributeEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\CoaleseEqualTokenEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\FlexibleDocStringEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\FnTokenEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\KeywordEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\MatchTokenEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\NullsafeTokenEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\NumericLiteralSeparatorEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\ReverseEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\TokenEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php', + 'PhpParser\\NameContext' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NameContext.php', + 'PhpParser\\Node' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node.php', + 'PhpParser\\NodeAbstract' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeAbstract.php', + 'PhpParser\\NodeDumper' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeDumper.php', + 'PhpParser\\NodeFinder' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeFinder.php', + 'PhpParser\\NodeTraverser' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeTraverser.php', + 'PhpParser\\NodeTraverserInterface' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php', + 'PhpParser\\NodeVisitor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeVisitor.php', + 'PhpParser\\NodeVisitorAbstract' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php', + 'PhpParser\\NodeVisitor\\CloningVisitor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php', + 'PhpParser\\NodeVisitor\\FindingVisitor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php', + 'PhpParser\\NodeVisitor\\FirstFindingVisitor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php', + 'PhpParser\\NodeVisitor\\NameResolver' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php', + 'PhpParser\\NodeVisitor\\NodeConnectingVisitor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php', + 'PhpParser\\NodeVisitor\\ParentConnectingVisitor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php', + 'PhpParser\\Node\\Arg' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Arg.php', + 'PhpParser\\Node\\Attribute' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Attribute.php', + 'PhpParser\\Node\\AttributeGroup' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php', + 'PhpParser\\Node\\Const_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Const_.php', + 'PhpParser\\Node\\Expr' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr.php', + 'PhpParser\\Node\\Expr\\ArrayDimFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php', + 'PhpParser\\Node\\Expr\\ArrayItem' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php', + 'PhpParser\\Node\\Expr\\Array_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php', + 'PhpParser\\Node\\Expr\\ArrowFunction' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php', + 'PhpParser\\Node\\Expr\\Assign' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php', + 'PhpParser\\Node\\Expr\\AssignOp' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php', + 'PhpParser\\Node\\Expr\\AssignOp\\BitwiseAnd' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', + 'PhpParser\\Node\\Expr\\AssignOp\\BitwiseOr' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', + 'PhpParser\\Node\\Expr\\AssignOp\\BitwiseXor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Coalesce' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Coalesce.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Concat' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Div' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Minus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Mod' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Mul' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Plus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Pow' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.php', + 'PhpParser\\Node\\Expr\\AssignOp\\ShiftLeft' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', + 'PhpParser\\Node\\Expr\\AssignOp\\ShiftRight' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', + 'PhpParser\\Node\\Expr\\AssignRef' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php', + 'PhpParser\\Node\\Expr\\BinaryOp' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseAnd' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseOr' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseXor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\BooleanAnd' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\BooleanOr' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Coalesce' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Concat' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Div' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Equal' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Greater' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\GreaterOrEqual' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Identical' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\LogicalAnd' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\LogicalOr' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\LogicalXor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Minus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Mod' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Mul' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\NotEqual' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\NotIdentical' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Plus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Pow' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\ShiftLeft' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\ShiftRight' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Smaller' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\SmallerOrEqual' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Spaceship' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', + 'PhpParser\\Node\\Expr\\BitwiseNot' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php', + 'PhpParser\\Node\\Expr\\BooleanNot' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php', + 'PhpParser\\Node\\Expr\\Cast' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php', + 'PhpParser\\Node\\Expr\\Cast\\Array_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php', + 'PhpParser\\Node\\Expr\\Cast\\Bool_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php', + 'PhpParser\\Node\\Expr\\Cast\\Double' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.php', + 'PhpParser\\Node\\Expr\\Cast\\Int_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php', + 'PhpParser\\Node\\Expr\\Cast\\Object_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.php', + 'PhpParser\\Node\\Expr\\Cast\\String_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php', + 'PhpParser\\Node\\Expr\\Cast\\Unset_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.php', + 'PhpParser\\Node\\Expr\\ClassConstFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php', + 'PhpParser\\Node\\Expr\\Clone_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php', + 'PhpParser\\Node\\Expr\\Closure' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php', + 'PhpParser\\Node\\Expr\\ClosureUse' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php', + 'PhpParser\\Node\\Expr\\ConstFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php', + 'PhpParser\\Node\\Expr\\Empty_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php', + 'PhpParser\\Node\\Expr\\Error' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php', + 'PhpParser\\Node\\Expr\\ErrorSuppress' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php', + 'PhpParser\\Node\\Expr\\Eval_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php', + 'PhpParser\\Node\\Expr\\Exit_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php', + 'PhpParser\\Node\\Expr\\FuncCall' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php', + 'PhpParser\\Node\\Expr\\Include_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php', + 'PhpParser\\Node\\Expr\\Instanceof_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php', + 'PhpParser\\Node\\Expr\\Isset_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php', + 'PhpParser\\Node\\Expr\\List_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php', + 'PhpParser\\Node\\Expr\\Match_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php', + 'PhpParser\\Node\\Expr\\MethodCall' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php', + 'PhpParser\\Node\\Expr\\New_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php', + 'PhpParser\\Node\\Expr\\NullsafeMethodCall' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php', + 'PhpParser\\Node\\Expr\\NullsafePropertyFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php', + 'PhpParser\\Node\\Expr\\PostDec' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php', + 'PhpParser\\Node\\Expr\\PostInc' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php', + 'PhpParser\\Node\\Expr\\PreDec' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php', + 'PhpParser\\Node\\Expr\\PreInc' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php', + 'PhpParser\\Node\\Expr\\Print_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php', + 'PhpParser\\Node\\Expr\\PropertyFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php', + 'PhpParser\\Node\\Expr\\ShellExec' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php', + 'PhpParser\\Node\\Expr\\StaticCall' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php', + 'PhpParser\\Node\\Expr\\StaticPropertyFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php', + 'PhpParser\\Node\\Expr\\Ternary' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php', + 'PhpParser\\Node\\Expr\\Throw_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php', + 'PhpParser\\Node\\Expr\\UnaryMinus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php', + 'PhpParser\\Node\\Expr\\UnaryPlus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php', + 'PhpParser\\Node\\Expr\\Variable' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php', + 'PhpParser\\Node\\Expr\\YieldFrom' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php', + 'PhpParser\\Node\\Expr\\Yield_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php', + 'PhpParser\\Node\\FunctionLike' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php', + 'PhpParser\\Node\\Identifier' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Identifier.php', + 'PhpParser\\Node\\MatchArm' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/MatchArm.php', + 'PhpParser\\Node\\Name' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Name.php', + 'PhpParser\\Node\\Name\\FullyQualified' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php', + 'PhpParser\\Node\\Name\\Relative' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php', + 'PhpParser\\Node\\NullableType' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/NullableType.php', + 'PhpParser\\Node\\Param' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Param.php', + 'PhpParser\\Node\\Scalar' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar.php', + 'PhpParser\\Node\\Scalar\\DNumber' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php', + 'PhpParser\\Node\\Scalar\\Encapsed' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php', + 'PhpParser\\Node\\Scalar\\EncapsedStringPart' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php', + 'PhpParser\\Node\\Scalar\\LNumber' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php', + 'PhpParser\\Node\\Scalar\\MagicConst' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Class_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Dir' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\File' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Function_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Line' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Method' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Namespace_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Trait_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', + 'PhpParser\\Node\\Scalar\\String_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php', + 'PhpParser\\Node\\Stmt' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt.php', + 'PhpParser\\Node\\Stmt\\Break_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php', + 'PhpParser\\Node\\Stmt\\Case_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php', + 'PhpParser\\Node\\Stmt\\Catch_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php', + 'PhpParser\\Node\\Stmt\\ClassConst' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php', + 'PhpParser\\Node\\Stmt\\ClassLike' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php', + 'PhpParser\\Node\\Stmt\\ClassMethod' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php', + 'PhpParser\\Node\\Stmt\\Class_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php', + 'PhpParser\\Node\\Stmt\\Const_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php', + 'PhpParser\\Node\\Stmt\\Continue_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php', + 'PhpParser\\Node\\Stmt\\DeclareDeclare' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php', + 'PhpParser\\Node\\Stmt\\Declare_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php', + 'PhpParser\\Node\\Stmt\\Do_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php', + 'PhpParser\\Node\\Stmt\\Echo_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php', + 'PhpParser\\Node\\Stmt\\ElseIf_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php', + 'PhpParser\\Node\\Stmt\\Else_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php', + 'PhpParser\\Node\\Stmt\\Expression' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php', + 'PhpParser\\Node\\Stmt\\Finally_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php', + 'PhpParser\\Node\\Stmt\\For_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php', + 'PhpParser\\Node\\Stmt\\Foreach_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php', + 'PhpParser\\Node\\Stmt\\Function_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php', + 'PhpParser\\Node\\Stmt\\Global_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php', + 'PhpParser\\Node\\Stmt\\Goto_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php', + 'PhpParser\\Node\\Stmt\\GroupUse' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php', + 'PhpParser\\Node\\Stmt\\HaltCompiler' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php', + 'PhpParser\\Node\\Stmt\\If_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php', + 'PhpParser\\Node\\Stmt\\InlineHTML' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php', + 'PhpParser\\Node\\Stmt\\Interface_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php', + 'PhpParser\\Node\\Stmt\\Label' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php', + 'PhpParser\\Node\\Stmt\\Namespace_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php', + 'PhpParser\\Node\\Stmt\\Nop' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php', + 'PhpParser\\Node\\Stmt\\Property' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php', + 'PhpParser\\Node\\Stmt\\PropertyProperty' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php', + 'PhpParser\\Node\\Stmt\\Return_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php', + 'PhpParser\\Node\\Stmt\\StaticVar' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php', + 'PhpParser\\Node\\Stmt\\Static_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php', + 'PhpParser\\Node\\Stmt\\Switch_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php', + 'PhpParser\\Node\\Stmt\\Throw_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php', + 'PhpParser\\Node\\Stmt\\TraitUse' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php', + 'PhpParser\\Node\\Stmt\\TraitUseAdaptation' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', + 'PhpParser\\Node\\Stmt\\TraitUseAdaptation\\Alias' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', + 'PhpParser\\Node\\Stmt\\TraitUseAdaptation\\Precedence' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', + 'PhpParser\\Node\\Stmt\\Trait_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php', + 'PhpParser\\Node\\Stmt\\TryCatch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php', + 'PhpParser\\Node\\Stmt\\Unset_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php', + 'PhpParser\\Node\\Stmt\\UseUse' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php', + 'PhpParser\\Node\\Stmt\\Use_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php', + 'PhpParser\\Node\\Stmt\\While_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php', + 'PhpParser\\Node\\UnionType' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/UnionType.php', + 'PhpParser\\Node\\VarLikeIdentifier' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php', + 'PhpParser\\Parser' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser.php', + 'PhpParser\\ParserAbstract' => $vendorDir . '/nikic/php-parser/lib/PhpParser/ParserAbstract.php', + 'PhpParser\\ParserFactory' => $vendorDir . '/nikic/php-parser/lib/PhpParser/ParserFactory.php', + 'PhpParser\\Parser\\Multiple' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser/Multiple.php', + 'PhpParser\\Parser\\Php5' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser/Php5.php', + 'PhpParser\\Parser\\Php7' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser/Php7.php', + 'PhpParser\\Parser\\Tokens' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser/Tokens.php', + 'PhpParser\\PrettyPrinterAbstract' => $vendorDir . '/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php', + 'PhpParser\\PrettyPrinter\\Standard' => $vendorDir . '/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php', + 'Psalm\\Aliases' => $vendorDir . '/vimeo/psalm/src/Psalm/Aliases.php', + 'Psalm\\CodeLocation' => $vendorDir . '/vimeo/psalm/src/Psalm/CodeLocation.php', + 'Psalm\\CodeLocation\\DocblockTypeLocation' => $vendorDir . '/vimeo/psalm/src/Psalm/CodeLocation/DocblockTypeLocation.php', + 'Psalm\\CodeLocation\\ParseErrorLocation' => $vendorDir . '/vimeo/psalm/src/Psalm/CodeLocation/ParseErrorLocation.php', + 'Psalm\\CodeLocation\\Raw' => $vendorDir . '/vimeo/psalm/src/Psalm/CodeLocation/Raw.php', + 'Psalm\\Codebase' => $vendorDir . '/vimeo/psalm/src/Psalm/Codebase.php', + 'Psalm\\Config' => $vendorDir . '/vimeo/psalm/src/Psalm/Config.php', + 'Psalm\\Config\\Creator' => $vendorDir . '/vimeo/psalm/src/Psalm/Config/Creator.php', + 'Psalm\\Config\\ErrorLevelFileFilter' => $vendorDir . '/vimeo/psalm/src/Psalm/Config/ErrorLevelFileFilter.php', + 'Psalm\\Config\\FileFilter' => $vendorDir . '/vimeo/psalm/src/Psalm/Config/FileFilter.php', + 'Psalm\\Config\\IssueHandler' => $vendorDir . '/vimeo/psalm/src/Psalm/Config/IssueHandler.php', + 'Psalm\\Config\\ProjectFileFilter' => $vendorDir . '/vimeo/psalm/src/Psalm/Config/ProjectFileFilter.php', + 'Psalm\\Config\\TaintAnalysisFileFilter' => $vendorDir . '/vimeo/psalm/src/Psalm/Config/TaintAnalysisFileFilter.php', + 'Psalm\\Context' => $vendorDir . '/vimeo/psalm/src/Psalm/Context.php', + 'Psalm\\DocComment' => $vendorDir . '/vimeo/psalm/src/Psalm/DocComment.php', + 'Psalm\\ErrorBaseline' => $vendorDir . '/vimeo/psalm/src/Psalm/ErrorBaseline.php', + 'Psalm\\Exception\\CircularReferenceException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/CircularReferenceException.php', + 'Psalm\\Exception\\CodeException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/CodeException.php', + 'Psalm\\Exception\\ComplicatedExpressionException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/ComplicatedExpressionException.php', + 'Psalm\\Exception\\ConfigCreationException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/ConfigCreationException.php', + 'Psalm\\Exception\\ConfigException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/ConfigException.php', + 'Psalm\\Exception\\DocblockParseException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/DocblockParseException.php', + 'Psalm\\Exception\\FileIncludeException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/FileIncludeException.php', + 'Psalm\\Exception\\IncorrectDocblockException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/IncorrectDocblockException.php', + 'Psalm\\Exception\\InvalidClasslikeOverrideException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/InvalidClasslikeOverrideException.php', + 'Psalm\\Exception\\InvalidMethodOverrideException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/InvalidMethodOverrideException.php', + 'Psalm\\Exception\\RefactorException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/RefactorException.php', + 'Psalm\\Exception\\ScopeAnalysisException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/ScopeAnalysisException.php', + 'Psalm\\Exception\\TypeParseTreeException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/TypeParseTreeException.php', + 'Psalm\\Exception\\UnanalyzedFileException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/UnanalyzedFileException.php', + 'Psalm\\Exception\\UnpopulatedClasslikeException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/UnpopulatedClasslikeException.php', + 'Psalm\\Exception\\UnpreparedAnalysisException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/UnpreparedAnalysisException.php', + 'Psalm\\Exception\\UnsupportedIssueToFixException' => $vendorDir . '/vimeo/psalm/src/Psalm/Exception/UnsupportedIssueToFixException.php', + 'Psalm\\FileBasedPluginAdapter' => $vendorDir . '/vimeo/psalm/src/Psalm/FileBasedPluginAdapter.php', + 'Psalm\\FileManipulation' => $vendorDir . '/vimeo/psalm/src/Psalm/FileManipulation.php', + 'Psalm\\FileSource' => $vendorDir . '/vimeo/psalm/src/Psalm/FileSource.php', + 'Psalm\\Internal\\Analyzer\\AlgebraAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/AlgebraAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\CanAlias' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/CanAlias.php', + 'Psalm\\Internal\\Analyzer\\ClassAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\ClassLikeAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\ClosureAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\CommentAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/CommentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\DataFlowNodeData' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/DataFlowNodeData.php', + 'Psalm\\Internal\\Analyzer\\FileAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\FunctionAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\FunctionLikeAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\FunctionLike\\ReturnTypeAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\FunctionLike\\ReturnTypeCollector' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php', + 'Psalm\\Internal\\Analyzer\\InterfaceAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\IssueData' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/IssueData.php', + 'Psalm\\Internal\\Analyzer\\MethodAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/MethodAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\MethodComparator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/MethodComparator.php', + 'Psalm\\Internal\\Analyzer\\NamespaceAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\ProjectAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\ScopeAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\SourceAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/SourceAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\StatementsAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\DoAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\ForAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\ForeachAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\IfAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\LoopAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\SwitchAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\SwitchCaseAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\TryAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\WhileAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/WhileAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\BreakAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\ContinueAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\EchoAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\ExpressionAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\ArrayAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\AssertionFinder' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\AssignmentAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Assignment\\ArrayAssignmentAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Assignment\\InstancePropertyAssignmentAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Assignment\\StaticPropertyAssignmentAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/StaticPropertyAssignmentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOpAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\AndAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\CoalesceAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\ConcatAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\NonComparisonOpAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonComparisonOpAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\NonDivArithmeticOpAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonDivArithmeticOpAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\OrAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BitwiseNotAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BooleanNotAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\CallAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\ArgumentAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\ArgumentMapPopulator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\ArgumentsAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\ArrayFunctionArgumentsAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\ClassTemplateParamCollector' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\FunctionCallAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\MethodCallAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\AtomicCallContext' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicCallContext.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\AtomicMethodCallAnalysisResult' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\AtomicMethodCallAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\MethodCallProhibitionAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallProhibitionAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\MethodCallPurityAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\MethodCallReturnTypeFetcher' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\MethodVisibilityAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodVisibilityAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\MissingMethodCallHandler' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\NewAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\StaticCallAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\CastAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\CloneAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\EmptyAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\EncapsulatedStringAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\EvalAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\ExitAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\ExpressionIdentifier' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ExpressionIdentifier.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\ArrayFetchAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\AtomicPropertyFetchAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\ClassConstFetchAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\ConstFetchAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\InstancePropertyFetchAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\StaticPropertyFetchAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/StaticPropertyFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\VariableFetchAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\IncDecExpressionAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\IncludeAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\InstanceofAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/InstanceofAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\IssetAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\MagicConstAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\MatchAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\NullsafeAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/NullsafeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\PrintAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\SimpleTypeInferer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\TernaryAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\UnaryPlusMinusAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\YieldAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\YieldFromAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\GlobalAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\ReturnAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\StaticAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\ThrowAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\UnsetAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\UnusedAssignmentRemover' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php', + 'Psalm\\Internal\\Analyzer\\TraitAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/TraitAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\TypeAnalyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Analyzer/TypeAnalyzer.php', + 'Psalm\\Internal\\Clause' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Clause.php', + 'Psalm\\Internal\\Codebase\\Analyzer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php', + 'Psalm\\Internal\\Codebase\\ClassLikes' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/ClassLikes.php', + 'Psalm\\Internal\\Codebase\\ConstantTypeResolver' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/ConstantTypeResolver.php', + 'Psalm\\Internal\\Codebase\\DataFlowGraph' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/DataFlowGraph.php', + 'Psalm\\Internal\\Codebase\\Functions' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/Functions.php', + 'Psalm\\Internal\\Codebase\\InternalCallMapHandler' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/InternalCallMapHandler.php', + 'Psalm\\Internal\\Codebase\\Methods' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/Methods.php', + 'Psalm\\Internal\\Codebase\\Populator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/Populator.php', + 'Psalm\\Internal\\Codebase\\Properties' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/Properties.php', + 'Psalm\\Internal\\Codebase\\PropertyMap' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/PropertyMap.php', + 'Psalm\\Internal\\Codebase\\ReferenceMapGenerator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/ReferenceMapGenerator.php', + 'Psalm\\Internal\\Codebase\\Reflection' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/Reflection.php', + 'Psalm\\Internal\\Codebase\\Scanner' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/Scanner.php', + 'Psalm\\Internal\\Codebase\\TaintFlowGraph' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/TaintFlowGraph.php', + 'Psalm\\Internal\\Codebase\\VariableUseGraph' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Codebase/VariableUseGraph.php', + 'Psalm\\Internal\\Composer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Composer.php', + 'Psalm\\Internal\\DataFlow\\DataFlowNode' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/DataFlow/DataFlowNode.php', + 'Psalm\\Internal\\DataFlow\\Path' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/DataFlow/Path.php', + 'Psalm\\Internal\\DataFlow\\TaintSink' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/DataFlow/TaintSink.php', + 'Psalm\\Internal\\DataFlow\\TaintSource' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/DataFlow/TaintSource.php', + 'Psalm\\Internal\\Diff\\AstDiffer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Diff/AstDiffer.php', + 'Psalm\\Internal\\Diff\\ClassStatementsDiffer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Diff/ClassStatementsDiffer.php', + 'Psalm\\Internal\\Diff\\DiffElem' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Diff/DiffElem.php', + 'Psalm\\Internal\\Diff\\FileDiffer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Diff/FileDiffer.php', + 'Psalm\\Internal\\Diff\\FileStatementsDiffer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Diff/FileStatementsDiffer.php', + 'Psalm\\Internal\\Diff\\NamespaceStatementsDiffer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php', + 'Psalm\\Internal\\ExecutionEnvironment\\BuildInfoCollector' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/BuildInfoCollector.php', + 'Psalm\\Internal\\ExecutionEnvironment\\GitInfoCollector' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php', + 'Psalm\\Internal\\ExecutionEnvironment\\SystemCommandExecutor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/SystemCommandExecutor.php', + 'Psalm\\Internal\\FileManipulation\\ClassDocblockManipulator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/FileManipulation/ClassDocblockManipulator.php', + 'Psalm\\Internal\\FileManipulation\\CodeMigration' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/FileManipulation/CodeMigration.php', + 'Psalm\\Internal\\FileManipulation\\FileManipulationBuffer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/FileManipulation/FileManipulationBuffer.php', + 'Psalm\\Internal\\FileManipulation\\FunctionDocblockManipulator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php', + 'Psalm\\Internal\\FileManipulation\\PropertyDocblockManipulator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php', + 'Psalm\\Internal\\Fork\\ForkMessage' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Fork/ForkMessage.php', + 'Psalm\\Internal\\Fork\\ForkProcessDoneMessage' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Fork/ForkProcessDoneMessage.php', + 'Psalm\\Internal\\Fork\\ForkProcessErrorMessage' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php', + 'Psalm\\Internal\\Fork\\ForkTaskDoneMessage' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php', + 'Psalm\\Internal\\Fork\\Pool' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php', + 'Psalm\\Internal\\Fork\\PsalmRestarter' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Fork/PsalmRestarter.php', + 'Psalm\\Internal\\IncludeCollector' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/IncludeCollector.php', + 'Psalm\\Internal\\Json\\Json' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Json/Json.php', + 'Psalm\\Internal\\LanguageServer\\ClientHandler' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/ClientHandler.php', + 'Psalm\\Internal\\LanguageServer\\Client\\TextDocument' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/Client/TextDocument.php', + 'Psalm\\Internal\\LanguageServer\\EmitterInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/EmitterInterface.php', + 'Psalm\\Internal\\LanguageServer\\EmitterTrait' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/EmitterTrait.php', + 'Psalm\\Internal\\LanguageServer\\IdGenerator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/IdGenerator.php', + 'Psalm\\Internal\\LanguageServer\\LanguageClient' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/LanguageClient.php', + 'Psalm\\Internal\\LanguageServer\\LanguageServer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/LanguageServer.php', + 'Psalm\\Internal\\LanguageServer\\Message' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/Message.php', + 'Psalm\\Internal\\LanguageServer\\ProtocolReader' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolReader.php', + 'Psalm\\Internal\\LanguageServer\\ProtocolStreamReader' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolStreamReader.php', + 'Psalm\\Internal\\LanguageServer\\ProtocolStreamWriter' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolStreamWriter.php', + 'Psalm\\Internal\\LanguageServer\\ProtocolWriter' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolWriter.php', + 'Psalm\\Internal\\LanguageServer\\Server\\TextDocument' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/Server/TextDocument.php', + 'Psalm\\Internal\\MethodIdentifier' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/MethodIdentifier.php', + 'Psalm\\Internal\\PhpTraverser\\CustomTraverser' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpTraverser/CustomTraverser.php', + 'Psalm\\Internal\\PhpVisitor\\AssignmentMapVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/AssignmentMapVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\CheckTrivialExprVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\CloningVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/CloningVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\ConditionCloningVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\NodeCleanerVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/NodeCleanerVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\NodeCounterVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/NodeCounterVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\OffsetShifterVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/OffsetShifterVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\ParamReplacementVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ParamReplacementVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\PartialParserVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\ReflectorVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\ShortClosureVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ShortClosureVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\SimpleNameResolver' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php', + 'Psalm\\Internal\\PhpVisitor\\TraitFinder' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/TraitFinder.php', + 'Psalm\\Internal\\PhpVisitor\\TypeMappingVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php', + 'Psalm\\Internal\\PluginManager\\Command\\DisableCommand' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/DisableCommand.php', + 'Psalm\\Internal\\PluginManager\\Command\\EnableCommand' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/EnableCommand.php', + 'Psalm\\Internal\\PluginManager\\Command\\ShowCommand' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/ShowCommand.php', + 'Psalm\\Internal\\PluginManager\\ComposerLock' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PluginManager/ComposerLock.php', + 'Psalm\\Internal\\PluginManager\\ConfigFile' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PluginManager/ConfigFile.php', + 'Psalm\\Internal\\PluginManager\\PluginList' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PluginManager/PluginList.php', + 'Psalm\\Internal\\PluginManager\\PluginListFactory' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/PluginManager/PluginListFactory.php', + 'Psalm\\Internal\\Provider\\ClassLikeStorageCacheProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php', + 'Psalm\\Internal\\Provider\\ClassLikeStorageProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php', + 'Psalm\\Internal\\Provider\\FileProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/FileProvider.php', + 'Psalm\\Internal\\Provider\\FileReferenceCacheProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php', + 'Psalm\\Internal\\Provider\\FileReferenceProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/FileReferenceProvider.php', + 'Psalm\\Internal\\Provider\\FileStorageCacheProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageCacheProvider.php', + 'Psalm\\Internal\\Provider\\FileStorageProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageProvider.php', + 'Psalm\\Internal\\Provider\\FunctionExistenceProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/FunctionExistenceProvider.php', + 'Psalm\\Internal\\Provider\\FunctionParamsProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/FunctionParamsProvider.php', + 'Psalm\\Internal\\Provider\\FunctionReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\MethodExistenceProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/MethodExistenceProvider.php', + 'Psalm\\Internal\\Provider\\MethodParamsProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/MethodParamsProvider.php', + 'Psalm\\Internal\\Provider\\MethodReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\MethodVisibilityProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/MethodVisibilityProvider.php', + 'Psalm\\Internal\\Provider\\NodeDataProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/NodeDataProvider.php', + 'Psalm\\Internal\\Provider\\ParserCacheProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ParserCacheProvider.php', + 'Psalm\\Internal\\Provider\\ProjectCacheProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ProjectCacheProvider.php', + 'Psalm\\Internal\\Provider\\PropertyExistenceProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/PropertyExistenceProvider.php', + 'Psalm\\Internal\\Provider\\PropertyTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/PropertyTypeProvider.php', + 'Psalm\\Internal\\Provider\\PropertyVisibilityProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php', + 'Psalm\\Internal\\Provider\\Providers' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/Providers.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayChunkReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayChunkReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayColumnReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayFillReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayFilterReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayMapReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayMergeReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayPadReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPadReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayPointerAdjustmentReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayPopReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayRandReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayReduceReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayReverseReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArraySliceReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySliceReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayUniqueReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayValuesReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayValuesReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ClosureFromCallableReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ClosureFromCallableReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\DomNodeAppendChild' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/DomNodeAppendChild.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ExplodeReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\FilterVarReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\FirstArgStringReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/FirstArgStringReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\GetClassMethodsReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/GetClassMethodsReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\GetObjectVarsReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\HexdecReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\IteratorToArrayReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/IteratorToArrayReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\MktimeReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ParseUrlReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\PdoStatementReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\PdoStatementSetFetchMode' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\SimpleXmlElementAsXml' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/SimpleXmlElementAsXml.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\StrReplaceReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\VersionCompareReturnTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/VersionCompareReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\StatementsProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Provider/StatementsProvider.php', + 'Psalm\\Internal\\ReferenceConstraint' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/ReferenceConstraint.php', + 'Psalm\\Internal\\RuntimeCaches' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/RuntimeCaches.php', + 'Psalm\\Internal\\Scanner\\ClassLikeDocblockComment' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/ClassLikeDocblockComment.php', + 'Psalm\\Internal\\Scanner\\DocblockParser' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/DocblockParser.php', + 'Psalm\\Internal\\Scanner\\FileScanner' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/FileScanner.php', + 'Psalm\\Internal\\Scanner\\FunctionDocblockComment' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/FunctionDocblockComment.php', + 'Psalm\\Internal\\Scanner\\ParsedDocblock' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/ParsedDocblock.php', + 'Psalm\\Internal\\Scanner\\PhpStormMetaScanner' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstantComponent' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\ArrayOffsetFetch' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\ArrayValue' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\ClassConstant' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\Constant' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\KeyValuePair' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\ScalarValue' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedAdditionOp' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedBinaryOp' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBinaryOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedBitwiseOr' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseOr.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedConcatOp' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedConcatOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedDivisionOp' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedDivisionOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedMultiplicationOp' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedMultiplicationOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedSubtractionOp' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedSubtractionOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedTernary' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedTernary.php', + 'Psalm\\Internal\\Scanner\\VarDocblockComment' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scanner/VarDocblockComment.php', + 'Psalm\\Internal\\Scope\\CaseScope' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scope/CaseScope.php', + 'Psalm\\Internal\\Scope\\FinallyScope' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scope/FinallyScope.php', + 'Psalm\\Internal\\Scope\\IfConditionalScope' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scope/IfConditionalScope.php', + 'Psalm\\Internal\\Scope\\IfScope' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scope/IfScope.php', + 'Psalm\\Internal\\Scope\\LoopScope' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scope/LoopScope.php', + 'Psalm\\Internal\\Scope\\SwitchScope' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Scope/SwitchScope.php', + 'Psalm\\Internal\\Stubs\\Generator\\ClassLikeStubGenerator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php', + 'Psalm\\Internal\\Stubs\\Generator\\StubsGenerator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php', + 'Psalm\\Internal\\TypeVisitor\\ContainsClassLikeVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php', + 'Psalm\\Internal\\TypeVisitor\\FromDocblockSetter' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/TypeVisitor/FromDocblockSetter.php', + 'Psalm\\Internal\\TypeVisitor\\TemplateTypeCollector' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php', + 'Psalm\\Internal\\TypeVisitor\\TypeChecker' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TypeChecker.php', + 'Psalm\\Internal\\TypeVisitor\\TypeScanner' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TypeScanner.php', + 'Psalm\\Internal\\Type\\ArrayType' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ArrayType.php', + 'Psalm\\Internal\\Type\\AssertionReconciler' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/AssertionReconciler.php', + 'Psalm\\Internal\\Type\\Comparator\\ArrayTypeComparator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\AtomicTypeComparator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\CallableTypeComparator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\ClassStringComparator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ClassStringComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\GenericTypeComparator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\KeyedArrayComparator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\ObjectComparator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ObjectComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\ScalarTypeComparator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\TypeComparisonResult' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php', + 'Psalm\\Internal\\Type\\Comparator\\UnionTypeComparator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php', + 'Psalm\\Internal\\Type\\NegatedAssertionReconciler' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/NegatedAssertionReconciler.php', + 'Psalm\\Internal\\Type\\ParseTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree.php', + 'Psalm\\Internal\\Type\\ParseTreeCreator' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTreeCreator.php', + 'Psalm\\Internal\\Type\\ParseTree\\CallableParamTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\CallableTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\CallableWithReturnTypeTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableWithReturnTypeTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\ConditionalTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/ConditionalTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\EncapsulationTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/EncapsulationTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\GenericTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/GenericTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\IndexedAccessTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/IndexedAccessTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\IntersectionTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/IntersectionTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\KeyedArrayPropertyTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/KeyedArrayPropertyTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\KeyedArrayTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/KeyedArrayTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\MethodParamTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodParamTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\MethodTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\MethodWithReturnTypeTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodWithReturnTypeTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\NullableTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/NullableTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\Root' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/Root.php', + 'Psalm\\Internal\\Type\\ParseTree\\TemplateAsTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/TemplateAsTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\TemplateIsTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\UnionTree' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/UnionTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\Value' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/Value.php', + 'Psalm\\Internal\\Type\\SimpleAssertionReconciler' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/SimpleAssertionReconciler.php', + 'Psalm\\Internal\\Type\\SimpleNegatedAssertionReconciler' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php', + 'Psalm\\Internal\\Type\\TemplateResult' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/TemplateResult.php', + 'Psalm\\Internal\\Type\\TypeAlias' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias.php', + 'Psalm\\Internal\\Type\\TypeAlias\\ClassTypeAlias' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/ClassTypeAlias.php', + 'Psalm\\Internal\\Type\\TypeAlias\\InlineTypeAlias' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php', + 'Psalm\\Internal\\Type\\TypeAlias\\LinkableTypeAlias' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php', + 'Psalm\\Internal\\Type\\TypeCombination' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/TypeCombination.php', + 'Psalm\\Internal\\Type\\TypeExpander' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/TypeExpander.php', + 'Psalm\\Internal\\Type\\TypeParser' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/TypeParser.php', + 'Psalm\\Internal\\Type\\TypeTokenizer' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/TypeTokenizer.php', + 'Psalm\\Internal\\Type\\UnionTemplateHandler' => $vendorDir . '/vimeo/psalm/src/Psalm/Internal/Type/UnionTemplateHandler.php', + 'Psalm\\IssueBuffer' => $vendorDir . '/vimeo/psalm/src/Psalm/IssueBuffer.php', + 'Psalm\\Issue\\AbstractInstantiation' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/AbstractInstantiation.php', + 'Psalm\\Issue\\AbstractMethodCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/AbstractMethodCall.php', + 'Psalm\\Issue\\ArgumentIssue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ArgumentIssue.php', + 'Psalm\\Issue\\ArgumentTypeCoercion' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ArgumentTypeCoercion.php', + 'Psalm\\Issue\\AssignmentToVoid' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/AssignmentToVoid.php', + 'Psalm\\Issue\\CircularReference' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/CircularReference.php', + 'Psalm\\Issue\\ClassIssue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ClassIssue.php', + 'Psalm\\Issue\\CodeIssue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/CodeIssue.php', + 'Psalm\\Issue\\ConflictingReferenceConstraint' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ConflictingReferenceConstraint.php', + 'Psalm\\Issue\\ConstructorSignatureMismatch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ConstructorSignatureMismatch.php', + 'Psalm\\Issue\\ContinueOutsideLoop' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ContinueOutsideLoop.php', + 'Psalm\\Issue\\DeprecatedClass' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DeprecatedClass.php', + 'Psalm\\Issue\\DeprecatedConstant' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DeprecatedConstant.php', + 'Psalm\\Issue\\DeprecatedFunction' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DeprecatedFunction.php', + 'Psalm\\Issue\\DeprecatedInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DeprecatedInterface.php', + 'Psalm\\Issue\\DeprecatedMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DeprecatedMethod.php', + 'Psalm\\Issue\\DeprecatedProperty' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DeprecatedProperty.php', + 'Psalm\\Issue\\DeprecatedTrait' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DeprecatedTrait.php', + 'Psalm\\Issue\\DocblockTypeContradiction' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DocblockTypeContradiction.php', + 'Psalm\\Issue\\DuplicateArrayKey' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DuplicateArrayKey.php', + 'Psalm\\Issue\\DuplicateClass' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DuplicateClass.php', + 'Psalm\\Issue\\DuplicateFunction' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DuplicateFunction.php', + 'Psalm\\Issue\\DuplicateMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DuplicateMethod.php', + 'Psalm\\Issue\\DuplicateParam' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/DuplicateParam.php', + 'Psalm\\Issue\\EmptyArrayAccess' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/EmptyArrayAccess.php', + 'Psalm\\Issue\\ExtensionRequirementViolation' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ExtensionRequirementViolation.php', + 'Psalm\\Issue\\FalsableReturnStatement' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/FalsableReturnStatement.php', + 'Psalm\\Issue\\FalseOperand' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/FalseOperand.php', + 'Psalm\\Issue\\ForbiddenCode' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ForbiddenCode.php', + 'Psalm\\Issue\\ForbiddenEcho' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ForbiddenEcho.php', + 'Psalm\\Issue\\FunctionIssue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/FunctionIssue.php', + 'Psalm\\Issue\\ImplementationRequirementViolation' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImplementationRequirementViolation.php', + 'Psalm\\Issue\\ImplementedParamTypeMismatch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImplementedParamTypeMismatch.php', + 'Psalm\\Issue\\ImplementedReturnTypeMismatch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImplementedReturnTypeMismatch.php', + 'Psalm\\Issue\\ImplicitToStringCast' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImplicitToStringCast.php', + 'Psalm\\Issue\\ImpureByReferenceAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImpureByReferenceAssignment.php', + 'Psalm\\Issue\\ImpureFunctionCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImpureFunctionCall.php', + 'Psalm\\Issue\\ImpureMethodCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImpureMethodCall.php', + 'Psalm\\Issue\\ImpurePropertyAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImpurePropertyAssignment.php', + 'Psalm\\Issue\\ImpurePropertyFetch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImpurePropertyFetch.php', + 'Psalm\\Issue\\ImpureStaticProperty' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImpureStaticProperty.php', + 'Psalm\\Issue\\ImpureStaticVariable' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImpureStaticVariable.php', + 'Psalm\\Issue\\ImpureVariable' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ImpureVariable.php', + 'Psalm\\Issue\\InaccessibleClassConstant' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InaccessibleClassConstant.php', + 'Psalm\\Issue\\InaccessibleMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InaccessibleMethod.php', + 'Psalm\\Issue\\InaccessibleProperty' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InaccessibleProperty.php', + 'Psalm\\Issue\\InterfaceInstantiation' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InterfaceInstantiation.php', + 'Psalm\\Issue\\InternalClass' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InternalClass.php', + 'Psalm\\Issue\\InternalMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InternalMethod.php', + 'Psalm\\Issue\\InternalProperty' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InternalProperty.php', + 'Psalm\\Issue\\InvalidArgument' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidArgument.php', + 'Psalm\\Issue\\InvalidArrayAccess' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidArrayAccess.php', + 'Psalm\\Issue\\InvalidArrayAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidArrayAssignment.php', + 'Psalm\\Issue\\InvalidArrayOffset' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidArrayOffset.php', + 'Psalm\\Issue\\InvalidCast' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidCast.php', + 'Psalm\\Issue\\InvalidCatch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidCatch.php', + 'Psalm\\Issue\\InvalidClass' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidClass.php', + 'Psalm\\Issue\\InvalidClone' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidClone.php', + 'Psalm\\Issue\\InvalidDocblock' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidDocblock.php', + 'Psalm\\Issue\\InvalidDocblockParamName' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidDocblockParamName.php', + 'Psalm\\Issue\\InvalidExtendClass' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidExtendClass.php', + 'Psalm\\Issue\\InvalidFalsableReturnType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidFalsableReturnType.php', + 'Psalm\\Issue\\InvalidFunctionCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidFunctionCall.php', + 'Psalm\\Issue\\InvalidGlobal' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidGlobal.php', + 'Psalm\\Issue\\InvalidIterator' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidIterator.php', + 'Psalm\\Issue\\InvalidLiteralArgument' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidLiteralArgument.php', + 'Psalm\\Issue\\InvalidMethodCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidMethodCall.php', + 'Psalm\\Issue\\InvalidNamedArgument' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidNamedArgument.php', + 'Psalm\\Issue\\InvalidNullableReturnType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidNullableReturnType.php', + 'Psalm\\Issue\\InvalidOperand' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidOperand.php', + 'Psalm\\Issue\\InvalidParamDefault' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidParamDefault.php', + 'Psalm\\Issue\\InvalidParent' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidParent.php', + 'Psalm\\Issue\\InvalidPassByReference' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidPassByReference.php', + 'Psalm\\Issue\\InvalidPropertyAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidPropertyAssignment.php', + 'Psalm\\Issue\\InvalidPropertyAssignmentValue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidPropertyAssignmentValue.php', + 'Psalm\\Issue\\InvalidPropertyFetch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidPropertyFetch.php', + 'Psalm\\Issue\\InvalidReturnStatement' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidReturnStatement.php', + 'Psalm\\Issue\\InvalidReturnType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidReturnType.php', + 'Psalm\\Issue\\InvalidScalarArgument' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidScalarArgument.php', + 'Psalm\\Issue\\InvalidScope' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidScope.php', + 'Psalm\\Issue\\InvalidStaticInvocation' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidStaticInvocation.php', + 'Psalm\\Issue\\InvalidStringClass' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidStringClass.php', + 'Psalm\\Issue\\InvalidTemplateParam' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidTemplateParam.php', + 'Psalm\\Issue\\InvalidThrow' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidThrow.php', + 'Psalm\\Issue\\InvalidToString' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidToString.php', + 'Psalm\\Issue\\InvalidTypeImport' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/InvalidTypeImport.php', + 'Psalm\\Issue\\LessSpecificImplementedReturnType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/LessSpecificImplementedReturnType.php', + 'Psalm\\Issue\\LessSpecificReturnStatement' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/LessSpecificReturnStatement.php', + 'Psalm\\Issue\\LessSpecificReturnType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/LessSpecificReturnType.php', + 'Psalm\\Issue\\LoopInvalidation' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/LoopInvalidation.php', + 'Psalm\\Issue\\MethodIssue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MethodIssue.php', + 'Psalm\\Issue\\MethodSignatureMismatch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MethodSignatureMismatch.php', + 'Psalm\\Issue\\MethodSignatureMustOmitReturnType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MethodSignatureMustOmitReturnType.php', + 'Psalm\\Issue\\MismatchingDocblockParamType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MismatchingDocblockParamType.php', + 'Psalm\\Issue\\MismatchingDocblockReturnType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MismatchingDocblockReturnType.php', + 'Psalm\\Issue\\MissingClosureParamType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingClosureParamType.php', + 'Psalm\\Issue\\MissingClosureReturnType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingClosureReturnType.php', + 'Psalm\\Issue\\MissingConstructor' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingConstructor.php', + 'Psalm\\Issue\\MissingDependency' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingDependency.php', + 'Psalm\\Issue\\MissingDocblockType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingDocblockType.php', + 'Psalm\\Issue\\MissingFile' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingFile.php', + 'Psalm\\Issue\\MissingImmutableAnnotation' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingImmutableAnnotation.php', + 'Psalm\\Issue\\MissingParamType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingParamType.php', + 'Psalm\\Issue\\MissingPropertyType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingPropertyType.php', + 'Psalm\\Issue\\MissingReturnType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingReturnType.php', + 'Psalm\\Issue\\MissingTemplateParam' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingTemplateParam.php', + 'Psalm\\Issue\\MissingThrowsDocblock' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MissingThrowsDocblock.php', + 'Psalm\\Issue\\MixedArgument' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedArgument.php', + 'Psalm\\Issue\\MixedArgumentTypeCoercion' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedArgumentTypeCoercion.php', + 'Psalm\\Issue\\MixedArrayAccess' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedArrayAccess.php', + 'Psalm\\Issue\\MixedArrayAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedArrayAssignment.php', + 'Psalm\\Issue\\MixedArrayOffset' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedArrayOffset.php', + 'Psalm\\Issue\\MixedArrayTypeCoercion' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedArrayTypeCoercion.php', + 'Psalm\\Issue\\MixedAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedAssignment.php', + 'Psalm\\Issue\\MixedClone' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedClone.php', + 'Psalm\\Issue\\MixedFunctionCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedFunctionCall.php', + 'Psalm\\Issue\\MixedInferredReturnType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedInferredReturnType.php', + 'Psalm\\Issue\\MixedMethodCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedMethodCall.php', + 'Psalm\\Issue\\MixedOperand' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedOperand.php', + 'Psalm\\Issue\\MixedPropertyAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedPropertyAssignment.php', + 'Psalm\\Issue\\MixedPropertyFetch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedPropertyFetch.php', + 'Psalm\\Issue\\MixedPropertyTypeCoercion' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedPropertyTypeCoercion.php', + 'Psalm\\Issue\\MixedReturnStatement' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedReturnStatement.php', + 'Psalm\\Issue\\MixedReturnTypeCoercion' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedReturnTypeCoercion.php', + 'Psalm\\Issue\\MixedStringOffsetAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MixedStringOffsetAssignment.php', + 'Psalm\\Issue\\MoreSpecificImplementedParamType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MoreSpecificImplementedParamType.php', + 'Psalm\\Issue\\MoreSpecificReturnType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MoreSpecificReturnType.php', + 'Psalm\\Issue\\MutableDependency' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/MutableDependency.php', + 'Psalm\\Issue\\NoInterfaceProperties' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NoInterfaceProperties.php', + 'Psalm\\Issue\\NoValue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NoValue.php', + 'Psalm\\Issue\\NonStaticSelfCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NonStaticSelfCall.php', + 'Psalm\\Issue\\NullArgument' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NullArgument.php', + 'Psalm\\Issue\\NullArrayAccess' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NullArrayAccess.php', + 'Psalm\\Issue\\NullArrayOffset' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NullArrayOffset.php', + 'Psalm\\Issue\\NullFunctionCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NullFunctionCall.php', + 'Psalm\\Issue\\NullIterator' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NullIterator.php', + 'Psalm\\Issue\\NullOperand' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NullOperand.php', + 'Psalm\\Issue\\NullPropertyAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NullPropertyAssignment.php', + 'Psalm\\Issue\\NullPropertyFetch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NullPropertyFetch.php', + 'Psalm\\Issue\\NullReference' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NullReference.php', + 'Psalm\\Issue\\NullableReturnStatement' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/NullableReturnStatement.php', + 'Psalm\\Issue\\OverriddenMethodAccess' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/OverriddenMethodAccess.php', + 'Psalm\\Issue\\OverriddenPropertyAccess' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/OverriddenPropertyAccess.php', + 'Psalm\\Issue\\ParadoxicalCondition' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ParadoxicalCondition.php', + 'Psalm\\Issue\\ParamNameMismatch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ParamNameMismatch.php', + 'Psalm\\Issue\\ParentNotFound' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ParentNotFound.php', + 'Psalm\\Issue\\ParseError' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ParseError.php', + 'Psalm\\Issue\\PluginIssue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PluginIssue.php', + 'Psalm\\Issue\\PossibleRawObjectIteration' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossibleRawObjectIteration.php', + 'Psalm\\Issue\\PossiblyFalseArgument' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyFalseArgument.php', + 'Psalm\\Issue\\PossiblyFalseIterator' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyFalseIterator.php', + 'Psalm\\Issue\\PossiblyFalseOperand' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyFalseOperand.php', + 'Psalm\\Issue\\PossiblyFalsePropertyAssignmentValue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyFalsePropertyAssignmentValue.php', + 'Psalm\\Issue\\PossiblyFalseReference' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyFalseReference.php', + 'Psalm\\Issue\\PossiblyInvalidArgument' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidArgument.php', + 'Psalm\\Issue\\PossiblyInvalidArrayAccess' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidArrayAccess.php', + 'Psalm\\Issue\\PossiblyInvalidArrayAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidArrayAssignment.php', + 'Psalm\\Issue\\PossiblyInvalidArrayOffset' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidArrayOffset.php', + 'Psalm\\Issue\\PossiblyInvalidCast' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidCast.php', + 'Psalm\\Issue\\PossiblyInvalidClone' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidClone.php', + 'Psalm\\Issue\\PossiblyInvalidFunctionCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidFunctionCall.php', + 'Psalm\\Issue\\PossiblyInvalidIterator' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidIterator.php', + 'Psalm\\Issue\\PossiblyInvalidMethodCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidMethodCall.php', + 'Psalm\\Issue\\PossiblyInvalidOperand' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidOperand.php', + 'Psalm\\Issue\\PossiblyInvalidPropertyAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidPropertyAssignment.php', + 'Psalm\\Issue\\PossiblyInvalidPropertyAssignmentValue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidPropertyAssignmentValue.php', + 'Psalm\\Issue\\PossiblyInvalidPropertyFetch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidPropertyFetch.php', + 'Psalm\\Issue\\PossiblyNullArgument' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullArgument.php', + 'Psalm\\Issue\\PossiblyNullArrayAccess' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullArrayAccess.php', + 'Psalm\\Issue\\PossiblyNullArrayAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullArrayAssignment.php', + 'Psalm\\Issue\\PossiblyNullArrayOffset' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullArrayOffset.php', + 'Psalm\\Issue\\PossiblyNullFunctionCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullFunctionCall.php', + 'Psalm\\Issue\\PossiblyNullIterator' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullIterator.php', + 'Psalm\\Issue\\PossiblyNullOperand' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullOperand.php', + 'Psalm\\Issue\\PossiblyNullPropertyAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullPropertyAssignment.php', + 'Psalm\\Issue\\PossiblyNullPropertyAssignmentValue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullPropertyAssignmentValue.php', + 'Psalm\\Issue\\PossiblyNullPropertyFetch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullPropertyFetch.php', + 'Psalm\\Issue\\PossiblyNullReference' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullReference.php', + 'Psalm\\Issue\\PossiblyUndefinedArrayOffset' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedArrayOffset.php', + 'Psalm\\Issue\\PossiblyUndefinedGlobalVariable' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedGlobalVariable.php', + 'Psalm\\Issue\\PossiblyUndefinedIntArrayOffset' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedIntArrayOffset.php', + 'Psalm\\Issue\\PossiblyUndefinedMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedMethod.php', + 'Psalm\\Issue\\PossiblyUndefinedStringArrayOffset' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedStringArrayOffset.php', + 'Psalm\\Issue\\PossiblyUndefinedVariable' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedVariable.php', + 'Psalm\\Issue\\PossiblyUnusedMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyUnusedMethod.php', + 'Psalm\\Issue\\PossiblyUnusedParam' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyUnusedParam.php', + 'Psalm\\Issue\\PossiblyUnusedProperty' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PossiblyUnusedProperty.php', + 'Psalm\\Issue\\PropertyIssue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PropertyIssue.php', + 'Psalm\\Issue\\PropertyNotSetInConstructor' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PropertyNotSetInConstructor.php', + 'Psalm\\Issue\\PropertyTypeCoercion' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PropertyTypeCoercion.php', + 'Psalm\\Issue\\PsalmInternalError' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/PsalmInternalError.php', + 'Psalm\\Issue\\RawObjectIteration' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/RawObjectIteration.php', + 'Psalm\\Issue\\RedundantCondition' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/RedundantCondition.php', + 'Psalm\\Issue\\RedundantConditionGivenDocblockType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/RedundantConditionGivenDocblockType.php', + 'Psalm\\Issue\\RedundantIdentityWithTrue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/RedundantIdentityWithTrue.php', + 'Psalm\\Issue\\ReferenceConstraintViolation' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ReferenceConstraintViolation.php', + 'Psalm\\Issue\\ReservedWord' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/ReservedWord.php', + 'Psalm\\Issue\\StringIncrement' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/StringIncrement.php', + 'Psalm\\Issue\\TaintedInput' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/TaintedInput.php', + 'Psalm\\Issue\\TooFewArguments' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/TooFewArguments.php', + 'Psalm\\Issue\\TooManyArguments' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/TooManyArguments.php', + 'Psalm\\Issue\\TooManyTemplateParams' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/TooManyTemplateParams.php', + 'Psalm\\Issue\\Trace' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/Trace.php', + 'Psalm\\Issue\\TraitMethodSignatureMismatch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/TraitMethodSignatureMismatch.php', + 'Psalm\\Issue\\TypeDoesNotContainNull' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/TypeDoesNotContainNull.php', + 'Psalm\\Issue\\TypeDoesNotContainType' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/TypeDoesNotContainType.php', + 'Psalm\\Issue\\UncaughtThrowInGlobalScope' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UncaughtThrowInGlobalScope.php', + 'Psalm\\Issue\\UndefinedClass' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedClass.php', + 'Psalm\\Issue\\UndefinedConstant' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedConstant.php', + 'Psalm\\Issue\\UndefinedDocblockClass' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedDocblockClass.php', + 'Psalm\\Issue\\UndefinedFunction' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedFunction.php', + 'Psalm\\Issue\\UndefinedGlobalVariable' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedGlobalVariable.php', + 'Psalm\\Issue\\UndefinedInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedInterface.php', + 'Psalm\\Issue\\UndefinedInterfaceMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedInterfaceMethod.php', + 'Psalm\\Issue\\UndefinedMagicMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedMagicMethod.php', + 'Psalm\\Issue\\UndefinedMagicPropertyAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedMagicPropertyAssignment.php', + 'Psalm\\Issue\\UndefinedMagicPropertyFetch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedMagicPropertyFetch.php', + 'Psalm\\Issue\\UndefinedMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedMethod.php', + 'Psalm\\Issue\\UndefinedPropertyAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedPropertyAssignment.php', + 'Psalm\\Issue\\UndefinedPropertyFetch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedPropertyFetch.php', + 'Psalm\\Issue\\UndefinedThisPropertyAssignment' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedThisPropertyAssignment.php', + 'Psalm\\Issue\\UndefinedThisPropertyFetch' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedThisPropertyFetch.php', + 'Psalm\\Issue\\UndefinedTrace' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedTrace.php', + 'Psalm\\Issue\\UndefinedTrait' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedTrait.php', + 'Psalm\\Issue\\UndefinedVariable' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UndefinedVariable.php', + 'Psalm\\Issue\\UnevaluatedCode' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnevaluatedCode.php', + 'Psalm\\Issue\\UnhandledMatchCondition' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnhandledMatchCondition.php', + 'Psalm\\Issue\\UnimplementedAbstractMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnimplementedAbstractMethod.php', + 'Psalm\\Issue\\UnimplementedInterfaceMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnimplementedInterfaceMethod.php', + 'Psalm\\Issue\\UninitializedProperty' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UninitializedProperty.php', + 'Psalm\\Issue\\UnnecessaryVarAnnotation' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnnecessaryVarAnnotation.php', + 'Psalm\\Issue\\UnrecognizedExpression' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnrecognizedExpression.php', + 'Psalm\\Issue\\UnrecognizedStatement' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnrecognizedStatement.php', + 'Psalm\\Issue\\UnresolvableInclude' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnresolvableInclude.php', + 'Psalm\\Issue\\UnsafeInstantiation' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnsafeInstantiation.php', + 'Psalm\\Issue\\UnusedClass' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnusedClass.php', + 'Psalm\\Issue\\UnusedClosureParam' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnusedClosureParam.php', + 'Psalm\\Issue\\UnusedFunctionCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnusedFunctionCall.php', + 'Psalm\\Issue\\UnusedMethod' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnusedMethod.php', + 'Psalm\\Issue\\UnusedMethodCall' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnusedMethodCall.php', + 'Psalm\\Issue\\UnusedParam' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnusedParam.php', + 'Psalm\\Issue\\UnusedProperty' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnusedProperty.php', + 'Psalm\\Issue\\UnusedPsalmSuppress' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnusedPsalmSuppress.php', + 'Psalm\\Issue\\UnusedVariable' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/UnusedVariable.php', + 'Psalm\\Issue\\VariableIssue' => $vendorDir . '/vimeo/psalm/src/Psalm/Issue/VariableIssue.php', + 'Psalm\\NodeTypeProvider' => $vendorDir . '/vimeo/psalm/src/Psalm/NodeTypeProvider.php', + 'Psalm\\PluginRegistrationSocket' => $vendorDir . '/vimeo/psalm/src/Psalm/PluginRegistrationSocket.php', + 'Psalm\\Plugin\\Hook\\AfterAnalysisInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterClassLikeAnalysisInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterClassLikeAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterClassLikeExistenceCheckInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterClassLikeExistenceCheckInterface.php', + 'Psalm\\Plugin\\Hook\\AfterClassLikeVisitInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterClassLikeVisitInterface.php', + 'Psalm\\Plugin\\Hook\\AfterCodebasePopulatedInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterCodebasePopulatedInterface.php', + 'Psalm\\Plugin\\Hook\\AfterEveryFunctionCallAnalysisInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterEveryFunctionCallAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterExpressionAnalysisInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterExpressionAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterFileAnalysisInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterFileAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterFunctionCallAnalysisInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterFunctionCallAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterFunctionLikeAnalysisInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterFunctionLikeAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterMethodCallAnalysisInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterMethodCallAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterStatementAnalysisInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterStatementAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\BeforeFileAnalysisInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/BeforeFileAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\FunctionExistenceProviderInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionExistenceProviderInterface.php', + 'Psalm\\Plugin\\Hook\\FunctionParamsProviderInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionParamsProviderInterface.php', + 'Psalm\\Plugin\\Hook\\FunctionReturnTypeProviderInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionReturnTypeProviderInterface.php', + 'Psalm\\Plugin\\Hook\\MethodExistenceProviderInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/MethodExistenceProviderInterface.php', + 'Psalm\\Plugin\\Hook\\MethodParamsProviderInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/MethodParamsProviderInterface.php', + 'Psalm\\Plugin\\Hook\\MethodReturnTypeProviderInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/MethodReturnTypeProviderInterface.php', + 'Psalm\\Plugin\\Hook\\MethodVisibilityProviderInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/MethodVisibilityProviderInterface.php', + 'Psalm\\Plugin\\Hook\\PropertyExistenceProviderInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyExistenceProviderInterface.php', + 'Psalm\\Plugin\\Hook\\PropertyTypeProviderInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyTypeProviderInterface.php', + 'Psalm\\Plugin\\Hook\\PropertyVisibilityProviderInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyVisibilityProviderInterface.php', + 'Psalm\\Plugin\\Hook\\StringInterpreterInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Hook/StringInterpreterInterface.php', + 'Psalm\\Plugin\\PluginEntryPointInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/PluginEntryPointInterface.php', + 'Psalm\\Plugin\\RegistrationInterface' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/RegistrationInterface.php', + 'Psalm\\Plugin\\Shepherd' => $vendorDir . '/vimeo/psalm/src/Psalm/Plugin/Shepherd.php', + 'Psalm\\Progress\\DebugProgress' => $vendorDir . '/vimeo/psalm/src/Psalm/Progress/DebugProgress.php', + 'Psalm\\Progress\\DefaultProgress' => $vendorDir . '/vimeo/psalm/src/Psalm/Progress/DefaultProgress.php', + 'Psalm\\Progress\\LongProgress' => $vendorDir . '/vimeo/psalm/src/Psalm/Progress/LongProgress.php', + 'Psalm\\Progress\\Progress' => $vendorDir . '/vimeo/psalm/src/Psalm/Progress/Progress.php', + 'Psalm\\Progress\\VoidProgress' => $vendorDir . '/vimeo/psalm/src/Psalm/Progress/VoidProgress.php', + 'Psalm\\Report' => $vendorDir . '/vimeo/psalm/src/Psalm/Report.php', + 'Psalm\\Report\\CheckstyleReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/CheckstyleReport.php', + 'Psalm\\Report\\CompactReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/CompactReport.php', + 'Psalm\\Report\\ConsoleReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/ConsoleReport.php', + 'Psalm\\Report\\EmacsReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/EmacsReport.php', + 'Psalm\\Report\\GithubActionsReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/GithubActionsReport.php', + 'Psalm\\Report\\JsonReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/JsonReport.php', + 'Psalm\\Report\\JsonSummaryReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/JsonSummaryReport.php', + 'Psalm\\Report\\JunitReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/JunitReport.php', + 'Psalm\\Report\\PhpStormReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/PhpStormReport.php', + 'Psalm\\Report\\PylintReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/PylintReport.php', + 'Psalm\\Report\\ReportOptions' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/ReportOptions.php', + 'Psalm\\Report\\SonarqubeReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/SonarqubeReport.php', + 'Psalm\\Report\\TextReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/TextReport.php', + 'Psalm\\Report\\XmlReport' => $vendorDir . '/vimeo/psalm/src/Psalm/Report/XmlReport.php', + 'Psalm\\SourceControl\\Git\\CommitInfo' => $vendorDir . '/vimeo/psalm/src/Psalm/SourceControl/Git/CommitInfo.php', + 'Psalm\\SourceControl\\Git\\GitInfo' => $vendorDir . '/vimeo/psalm/src/Psalm/SourceControl/Git/GitInfo.php', + 'Psalm\\SourceControl\\Git\\RemoteInfo' => $vendorDir . '/vimeo/psalm/src/Psalm/SourceControl/Git/RemoteInfo.php', + 'Psalm\\SourceControl\\SourceControlInfo' => $vendorDir . '/vimeo/psalm/src/Psalm/SourceControl/SourceControlInfo.php', + 'Psalm\\StatementsSource' => $vendorDir . '/vimeo/psalm/src/Psalm/StatementsSource.php', + 'Psalm\\Storage\\Assertion' => $vendorDir . '/vimeo/psalm/src/Psalm/Storage/Assertion.php', + 'Psalm\\Storage\\ClassConstantStorage' => $vendorDir . '/vimeo/psalm/src/Psalm/Storage/ClassConstantStorage.php', + 'Psalm\\Storage\\ClassLikeStorage' => $vendorDir . '/vimeo/psalm/src/Psalm/Storage/ClassLikeStorage.php', + 'Psalm\\Storage\\CustomMetadataTrait' => $vendorDir . '/vimeo/psalm/src/Psalm/Storage/CustomMetadataTrait.php', + 'Psalm\\Storage\\FileStorage' => $vendorDir . '/vimeo/psalm/src/Psalm/Storage/FileStorage.php', + 'Psalm\\Storage\\FunctionLikeParameter' => $vendorDir . '/vimeo/psalm/src/Psalm/Storage/FunctionLikeParameter.php', + 'Psalm\\Storage\\FunctionLikeStorage' => $vendorDir . '/vimeo/psalm/src/Psalm/Storage/FunctionLikeStorage.php', + 'Psalm\\Storage\\FunctionStorage' => $vendorDir . '/vimeo/psalm/src/Psalm/Storage/FunctionStorage.php', + 'Psalm\\Storage\\MethodStorage' => $vendorDir . '/vimeo/psalm/src/Psalm/Storage/MethodStorage.php', + 'Psalm\\Storage\\PropertyStorage' => $vendorDir . '/vimeo/psalm/src/Psalm/Storage/PropertyStorage.php', + 'Psalm\\Type' => $vendorDir . '/vimeo/psalm/src/Psalm/Type.php', + 'Psalm\\Type\\Algebra' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Algebra.php', + 'Psalm\\Type\\Atomic' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic.php', + 'Psalm\\Type\\Atomic\\CallableTrait' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/CallableTrait.php', + 'Psalm\\Type\\Atomic\\GenericTrait' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/GenericTrait.php', + 'Psalm\\Type\\Atomic\\HasIntersectionTrait' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/HasIntersectionTrait.php', + 'Psalm\\Type\\Atomic\\Scalar' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/Scalar.php', + 'Psalm\\Type\\Atomic\\TAnonymousClassInstance' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TAnonymousClassInstance.php', + 'Psalm\\Type\\Atomic\\TArray' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TArray.php', + 'Psalm\\Type\\Atomic\\TArrayKey' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TArrayKey.php', + 'Psalm\\Type\\Atomic\\TAssertionFalsy' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TAssertionFalsy.php', + 'Psalm\\Type\\Atomic\\TBool' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TBool.php', + 'Psalm\\Type\\Atomic\\TCallable' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallable.php', + 'Psalm\\Type\\Atomic\\TCallableArray' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallableArray.php', + 'Psalm\\Type\\Atomic\\TCallableKeyedArray' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallableKeyedArray.php', + 'Psalm\\Type\\Atomic\\TCallableList' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallableList.php', + 'Psalm\\Type\\Atomic\\TCallableObject' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallableObject.php', + 'Psalm\\Type\\Atomic\\TCallableString' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallableString.php', + 'Psalm\\Type\\Atomic\\TClassString' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TClassString.php', + 'Psalm\\Type\\Atomic\\TClassStringMap' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TClassStringMap.php', + 'Psalm\\Type\\Atomic\\TClosedResource' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TClosedResource.php', + 'Psalm\\Type\\Atomic\\TClosure' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TClosure.php', + 'Psalm\\Type\\Atomic\\TConditional' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TConditional.php', + 'Psalm\\Type\\Atomic\\TDependentGetClass' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetClass.php', + 'Psalm\\Type\\Atomic\\TDependentGetDebugType' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetDebugType.php', + 'Psalm\\Type\\Atomic\\TDependentGetType' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetType.php', + 'Psalm\\Type\\Atomic\\TEmpty' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TEmpty.php', + 'Psalm\\Type\\Atomic\\TEmptyMixed' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TEmptyMixed.php', + 'Psalm\\Type\\Atomic\\TEmptyNumeric' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TEmptyNumeric.php', + 'Psalm\\Type\\Atomic\\TEmptyScalar' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TEmptyScalar.php', + 'Psalm\\Type\\Atomic\\TFalse' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TFalse.php', + 'Psalm\\Type\\Atomic\\TFloat' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TFloat.php', + 'Psalm\\Type\\Atomic\\TGenericObject' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TGenericObject.php', + 'Psalm\\Type\\Atomic\\THtmlEscapedString' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/THtmlEscapedString.php', + 'Psalm\\Type\\Atomic\\TInt' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TInt.php', + 'Psalm\\Type\\Atomic\\TIterable' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TIterable.php', + 'Psalm\\Type\\Atomic\\TKeyOfClassConstant' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TKeyOfClassConstant.php', + 'Psalm\\Type\\Atomic\\TKeyedArray' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TKeyedArray.php', + 'Psalm\\Type\\Atomic\\TList' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TList.php', + 'Psalm\\Type\\Atomic\\TLiteralClassString' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralClassString.php', + 'Psalm\\Type\\Atomic\\TLiteralFloat' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralFloat.php', + 'Psalm\\Type\\Atomic\\TLiteralInt' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralInt.php', + 'Psalm\\Type\\Atomic\\TLiteralString' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralString.php', + 'Psalm\\Type\\Atomic\\TLowercaseString' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TLowercaseString.php', + 'Psalm\\Type\\Atomic\\TMixed' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TMixed.php', + 'Psalm\\Type\\Atomic\\TNamedObject' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TNamedObject.php', + 'Psalm\\Type\\Atomic\\TNever' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TNever.php', + 'Psalm\\Type\\Atomic\\TNonEmptyArray' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyArray.php', + 'Psalm\\Type\\Atomic\\TNonEmptyList' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyList.php', + 'Psalm\\Type\\Atomic\\TNonEmptyLowercaseString' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyLowercaseString.php', + 'Psalm\\Type\\Atomic\\TNonEmptyMixed' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyMixed.php', + 'Psalm\\Type\\Atomic\\TNonEmptyScalar' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyScalar.php', + 'Psalm\\Type\\Atomic\\TNonEmptyString' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyString.php', + 'Psalm\\Type\\Atomic\\TNull' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TNull.php', + 'Psalm\\Type\\Atomic\\TNumeric' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TNumeric.php', + 'Psalm\\Type\\Atomic\\TNumericString' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TNumericString.php', + 'Psalm\\Type\\Atomic\\TObject' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TObject.php', + 'Psalm\\Type\\Atomic\\TObjectWithProperties' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TObjectWithProperties.php', + 'Psalm\\Type\\Atomic\\TPositiveInt' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TPositiveInt.php', + 'Psalm\\Type\\Atomic\\TResource' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TResource.php', + 'Psalm\\Type\\Atomic\\TScalar' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TScalar.php', + 'Psalm\\Type\\Atomic\\TScalarClassConstant' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TScalarClassConstant.php', + 'Psalm\\Type\\Atomic\\TSingleLetter' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TSingleLetter.php', + 'Psalm\\Type\\Atomic\\TString' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TString.php', + 'Psalm\\Type\\Atomic\\TTemplateIndexedAccess' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php', + 'Psalm\\Type\\Atomic\\TTemplateKeyOf' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateKeyOf.php', + 'Psalm\\Type\\Atomic\\TTemplateParam' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateParam.php', + 'Psalm\\Type\\Atomic\\TTemplateParamClass' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateParamClass.php', + 'Psalm\\Type\\Atomic\\TTraitString' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TTraitString.php', + 'Psalm\\Type\\Atomic\\TTrue' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TTrue.php', + 'Psalm\\Type\\Atomic\\TTypeAlias' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TTypeAlias.php', + 'Psalm\\Type\\Atomic\\TValueOfClassConstant' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TValueOfClassConstant.php', + 'Psalm\\Type\\Atomic\\TVoid' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Atomic/TVoid.php', + 'Psalm\\Type\\NodeVisitor' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/NodeVisitor.php', + 'Psalm\\Type\\Reconciler' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Reconciler.php', + 'Psalm\\Type\\TaintKind' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/TaintKind.php', + 'Psalm\\Type\\TaintKindGroup' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/TaintKindGroup.php', + 'Psalm\\Type\\TypeNode' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/TypeNode.php', + 'Psalm\\Type\\Union' => $vendorDir . '/vimeo/psalm/src/Psalm/Type/Union.php', + 'Psr\\Container\\ContainerExceptionInterface' => $vendorDir . '/psr/container/src/ContainerExceptionInterface.php', + 'Psr\\Container\\ContainerInterface' => $vendorDir . '/psr/container/src/ContainerInterface.php', + 'Psr\\Container\\NotFoundExceptionInterface' => $vendorDir . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/psr/event-dispatcher/src/EventDispatcherInterface.php', + 'Psr\\EventDispatcher\\ListenerProviderInterface' => $vendorDir . '/psr/event-dispatcher/src/ListenerProviderInterface.php', + 'Psr\\EventDispatcher\\StoppableEventInterface' => $vendorDir . '/psr/event-dispatcher/src/StoppableEventInterface.php', + 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/DummyTest.php', + 'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\TestLogger' => $vendorDir . '/psr/log/Psr/Log/Test/TestLogger.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', + 'SessionUpdateTimestampHandlerInterface' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php', + 'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => $vendorDir . '/symfony/console/CommandLoader/CommandLoaderInterface.php', + 'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/ContainerCommandLoader.php', + 'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/FactoryCommandLoader.php', + 'Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Command/Command.php', + 'Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Command/HelpCommand.php', + 'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php', + 'Symfony\\Component\\Console\\Command\\LockableTrait' => $vendorDir . '/symfony/console/Command/LockableTrait.php', + 'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php', + 'Symfony\\Component\\Console\\Cursor' => $vendorDir . '/symfony/console/Cursor.php', + 'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => $vendorDir . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', + 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php', + 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php', + 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php', + 'Symfony\\Component\\Console\\EventListener\\ErrorListener' => $vendorDir . '/symfony/console/EventListener/ErrorListener.php', + 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => $vendorDir . '/symfony/console/Event/ConsoleErrorEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Event/ConsoleTerminateEvent.php', + 'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => $vendorDir . '/symfony/console/Exception/CommandNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/console/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/console/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => $vendorDir . '/symfony/console/Exception/InvalidOptionException.php', + 'Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php', + 'Symfony\\Component\\Console\\Exception\\MissingInputException' => $vendorDir . '/symfony/console/Exception/MissingInputException.php', + 'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => $vendorDir . '/symfony/console/Exception/NamespaceNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Formatter/OutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleStack.php', + 'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\Dumper' => $vendorDir . '/symfony/console/Helper/Dumper.php', + 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php', + 'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php', + 'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php', + 'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php', + 'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php', + 'Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => $vendorDir . '/symfony/console/Helper/SymfonyQuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\Table' => $vendorDir . '/symfony/console/Helper/Table.php', + 'Symfony\\Component\\Console\\Helper\\TableCell' => $vendorDir . '/symfony/console/Helper/TableCell.php', + 'Symfony\\Component\\Console\\Helper\\TableRows' => $vendorDir . '/symfony/console/Helper/TableRows.php', + 'Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php', + 'Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php', + 'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php', + 'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php', + 'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php', + 'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php', + 'Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php', + 'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php', + 'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php', + 'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php', + 'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => $vendorDir . '/symfony/console/Input/StreamableInputInterface.php', + 'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php', + 'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php', + 'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php', + 'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => $vendorDir . '/symfony/console/Output/ConsoleSectionOutput.php', + 'Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Output/NullOutput.php', + 'Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Output/Output.php', + 'Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Output/OutputInterface.php', + 'Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Output/StreamOutput.php', + 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php', + 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php', + 'Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\SingleCommandApplication' => $vendorDir . '/symfony/console/SingleCommandApplication.php', + 'Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php', + 'Symfony\\Component\\Console\\Style\\StyleInterface' => $vendorDir . '/symfony/console/Style/StyleInterface.php', + 'Symfony\\Component\\Console\\Style\\SymfonyStyle' => $vendorDir . '/symfony/console/Style/SymfonyStyle.php', + 'Symfony\\Component\\Console\\Terminal' => $vendorDir . '/symfony/console/Terminal.php', + 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php', + 'Symfony\\Component\\Console\\Tester\\TesterTrait' => $vendorDir . '/symfony/console/Tester/TesterTrait.php', + 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => $vendorDir . '/symfony/event-dispatcher/Debug/WrappedListener.php', + 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\AddEventAliasesPass' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php', + 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php', + 'Symfony\\Component\\EventDispatcher\\EventDispatcher' => $vendorDir . '/symfony/event-dispatcher/EventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher/EventDispatcherInterface.php', + 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/symfony/event-dispatcher/EventSubscriberInterface.php', + 'Symfony\\Component\\EventDispatcher\\GenericEvent' => $vendorDir . '/symfony/event-dispatcher/GenericEvent.php', + 'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => $vendorDir . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php', + 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/filesystem/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php', + 'Symfony\\Component\\Finder\\Comparator\\Comparator' => $vendorDir . '/symfony/finder/Comparator/Comparator.php', + 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => $vendorDir . '/symfony/finder/Comparator/DateComparator.php', + 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => $vendorDir . '/symfony/finder/Comparator/NumberComparator.php', + 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/finder/Exception/AccessDeniedException.php', + 'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => $vendorDir . '/symfony/finder/Exception/DirectoryNotFoundException.php', + 'Symfony\\Component\\Finder\\Finder' => $vendorDir . '/symfony/finder/Finder.php', + 'Symfony\\Component\\Finder\\Gitignore' => $vendorDir . '/symfony/finder/Gitignore.php', + 'Symfony\\Component\\Finder\\Glob' => $vendorDir . '/symfony/finder/Glob.php', + 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => $vendorDir . '/symfony/finder/Iterator/CustomFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DateRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DepthRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => $vendorDir . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FileTypeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilecontentFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilenameFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => $vendorDir . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => $vendorDir . '/symfony/finder/Iterator/PathFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => $vendorDir . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/SizeRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => $vendorDir . '/symfony/finder/Iterator/SortableIterator.php', + 'Symfony\\Component\\Finder\\SplFileInfo' => $vendorDir . '/symfony/finder/SplFileInfo.php', + 'Symfony\\Component\\OptionsResolver\\Debug\\OptionsResolverIntrospector' => $vendorDir . '/symfony/options-resolver/Debug/OptionsResolverIntrospector.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\AccessException' => $vendorDir . '/symfony/options-resolver/Exception/AccessException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/options-resolver/Exception/ExceptionInterface.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/options-resolver/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException' => $vendorDir . '/symfony/options-resolver/Exception/InvalidOptionsException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\MissingOptionsException' => $vendorDir . '/symfony/options-resolver/Exception/MissingOptionsException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\NoConfigurationException' => $vendorDir . '/symfony/options-resolver/Exception/NoConfigurationException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\NoSuchOptionException' => $vendorDir . '/symfony/options-resolver/Exception/NoSuchOptionException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\OptionDefinitionException' => $vendorDir . '/symfony/options-resolver/Exception/OptionDefinitionException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\UndefinedOptionsException' => $vendorDir . '/symfony/options-resolver/Exception/UndefinedOptionsException.php', + 'Symfony\\Component\\OptionsResolver\\OptionConfigurator' => $vendorDir . '/symfony/options-resolver/OptionConfigurator.php', + 'Symfony\\Component\\OptionsResolver\\Options' => $vendorDir . '/symfony/options-resolver/Options.php', + 'Symfony\\Component\\OptionsResolver\\OptionsResolver' => $vendorDir . '/symfony/options-resolver/OptionsResolver.php', + 'Symfony\\Component\\Process\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/process/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/process/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Process\\Exception\\LogicException' => $vendorDir . '/symfony/process/Exception/LogicException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => $vendorDir . '/symfony/process/Exception/ProcessFailedException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => $vendorDir . '/symfony/process/Exception/ProcessSignaledException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => $vendorDir . '/symfony/process/Exception/ProcessTimedOutException.php', + 'Symfony\\Component\\Process\\Exception\\RuntimeException' => $vendorDir . '/symfony/process/Exception/RuntimeException.php', + 'Symfony\\Component\\Process\\ExecutableFinder' => $vendorDir . '/symfony/process/ExecutableFinder.php', + 'Symfony\\Component\\Process\\InputStream' => $vendorDir . '/symfony/process/InputStream.php', + 'Symfony\\Component\\Process\\PhpExecutableFinder' => $vendorDir . '/symfony/process/PhpExecutableFinder.php', + 'Symfony\\Component\\Process\\PhpProcess' => $vendorDir . '/symfony/process/PhpProcess.php', + 'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => $vendorDir . '/symfony/process/Pipes/AbstractPipes.php', + 'Symfony\\Component\\Process\\Pipes\\PipesInterface' => $vendorDir . '/symfony/process/Pipes/PipesInterface.php', + 'Symfony\\Component\\Process\\Pipes\\UnixPipes' => $vendorDir . '/symfony/process/Pipes/UnixPipes.php', + 'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => $vendorDir . '/symfony/process/Pipes/WindowsPipes.php', + 'Symfony\\Component\\Process\\Process' => $vendorDir . '/symfony/process/Process.php', + 'Symfony\\Component\\Process\\ProcessUtils' => $vendorDir . '/symfony/process/ProcessUtils.php', + 'Symfony\\Component\\Stopwatch\\Section' => $vendorDir . '/symfony/stopwatch/Section.php', + 'Symfony\\Component\\Stopwatch\\Stopwatch' => $vendorDir . '/symfony/stopwatch/Stopwatch.php', + 'Symfony\\Component\\Stopwatch\\StopwatchEvent' => $vendorDir . '/symfony/stopwatch/StopwatchEvent.php', + 'Symfony\\Component\\Stopwatch\\StopwatchPeriod' => $vendorDir . '/symfony/stopwatch/StopwatchPeriod.php', + 'Symfony\\Component\\String\\AbstractString' => $vendorDir . '/symfony/string/AbstractString.php', + 'Symfony\\Component\\String\\AbstractUnicodeString' => $vendorDir . '/symfony/string/AbstractUnicodeString.php', + 'Symfony\\Component\\String\\ByteString' => $vendorDir . '/symfony/string/ByteString.php', + 'Symfony\\Component\\String\\CodePointString' => $vendorDir . '/symfony/string/CodePointString.php', + 'Symfony\\Component\\String\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/string/Exception/ExceptionInterface.php', + 'Symfony\\Component\\String\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/string/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\String\\Exception\\RuntimeException' => $vendorDir . '/symfony/string/Exception/RuntimeException.php', + 'Symfony\\Component\\String\\Inflector\\EnglishInflector' => $vendorDir . '/symfony/string/Inflector/EnglishInflector.php', + 'Symfony\\Component\\String\\Inflector\\InflectorInterface' => $vendorDir . '/symfony/string/Inflector/InflectorInterface.php', + 'Symfony\\Component\\String\\LazyString' => $vendorDir . '/symfony/string/LazyString.php', + 'Symfony\\Component\\String\\Slugger\\AsciiSlugger' => $vendorDir . '/symfony/string/Slugger/AsciiSlugger.php', + 'Symfony\\Component\\String\\Slugger\\SluggerInterface' => $vendorDir . '/symfony/string/Slugger/SluggerInterface.php', + 'Symfony\\Component\\String\\UnicodeString' => $vendorDir . '/symfony/string/UnicodeString.php', + 'Symfony\\Contracts\\EventDispatcher\\Event' => $vendorDir . '/symfony/event-dispatcher-contracts/Event.php', + 'Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher-contracts/EventDispatcherInterface.php', + 'Symfony\\Contracts\\Service\\Attribute\\Required' => $vendorDir . '/symfony/service-contracts/Attribute/Required.php', + 'Symfony\\Contracts\\Service\\ResetInterface' => $vendorDir . '/symfony/service-contracts/ResetInterface.php', + 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/service-contracts/ServiceLocatorTrait.php', + 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => $vendorDir . '/symfony/service-contracts/ServiceProviderInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberTrait.php', + 'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => $vendorDir . '/symfony/service-contracts/Test/ServiceLocatorTest.php', + 'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => $vendorDir . '/symfony/polyfill-intl-grapheme/Grapheme.php', + 'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Normalizer.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php70\\Php70' => $vendorDir . '/symfony/polyfill-php70/Php70.php', + 'Symfony\\Polyfill\\Php72\\Php72' => $vendorDir . '/symfony/polyfill-php72/Php72.php', + 'Symfony\\Polyfill\\Php73\\Php73' => $vendorDir . '/symfony/polyfill-php73/Php73.php', + 'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php', + 'TypeError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/TypeError.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'Webmozart\\Assert\\Assert' => $vendorDir . '/webmozart/assert/src/Assert.php', + 'Webmozart\\Assert\\Mixin' => $vendorDir . '/webmozart/assert/src/Mixin.php', + 'Webmozart\\Glob\\Glob' => $vendorDir . '/webmozart/glob/src/Glob.php', + 'Webmozart\\Glob\\Iterator\\GlobFilterIterator' => $vendorDir . '/webmozart/glob/src/Iterator/GlobFilterIterator.php', + 'Webmozart\\Glob\\Iterator\\GlobIterator' => $vendorDir . '/webmozart/glob/src/Iterator/GlobIterator.php', + 'Webmozart\\Glob\\Iterator\\RecursiveDirectoryIterator' => $vendorDir . '/webmozart/glob/src/Iterator/RecursiveDirectoryIterator.php', + 'Webmozart\\Glob\\Iterator\\RegexFilterIterator' => $vendorDir . '/webmozart/glob/src/Iterator/RegexFilterIterator.php', + 'Webmozart\\Glob\\Test\\TestUtil' => $vendorDir . '/webmozart/glob/src/Test/TestUtil.php', + 'Webmozart\\PathUtil\\Path' => $vendorDir . '/webmozart/path-util/src/Path.php', + 'Webmozart\\PathUtil\\Url' => $vendorDir . '/webmozart/path-util/src/Url.php', + 'XdgBaseDir\\Xdg' => $vendorDir . '/dnoegel/php-xdg-base-dir/src/Xdg.php', + 'phpDocumentor\\Reflection\\DocBlock' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock.php', + 'phpDocumentor\\Reflection\\DocBlockFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlockFactory.php', + 'phpDocumentor\\Reflection\\DocBlockFactoryInterface' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php', + 'phpDocumentor\\Reflection\\DocBlock\\Description' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Description.php', + 'phpDocumentor\\Reflection\\DocBlock\\DescriptionFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\ExampleFinder' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php', + 'phpDocumentor\\Reflection\\DocBlock\\Serializer' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php', + 'phpDocumentor\\Reflection\\DocBlock\\StandardTagFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tag' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php', + 'phpDocumentor\\Reflection\\DocBlock\\TagFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Author' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\BaseTag' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Covers' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Deprecated' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Example' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\StaticMethod' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\AlignFormatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\PassthroughFormatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Generic' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\InvalidTag' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Link' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Method' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Param' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Property' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyRead' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyWrite' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Fqsen' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Reference' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Url' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Return_' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\See' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Since' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Source' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\TagWithType' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Throws' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Uses' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Var_' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Version' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php', + 'phpDocumentor\\Reflection\\Element' => $vendorDir . '/phpdocumentor/reflection-common/src/Element.php', + 'phpDocumentor\\Reflection\\Exception\\PcreException' => $vendorDir . '/phpdocumentor/reflection-docblock/src/Exception/PcreException.php', + 'phpDocumentor\\Reflection\\File' => $vendorDir . '/phpdocumentor/reflection-common/src/File.php', + 'phpDocumentor\\Reflection\\Fqsen' => $vendorDir . '/phpdocumentor/reflection-common/src/Fqsen.php', + 'phpDocumentor\\Reflection\\FqsenResolver' => $vendorDir . '/phpdocumentor/type-resolver/src/FqsenResolver.php', + 'phpDocumentor\\Reflection\\Location' => $vendorDir . '/phpdocumentor/reflection-common/src/Location.php', + 'phpDocumentor\\Reflection\\Project' => $vendorDir . '/phpdocumentor/reflection-common/src/Project.php', + 'phpDocumentor\\Reflection\\ProjectFactory' => $vendorDir . '/phpdocumentor/reflection-common/src/ProjectFactory.php', + 'phpDocumentor\\Reflection\\PseudoType' => $vendorDir . '/phpdocumentor/type-resolver/src/PseudoType.php', + 'phpDocumentor\\Reflection\\PseudoTypes\\False_' => $vendorDir . '/phpdocumentor/type-resolver/src/PseudoTypes/False_.php', + 'phpDocumentor\\Reflection\\PseudoTypes\\True_' => $vendorDir . '/phpdocumentor/type-resolver/src/PseudoTypes/True_.php', + 'phpDocumentor\\Reflection\\Type' => $vendorDir . '/phpdocumentor/type-resolver/src/Type.php', + 'phpDocumentor\\Reflection\\TypeResolver' => $vendorDir . '/phpdocumentor/type-resolver/src/TypeResolver.php', + 'phpDocumentor\\Reflection\\Types\\AbstractList' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/AbstractList.php', + 'phpDocumentor\\Reflection\\Types\\AggregatedType' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/AggregatedType.php', + 'phpDocumentor\\Reflection\\Types\\Array_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Array_.php', + 'phpDocumentor\\Reflection\\Types\\Boolean' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Boolean.php', + 'phpDocumentor\\Reflection\\Types\\Callable_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Callable_.php', + 'phpDocumentor\\Reflection\\Types\\ClassString' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/ClassString.php', + 'phpDocumentor\\Reflection\\Types\\Collection' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Collection.php', + 'phpDocumentor\\Reflection\\Types\\Compound' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Compound.php', + 'phpDocumentor\\Reflection\\Types\\Context' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Context.php', + 'phpDocumentor\\Reflection\\Types\\ContextFactory' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/ContextFactory.php', + 'phpDocumentor\\Reflection\\Types\\Expression' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Expression.php', + 'phpDocumentor\\Reflection\\Types\\Float_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Float_.php', + 'phpDocumentor\\Reflection\\Types\\Integer' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Integer.php', + 'phpDocumentor\\Reflection\\Types\\Intersection' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Intersection.php', + 'phpDocumentor\\Reflection\\Types\\Iterable_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Iterable_.php', + 'phpDocumentor\\Reflection\\Types\\Mixed_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Mixed_.php', + 'phpDocumentor\\Reflection\\Types\\Null_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Null_.php', + 'phpDocumentor\\Reflection\\Types\\Nullable' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Nullable.php', + 'phpDocumentor\\Reflection\\Types\\Object_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Object_.php', + 'phpDocumentor\\Reflection\\Types\\Parent_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Parent_.php', + 'phpDocumentor\\Reflection\\Types\\Resource_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Resource_.php', + 'phpDocumentor\\Reflection\\Types\\Scalar' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Scalar.php', + 'phpDocumentor\\Reflection\\Types\\Self_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Self_.php', + 'phpDocumentor\\Reflection\\Types\\Static_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Static_.php', + 'phpDocumentor\\Reflection\\Types\\String_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/String_.php', + 'phpDocumentor\\Reflection\\Types\\This' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/This.php', + 'phpDocumentor\\Reflection\\Types\\Void_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Void_.php', + 'phpDocumentor\\Reflection\\Utils' => $vendorDir . '/phpdocumentor/reflection-docblock/src/Utils.php', ); diff --git a/lib/composer/composer/autoload_files.php b/lib/composer/composer/autoload_files.php new file mode 100644 index 0000000000000000000000000000000000000000..853c2ecd85eaf66067b74fada7b500175a352589 --- /dev/null +++ b/lib/composer/composer/autoload_files.php @@ -0,0 +1,24 @@ + $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + 'e8aa6e4b5a1db2f56ae794f1505391a8' => $vendorDir . '/amphp/amp/lib/functions.php', + '76cd0796156622033397994f25b0d8fc' => $vendorDir . '/amphp/amp/lib/Internal/functions.php', + 'dc51568953534d6c54b08731e61104e2' => $vendorDir . '/vimeo/psalm/src/functions.php', + '8e4171839e12546525126d38dac3dafa' => $vendorDir . '/vimeo/psalm/src/spl_object_id.php', + '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', + '023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php', + '6cd5651c4fef5ed6b63e8d8b8ffbf3cc' => $vendorDir . '/amphp/byte-stream/lib/functions.php', +); diff --git a/lib/composer/composer/autoload_namespaces.php b/lib/composer/composer/autoload_namespaces.php index 4a9c20beed0715a492b947b0a858acdf2fe0066d..7e01061f095448bf3b2d709ec5d28e16079a752a 100644 --- a/lib/composer/composer/autoload_namespaces.php +++ b/lib/composer/composer/autoload_namespaces.php @@ -6,4 +6,6 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname(dirname($vendorDir)); return array( + 'LSS' => array($vendorDir . '/openlss/lib-array2xml'), + 'JsonMapper' => array($vendorDir . '/netresearch/jsonmapper/src'), ); diff --git a/lib/composer/composer/autoload_psr4.php b/lib/composer/composer/autoload_psr4.php index b641d9c6a03195b92c308e6fe4929b1fbd9fe508..b0a1d079ba260bd29dd0879ed1b07908a622b1e8 100644 --- a/lib/composer/composer/autoload_psr4.php +++ b/lib/composer/composer/autoload_psr4.php @@ -6,8 +6,47 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname(dirname($vendorDir)); return array( + 'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/type-resolver/src', $vendorDir . '/phpdocumentor/reflection-docblock/src'), + 'XdgBaseDir\\' => array($vendorDir . '/dnoegel/php-xdg-base-dir/src'), + 'Webmozart\\PathUtil\\' => array($vendorDir . '/webmozart/path-util/src'), + 'Webmozart\\Glob\\' => array($vendorDir . '/webmozart/glob/src'), + 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), + 'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), + 'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'), + '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'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psalm\\' => array($vendorDir . '/vimeo/psalm/src/Psalm'), + 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), + 'PhpCsFixer\\' => array($vendorDir . '/friendsofphp/php-cs-fixer/src'), + 'PackageVersions\\' => array($vendorDir . '/composer/package-versions-deprecated/src/PackageVersions'), 'OC\\Core\\' => array($baseDir . '/core'), 'OC\\' => array($baseDir . '/lib/private'), 'OCP\\' => array($baseDir . '/lib/public'), + 'Nextcloud\\CodingStandard\\' => array($vendorDir . '/nextcloud/coding-standard/src'), + 'LanguageServerProtocol\\' => array($vendorDir . '/felixfbecker/language-server-protocol/src'), + 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'), + 'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'), + 'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'), + 'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), + 'Amp\\ByteStream\\' => array($vendorDir . '/amphp/byte-stream/lib'), + 'Amp\\' => array($vendorDir . '/amphp/amp/lib'), + 'AdvancedJsonRpc\\' => array($vendorDir . '/felixfbecker/advanced-json-rpc/lib'), '' => array($baseDir . '/lib/private/legacy'), ); diff --git a/lib/composer/composer/autoload_real.php b/lib/composer/composer/autoload_real.php index a17f25c5eaf17591221172953203cb4bffea042c..647511f05dad68795a5c84d9e74aa370592e2be2 100644 --- a/lib/composer/composer/autoload_real.php +++ b/lib/composer/composer/autoload_real.php @@ -22,13 +22,15 @@ class ComposerAutoloaderInit53792487c5a8370acc0b06b1a864ff4c return self::$loader; } + require __DIR__ . '/platform_check.php'; + spl_autoload_register(array('ComposerAutoloaderInit53792487c5a8370acc0b06b1a864ff4c', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInit53792487c5a8370acc0b06b1a864ff4c', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { - require_once __DIR__ . '/autoload_static.php'; + require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::getInitializer($loader)); } else { @@ -50,6 +52,24 @@ class ComposerAutoloaderInit53792487c5a8370acc0b06b1a864ff4c $loader->register(true); + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire53792487c5a8370acc0b06b1a864ff4c($fileIdentifier, $file); + } + return $loader; } } + +function composerRequire53792487c5a8370acc0b06b1a864ff4c($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 92692d2188c883b42a5546c79df272adc7a76b4c..0e598b64688ffd02c4c2da8ba2868a6fa4c2ea3e 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -6,16 +6,225 @@ namespace Composer\Autoload; class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c { + public static $files = array ( + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + 'e8aa6e4b5a1db2f56ae794f1505391a8' => __DIR__ . '/..' . '/amphp/amp/lib/functions.php', + '76cd0796156622033397994f25b0d8fc' => __DIR__ . '/..' . '/amphp/amp/lib/Internal/functions.php', + 'dc51568953534d6c54b08731e61104e2' => __DIR__ . '/..' . '/vimeo/psalm/src/functions.php', + '8e4171839e12546525126d38dac3dafa' => __DIR__ . '/..' . '/vimeo/psalm/src/spl_object_id.php', + '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', + '023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php', + '6cd5651c4fef5ed6b63e8d8b8ffbf3cc' => __DIR__ . '/..' . '/amphp/byte-stream/lib/functions.php', + ); + public static $prefixLengthsPsr4 = array ( + 'p' => + array ( + 'phpDocumentor\\Reflection\\' => 25, + ), + 'X' => + array ( + 'XdgBaseDir\\' => 11, + ), + 'W' => + array ( + 'Webmozart\\PathUtil\\' => 19, + 'Webmozart\\Glob\\' => 15, + 'Webmozart\\Assert\\' => 17, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Php73\\' => 23, + 'Symfony\\Polyfill\\Php72\\' => 23, + 'Symfony\\Polyfill\\Php70\\' => 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, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Psr\\EventDispatcher\\' => 20, + 'Psr\\Container\\' => 14, + 'Psalm\\' => 6, + 'PhpParser\\' => 10, + 'PhpCsFixer\\' => 11, + 'PackageVersions\\' => 16, + ), 'O' => array ( 'OC\\Core\\' => 8, 'OC\\' => 3, 'OCP\\' => 4, ), + 'N' => + array ( + 'Nextcloud\\CodingStandard\\' => 25, + ), + 'L' => + array ( + 'LanguageServerProtocol\\' => 23, + ), + 'D' => + array ( + 'Doctrine\\Common\\Lexer\\' => 22, + 'Doctrine\\Common\\Annotations\\' => 28, + ), + 'C' => + array ( + 'Composer\\XdebugHandler\\' => 23, + 'Composer\\Semver\\' => 16, + ), + 'A' => + array ( + 'Amp\\ByteStream\\' => 15, + 'Amp\\' => 4, + 'AdvancedJsonRpc\\' => 16, + ), ); public static $prefixDirsPsr4 = array ( + 'phpDocumentor\\Reflection\\' => + array ( + 0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src', + 1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src', + 2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src', + ), + 'XdgBaseDir\\' => + array ( + 0 => __DIR__ . '/..' . '/dnoegel/php-xdg-base-dir/src', + ), + 'Webmozart\\PathUtil\\' => + array ( + 0 => __DIR__ . '/..' . '/webmozart/path-util/src', + ), + 'Webmozart\\Glob\\' => + array ( + 0 => __DIR__ . '/..' . '/webmozart/glob/src', + ), + 'Webmozart\\Assert\\' => + array ( + 0 => __DIR__ . '/..' . '/webmozart/assert/src', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Php73\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', + ), + 'Symfony\\Polyfill\\Php72\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php72', + ), + 'Symfony\\Polyfill\\Php70\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php70', + ), + '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', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Psr\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/event-dispatcher/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psalm\\' => + array ( + 0 => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm', + ), + 'PhpParser\\' => + array ( + 0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser', + ), + 'PhpCsFixer\\' => + array ( + 0 => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src', + ), + 'PackageVersions\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/package-versions-deprecated/src/PackageVersions', + ), 'OC\\Core\\' => array ( 0 => __DIR__ . '/../../..' . '/core', @@ -28,13 +237,232 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c array ( 0 => __DIR__ . '/../../..' . '/lib/public', ), + 'Nextcloud\\CodingStandard\\' => + array ( + 0 => __DIR__ . '/..' . '/nextcloud/coding-standard/src', + ), + 'LanguageServerProtocol\\' => + array ( + 0 => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src', + ), + 'Doctrine\\Common\\Lexer\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer', + ), + 'Doctrine\\Common\\Annotations\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations', + ), + 'Composer\\XdebugHandler\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/xdebug-handler/src', + ), + 'Composer\\Semver\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/semver/src', + ), + 'Amp\\ByteStream\\' => + array ( + 0 => __DIR__ . '/..' . '/amphp/byte-stream/lib', + ), + 'Amp\\' => + array ( + 0 => __DIR__ . '/..' . '/amphp/amp/lib', + ), + 'AdvancedJsonRpc\\' => + array ( + 0 => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib', + ), ); public static $fallbackDirsPsr4 = array ( 0 => __DIR__ . '/../../..' . '/lib/private/legacy', ); + public static $prefixesPsr0 = array ( + 'L' => + array ( + 'LSS' => + array ( + 0 => __DIR__ . '/..' . '/openlss/lib-array2xml', + ), + ), + 'J' => + array ( + 'JsonMapper' => + array ( + 0 => __DIR__ . '/..' . '/netresearch/jsonmapper/src', + ), + ), + ); + public static $classMap = array ( + 'AdvancedJsonRpc\\Dispatcher' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Dispatcher.php', + 'AdvancedJsonRpc\\Error' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Error.php', + 'AdvancedJsonRpc\\ErrorCode' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/ErrorCode.php', + 'AdvancedJsonRpc\\ErrorResponse' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/ErrorResponse.php', + 'AdvancedJsonRpc\\Message' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Message.php', + 'AdvancedJsonRpc\\Notification' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Notification.php', + 'AdvancedJsonRpc\\Request' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Request.php', + 'AdvancedJsonRpc\\Response' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Response.php', + 'AdvancedJsonRpc\\SuccessResponse' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/SuccessResponse.php', + 'Amp\\ByteStream\\Base64\\Base64DecodingInputStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/Base64/Base64DecodingInputStream.php', + 'Amp\\ByteStream\\Base64\\Base64DecodingOutputStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/Base64/Base64DecodingOutputStream.php', + 'Amp\\ByteStream\\Base64\\Base64EncodingInputStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/Base64/Base64EncodingInputStream.php', + 'Amp\\ByteStream\\Base64\\Base64EncodingOutputStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/Base64/Base64EncodingOutputStream.php', + 'Amp\\ByteStream\\ClosedException' => __DIR__ . '/..' . '/amphp/byte-stream/lib/ClosedException.php', + 'Amp\\ByteStream\\InMemoryStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/InMemoryStream.php', + 'Amp\\ByteStream\\InputStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/InputStream.php', + 'Amp\\ByteStream\\InputStreamChain' => __DIR__ . '/..' . '/amphp/byte-stream/lib/InputStreamChain.php', + 'Amp\\ByteStream\\IteratorStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/IteratorStream.php', + 'Amp\\ByteStream\\LineReader' => __DIR__ . '/..' . '/amphp/byte-stream/lib/LineReader.php', + 'Amp\\ByteStream\\Message' => __DIR__ . '/..' . '/amphp/byte-stream/lib/Message.php', + 'Amp\\ByteStream\\OutputBuffer' => __DIR__ . '/..' . '/amphp/byte-stream/lib/OutputBuffer.php', + 'Amp\\ByteStream\\OutputStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/OutputStream.php', + 'Amp\\ByteStream\\Payload' => __DIR__ . '/..' . '/amphp/byte-stream/lib/Payload.php', + 'Amp\\ByteStream\\PendingReadError' => __DIR__ . '/..' . '/amphp/byte-stream/lib/PendingReadError.php', + 'Amp\\ByteStream\\ResourceInputStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/ResourceInputStream.php', + 'Amp\\ByteStream\\ResourceOutputStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/ResourceOutputStream.php', + 'Amp\\ByteStream\\StreamException' => __DIR__ . '/..' . '/amphp/byte-stream/lib/StreamException.php', + 'Amp\\ByteStream\\ZlibInputStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/ZlibInputStream.php', + 'Amp\\ByteStream\\ZlibOutputStream' => __DIR__ . '/..' . '/amphp/byte-stream/lib/ZlibOutputStream.php', + 'Amp\\CallableMaker' => __DIR__ . '/..' . '/amphp/amp/lib/CallableMaker.php', + 'Amp\\CancellationToken' => __DIR__ . '/..' . '/amphp/amp/lib/CancellationToken.php', + 'Amp\\CancellationTokenSource' => __DIR__ . '/..' . '/amphp/amp/lib/CancellationTokenSource.php', + 'Amp\\CancelledException' => __DIR__ . '/..' . '/amphp/amp/lib/CancelledException.php', + 'Amp\\CombinedCancellationToken' => __DIR__ . '/..' . '/amphp/amp/lib/CombinedCancellationToken.php', + 'Amp\\Coroutine' => __DIR__ . '/..' . '/amphp/amp/lib/Coroutine.php', + 'Amp\\Deferred' => __DIR__ . '/..' . '/amphp/amp/lib/Deferred.php', + 'Amp\\Delayed' => __DIR__ . '/..' . '/amphp/amp/lib/Delayed.php', + 'Amp\\Emitter' => __DIR__ . '/..' . '/amphp/amp/lib/Emitter.php', + 'Amp\\Failure' => __DIR__ . '/..' . '/amphp/amp/lib/Failure.php', + 'Amp\\Internal\\Placeholder' => __DIR__ . '/..' . '/amphp/amp/lib/Internal/Placeholder.php', + 'Amp\\Internal\\PrivateIterator' => __DIR__ . '/..' . '/amphp/amp/lib/Internal/PrivateIterator.php', + 'Amp\\Internal\\PrivatePromise' => __DIR__ . '/..' . '/amphp/amp/lib/Internal/PrivatePromise.php', + 'Amp\\Internal\\Producer' => __DIR__ . '/..' . '/amphp/amp/lib/Internal/Producer.php', + 'Amp\\Internal\\ResolutionQueue' => __DIR__ . '/..' . '/amphp/amp/lib/Internal/ResolutionQueue.php', + 'Amp\\InvalidYieldError' => __DIR__ . '/..' . '/amphp/amp/lib/InvalidYieldError.php', + 'Amp\\Iterator' => __DIR__ . '/..' . '/amphp/amp/lib/Iterator.php', + 'Amp\\LazyPromise' => __DIR__ . '/..' . '/amphp/amp/lib/LazyPromise.php', + 'Amp\\Loop' => __DIR__ . '/..' . '/amphp/amp/lib/Loop.php', + 'Amp\\Loop\\Driver' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/Driver.php', + 'Amp\\Loop\\DriverFactory' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/DriverFactory.php', + 'Amp\\Loop\\EvDriver' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/EvDriver.php', + 'Amp\\Loop\\EventDriver' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/EventDriver.php', + 'Amp\\Loop\\Internal\\TimerQueue' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/Internal/TimerQueue.php', + 'Amp\\Loop\\Internal\\TimerQueueEntry' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/Internal/TimerQueueEntry.php', + 'Amp\\Loop\\InvalidWatcherError' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/InvalidWatcherError.php', + 'Amp\\Loop\\NativeDriver' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/NativeDriver.php', + 'Amp\\Loop\\TracingDriver' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/TracingDriver.php', + 'Amp\\Loop\\UnsupportedFeatureException' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/UnsupportedFeatureException.php', + 'Amp\\Loop\\UvDriver' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/UvDriver.php', + 'Amp\\Loop\\Watcher' => __DIR__ . '/..' . '/amphp/amp/lib/Loop/Watcher.php', + 'Amp\\MultiReasonException' => __DIR__ . '/..' . '/amphp/amp/lib/MultiReasonException.php', + 'Amp\\NullCancellationToken' => __DIR__ . '/..' . '/amphp/amp/lib/NullCancellationToken.php', + 'Amp\\Producer' => __DIR__ . '/..' . '/amphp/amp/lib/Producer.php', + 'Amp\\Promise' => __DIR__ . '/..' . '/amphp/amp/lib/Promise.php', + 'Amp\\Struct' => __DIR__ . '/..' . '/amphp/amp/lib/Struct.php', + 'Amp\\Success' => __DIR__ . '/..' . '/amphp/amp/lib/Success.php', + 'Amp\\TimeoutCancellationToken' => __DIR__ . '/..' . '/amphp/amp/lib/TimeoutCancellationToken.php', + 'Amp\\TimeoutException' => __DIR__ . '/..' . '/amphp/amp/lib/TimeoutException.php', + 'ArithmeticError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php', + 'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Composer\\Semver\\Comparator' => __DIR__ . '/..' . '/composer/semver/src/Comparator.php', + 'Composer\\Semver\\Constraint\\AbstractConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/AbstractConstraint.php', + 'Composer\\Semver\\Constraint\\Constraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Constraint.php', + 'Composer\\Semver\\Constraint\\ConstraintInterface' => __DIR__ . '/..' . '/composer/semver/src/Constraint/ConstraintInterface.php', + 'Composer\\Semver\\Constraint\\EmptyConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/EmptyConstraint.php', + 'Composer\\Semver\\Constraint\\MultiConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MultiConstraint.php', + 'Composer\\Semver\\Semver' => __DIR__ . '/..' . '/composer/semver/src/Semver.php', + 'Composer\\Semver\\VersionParser' => __DIR__ . '/..' . '/composer/semver/src/VersionParser.php', + 'Composer\\XdebugHandler\\PhpConfig' => __DIR__ . '/..' . '/composer/xdebug-handler/src/PhpConfig.php', + 'Composer\\XdebugHandler\\Process' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Process.php', + 'Composer\\XdebugHandler\\Status' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Status.php', + 'Composer\\XdebugHandler\\XdebugHandler' => __DIR__ . '/..' . '/composer/xdebug-handler/src/XdebugHandler.php', + 'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', + 'Doctrine\\Common\\Annotations\\Annotation' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php', + 'Doctrine\\Common\\Annotations\\AnnotationException' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php', + 'Doctrine\\Common\\Annotations\\AnnotationReader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php', + 'Doctrine\\Common\\Annotations\\AnnotationRegistry' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Attribute' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Attributes' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Enum' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php', + 'Doctrine\\Common\\Annotations\\Annotation\\IgnoreAnnotation' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Required' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Target' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php', + 'Doctrine\\Common\\Annotations\\CachedReader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php', + 'Doctrine\\Common\\Annotations\\DocLexer' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php', + 'Doctrine\\Common\\Annotations\\DocParser' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php', + 'Doctrine\\Common\\Annotations\\FileCacheReader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php', + 'Doctrine\\Common\\Annotations\\IndexedReader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php', + 'Doctrine\\Common\\Annotations\\PhpParser' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php', + 'Doctrine\\Common\\Annotations\\Reader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php', + 'Doctrine\\Common\\Annotations\\SimpleAnnotationReader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php', + 'Doctrine\\Common\\Annotations\\TokenParser' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php', + 'Doctrine\\Common\\Lexer\\AbstractLexer' => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php', + 'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php', + 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'JsonMapper' => __DIR__ . '/..' . '/netresearch/jsonmapper/src/JsonMapper.php', + 'JsonMapper_Exception' => __DIR__ . '/..' . '/netresearch/jsonmapper/src/JsonMapper/Exception.php', + 'LSS\\Array2XML' => __DIR__ . '/..' . '/openlss/lib-array2xml/LSS/Array2XML.php', + 'LSS\\XML2Array' => __DIR__ . '/..' . '/openlss/lib-array2xml/LSS/XML2Array.php', + 'LanguageServerProtocol\\ClientCapabilities' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/ClientCapabilities.php', + 'LanguageServerProtocol\\CodeActionContext' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/CodeActionContext.php', + 'LanguageServerProtocol\\CodeLens' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/CodeLens.php', + 'LanguageServerProtocol\\CodeLensOptions' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/CodeLensOptions.php', + 'LanguageServerProtocol\\Command' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/Command.php', + 'LanguageServerProtocol\\CompletionContext' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/CompletionContext.php', + 'LanguageServerProtocol\\CompletionItem' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/CompletionItem.php', + 'LanguageServerProtocol\\CompletionItemKind' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/CompletionItemKind.php', + 'LanguageServerProtocol\\CompletionList' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/CompletionList.php', + 'LanguageServerProtocol\\CompletionOptions' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/CompletionOptions.php', + 'LanguageServerProtocol\\CompletionTriggerKind' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/CompletionTriggerKind.php', + 'LanguageServerProtocol\\ContentChangeEvent' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/ContentChangeEvent.php', + 'LanguageServerProtocol\\DependencyReference' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/DependencyReference.php', + 'LanguageServerProtocol\\Diagnostic' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/Diagnostic.php', + 'LanguageServerProtocol\\DiagnosticSeverity' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/DiagnosticSeverity.php', + 'LanguageServerProtocol\\DocumentHighlight' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/DocumentHighlight.php', + 'LanguageServerProtocol\\DocumentHighlightKind' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/DocumentHighlightKind.php', + 'LanguageServerProtocol\\DocumentOnTypeFormattingOptions' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/DocumentOnTypeFormattingOptions.php', + 'LanguageServerProtocol\\ErrorCode' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/ErrorCode.php', + 'LanguageServerProtocol\\FileChangeType' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/FileChangeType.php', + 'LanguageServerProtocol\\FileEvent' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/FileEvent.php', + 'LanguageServerProtocol\\FormattingOptions' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/FormattingOptions.php', + 'LanguageServerProtocol\\Hover' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/Hover.php', + 'LanguageServerProtocol\\InitializeResult' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/InitializeResult.php', + 'LanguageServerProtocol\\InsertTextFormat' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/InsertTextFormat.php', + 'LanguageServerProtocol\\Location' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/Location.php', + 'LanguageServerProtocol\\MarkedString' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/MarkedString.php', + 'LanguageServerProtocol\\MarkupContent' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/MarkupContent.php', + 'LanguageServerProtocol\\MarkupKind' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/MarkupKind.php', + 'LanguageServerProtocol\\MessageActionItem' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/MessageActionItem.php', + 'LanguageServerProtocol\\MessageType' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/MessageType.php', + 'LanguageServerProtocol\\PackageDescriptor' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/PackageDescriptor.php', + 'LanguageServerProtocol\\ParameterInformation' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/ParameterInformation.php', + 'LanguageServerProtocol\\Position' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/Position.php', + 'LanguageServerProtocol\\Range' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/Range.php', + 'LanguageServerProtocol\\ReferenceContext' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/ReferenceContext.php', + 'LanguageServerProtocol\\ReferenceInformation' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/ReferenceInformation.php', + 'LanguageServerProtocol\\SaveOptions' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/SaveOptions.php', + 'LanguageServerProtocol\\ServerCapabilities' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/ServerCapabilities.php', + 'LanguageServerProtocol\\SignatureHelp' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/SignatureHelp.php', + 'LanguageServerProtocol\\SignatureHelpOptions' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/SignatureHelpOptions.php', + 'LanguageServerProtocol\\SignatureInformation' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/SignatureInformation.php', + 'LanguageServerProtocol\\SymbolDescriptor' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/SymbolDescriptor.php', + 'LanguageServerProtocol\\SymbolInformation' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/SymbolInformation.php', + 'LanguageServerProtocol\\SymbolKind' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/SymbolKind.php', + 'LanguageServerProtocol\\SymbolLocationInformation' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/SymbolLocationInformation.php', + 'LanguageServerProtocol\\TextDocumentContentChangeEvent' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/TextDocumentContentChangeEvent.php', + 'LanguageServerProtocol\\TextDocumentIdentifier' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/TextDocumentIdentifier.php', + 'LanguageServerProtocol\\TextDocumentItem' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/TextDocumentItem.php', + 'LanguageServerProtocol\\TextDocumentSyncKind' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/TextDocumentSyncKind.php', + 'LanguageServerProtocol\\TextDocumentSyncOptions' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/TextDocumentSyncOptions.php', + 'LanguageServerProtocol\\TextEdit' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/TextEdit.php', + 'LanguageServerProtocol\\VersionedTextDocumentIdentifier' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/VersionedTextDocumentIdentifier.php', + 'LanguageServerProtocol\\WorkspaceEdit' => __DIR__ . '/..' . '/felixfbecker/language-server-protocol/src/WorkspaceEdit.php', + 'Nextcloud\\CodingStandard\\Config' => __DIR__ . '/..' . '/nextcloud/coding-standard/src/Config.php', + 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'OCP\\API' => __DIR__ . '/../../..' . '/lib/public/API.php', 'OCP\\Accounts\\IAccount' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccount.php', 'OCP\\Accounts\\IAccountManager' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccountManager.php', @@ -1417,6 +1845,1752 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC_Template' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Template.php', 'OC_User' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_User.php', 'OC_Util' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Util.php', + 'PackageVersions\\FallbackVersions' => __DIR__ . '/..' . '/composer/package-versions-deprecated/src/PackageVersions/FallbackVersions.php', + 'PackageVersions\\Installer' => __DIR__ . '/..' . '/composer/package-versions-deprecated/src/PackageVersions/Installer.php', + 'PackageVersions\\Versions' => __DIR__ . '/..' . '/composer/package-versions-deprecated/src/PackageVersions/Versions.php', + 'ParseError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ParseError.php', + 'PhpCsFixer\\AbstractAlignFixerHelper' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/AbstractAlignFixerHelper.php', + 'PhpCsFixer\\AbstractDoctrineAnnotationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.php', + 'PhpCsFixer\\AbstractFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/AbstractFixer.php', + 'PhpCsFixer\\AbstractFopenFlagFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.php', + 'PhpCsFixer\\AbstractFunctionReferenceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.php', + 'PhpCsFixer\\AbstractLinesBeforeNamespaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/AbstractLinesBeforeNamespaceFixer.php', + 'PhpCsFixer\\AbstractNoUselessElseFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.php', + 'PhpCsFixer\\AbstractPhpdocTypesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.php', + 'PhpCsFixer\\AbstractProxyFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.php', + 'PhpCsFixer\\AbstractPsrAutoloadingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/AbstractPsrAutoloadingFixer.php', + 'PhpCsFixer\\Cache\\Cache' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Cache/Cache.php', + 'PhpCsFixer\\Cache\\CacheInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.php', + 'PhpCsFixer\\Cache\\CacheManagerInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.php', + 'PhpCsFixer\\Cache\\Directory' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Cache/Directory.php', + 'PhpCsFixer\\Cache\\DirectoryInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Cache/DirectoryInterface.php', + 'PhpCsFixer\\Cache\\FileCacheManager' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.php', + 'PhpCsFixer\\Cache\\FileHandler' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Cache/FileHandler.php', + 'PhpCsFixer\\Cache\\FileHandlerInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Cache/FileHandlerInterface.php', + 'PhpCsFixer\\Cache\\NullCacheManager' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Cache/NullCacheManager.php', + 'PhpCsFixer\\Cache\\Signature' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Cache/Signature.php', + 'PhpCsFixer\\Cache\\SignatureInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.php', + 'PhpCsFixer\\Config' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Config.php', + 'PhpCsFixer\\ConfigInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/ConfigInterface.php', + 'PhpCsFixer\\ConfigurationException\\InvalidConfigurationException' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.php', + 'PhpCsFixer\\ConfigurationException\\InvalidFixerConfigurationException' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.php', + 'PhpCsFixer\\ConfigurationException\\InvalidForEnvFixerConfigurationException' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidForEnvFixerConfigurationException.php', + 'PhpCsFixer\\ConfigurationException\\RequiredFixerConfigurationException' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/ConfigurationException/RequiredFixerConfigurationException.php', + 'PhpCsFixer\\Console\\Application' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Application.php', + 'PhpCsFixer\\Console\\Command\\DescribeCommand' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php', + 'PhpCsFixer\\Console\\Command\\DescribeNameNotFoundException' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Command/DescribeNameNotFoundException.php', + 'PhpCsFixer\\Console\\Command\\FixCommand' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php', + 'PhpCsFixer\\Console\\Command\\FixCommandExitStatusCalculator' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.php', + 'PhpCsFixer\\Console\\Command\\HelpCommand' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php', + 'PhpCsFixer\\Console\\Command\\ReadmeCommand' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Command/ReadmeCommand.php', + 'PhpCsFixer\\Console\\Command\\SelfUpdateCommand' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Command/SelfUpdateCommand.php', + 'PhpCsFixer\\Console\\ConfigurationResolver' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php', + 'PhpCsFixer\\Console\\Output\\ErrorOutput' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php', + 'PhpCsFixer\\Console\\Output\\NullOutput' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Output/NullOutput.php', + 'PhpCsFixer\\Console\\Output\\ProcessOutput' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutput.php', + 'PhpCsFixer\\Console\\Output\\ProcessOutputInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutputInterface.php', + 'PhpCsFixer\\Console\\SelfUpdate\\GithubClient' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.php', + 'PhpCsFixer\\Console\\SelfUpdate\\GithubClientInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.php', + 'PhpCsFixer\\Console\\SelfUpdate\\NewVersionChecker' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php', + 'PhpCsFixer\\Console\\SelfUpdate\\NewVersionCheckerInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionCheckerInterface.php', + 'PhpCsFixer\\Console\\WarningsDetector' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.php', + 'PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\ConfigurationException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/ConfigurationException.php', + 'PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/UnifiedDiffOutputBuilder.php', + 'PhpCsFixer\\Diff\\v1_4\\Chunk' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Chunk.php', + 'PhpCsFixer\\Diff\\v1_4\\Diff' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Diff.php', + 'PhpCsFixer\\Diff\\v1_4\\Differ' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Differ.php', + 'PhpCsFixer\\Diff\\v1_4\\LCS\\LongestCommonSubsequence' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/LCS/LongestCommonSubsequence.php', + 'PhpCsFixer\\Diff\\v1_4\\LCS\\MemoryEfficientImplementation' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php', + 'PhpCsFixer\\Diff\\v1_4\\LCS\\TimeEfficientImplementation' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php', + 'PhpCsFixer\\Diff\\v1_4\\Line' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Line.php', + 'PhpCsFixer\\Diff\\v1_4\\Parser' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Parser.php', + 'PhpCsFixer\\Diff\\v2_0\\Chunk' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Chunk.php', + 'PhpCsFixer\\Diff\\v2_0\\Diff' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Diff.php', + 'PhpCsFixer\\Diff\\v2_0\\Differ' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Differ.php', + 'PhpCsFixer\\Diff\\v2_0\\Exception' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Exception/Exception.php', + 'PhpCsFixer\\Diff\\v2_0\\InvalidArgumentException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Exception/InvalidArgumentException.php', + 'PhpCsFixer\\Diff\\v2_0\\Line' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Line.php', + 'PhpCsFixer\\Diff\\v2_0\\LongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/LongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Diff\\v2_0\\MemoryEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/MemoryEfficientLongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Diff\\v2_0\\Output\\AbstractChunkOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/AbstractChunkOutputBuilder.php', + 'PhpCsFixer\\Diff\\v2_0\\Output\\DiffOnlyOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/DiffOnlyOutputBuilder.php', + 'PhpCsFixer\\Diff\\v2_0\\Output\\DiffOutputBuilderInterface' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/DiffOutputBuilderInterface.php', + 'PhpCsFixer\\Diff\\v2_0\\Output\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/UnifiedDiffOutputBuilder.php', + 'PhpCsFixer\\Diff\\v2_0\\Parser' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Parser.php', + 'PhpCsFixer\\Diff\\v2_0\\TimeEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/TimeEfficientLongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Diff\\v3_0\\Chunk' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Chunk.php', + 'PhpCsFixer\\Diff\\v3_0\\ConfigurationException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Exception/ConfigurationException.php', + 'PhpCsFixer\\Diff\\v3_0\\Diff' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Diff.php', + 'PhpCsFixer\\Diff\\v3_0\\Differ' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Differ.php', + 'PhpCsFixer\\Diff\\v3_0\\Exception' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Exception/Exception.php', + 'PhpCsFixer\\Diff\\v3_0\\InvalidArgumentException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Exception/InvalidArgumentException.php', + 'PhpCsFixer\\Diff\\v3_0\\Line' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Line.php', + 'PhpCsFixer\\Diff\\v3_0\\LongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/LongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Diff\\v3_0\\MemoryEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/MemoryEfficientLongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Diff\\v3_0\\Output\\AbstractChunkOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/AbstractChunkOutputBuilder.php', + 'PhpCsFixer\\Diff\\v3_0\\Output\\DiffOnlyOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/DiffOnlyOutputBuilder.php', + 'PhpCsFixer\\Diff\\v3_0\\Output\\DiffOutputBuilderInterface' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/DiffOutputBuilderInterface.php', + 'PhpCsFixer\\Diff\\v3_0\\Output\\StrictUnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/StrictUnifiedDiffOutputBuilder.php', + 'PhpCsFixer\\Diff\\v3_0\\Output\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/UnifiedDiffOutputBuilder.php', + 'PhpCsFixer\\Diff\\v3_0\\Parser' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Parser.php', + 'PhpCsFixer\\Diff\\v3_0\\TimeEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/TimeEfficientLongestCommonSubsequenceCalculator.php', + 'PhpCsFixer\\Differ\\DiffConsoleFormatter' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.php', + 'PhpCsFixer\\Differ\\DifferInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Differ/DifferInterface.php', + 'PhpCsFixer\\Differ\\FullDiffer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php', + 'PhpCsFixer\\Differ\\NullDiffer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.php', + 'PhpCsFixer\\Differ\\SebastianBergmannDiffer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Differ/SebastianBergmannDiffer.php', + 'PhpCsFixer\\Differ\\SebastianBergmannShortDiffer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Differ/SebastianBergmannShortDiffer.php', + 'PhpCsFixer\\Differ\\UnifiedDiffer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.php', + 'PhpCsFixer\\DocBlock\\Annotation' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php', + 'PhpCsFixer\\DocBlock\\DocBlock' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php', + 'PhpCsFixer\\DocBlock\\Line' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/DocBlock/Line.php', + 'PhpCsFixer\\DocBlock\\ShortDescription' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/DocBlock/ShortDescription.php', + 'PhpCsFixer\\DocBlock\\Tag' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/DocBlock/Tag.php', + 'PhpCsFixer\\DocBlock\\TagComparator' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.php', + 'PhpCsFixer\\Doctrine\\Annotation\\Token' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.php', + 'PhpCsFixer\\Doctrine\\Annotation\\Tokens' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php', + 'PhpCsFixer\\Error\\Error' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Error/Error.php', + 'PhpCsFixer\\Error\\ErrorsManager' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.php', + 'PhpCsFixer\\Event\\Event' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Event/Event.php', + 'PhpCsFixer\\FileReader' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FileReader.php', + 'PhpCsFixer\\FileRemoval' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FileRemoval.php', + 'PhpCsFixer\\Finder' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Finder.php', + 'PhpCsFixer\\FixerConfiguration\\AliasedFixerOption' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.php', + 'PhpCsFixer\\FixerConfiguration\\AliasedFixerOptionBuilder' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.php', + 'PhpCsFixer\\FixerConfiguration\\AllowedValueSubset' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.php', + 'PhpCsFixer\\FixerConfiguration\\DeprecatedFixerOption' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.php', + 'PhpCsFixer\\FixerConfiguration\\DeprecatedFixerOptionInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOptionInterface.php', + 'PhpCsFixer\\FixerConfiguration\\FixerConfigurationResolver' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.php', + 'PhpCsFixer\\FixerConfiguration\\FixerConfigurationResolverInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.php', + 'PhpCsFixer\\FixerConfiguration\\FixerConfigurationResolverRootless' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverRootless.php', + 'PhpCsFixer\\FixerConfiguration\\FixerOption' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.php', + 'PhpCsFixer\\FixerConfiguration\\FixerOptionBuilder' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php', + 'PhpCsFixer\\FixerConfiguration\\FixerOptionInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.php', + 'PhpCsFixer\\FixerConfiguration\\InvalidOptionsForEnvException' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerConfiguration/InvalidOptionsForEnvException.php', + 'PhpCsFixer\\FixerDefinition\\CodeSample' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.php', + 'PhpCsFixer\\FixerDefinition\\CodeSampleInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.php', + 'PhpCsFixer\\FixerDefinition\\FileSpecificCodeSample' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.php', + 'PhpCsFixer\\FixerDefinition\\FileSpecificCodeSampleInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSampleInterface.php', + 'PhpCsFixer\\FixerDefinition\\FixerDefinition' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.php', + 'PhpCsFixer\\FixerDefinition\\FixerDefinitionInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.php', + 'PhpCsFixer\\FixerDefinition\\VersionSpecificCodeSample' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.php', + 'PhpCsFixer\\FixerDefinition\\VersionSpecificCodeSampleInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSampleInterface.php', + 'PhpCsFixer\\FixerDefinition\\VersionSpecification' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.php', + 'PhpCsFixer\\FixerDefinition\\VersionSpecificationInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificationInterface.php', + 'PhpCsFixer\\FixerFactory' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerFactory.php', + 'PhpCsFixer\\FixerFileProcessedEvent' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.php', + 'PhpCsFixer\\FixerNameValidator' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/FixerNameValidator.php', + 'PhpCsFixer\\Fixer\\Alias\\BacktickToShellExecFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\EregToPregFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\MbStrFunctionsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\NoAliasFunctionsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\NoMixedEchoPrintFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\PowToExponentiationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\RandomApiMigrationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.php', + 'PhpCsFixer\\Fixer\\Alias\\SetTypeToCastFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\ArraySyntaxFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\NoMultilineWhitespaceAroundDoubleArrowFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\NoTrailingCommaInSinglelineArrayFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\NoWhitespaceBeforeCommaInArrayFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\NormalizeIndexBraceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\TrailingCommaInMultilineArrayFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrailingCommaInMultilineArrayFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\TrimArraySpacesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.php', + 'PhpCsFixer\\Fixer\\ArrayNotation\\WhitespaceAfterCommaInArrayFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php', + 'PhpCsFixer\\Fixer\\Basic\\BracesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php', + 'PhpCsFixer\\Fixer\\Basic\\EncodingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.php', + 'PhpCsFixer\\Fixer\\Basic\\NonPrintableCharacterFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php', + 'PhpCsFixer\\Fixer\\Basic\\Psr0Fixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Basic/Psr0Fixer.php', + 'PhpCsFixer\\Fixer\\Basic\\Psr4Fixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Basic/Psr4Fixer.php', + 'PhpCsFixer\\Fixer\\Casing\\ConstantCaseFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\LowercaseConstantsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseConstantsFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\LowercaseKeywordsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\LowercaseStaticReferenceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\MagicConstantCasingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\MagicMethodCasingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\NativeFunctionCasingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.php', + 'PhpCsFixer\\Fixer\\Casing\\NativeFunctionTypeDeclarationCasingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\CastSpacesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\LowercaseCastFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\ModernizeTypesCastingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\NoShortBoolCastFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\NoUnsetCastFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.php', + 'PhpCsFixer\\Fixer\\CastNotation\\ShortScalarCastFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\ClassAttributesSeparationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\ClassDefinitionFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\FinalClassFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\FinalInternalClassFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\FinalPublicMethodForAbstractClassFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\FinalStaticAccessFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalStaticAccessFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\MethodSeparationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/MethodSeparationFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\NoBlankLinesAfterClassOpeningFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\NoNullPropertyInitializationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\NoPhp4ConstructorFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\NoUnneededFinalMethodFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\OrderedClassElementsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\OrderedInterfacesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\ProtectedToPrivateFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\SelfAccessorFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\SelfStaticAccessorFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\SingleClassElementPerStatementFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\SingleTraitInsertPerStatementFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php', + 'PhpCsFixer\\Fixer\\ClassNotation\\VisibilityRequiredFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.php', + 'PhpCsFixer\\Fixer\\ClassUsage\\DateTimeImmutableFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\CommentToPhpdocFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\HashToSlashCommentFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/HashToSlashCommentFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\HeaderCommentFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\MultilineCommentOpeningClosingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\NoEmptyCommentFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\NoTrailingWhitespaceInCommentFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php', + 'PhpCsFixer\\Fixer\\Comment\\SingleLineCommentStyleFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.php', + 'PhpCsFixer\\Fixer\\ConfigurableFixerInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.php', + 'PhpCsFixer\\Fixer\\ConfigurationDefinitionFixerInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ConfigurationDefinitionFixerInterface.php', + 'PhpCsFixer\\Fixer\\ConstantNotation\\NativeConstantInvocationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\ElseifFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\IncludeFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/IncludeFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoAlternativeSyntaxFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoBreakCommentFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoSuperfluousElseifFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoTrailingCommaInListCallFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoUnneededControlParenthesesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoUnneededCurlyBracesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\NoUselessElseFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\SwitchCaseSemicolonToColonFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\SwitchCaseSpaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php', + 'PhpCsFixer\\Fixer\\ControlStructure\\YodaStyleFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php', + 'PhpCsFixer\\Fixer\\DefinedFixerInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/DefinedFixerInterface.php', + 'PhpCsFixer\\Fixer\\DeprecatedFixerInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.php', + 'PhpCsFixer\\Fixer\\DoctrineAnnotation\\DoctrineAnnotationArrayAssignmentFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php', + 'PhpCsFixer\\Fixer\\DoctrineAnnotation\\DoctrineAnnotationBracesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php', + 'PhpCsFixer\\Fixer\\DoctrineAnnotation\\DoctrineAnnotationIndentationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php', + 'PhpCsFixer\\Fixer\\DoctrineAnnotation\\DoctrineAnnotationSpacesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php', + 'PhpCsFixer\\Fixer\\FixerInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FixerInterface.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\CombineNestedDirnameFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\FopenFlagOrderFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\FopenFlagsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\FunctionDeclarationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\FunctionTypehintSpaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\ImplodeCallFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\MethodArgumentSpaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\NativeFunctionInvocationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\NoSpacesAfterFunctionNameFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\NoUnreachableDefaultArgumentValueFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\NullableTypeDeclarationForDefaultNullValueFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\PhpdocToParamTypeFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\PhpdocToReturnTypeFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\ReturnTypeDeclarationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\SingleLineThrowFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\StaticLambdaFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.php', + 'PhpCsFixer\\Fixer\\FunctionNotation\\VoidReturnFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php', + 'PhpCsFixer\\Fixer\\Import\\FullyQualifiedStrictTypesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php', + 'PhpCsFixer\\Fixer\\Import\\GlobalNamespaceImportFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php', + 'PhpCsFixer\\Fixer\\Import\\NoLeadingImportSlashFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.php', + 'PhpCsFixer\\Fixer\\Import\\NoUnusedImportsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php', + 'PhpCsFixer\\Fixer\\Import\\OrderedImportsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.php', + 'PhpCsFixer\\Fixer\\Import\\SingleImportPerStatementFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.php', + 'PhpCsFixer\\Fixer\\Import\\SingleLineAfterImportsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\ClassKeywordRemoveFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\CombineConsecutiveIssetsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\CombineConsecutiveUnsetsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\DeclareEqualNormalizeFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\DirConstantFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\ErrorSuppressionFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\ExplicitIndirectVariableFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\FunctionToConstantFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\IsNullFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\NoUnsetOnPropertyFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php', + 'PhpCsFixer\\Fixer\\LanguageConstruct\\SilencedDeprecationErrorFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SilencedDeprecationErrorFixer.php', + 'PhpCsFixer\\Fixer\\ListNotation\\ListSyntaxFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.php', + 'PhpCsFixer\\Fixer\\NamespaceNotation\\BlankLineAfterNamespaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php', + 'PhpCsFixer\\Fixer\\NamespaceNotation\\NoBlankLinesBeforeNamespaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php', + 'PhpCsFixer\\Fixer\\NamespaceNotation\\NoLeadingNamespaceWhitespaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php', + 'PhpCsFixer\\Fixer\\NamespaceNotation\\SingleBlankLineBeforeNamespaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php', + 'PhpCsFixer\\Fixer\\Naming\\NoHomoglyphNamesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\AlignDoubleArrowFixerHelper' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/AlignDoubleArrowFixerHelper.php', + 'PhpCsFixer\\Fixer\\Operator\\AlignEqualsFixerHelper' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/AlignEqualsFixerHelper.php', + 'PhpCsFixer\\Fixer\\Operator\\BinaryOperatorSpacesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\ConcatSpaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\IncrementStyleFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\LogicalOperatorsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\NewWithBracesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\NotOperatorWithSpaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\NotOperatorWithSuccessorSpaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\ObjectOperatorWithoutWhitespaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\PreIncrementFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/PreIncrementFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\StandardizeIncrementFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\StandardizeNotEqualsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\TernaryOperatorSpacesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\TernaryToNullCoalescingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php', + 'PhpCsFixer\\Fixer\\Operator\\UnaryOperatorSpacesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.php', + 'PhpCsFixer\\Fixer\\PhpTag\\BlankLineAfterOpeningTagFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php', + 'PhpCsFixer\\Fixer\\PhpTag\\FullOpeningTagFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.php', + 'PhpCsFixer\\Fixer\\PhpTag\\LinebreakAfterOpeningTagFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php', + 'PhpCsFixer\\Fixer\\PhpTag\\NoClosingTagFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.php', + 'PhpCsFixer\\Fixer\\PhpTag\\NoShortEchoTagFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoShortEchoTagFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitConstructFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitDedicateAssertFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitDedicateAssertInternalTypeFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitExpectationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitFqcnAnnotationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitInternalClassFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitMethodCasingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitMockFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitMockShortWillReturnFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitNamespacedFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitNoExpectationAnnotationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitOrderedCoversFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitSetUpTearDownVisibilityFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitSizeClassFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitStrictFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitTargetVersion' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitTestAnnotationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitTestCaseStaticMethodCallsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php', + 'PhpCsFixer\\Fixer\\PhpUnit\\PhpUnitTestClassRequiresCoversFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\AlignMultilineCommentFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\GeneralPhpdocAnnotationRemoveFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\NoBlankLinesAfterPhpdocFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\NoEmptyPhpdocFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\NoSuperfluousPhpdocTagsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocAddMissingParamAnnotationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocAlignFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocAnnotationWithoutDotFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocIndentFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocInlineTagFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocLineSpanFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocLineSpanFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocNoAccessFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocNoAliasTagFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocNoEmptyReturnFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocNoPackageFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocNoUselessInheritdocFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocOrderFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocReturnSelfReferenceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocScalarFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocSeparationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocSingleLineVarSpacingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocSummaryFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocToCommentFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocTrimConsecutiveBlankLineSeparationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocTrimFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocTypesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocTypesOrderFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocVarAnnotationCorrectOrderFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php', + 'PhpCsFixer\\Fixer\\Phpdoc\\PhpdocVarWithoutNameFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php', + 'PhpCsFixer\\Fixer\\ReturnNotation\\BlankLineBeforeReturnFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/BlankLineBeforeReturnFixer.php', + 'PhpCsFixer\\Fixer\\ReturnNotation\\NoUselessReturnFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php', + 'PhpCsFixer\\Fixer\\ReturnNotation\\ReturnAssignmentFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php', + 'PhpCsFixer\\Fixer\\ReturnNotation\\SimplifiedNullReturnFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\MultilineWhitespaceBeforeSemicolonsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\NoEmptyStatementFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\NoMultilineWhitespaceBeforeSemicolonsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoMultilineWhitespaceBeforeSemicolonsFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\NoSinglelineWhitespaceBeforeSemicolonsFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\SemicolonAfterInstructionFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php', + 'PhpCsFixer\\Fixer\\Semicolon\\SpaceAfterSemicolonFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php', + 'PhpCsFixer\\Fixer\\Strict\\DeclareStrictTypesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.php', + 'PhpCsFixer\\Fixer\\Strict\\StrictComparisonFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.php', + 'PhpCsFixer\\Fixer\\Strict\\StrictParamFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\EscapeImplicitBackslashesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\ExplicitStringVariableFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\HeredocToNowdocFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\NoBinaryStringFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\SimpleToComplexStringVariableFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\SingleQuoteFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.php', + 'PhpCsFixer\\Fixer\\StringNotation\\StringLineEndingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\ArrayIndentationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\BlankLineBeforeStatementFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\CompactNullableTypehintFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\HeredocIndentationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/HeredocIndentationFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\IndentationTypeFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\LineEndingFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\MethodChainingIndentationFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoExtraBlankLinesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoExtraConsecutiveBlankLinesFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraConsecutiveBlankLinesFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoSpacesAroundOffsetFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoSpacesInsideParenthesisFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoTrailingWhitespaceFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\NoWhitespaceInBlankLineFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php', + 'PhpCsFixer\\Fixer\\Whitespace\\SingleBlankLineAtEofFixer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php', + 'PhpCsFixer\\Fixer\\WhitespacesAwareFixerInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Fixer/WhitespacesAwareFixerInterface.php', + 'PhpCsFixer\\Indicator\\PhpUnitTestCaseIndicator' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.php', + 'PhpCsFixer\\Linter\\CachingLinter' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.php', + 'PhpCsFixer\\Linter\\Linter' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Linter/Linter.php', + 'PhpCsFixer\\Linter\\LinterInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Linter/LinterInterface.php', + 'PhpCsFixer\\Linter\\LintingException' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Linter/LintingException.php', + 'PhpCsFixer\\Linter\\LintingResultInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Linter/LintingResultInterface.php', + 'PhpCsFixer\\Linter\\ProcessLinter' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.php', + 'PhpCsFixer\\Linter\\ProcessLinterProcessBuilder' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Linter/ProcessLinterProcessBuilder.php', + 'PhpCsFixer\\Linter\\ProcessLintingResult' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.php', + 'PhpCsFixer\\Linter\\TokenizerLinter' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.php', + 'PhpCsFixer\\Linter\\TokenizerLintingResult' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.php', + 'PhpCsFixer\\Linter\\UnavailableLinterException' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.php', + 'PhpCsFixer\\PharChecker' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/PharChecker.php', + 'PhpCsFixer\\PharCheckerInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/PharCheckerInterface.php', + 'PhpCsFixer\\Preg' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Preg.php', + 'PhpCsFixer\\PregException' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/PregException.php', + 'PhpCsFixer\\Report\\CheckstyleReporter' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Report/CheckstyleReporter.php', + 'PhpCsFixer\\Report\\GitlabReporter' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Report/GitlabReporter.php', + 'PhpCsFixer\\Report\\JsonReporter' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Report/JsonReporter.php', + 'PhpCsFixer\\Report\\JunitReporter' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Report/JunitReporter.php', + 'PhpCsFixer\\Report\\ReportSummary' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Report/ReportSummary.php', + 'PhpCsFixer\\Report\\ReporterFactory' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Report/ReporterFactory.php', + 'PhpCsFixer\\Report\\ReporterInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Report/ReporterInterface.php', + 'PhpCsFixer\\Report\\TextReporter' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Report/TextReporter.php', + 'PhpCsFixer\\Report\\XmlReporter' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Report/XmlReporter.php', + 'PhpCsFixer\\RuleSet' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/RuleSet.php', + 'PhpCsFixer\\RuleSetInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/RuleSetInterface.php', + 'PhpCsFixer\\Runner\\FileCachingLintingIterator' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingIterator.php', + 'PhpCsFixer\\Runner\\FileFilterIterator' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.php', + 'PhpCsFixer\\Runner\\FileLintingIterator' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Runner/FileLintingIterator.php', + 'PhpCsFixer\\Runner\\Runner' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Runner/Runner.php', + 'PhpCsFixer\\StdinFileInfo' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/StdinFileInfo.php', + 'PhpCsFixer\\Test\\AbstractFixerTestCase' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Test/AbstractFixerTestCase.php', + 'PhpCsFixer\\Test\\AbstractIntegrationTestCase' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Test/AbstractIntegrationTestCase.php', + 'PhpCsFixer\\Test\\AccessibleObject' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Test/AccessibleObject.php', + 'PhpCsFixer\\Test\\IntegrationCase' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Test/IntegrationCase.php', + 'PhpCsFixer\\Tests\\TestCase' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/tests/TestCase.php', + 'PhpCsFixer\\Tests\\Test\\AbstractFixerTestCase' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/tests/Test/AbstractFixerTestCase.php', + 'PhpCsFixer\\Tests\\Test\\AbstractIntegrationCaseFactory' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/tests/Test/AbstractIntegrationCaseFactory.php', + 'PhpCsFixer\\Tests\\Test\\AbstractIntegrationTestCase' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/tests/Test/AbstractIntegrationTestCase.php', + 'PhpCsFixer\\Tests\\Test\\Assert\\AssertTokensTrait' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/tests/Test/Assert/AssertTokensTrait.php', + 'PhpCsFixer\\Tests\\Test\\IntegrationCase' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/tests/Test/IntegrationCase.php', + 'PhpCsFixer\\Tests\\Test\\IntegrationCaseFactory' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/tests/Test/IntegrationCaseFactory.php', + 'PhpCsFixer\\Tests\\Test\\IntegrationCaseFactoryInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/tests/Test/IntegrationCaseFactoryInterface.php', + 'PhpCsFixer\\Tests\\Test\\InternalIntegrationCaseFactory' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/tests/Test/InternalIntegrationCaseFactory.php', + 'PhpCsFixer\\Tests\\Test\\IsIdenticalConstraint' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/tests/Test/IsIdenticalConstraint.php', + 'PhpCsFixer\\Tokenizer\\AbstractTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\Analysis\\ArgumentAnalysis' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\Analysis\\NamespaceAnalysis' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceAnalysis.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\Analysis\\NamespaceUseAnalysis' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\Analysis\\StartEndTokenAwareAnalysis' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/StartEndTokenAwareAnalysis.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\Analysis\\TypeAnalysis' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\ArgumentsAnalyzer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\BlocksAnalyzer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\ClassyAnalyzer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\CommentsAnalyzer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\FunctionsAnalyzer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\NamespaceUsesAnalyzer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\Analyzer\\NamespacesAnalyzer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\CT' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/CT.php', + 'PhpCsFixer\\Tokenizer\\CodeHasher' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/CodeHasher.php', + 'PhpCsFixer\\Tokenizer\\Generator\\NamespacedStringTokenGenerator' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Generator/NamespacedStringTokenGenerator.php', + 'PhpCsFixer\\Tokenizer\\Resolver\\TypeShortNameResolver' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Resolver/TypeShortNameResolver.php', + 'PhpCsFixer\\Tokenizer\\Token' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php', + 'PhpCsFixer\\Tokenizer\\Tokens' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php', + 'PhpCsFixer\\Tokenizer\\TokensAnalyzer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php', + 'PhpCsFixer\\Tokenizer\\TransformerInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\ArrayTypehintTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\BraceClassInstantiationTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\ClassConstantTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\CurlyBraceTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/CurlyBraceTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\ImportTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\NamespaceOperatorTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\NullableTypeTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\ReturnRefTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\SquareBraceTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\TypeAlternationTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\TypeColonTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\UseTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformer\\WhitespacyCommentTransformer' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php', + 'PhpCsFixer\\Tokenizer\\Transformers' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Tokenizer/Transformers.php', + 'PhpCsFixer\\ToolInfo' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/ToolInfo.php', + 'PhpCsFixer\\ToolInfoInterface' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/ToolInfoInterface.php', + 'PhpCsFixer\\Utils' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/Utils.php', + 'PhpCsFixer\\WhitespacesFixerConfig' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/WhitespacesFixerConfig.php', + 'PhpCsFixer\\WordMatcher' => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src/WordMatcher.php', + 'PhpParser\\Builder' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder.php', + 'PhpParser\\BuilderFactory' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php', + 'PhpParser\\BuilderHelpers' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderHelpers.php', + 'PhpParser\\Builder\\Class_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Class_.php', + 'PhpParser\\Builder\\Declaration' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Declaration.php', + 'PhpParser\\Builder\\FunctionLike' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php', + 'PhpParser\\Builder\\Function_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Function_.php', + 'PhpParser\\Builder\\Interface_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Interface_.php', + 'PhpParser\\Builder\\Method' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Method.php', + 'PhpParser\\Builder\\Namespace_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php', + 'PhpParser\\Builder\\Param' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Param.php', + 'PhpParser\\Builder\\Property' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Property.php', + 'PhpParser\\Builder\\TraitUse' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php', + 'PhpParser\\Builder\\TraitUseAdaptation' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php', + 'PhpParser\\Builder\\Trait_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Trait_.php', + 'PhpParser\\Builder\\Use_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Use_.php', + 'PhpParser\\Comment' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Comment.php', + 'PhpParser\\Comment\\Doc' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Comment/Doc.php', + 'PhpParser\\ConstExprEvaluationException' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/ConstExprEvaluationException.php', + 'PhpParser\\ConstExprEvaluator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php', + 'PhpParser\\Error' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Error.php', + 'PhpParser\\ErrorHandler' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/ErrorHandler.php', + 'PhpParser\\ErrorHandler\\Collecting' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/ErrorHandler/Collecting.php', + 'PhpParser\\ErrorHandler\\Throwing' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php', + 'PhpParser\\Internal\\DiffElem' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Internal/DiffElem.php', + 'PhpParser\\Internal\\Differ' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Internal/Differ.php', + 'PhpParser\\Internal\\PrintableNewAnonClassNode' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php', + 'PhpParser\\Internal\\TokenStream' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php', + 'PhpParser\\JsonDecoder' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/JsonDecoder.php', + 'PhpParser\\Lexer' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer.php', + 'PhpParser\\Lexer\\Emulative' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php', + 'PhpParser\\Lexer\\TokenEmulator\\AttributeEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\CoaleseEqualTokenEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\FlexibleDocStringEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\FnTokenEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\KeywordEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\MatchTokenEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\NullsafeTokenEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\NumericLiteralSeparatorEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\ReverseEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\TokenEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php', + 'PhpParser\\NameContext' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NameContext.php', + 'PhpParser\\Node' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node.php', + 'PhpParser\\NodeAbstract' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeAbstract.php', + 'PhpParser\\NodeDumper' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeDumper.php', + 'PhpParser\\NodeFinder' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeFinder.php', + 'PhpParser\\NodeTraverser' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeTraverser.php', + 'PhpParser\\NodeTraverserInterface' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php', + 'PhpParser\\NodeVisitor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeVisitor.php', + 'PhpParser\\NodeVisitorAbstract' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php', + 'PhpParser\\NodeVisitor\\CloningVisitor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php', + 'PhpParser\\NodeVisitor\\FindingVisitor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php', + 'PhpParser\\NodeVisitor\\FirstFindingVisitor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php', + 'PhpParser\\NodeVisitor\\NameResolver' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php', + 'PhpParser\\NodeVisitor\\NodeConnectingVisitor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php', + 'PhpParser\\NodeVisitor\\ParentConnectingVisitor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php', + 'PhpParser\\Node\\Arg' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Arg.php', + 'PhpParser\\Node\\Attribute' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Attribute.php', + 'PhpParser\\Node\\AttributeGroup' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php', + 'PhpParser\\Node\\Const_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Const_.php', + 'PhpParser\\Node\\Expr' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr.php', + 'PhpParser\\Node\\Expr\\ArrayDimFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php', + 'PhpParser\\Node\\Expr\\ArrayItem' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php', + 'PhpParser\\Node\\Expr\\Array_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php', + 'PhpParser\\Node\\Expr\\ArrowFunction' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php', + 'PhpParser\\Node\\Expr\\Assign' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php', + 'PhpParser\\Node\\Expr\\AssignOp' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php', + 'PhpParser\\Node\\Expr\\AssignOp\\BitwiseAnd' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', + 'PhpParser\\Node\\Expr\\AssignOp\\BitwiseOr' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', + 'PhpParser\\Node\\Expr\\AssignOp\\BitwiseXor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Coalesce' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Coalesce.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Concat' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Div' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Minus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Mod' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Mul' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Plus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.php', + 'PhpParser\\Node\\Expr\\AssignOp\\Pow' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.php', + 'PhpParser\\Node\\Expr\\AssignOp\\ShiftLeft' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', + 'PhpParser\\Node\\Expr\\AssignOp\\ShiftRight' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', + 'PhpParser\\Node\\Expr\\AssignRef' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php', + 'PhpParser\\Node\\Expr\\BinaryOp' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseAnd' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseOr' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseXor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\BooleanAnd' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\BooleanOr' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Coalesce' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Concat' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Div' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Equal' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Greater' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\GreaterOrEqual' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Identical' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\LogicalAnd' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\LogicalOr' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\LogicalXor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Minus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Mod' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Mul' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\NotEqual' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\NotIdentical' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Plus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Pow' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\ShiftLeft' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\ShiftRight' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Smaller' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\SmallerOrEqual' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', + 'PhpParser\\Node\\Expr\\BinaryOp\\Spaceship' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', + 'PhpParser\\Node\\Expr\\BitwiseNot' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php', + 'PhpParser\\Node\\Expr\\BooleanNot' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php', + 'PhpParser\\Node\\Expr\\Cast' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php', + 'PhpParser\\Node\\Expr\\Cast\\Array_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php', + 'PhpParser\\Node\\Expr\\Cast\\Bool_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php', + 'PhpParser\\Node\\Expr\\Cast\\Double' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.php', + 'PhpParser\\Node\\Expr\\Cast\\Int_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php', + 'PhpParser\\Node\\Expr\\Cast\\Object_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.php', + 'PhpParser\\Node\\Expr\\Cast\\String_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php', + 'PhpParser\\Node\\Expr\\Cast\\Unset_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.php', + 'PhpParser\\Node\\Expr\\ClassConstFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php', + 'PhpParser\\Node\\Expr\\Clone_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php', + 'PhpParser\\Node\\Expr\\Closure' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php', + 'PhpParser\\Node\\Expr\\ClosureUse' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php', + 'PhpParser\\Node\\Expr\\ConstFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php', + 'PhpParser\\Node\\Expr\\Empty_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php', + 'PhpParser\\Node\\Expr\\Error' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php', + 'PhpParser\\Node\\Expr\\ErrorSuppress' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php', + 'PhpParser\\Node\\Expr\\Eval_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php', + 'PhpParser\\Node\\Expr\\Exit_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php', + 'PhpParser\\Node\\Expr\\FuncCall' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php', + 'PhpParser\\Node\\Expr\\Include_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php', + 'PhpParser\\Node\\Expr\\Instanceof_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php', + 'PhpParser\\Node\\Expr\\Isset_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php', + 'PhpParser\\Node\\Expr\\List_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php', + 'PhpParser\\Node\\Expr\\Match_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php', + 'PhpParser\\Node\\Expr\\MethodCall' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php', + 'PhpParser\\Node\\Expr\\New_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php', + 'PhpParser\\Node\\Expr\\NullsafeMethodCall' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php', + 'PhpParser\\Node\\Expr\\NullsafePropertyFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php', + 'PhpParser\\Node\\Expr\\PostDec' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php', + 'PhpParser\\Node\\Expr\\PostInc' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php', + 'PhpParser\\Node\\Expr\\PreDec' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php', + 'PhpParser\\Node\\Expr\\PreInc' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php', + 'PhpParser\\Node\\Expr\\Print_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php', + 'PhpParser\\Node\\Expr\\PropertyFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php', + 'PhpParser\\Node\\Expr\\ShellExec' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php', + 'PhpParser\\Node\\Expr\\StaticCall' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php', + 'PhpParser\\Node\\Expr\\StaticPropertyFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php', + 'PhpParser\\Node\\Expr\\Ternary' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php', + 'PhpParser\\Node\\Expr\\Throw_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php', + 'PhpParser\\Node\\Expr\\UnaryMinus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php', + 'PhpParser\\Node\\Expr\\UnaryPlus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php', + 'PhpParser\\Node\\Expr\\Variable' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php', + 'PhpParser\\Node\\Expr\\YieldFrom' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php', + 'PhpParser\\Node\\Expr\\Yield_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php', + 'PhpParser\\Node\\FunctionLike' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php', + 'PhpParser\\Node\\Identifier' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Identifier.php', + 'PhpParser\\Node\\MatchArm' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/MatchArm.php', + 'PhpParser\\Node\\Name' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Name.php', + 'PhpParser\\Node\\Name\\FullyQualified' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php', + 'PhpParser\\Node\\Name\\Relative' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php', + 'PhpParser\\Node\\NullableType' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/NullableType.php', + 'PhpParser\\Node\\Param' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Param.php', + 'PhpParser\\Node\\Scalar' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar.php', + 'PhpParser\\Node\\Scalar\\DNumber' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php', + 'PhpParser\\Node\\Scalar\\Encapsed' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php', + 'PhpParser\\Node\\Scalar\\EncapsedStringPart' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php', + 'PhpParser\\Node\\Scalar\\LNumber' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php', + 'PhpParser\\Node\\Scalar\\MagicConst' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Class_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Dir' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\File' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Function_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Line' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Method' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Namespace_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', + 'PhpParser\\Node\\Scalar\\MagicConst\\Trait_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', + 'PhpParser\\Node\\Scalar\\String_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php', + 'PhpParser\\Node\\Stmt' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt.php', + 'PhpParser\\Node\\Stmt\\Break_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php', + 'PhpParser\\Node\\Stmt\\Case_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php', + 'PhpParser\\Node\\Stmt\\Catch_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php', + 'PhpParser\\Node\\Stmt\\ClassConst' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php', + 'PhpParser\\Node\\Stmt\\ClassLike' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php', + 'PhpParser\\Node\\Stmt\\ClassMethod' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php', + 'PhpParser\\Node\\Stmt\\Class_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php', + 'PhpParser\\Node\\Stmt\\Const_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php', + 'PhpParser\\Node\\Stmt\\Continue_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php', + 'PhpParser\\Node\\Stmt\\DeclareDeclare' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php', + 'PhpParser\\Node\\Stmt\\Declare_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php', + 'PhpParser\\Node\\Stmt\\Do_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php', + 'PhpParser\\Node\\Stmt\\Echo_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php', + 'PhpParser\\Node\\Stmt\\ElseIf_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php', + 'PhpParser\\Node\\Stmt\\Else_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php', + 'PhpParser\\Node\\Stmt\\Expression' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php', + 'PhpParser\\Node\\Stmt\\Finally_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php', + 'PhpParser\\Node\\Stmt\\For_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php', + 'PhpParser\\Node\\Stmt\\Foreach_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php', + 'PhpParser\\Node\\Stmt\\Function_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php', + 'PhpParser\\Node\\Stmt\\Global_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php', + 'PhpParser\\Node\\Stmt\\Goto_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php', + 'PhpParser\\Node\\Stmt\\GroupUse' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php', + 'PhpParser\\Node\\Stmt\\HaltCompiler' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php', + 'PhpParser\\Node\\Stmt\\If_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php', + 'PhpParser\\Node\\Stmt\\InlineHTML' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php', + 'PhpParser\\Node\\Stmt\\Interface_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php', + 'PhpParser\\Node\\Stmt\\Label' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php', + 'PhpParser\\Node\\Stmt\\Namespace_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php', + 'PhpParser\\Node\\Stmt\\Nop' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php', + 'PhpParser\\Node\\Stmt\\Property' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php', + 'PhpParser\\Node\\Stmt\\PropertyProperty' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php', + 'PhpParser\\Node\\Stmt\\Return_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php', + 'PhpParser\\Node\\Stmt\\StaticVar' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php', + 'PhpParser\\Node\\Stmt\\Static_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php', + 'PhpParser\\Node\\Stmt\\Switch_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php', + 'PhpParser\\Node\\Stmt\\Throw_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php', + 'PhpParser\\Node\\Stmt\\TraitUse' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php', + 'PhpParser\\Node\\Stmt\\TraitUseAdaptation' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', + 'PhpParser\\Node\\Stmt\\TraitUseAdaptation\\Alias' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', + 'PhpParser\\Node\\Stmt\\TraitUseAdaptation\\Precedence' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', + 'PhpParser\\Node\\Stmt\\Trait_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php', + 'PhpParser\\Node\\Stmt\\TryCatch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php', + 'PhpParser\\Node\\Stmt\\Unset_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php', + 'PhpParser\\Node\\Stmt\\UseUse' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php', + 'PhpParser\\Node\\Stmt\\Use_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php', + 'PhpParser\\Node\\Stmt\\While_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php', + 'PhpParser\\Node\\UnionType' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/UnionType.php', + 'PhpParser\\Node\\VarLikeIdentifier' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php', + 'PhpParser\\Parser' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser.php', + 'PhpParser\\ParserAbstract' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/ParserAbstract.php', + 'PhpParser\\ParserFactory' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/ParserFactory.php', + 'PhpParser\\Parser\\Multiple' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser/Multiple.php', + 'PhpParser\\Parser\\Php5' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser/Php5.php', + 'PhpParser\\Parser\\Php7' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser/Php7.php', + 'PhpParser\\Parser\\Tokens' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser/Tokens.php', + 'PhpParser\\PrettyPrinterAbstract' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php', + 'PhpParser\\PrettyPrinter\\Standard' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php', + 'Psalm\\Aliases' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Aliases.php', + 'Psalm\\CodeLocation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/CodeLocation.php', + 'Psalm\\CodeLocation\\DocblockTypeLocation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/CodeLocation/DocblockTypeLocation.php', + 'Psalm\\CodeLocation\\ParseErrorLocation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/CodeLocation/ParseErrorLocation.php', + 'Psalm\\CodeLocation\\Raw' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/CodeLocation/Raw.php', + 'Psalm\\Codebase' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Codebase.php', + 'Psalm\\Config' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Config.php', + 'Psalm\\Config\\Creator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Config/Creator.php', + 'Psalm\\Config\\ErrorLevelFileFilter' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Config/ErrorLevelFileFilter.php', + 'Psalm\\Config\\FileFilter' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Config/FileFilter.php', + 'Psalm\\Config\\IssueHandler' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Config/IssueHandler.php', + 'Psalm\\Config\\ProjectFileFilter' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Config/ProjectFileFilter.php', + 'Psalm\\Config\\TaintAnalysisFileFilter' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Config/TaintAnalysisFileFilter.php', + 'Psalm\\Context' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Context.php', + 'Psalm\\DocComment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/DocComment.php', + 'Psalm\\ErrorBaseline' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/ErrorBaseline.php', + 'Psalm\\Exception\\CircularReferenceException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/CircularReferenceException.php', + 'Psalm\\Exception\\CodeException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/CodeException.php', + 'Psalm\\Exception\\ComplicatedExpressionException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/ComplicatedExpressionException.php', + 'Psalm\\Exception\\ConfigCreationException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/ConfigCreationException.php', + 'Psalm\\Exception\\ConfigException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/ConfigException.php', + 'Psalm\\Exception\\DocblockParseException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/DocblockParseException.php', + 'Psalm\\Exception\\FileIncludeException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/FileIncludeException.php', + 'Psalm\\Exception\\IncorrectDocblockException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/IncorrectDocblockException.php', + 'Psalm\\Exception\\InvalidClasslikeOverrideException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/InvalidClasslikeOverrideException.php', + 'Psalm\\Exception\\InvalidMethodOverrideException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/InvalidMethodOverrideException.php', + 'Psalm\\Exception\\RefactorException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/RefactorException.php', + 'Psalm\\Exception\\ScopeAnalysisException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/ScopeAnalysisException.php', + 'Psalm\\Exception\\TypeParseTreeException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/TypeParseTreeException.php', + 'Psalm\\Exception\\UnanalyzedFileException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/UnanalyzedFileException.php', + 'Psalm\\Exception\\UnpopulatedClasslikeException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/UnpopulatedClasslikeException.php', + 'Psalm\\Exception\\UnpreparedAnalysisException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/UnpreparedAnalysisException.php', + 'Psalm\\Exception\\UnsupportedIssueToFixException' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Exception/UnsupportedIssueToFixException.php', + 'Psalm\\FileBasedPluginAdapter' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/FileBasedPluginAdapter.php', + 'Psalm\\FileManipulation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/FileManipulation.php', + 'Psalm\\FileSource' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/FileSource.php', + 'Psalm\\Internal\\Analyzer\\AlgebraAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/AlgebraAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\CanAlias' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/CanAlias.php', + 'Psalm\\Internal\\Analyzer\\ClassAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\ClassLikeAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\ClosureAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\CommentAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/CommentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\DataFlowNodeData' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/DataFlowNodeData.php', + 'Psalm\\Internal\\Analyzer\\FileAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\FunctionAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\FunctionLikeAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\FunctionLike\\ReturnTypeAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\FunctionLike\\ReturnTypeCollector' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php', + 'Psalm\\Internal\\Analyzer\\InterfaceAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\IssueData' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/IssueData.php', + 'Psalm\\Internal\\Analyzer\\MethodAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/MethodAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\MethodComparator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/MethodComparator.php', + 'Psalm\\Internal\\Analyzer\\NamespaceAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\ProjectAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\ScopeAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\SourceAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/SourceAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\StatementsAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\DoAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\ForAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\ForeachAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\IfAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\LoopAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\SwitchAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\SwitchCaseAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\TryAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Block\\WhileAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/WhileAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\BreakAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\ContinueAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\EchoAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\ExpressionAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\ArrayAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\AssertionFinder' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\AssignmentAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Assignment\\ArrayAssignmentAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Assignment\\InstancePropertyAssignmentAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Assignment\\StaticPropertyAssignmentAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/StaticPropertyAssignmentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOpAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\AndAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\CoalesceAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\ConcatAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\NonComparisonOpAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonComparisonOpAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\NonDivArithmeticOpAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonDivArithmeticOpAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BinaryOp\\OrAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BitwiseNotAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\BooleanNotAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\CallAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\ArgumentAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\ArgumentMapPopulator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\ArgumentsAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\ArrayFunctionArgumentsAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\ClassTemplateParamCollector' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\FunctionCallAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\MethodCallAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\AtomicCallContext' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicCallContext.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\AtomicMethodCallAnalysisResult' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\AtomicMethodCallAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\MethodCallProhibitionAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallProhibitionAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\MethodCallPurityAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\MethodCallReturnTypeFetcher' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\MethodVisibilityAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodVisibilityAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\Method\\MissingMethodCallHandler' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\NewAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Call\\StaticCallAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\CastAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\CloneAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\EmptyAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\EncapsulatedStringAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\EvalAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\ExitAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\ExpressionIdentifier' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ExpressionIdentifier.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\ArrayFetchAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\AtomicPropertyFetchAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\ClassConstFetchAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\ConstFetchAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\InstancePropertyFetchAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\StaticPropertyFetchAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/StaticPropertyFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\Fetch\\VariableFetchAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\IncDecExpressionAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\IncludeAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\InstanceofAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/InstanceofAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\IssetAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\MagicConstAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\MatchAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\NullsafeAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/NullsafeAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\PrintAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\SimpleTypeInferer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\TernaryAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\UnaryPlusMinusAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\YieldAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\Expression\\YieldFromAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\GlobalAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\ReturnAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\StaticAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\ThrowAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\UnsetAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\Statements\\UnusedAssignmentRemover' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php', + 'Psalm\\Internal\\Analyzer\\TraitAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/TraitAnalyzer.php', + 'Psalm\\Internal\\Analyzer\\TypeAnalyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Analyzer/TypeAnalyzer.php', + 'Psalm\\Internal\\Clause' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Clause.php', + 'Psalm\\Internal\\Codebase\\Analyzer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php', + 'Psalm\\Internal\\Codebase\\ClassLikes' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/ClassLikes.php', + 'Psalm\\Internal\\Codebase\\ConstantTypeResolver' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/ConstantTypeResolver.php', + 'Psalm\\Internal\\Codebase\\DataFlowGraph' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/DataFlowGraph.php', + 'Psalm\\Internal\\Codebase\\Functions' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/Functions.php', + 'Psalm\\Internal\\Codebase\\InternalCallMapHandler' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/InternalCallMapHandler.php', + 'Psalm\\Internal\\Codebase\\Methods' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/Methods.php', + 'Psalm\\Internal\\Codebase\\Populator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/Populator.php', + 'Psalm\\Internal\\Codebase\\Properties' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/Properties.php', + 'Psalm\\Internal\\Codebase\\PropertyMap' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/PropertyMap.php', + 'Psalm\\Internal\\Codebase\\ReferenceMapGenerator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/ReferenceMapGenerator.php', + 'Psalm\\Internal\\Codebase\\Reflection' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/Reflection.php', + 'Psalm\\Internal\\Codebase\\Scanner' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/Scanner.php', + 'Psalm\\Internal\\Codebase\\TaintFlowGraph' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/TaintFlowGraph.php', + 'Psalm\\Internal\\Codebase\\VariableUseGraph' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Codebase/VariableUseGraph.php', + 'Psalm\\Internal\\Composer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Composer.php', + 'Psalm\\Internal\\DataFlow\\DataFlowNode' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/DataFlow/DataFlowNode.php', + 'Psalm\\Internal\\DataFlow\\Path' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/DataFlow/Path.php', + 'Psalm\\Internal\\DataFlow\\TaintSink' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/DataFlow/TaintSink.php', + 'Psalm\\Internal\\DataFlow\\TaintSource' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/DataFlow/TaintSource.php', + 'Psalm\\Internal\\Diff\\AstDiffer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Diff/AstDiffer.php', + 'Psalm\\Internal\\Diff\\ClassStatementsDiffer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Diff/ClassStatementsDiffer.php', + 'Psalm\\Internal\\Diff\\DiffElem' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Diff/DiffElem.php', + 'Psalm\\Internal\\Diff\\FileDiffer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Diff/FileDiffer.php', + 'Psalm\\Internal\\Diff\\FileStatementsDiffer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Diff/FileStatementsDiffer.php', + 'Psalm\\Internal\\Diff\\NamespaceStatementsDiffer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php', + 'Psalm\\Internal\\ExecutionEnvironment\\BuildInfoCollector' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/BuildInfoCollector.php', + 'Psalm\\Internal\\ExecutionEnvironment\\GitInfoCollector' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php', + 'Psalm\\Internal\\ExecutionEnvironment\\SystemCommandExecutor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/SystemCommandExecutor.php', + 'Psalm\\Internal\\FileManipulation\\ClassDocblockManipulator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/FileManipulation/ClassDocblockManipulator.php', + 'Psalm\\Internal\\FileManipulation\\CodeMigration' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/FileManipulation/CodeMigration.php', + 'Psalm\\Internal\\FileManipulation\\FileManipulationBuffer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/FileManipulation/FileManipulationBuffer.php', + 'Psalm\\Internal\\FileManipulation\\FunctionDocblockManipulator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php', + 'Psalm\\Internal\\FileManipulation\\PropertyDocblockManipulator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php', + 'Psalm\\Internal\\Fork\\ForkMessage' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Fork/ForkMessage.php', + 'Psalm\\Internal\\Fork\\ForkProcessDoneMessage' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Fork/ForkProcessDoneMessage.php', + 'Psalm\\Internal\\Fork\\ForkProcessErrorMessage' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php', + 'Psalm\\Internal\\Fork\\ForkTaskDoneMessage' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php', + 'Psalm\\Internal\\Fork\\Pool' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php', + 'Psalm\\Internal\\Fork\\PsalmRestarter' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Fork/PsalmRestarter.php', + 'Psalm\\Internal\\IncludeCollector' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/IncludeCollector.php', + 'Psalm\\Internal\\Json\\Json' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Json/Json.php', + 'Psalm\\Internal\\LanguageServer\\ClientHandler' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/ClientHandler.php', + 'Psalm\\Internal\\LanguageServer\\Client\\TextDocument' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/Client/TextDocument.php', + 'Psalm\\Internal\\LanguageServer\\EmitterInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/EmitterInterface.php', + 'Psalm\\Internal\\LanguageServer\\EmitterTrait' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/EmitterTrait.php', + 'Psalm\\Internal\\LanguageServer\\IdGenerator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/IdGenerator.php', + 'Psalm\\Internal\\LanguageServer\\LanguageClient' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/LanguageClient.php', + 'Psalm\\Internal\\LanguageServer\\LanguageServer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/LanguageServer.php', + 'Psalm\\Internal\\LanguageServer\\Message' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/Message.php', + 'Psalm\\Internal\\LanguageServer\\ProtocolReader' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolReader.php', + 'Psalm\\Internal\\LanguageServer\\ProtocolStreamReader' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolStreamReader.php', + 'Psalm\\Internal\\LanguageServer\\ProtocolStreamWriter' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolStreamWriter.php', + 'Psalm\\Internal\\LanguageServer\\ProtocolWriter' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolWriter.php', + 'Psalm\\Internal\\LanguageServer\\Server\\TextDocument' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/LanguageServer/Server/TextDocument.php', + 'Psalm\\Internal\\MethodIdentifier' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/MethodIdentifier.php', + 'Psalm\\Internal\\PhpTraverser\\CustomTraverser' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpTraverser/CustomTraverser.php', + 'Psalm\\Internal\\PhpVisitor\\AssignmentMapVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/AssignmentMapVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\CheckTrivialExprVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\CloningVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/CloningVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\ConditionCloningVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\NodeCleanerVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/NodeCleanerVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\NodeCounterVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/NodeCounterVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\OffsetShifterVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/OffsetShifterVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\ParamReplacementVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ParamReplacementVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\PartialParserVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\ReflectorVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\ShortClosureVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ShortClosureVisitor.php', + 'Psalm\\Internal\\PhpVisitor\\SimpleNameResolver' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php', + 'Psalm\\Internal\\PhpVisitor\\TraitFinder' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/TraitFinder.php', + 'Psalm\\Internal\\PhpVisitor\\TypeMappingVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php', + 'Psalm\\Internal\\PluginManager\\Command\\DisableCommand' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/DisableCommand.php', + 'Psalm\\Internal\\PluginManager\\Command\\EnableCommand' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/EnableCommand.php', + 'Psalm\\Internal\\PluginManager\\Command\\ShowCommand' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/ShowCommand.php', + 'Psalm\\Internal\\PluginManager\\ComposerLock' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PluginManager/ComposerLock.php', + 'Psalm\\Internal\\PluginManager\\ConfigFile' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PluginManager/ConfigFile.php', + 'Psalm\\Internal\\PluginManager\\PluginList' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PluginManager/PluginList.php', + 'Psalm\\Internal\\PluginManager\\PluginListFactory' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/PluginManager/PluginListFactory.php', + 'Psalm\\Internal\\Provider\\ClassLikeStorageCacheProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php', + 'Psalm\\Internal\\Provider\\ClassLikeStorageProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php', + 'Psalm\\Internal\\Provider\\FileProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/FileProvider.php', + 'Psalm\\Internal\\Provider\\FileReferenceCacheProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php', + 'Psalm\\Internal\\Provider\\FileReferenceProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/FileReferenceProvider.php', + 'Psalm\\Internal\\Provider\\FileStorageCacheProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageCacheProvider.php', + 'Psalm\\Internal\\Provider\\FileStorageProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageProvider.php', + 'Psalm\\Internal\\Provider\\FunctionExistenceProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/FunctionExistenceProvider.php', + 'Psalm\\Internal\\Provider\\FunctionParamsProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/FunctionParamsProvider.php', + 'Psalm\\Internal\\Provider\\FunctionReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\MethodExistenceProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/MethodExistenceProvider.php', + 'Psalm\\Internal\\Provider\\MethodParamsProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/MethodParamsProvider.php', + 'Psalm\\Internal\\Provider\\MethodReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\MethodVisibilityProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/MethodVisibilityProvider.php', + 'Psalm\\Internal\\Provider\\NodeDataProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/NodeDataProvider.php', + 'Psalm\\Internal\\Provider\\ParserCacheProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ParserCacheProvider.php', + 'Psalm\\Internal\\Provider\\ProjectCacheProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ProjectCacheProvider.php', + 'Psalm\\Internal\\Provider\\PropertyExistenceProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/PropertyExistenceProvider.php', + 'Psalm\\Internal\\Provider\\PropertyTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/PropertyTypeProvider.php', + 'Psalm\\Internal\\Provider\\PropertyVisibilityProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php', + 'Psalm\\Internal\\Provider\\Providers' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/Providers.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayChunkReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayChunkReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayColumnReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayFillReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayFilterReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayMapReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayMergeReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayPadReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPadReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayPointerAdjustmentReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayPopReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayRandReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayReduceReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayReverseReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArraySliceReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySliceReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayUniqueReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ArrayValuesReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayValuesReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ClosureFromCallableReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ClosureFromCallableReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\DomNodeAppendChild' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/DomNodeAppendChild.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ExplodeReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\FilterVarReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\FirstArgStringReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/FirstArgStringReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\GetClassMethodsReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/GetClassMethodsReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\GetObjectVarsReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\HexdecReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\IteratorToArrayReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/IteratorToArrayReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\MktimeReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\ParseUrlReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\PdoStatementReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\PdoStatementSetFetchMode' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\SimpleXmlElementAsXml' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/SimpleXmlElementAsXml.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\StrReplaceReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\ReturnTypeProvider\\VersionCompareReturnTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/VersionCompareReturnTypeProvider.php', + 'Psalm\\Internal\\Provider\\StatementsProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Provider/StatementsProvider.php', + 'Psalm\\Internal\\ReferenceConstraint' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/ReferenceConstraint.php', + 'Psalm\\Internal\\RuntimeCaches' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/RuntimeCaches.php', + 'Psalm\\Internal\\Scanner\\ClassLikeDocblockComment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/ClassLikeDocblockComment.php', + 'Psalm\\Internal\\Scanner\\DocblockParser' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/DocblockParser.php', + 'Psalm\\Internal\\Scanner\\FileScanner' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/FileScanner.php', + 'Psalm\\Internal\\Scanner\\FunctionDocblockComment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/FunctionDocblockComment.php', + 'Psalm\\Internal\\Scanner\\ParsedDocblock' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/ParsedDocblock.php', + 'Psalm\\Internal\\Scanner\\PhpStormMetaScanner' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstantComponent' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\ArrayOffsetFetch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\ArrayValue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\ClassConstant' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\Constant' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\KeyValuePair' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\ScalarValue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedAdditionOp' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedBinaryOp' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBinaryOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedBitwiseOr' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseOr.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedConcatOp' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedConcatOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedDivisionOp' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedDivisionOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedMultiplicationOp' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedMultiplicationOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedSubtractionOp' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedSubtractionOp.php', + 'Psalm\\Internal\\Scanner\\UnresolvedConstant\\UnresolvedTernary' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedTernary.php', + 'Psalm\\Internal\\Scanner\\VarDocblockComment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scanner/VarDocblockComment.php', + 'Psalm\\Internal\\Scope\\CaseScope' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scope/CaseScope.php', + 'Psalm\\Internal\\Scope\\FinallyScope' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scope/FinallyScope.php', + 'Psalm\\Internal\\Scope\\IfConditionalScope' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scope/IfConditionalScope.php', + 'Psalm\\Internal\\Scope\\IfScope' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scope/IfScope.php', + 'Psalm\\Internal\\Scope\\LoopScope' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scope/LoopScope.php', + 'Psalm\\Internal\\Scope\\SwitchScope' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Scope/SwitchScope.php', + 'Psalm\\Internal\\Stubs\\Generator\\ClassLikeStubGenerator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php', + 'Psalm\\Internal\\Stubs\\Generator\\StubsGenerator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php', + 'Psalm\\Internal\\TypeVisitor\\ContainsClassLikeVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php', + 'Psalm\\Internal\\TypeVisitor\\FromDocblockSetter' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/TypeVisitor/FromDocblockSetter.php', + 'Psalm\\Internal\\TypeVisitor\\TemplateTypeCollector' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php', + 'Psalm\\Internal\\TypeVisitor\\TypeChecker' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TypeChecker.php', + 'Psalm\\Internal\\TypeVisitor\\TypeScanner' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TypeScanner.php', + 'Psalm\\Internal\\Type\\ArrayType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ArrayType.php', + 'Psalm\\Internal\\Type\\AssertionReconciler' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/AssertionReconciler.php', + 'Psalm\\Internal\\Type\\Comparator\\ArrayTypeComparator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\AtomicTypeComparator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\CallableTypeComparator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\ClassStringComparator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ClassStringComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\GenericTypeComparator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\KeyedArrayComparator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\ObjectComparator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ObjectComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\ScalarTypeComparator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php', + 'Psalm\\Internal\\Type\\Comparator\\TypeComparisonResult' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php', + 'Psalm\\Internal\\Type\\Comparator\\UnionTypeComparator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php', + 'Psalm\\Internal\\Type\\NegatedAssertionReconciler' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/NegatedAssertionReconciler.php', + 'Psalm\\Internal\\Type\\ParseTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree.php', + 'Psalm\\Internal\\Type\\ParseTreeCreator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTreeCreator.php', + 'Psalm\\Internal\\Type\\ParseTree\\CallableParamTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\CallableTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\CallableWithReturnTypeTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableWithReturnTypeTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\ConditionalTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/ConditionalTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\EncapsulationTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/EncapsulationTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\GenericTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/GenericTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\IndexedAccessTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/IndexedAccessTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\IntersectionTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/IntersectionTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\KeyedArrayPropertyTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/KeyedArrayPropertyTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\KeyedArrayTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/KeyedArrayTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\MethodParamTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodParamTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\MethodTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\MethodWithReturnTypeTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodWithReturnTypeTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\NullableTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/NullableTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\Root' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/Root.php', + 'Psalm\\Internal\\Type\\ParseTree\\TemplateAsTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/TemplateAsTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\TemplateIsTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\UnionTree' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/UnionTree.php', + 'Psalm\\Internal\\Type\\ParseTree\\Value' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/Value.php', + 'Psalm\\Internal\\Type\\SimpleAssertionReconciler' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/SimpleAssertionReconciler.php', + 'Psalm\\Internal\\Type\\SimpleNegatedAssertionReconciler' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php', + 'Psalm\\Internal\\Type\\TemplateResult' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/TemplateResult.php', + 'Psalm\\Internal\\Type\\TypeAlias' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias.php', + 'Psalm\\Internal\\Type\\TypeAlias\\ClassTypeAlias' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/ClassTypeAlias.php', + 'Psalm\\Internal\\Type\\TypeAlias\\InlineTypeAlias' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php', + 'Psalm\\Internal\\Type\\TypeAlias\\LinkableTypeAlias' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php', + 'Psalm\\Internal\\Type\\TypeCombination' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/TypeCombination.php', + 'Psalm\\Internal\\Type\\TypeExpander' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/TypeExpander.php', + 'Psalm\\Internal\\Type\\TypeParser' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/TypeParser.php', + 'Psalm\\Internal\\Type\\TypeTokenizer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/TypeTokenizer.php', + 'Psalm\\Internal\\Type\\UnionTemplateHandler' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Internal/Type/UnionTemplateHandler.php', + 'Psalm\\IssueBuffer' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/IssueBuffer.php', + 'Psalm\\Issue\\AbstractInstantiation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/AbstractInstantiation.php', + 'Psalm\\Issue\\AbstractMethodCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/AbstractMethodCall.php', + 'Psalm\\Issue\\ArgumentIssue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ArgumentIssue.php', + 'Psalm\\Issue\\ArgumentTypeCoercion' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ArgumentTypeCoercion.php', + 'Psalm\\Issue\\AssignmentToVoid' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/AssignmentToVoid.php', + 'Psalm\\Issue\\CircularReference' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/CircularReference.php', + 'Psalm\\Issue\\ClassIssue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ClassIssue.php', + 'Psalm\\Issue\\CodeIssue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/CodeIssue.php', + 'Psalm\\Issue\\ConflictingReferenceConstraint' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ConflictingReferenceConstraint.php', + 'Psalm\\Issue\\ConstructorSignatureMismatch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ConstructorSignatureMismatch.php', + 'Psalm\\Issue\\ContinueOutsideLoop' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ContinueOutsideLoop.php', + 'Psalm\\Issue\\DeprecatedClass' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DeprecatedClass.php', + 'Psalm\\Issue\\DeprecatedConstant' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DeprecatedConstant.php', + 'Psalm\\Issue\\DeprecatedFunction' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DeprecatedFunction.php', + 'Psalm\\Issue\\DeprecatedInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DeprecatedInterface.php', + 'Psalm\\Issue\\DeprecatedMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DeprecatedMethod.php', + 'Psalm\\Issue\\DeprecatedProperty' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DeprecatedProperty.php', + 'Psalm\\Issue\\DeprecatedTrait' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DeprecatedTrait.php', + 'Psalm\\Issue\\DocblockTypeContradiction' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DocblockTypeContradiction.php', + 'Psalm\\Issue\\DuplicateArrayKey' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DuplicateArrayKey.php', + 'Psalm\\Issue\\DuplicateClass' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DuplicateClass.php', + 'Psalm\\Issue\\DuplicateFunction' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DuplicateFunction.php', + 'Psalm\\Issue\\DuplicateMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DuplicateMethod.php', + 'Psalm\\Issue\\DuplicateParam' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/DuplicateParam.php', + 'Psalm\\Issue\\EmptyArrayAccess' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/EmptyArrayAccess.php', + 'Psalm\\Issue\\ExtensionRequirementViolation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ExtensionRequirementViolation.php', + 'Psalm\\Issue\\FalsableReturnStatement' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/FalsableReturnStatement.php', + 'Psalm\\Issue\\FalseOperand' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/FalseOperand.php', + 'Psalm\\Issue\\ForbiddenCode' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ForbiddenCode.php', + 'Psalm\\Issue\\ForbiddenEcho' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ForbiddenEcho.php', + 'Psalm\\Issue\\FunctionIssue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/FunctionIssue.php', + 'Psalm\\Issue\\ImplementationRequirementViolation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImplementationRequirementViolation.php', + 'Psalm\\Issue\\ImplementedParamTypeMismatch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImplementedParamTypeMismatch.php', + 'Psalm\\Issue\\ImplementedReturnTypeMismatch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImplementedReturnTypeMismatch.php', + 'Psalm\\Issue\\ImplicitToStringCast' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImplicitToStringCast.php', + 'Psalm\\Issue\\ImpureByReferenceAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImpureByReferenceAssignment.php', + 'Psalm\\Issue\\ImpureFunctionCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImpureFunctionCall.php', + 'Psalm\\Issue\\ImpureMethodCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImpureMethodCall.php', + 'Psalm\\Issue\\ImpurePropertyAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImpurePropertyAssignment.php', + 'Psalm\\Issue\\ImpurePropertyFetch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImpurePropertyFetch.php', + 'Psalm\\Issue\\ImpureStaticProperty' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImpureStaticProperty.php', + 'Psalm\\Issue\\ImpureStaticVariable' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImpureStaticVariable.php', + 'Psalm\\Issue\\ImpureVariable' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ImpureVariable.php', + 'Psalm\\Issue\\InaccessibleClassConstant' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InaccessibleClassConstant.php', + 'Psalm\\Issue\\InaccessibleMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InaccessibleMethod.php', + 'Psalm\\Issue\\InaccessibleProperty' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InaccessibleProperty.php', + 'Psalm\\Issue\\InterfaceInstantiation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InterfaceInstantiation.php', + 'Psalm\\Issue\\InternalClass' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InternalClass.php', + 'Psalm\\Issue\\InternalMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InternalMethod.php', + 'Psalm\\Issue\\InternalProperty' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InternalProperty.php', + 'Psalm\\Issue\\InvalidArgument' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidArgument.php', + 'Psalm\\Issue\\InvalidArrayAccess' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidArrayAccess.php', + 'Psalm\\Issue\\InvalidArrayAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidArrayAssignment.php', + 'Psalm\\Issue\\InvalidArrayOffset' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidArrayOffset.php', + 'Psalm\\Issue\\InvalidCast' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidCast.php', + 'Psalm\\Issue\\InvalidCatch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidCatch.php', + 'Psalm\\Issue\\InvalidClass' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidClass.php', + 'Psalm\\Issue\\InvalidClone' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidClone.php', + 'Psalm\\Issue\\InvalidDocblock' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidDocblock.php', + 'Psalm\\Issue\\InvalidDocblockParamName' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidDocblockParamName.php', + 'Psalm\\Issue\\InvalidExtendClass' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidExtendClass.php', + 'Psalm\\Issue\\InvalidFalsableReturnType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidFalsableReturnType.php', + 'Psalm\\Issue\\InvalidFunctionCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidFunctionCall.php', + 'Psalm\\Issue\\InvalidGlobal' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidGlobal.php', + 'Psalm\\Issue\\InvalidIterator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidIterator.php', + 'Psalm\\Issue\\InvalidLiteralArgument' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidLiteralArgument.php', + 'Psalm\\Issue\\InvalidMethodCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidMethodCall.php', + 'Psalm\\Issue\\InvalidNamedArgument' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidNamedArgument.php', + 'Psalm\\Issue\\InvalidNullableReturnType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidNullableReturnType.php', + 'Psalm\\Issue\\InvalidOperand' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidOperand.php', + 'Psalm\\Issue\\InvalidParamDefault' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidParamDefault.php', + 'Psalm\\Issue\\InvalidParent' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidParent.php', + 'Psalm\\Issue\\InvalidPassByReference' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidPassByReference.php', + 'Psalm\\Issue\\InvalidPropertyAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidPropertyAssignment.php', + 'Psalm\\Issue\\InvalidPropertyAssignmentValue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidPropertyAssignmentValue.php', + 'Psalm\\Issue\\InvalidPropertyFetch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidPropertyFetch.php', + 'Psalm\\Issue\\InvalidReturnStatement' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidReturnStatement.php', + 'Psalm\\Issue\\InvalidReturnType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidReturnType.php', + 'Psalm\\Issue\\InvalidScalarArgument' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidScalarArgument.php', + 'Psalm\\Issue\\InvalidScope' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidScope.php', + 'Psalm\\Issue\\InvalidStaticInvocation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidStaticInvocation.php', + 'Psalm\\Issue\\InvalidStringClass' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidStringClass.php', + 'Psalm\\Issue\\InvalidTemplateParam' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidTemplateParam.php', + 'Psalm\\Issue\\InvalidThrow' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidThrow.php', + 'Psalm\\Issue\\InvalidToString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidToString.php', + 'Psalm\\Issue\\InvalidTypeImport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/InvalidTypeImport.php', + 'Psalm\\Issue\\LessSpecificImplementedReturnType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/LessSpecificImplementedReturnType.php', + 'Psalm\\Issue\\LessSpecificReturnStatement' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/LessSpecificReturnStatement.php', + 'Psalm\\Issue\\LessSpecificReturnType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/LessSpecificReturnType.php', + 'Psalm\\Issue\\LoopInvalidation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/LoopInvalidation.php', + 'Psalm\\Issue\\MethodIssue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MethodIssue.php', + 'Psalm\\Issue\\MethodSignatureMismatch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MethodSignatureMismatch.php', + 'Psalm\\Issue\\MethodSignatureMustOmitReturnType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MethodSignatureMustOmitReturnType.php', + 'Psalm\\Issue\\MismatchingDocblockParamType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MismatchingDocblockParamType.php', + 'Psalm\\Issue\\MismatchingDocblockReturnType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MismatchingDocblockReturnType.php', + 'Psalm\\Issue\\MissingClosureParamType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingClosureParamType.php', + 'Psalm\\Issue\\MissingClosureReturnType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingClosureReturnType.php', + 'Psalm\\Issue\\MissingConstructor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingConstructor.php', + 'Psalm\\Issue\\MissingDependency' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingDependency.php', + 'Psalm\\Issue\\MissingDocblockType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingDocblockType.php', + 'Psalm\\Issue\\MissingFile' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingFile.php', + 'Psalm\\Issue\\MissingImmutableAnnotation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingImmutableAnnotation.php', + 'Psalm\\Issue\\MissingParamType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingParamType.php', + 'Psalm\\Issue\\MissingPropertyType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingPropertyType.php', + 'Psalm\\Issue\\MissingReturnType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingReturnType.php', + 'Psalm\\Issue\\MissingTemplateParam' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingTemplateParam.php', + 'Psalm\\Issue\\MissingThrowsDocblock' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MissingThrowsDocblock.php', + 'Psalm\\Issue\\MixedArgument' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedArgument.php', + 'Psalm\\Issue\\MixedArgumentTypeCoercion' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedArgumentTypeCoercion.php', + 'Psalm\\Issue\\MixedArrayAccess' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedArrayAccess.php', + 'Psalm\\Issue\\MixedArrayAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedArrayAssignment.php', + 'Psalm\\Issue\\MixedArrayOffset' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedArrayOffset.php', + 'Psalm\\Issue\\MixedArrayTypeCoercion' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedArrayTypeCoercion.php', + 'Psalm\\Issue\\MixedAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedAssignment.php', + 'Psalm\\Issue\\MixedClone' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedClone.php', + 'Psalm\\Issue\\MixedFunctionCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedFunctionCall.php', + 'Psalm\\Issue\\MixedInferredReturnType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedInferredReturnType.php', + 'Psalm\\Issue\\MixedMethodCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedMethodCall.php', + 'Psalm\\Issue\\MixedOperand' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedOperand.php', + 'Psalm\\Issue\\MixedPropertyAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedPropertyAssignment.php', + 'Psalm\\Issue\\MixedPropertyFetch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedPropertyFetch.php', + 'Psalm\\Issue\\MixedPropertyTypeCoercion' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedPropertyTypeCoercion.php', + 'Psalm\\Issue\\MixedReturnStatement' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedReturnStatement.php', + 'Psalm\\Issue\\MixedReturnTypeCoercion' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedReturnTypeCoercion.php', + 'Psalm\\Issue\\MixedStringOffsetAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MixedStringOffsetAssignment.php', + 'Psalm\\Issue\\MoreSpecificImplementedParamType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MoreSpecificImplementedParamType.php', + 'Psalm\\Issue\\MoreSpecificReturnType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MoreSpecificReturnType.php', + 'Psalm\\Issue\\MutableDependency' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/MutableDependency.php', + 'Psalm\\Issue\\NoInterfaceProperties' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NoInterfaceProperties.php', + 'Psalm\\Issue\\NoValue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NoValue.php', + 'Psalm\\Issue\\NonStaticSelfCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NonStaticSelfCall.php', + 'Psalm\\Issue\\NullArgument' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NullArgument.php', + 'Psalm\\Issue\\NullArrayAccess' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NullArrayAccess.php', + 'Psalm\\Issue\\NullArrayOffset' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NullArrayOffset.php', + 'Psalm\\Issue\\NullFunctionCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NullFunctionCall.php', + 'Psalm\\Issue\\NullIterator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NullIterator.php', + 'Psalm\\Issue\\NullOperand' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NullOperand.php', + 'Psalm\\Issue\\NullPropertyAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NullPropertyAssignment.php', + 'Psalm\\Issue\\NullPropertyFetch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NullPropertyFetch.php', + 'Psalm\\Issue\\NullReference' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NullReference.php', + 'Psalm\\Issue\\NullableReturnStatement' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/NullableReturnStatement.php', + 'Psalm\\Issue\\OverriddenMethodAccess' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/OverriddenMethodAccess.php', + 'Psalm\\Issue\\OverriddenPropertyAccess' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/OverriddenPropertyAccess.php', + 'Psalm\\Issue\\ParadoxicalCondition' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ParadoxicalCondition.php', + 'Psalm\\Issue\\ParamNameMismatch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ParamNameMismatch.php', + 'Psalm\\Issue\\ParentNotFound' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ParentNotFound.php', + 'Psalm\\Issue\\ParseError' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ParseError.php', + 'Psalm\\Issue\\PluginIssue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PluginIssue.php', + 'Psalm\\Issue\\PossibleRawObjectIteration' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossibleRawObjectIteration.php', + 'Psalm\\Issue\\PossiblyFalseArgument' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyFalseArgument.php', + 'Psalm\\Issue\\PossiblyFalseIterator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyFalseIterator.php', + 'Psalm\\Issue\\PossiblyFalseOperand' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyFalseOperand.php', + 'Psalm\\Issue\\PossiblyFalsePropertyAssignmentValue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyFalsePropertyAssignmentValue.php', + 'Psalm\\Issue\\PossiblyFalseReference' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyFalseReference.php', + 'Psalm\\Issue\\PossiblyInvalidArgument' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidArgument.php', + 'Psalm\\Issue\\PossiblyInvalidArrayAccess' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidArrayAccess.php', + 'Psalm\\Issue\\PossiblyInvalidArrayAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidArrayAssignment.php', + 'Psalm\\Issue\\PossiblyInvalidArrayOffset' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidArrayOffset.php', + 'Psalm\\Issue\\PossiblyInvalidCast' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidCast.php', + 'Psalm\\Issue\\PossiblyInvalidClone' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidClone.php', + 'Psalm\\Issue\\PossiblyInvalidFunctionCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidFunctionCall.php', + 'Psalm\\Issue\\PossiblyInvalidIterator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidIterator.php', + 'Psalm\\Issue\\PossiblyInvalidMethodCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidMethodCall.php', + 'Psalm\\Issue\\PossiblyInvalidOperand' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidOperand.php', + 'Psalm\\Issue\\PossiblyInvalidPropertyAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidPropertyAssignment.php', + 'Psalm\\Issue\\PossiblyInvalidPropertyAssignmentValue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidPropertyAssignmentValue.php', + 'Psalm\\Issue\\PossiblyInvalidPropertyFetch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyInvalidPropertyFetch.php', + 'Psalm\\Issue\\PossiblyNullArgument' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullArgument.php', + 'Psalm\\Issue\\PossiblyNullArrayAccess' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullArrayAccess.php', + 'Psalm\\Issue\\PossiblyNullArrayAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullArrayAssignment.php', + 'Psalm\\Issue\\PossiblyNullArrayOffset' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullArrayOffset.php', + 'Psalm\\Issue\\PossiblyNullFunctionCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullFunctionCall.php', + 'Psalm\\Issue\\PossiblyNullIterator' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullIterator.php', + 'Psalm\\Issue\\PossiblyNullOperand' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullOperand.php', + 'Psalm\\Issue\\PossiblyNullPropertyAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullPropertyAssignment.php', + 'Psalm\\Issue\\PossiblyNullPropertyAssignmentValue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullPropertyAssignmentValue.php', + 'Psalm\\Issue\\PossiblyNullPropertyFetch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullPropertyFetch.php', + 'Psalm\\Issue\\PossiblyNullReference' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyNullReference.php', + 'Psalm\\Issue\\PossiblyUndefinedArrayOffset' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedArrayOffset.php', + 'Psalm\\Issue\\PossiblyUndefinedGlobalVariable' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedGlobalVariable.php', + 'Psalm\\Issue\\PossiblyUndefinedIntArrayOffset' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedIntArrayOffset.php', + 'Psalm\\Issue\\PossiblyUndefinedMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedMethod.php', + 'Psalm\\Issue\\PossiblyUndefinedStringArrayOffset' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedStringArrayOffset.php', + 'Psalm\\Issue\\PossiblyUndefinedVariable' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyUndefinedVariable.php', + 'Psalm\\Issue\\PossiblyUnusedMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyUnusedMethod.php', + 'Psalm\\Issue\\PossiblyUnusedParam' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyUnusedParam.php', + 'Psalm\\Issue\\PossiblyUnusedProperty' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PossiblyUnusedProperty.php', + 'Psalm\\Issue\\PropertyIssue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PropertyIssue.php', + 'Psalm\\Issue\\PropertyNotSetInConstructor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PropertyNotSetInConstructor.php', + 'Psalm\\Issue\\PropertyTypeCoercion' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PropertyTypeCoercion.php', + 'Psalm\\Issue\\PsalmInternalError' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/PsalmInternalError.php', + 'Psalm\\Issue\\RawObjectIteration' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/RawObjectIteration.php', + 'Psalm\\Issue\\RedundantCondition' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/RedundantCondition.php', + 'Psalm\\Issue\\RedundantConditionGivenDocblockType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/RedundantConditionGivenDocblockType.php', + 'Psalm\\Issue\\RedundantIdentityWithTrue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/RedundantIdentityWithTrue.php', + 'Psalm\\Issue\\ReferenceConstraintViolation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ReferenceConstraintViolation.php', + 'Psalm\\Issue\\ReservedWord' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/ReservedWord.php', + 'Psalm\\Issue\\StringIncrement' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/StringIncrement.php', + 'Psalm\\Issue\\TaintedInput' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/TaintedInput.php', + 'Psalm\\Issue\\TooFewArguments' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/TooFewArguments.php', + 'Psalm\\Issue\\TooManyArguments' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/TooManyArguments.php', + 'Psalm\\Issue\\TooManyTemplateParams' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/TooManyTemplateParams.php', + 'Psalm\\Issue\\Trace' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/Trace.php', + 'Psalm\\Issue\\TraitMethodSignatureMismatch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/TraitMethodSignatureMismatch.php', + 'Psalm\\Issue\\TypeDoesNotContainNull' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/TypeDoesNotContainNull.php', + 'Psalm\\Issue\\TypeDoesNotContainType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/TypeDoesNotContainType.php', + 'Psalm\\Issue\\UncaughtThrowInGlobalScope' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UncaughtThrowInGlobalScope.php', + 'Psalm\\Issue\\UndefinedClass' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedClass.php', + 'Psalm\\Issue\\UndefinedConstant' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedConstant.php', + 'Psalm\\Issue\\UndefinedDocblockClass' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedDocblockClass.php', + 'Psalm\\Issue\\UndefinedFunction' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedFunction.php', + 'Psalm\\Issue\\UndefinedGlobalVariable' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedGlobalVariable.php', + 'Psalm\\Issue\\UndefinedInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedInterface.php', + 'Psalm\\Issue\\UndefinedInterfaceMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedInterfaceMethod.php', + 'Psalm\\Issue\\UndefinedMagicMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedMagicMethod.php', + 'Psalm\\Issue\\UndefinedMagicPropertyAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedMagicPropertyAssignment.php', + 'Psalm\\Issue\\UndefinedMagicPropertyFetch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedMagicPropertyFetch.php', + 'Psalm\\Issue\\UndefinedMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedMethod.php', + 'Psalm\\Issue\\UndefinedPropertyAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedPropertyAssignment.php', + 'Psalm\\Issue\\UndefinedPropertyFetch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedPropertyFetch.php', + 'Psalm\\Issue\\UndefinedThisPropertyAssignment' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedThisPropertyAssignment.php', + 'Psalm\\Issue\\UndefinedThisPropertyFetch' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedThisPropertyFetch.php', + 'Psalm\\Issue\\UndefinedTrace' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedTrace.php', + 'Psalm\\Issue\\UndefinedTrait' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedTrait.php', + 'Psalm\\Issue\\UndefinedVariable' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UndefinedVariable.php', + 'Psalm\\Issue\\UnevaluatedCode' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnevaluatedCode.php', + 'Psalm\\Issue\\UnhandledMatchCondition' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnhandledMatchCondition.php', + 'Psalm\\Issue\\UnimplementedAbstractMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnimplementedAbstractMethod.php', + 'Psalm\\Issue\\UnimplementedInterfaceMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnimplementedInterfaceMethod.php', + 'Psalm\\Issue\\UninitializedProperty' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UninitializedProperty.php', + 'Psalm\\Issue\\UnnecessaryVarAnnotation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnnecessaryVarAnnotation.php', + 'Psalm\\Issue\\UnrecognizedExpression' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnrecognizedExpression.php', + 'Psalm\\Issue\\UnrecognizedStatement' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnrecognizedStatement.php', + 'Psalm\\Issue\\UnresolvableInclude' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnresolvableInclude.php', + 'Psalm\\Issue\\UnsafeInstantiation' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnsafeInstantiation.php', + 'Psalm\\Issue\\UnusedClass' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnusedClass.php', + 'Psalm\\Issue\\UnusedClosureParam' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnusedClosureParam.php', + 'Psalm\\Issue\\UnusedFunctionCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnusedFunctionCall.php', + 'Psalm\\Issue\\UnusedMethod' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnusedMethod.php', + 'Psalm\\Issue\\UnusedMethodCall' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnusedMethodCall.php', + 'Psalm\\Issue\\UnusedParam' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnusedParam.php', + 'Psalm\\Issue\\UnusedProperty' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnusedProperty.php', + 'Psalm\\Issue\\UnusedPsalmSuppress' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnusedPsalmSuppress.php', + 'Psalm\\Issue\\UnusedVariable' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/UnusedVariable.php', + 'Psalm\\Issue\\VariableIssue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Issue/VariableIssue.php', + 'Psalm\\NodeTypeProvider' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/NodeTypeProvider.php', + 'Psalm\\PluginRegistrationSocket' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/PluginRegistrationSocket.php', + 'Psalm\\Plugin\\Hook\\AfterAnalysisInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterClassLikeAnalysisInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterClassLikeAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterClassLikeExistenceCheckInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterClassLikeExistenceCheckInterface.php', + 'Psalm\\Plugin\\Hook\\AfterClassLikeVisitInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterClassLikeVisitInterface.php', + 'Psalm\\Plugin\\Hook\\AfterCodebasePopulatedInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterCodebasePopulatedInterface.php', + 'Psalm\\Plugin\\Hook\\AfterEveryFunctionCallAnalysisInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterEveryFunctionCallAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterExpressionAnalysisInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterExpressionAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterFileAnalysisInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterFileAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterFunctionCallAnalysisInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterFunctionCallAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterFunctionLikeAnalysisInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterFunctionLikeAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterMethodCallAnalysisInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterMethodCallAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\AfterStatementAnalysisInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/AfterStatementAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\BeforeFileAnalysisInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/BeforeFileAnalysisInterface.php', + 'Psalm\\Plugin\\Hook\\FunctionExistenceProviderInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionExistenceProviderInterface.php', + 'Psalm\\Plugin\\Hook\\FunctionParamsProviderInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionParamsProviderInterface.php', + 'Psalm\\Plugin\\Hook\\FunctionReturnTypeProviderInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionReturnTypeProviderInterface.php', + 'Psalm\\Plugin\\Hook\\MethodExistenceProviderInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/MethodExistenceProviderInterface.php', + 'Psalm\\Plugin\\Hook\\MethodParamsProviderInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/MethodParamsProviderInterface.php', + 'Psalm\\Plugin\\Hook\\MethodReturnTypeProviderInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/MethodReturnTypeProviderInterface.php', + 'Psalm\\Plugin\\Hook\\MethodVisibilityProviderInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/MethodVisibilityProviderInterface.php', + 'Psalm\\Plugin\\Hook\\PropertyExistenceProviderInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyExistenceProviderInterface.php', + 'Psalm\\Plugin\\Hook\\PropertyTypeProviderInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyTypeProviderInterface.php', + 'Psalm\\Plugin\\Hook\\PropertyVisibilityProviderInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyVisibilityProviderInterface.php', + 'Psalm\\Plugin\\Hook\\StringInterpreterInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Hook/StringInterpreterInterface.php', + 'Psalm\\Plugin\\PluginEntryPointInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/PluginEntryPointInterface.php', + 'Psalm\\Plugin\\RegistrationInterface' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/RegistrationInterface.php', + 'Psalm\\Plugin\\Shepherd' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Plugin/Shepherd.php', + 'Psalm\\Progress\\DebugProgress' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Progress/DebugProgress.php', + 'Psalm\\Progress\\DefaultProgress' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Progress/DefaultProgress.php', + 'Psalm\\Progress\\LongProgress' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Progress/LongProgress.php', + 'Psalm\\Progress\\Progress' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Progress/Progress.php', + 'Psalm\\Progress\\VoidProgress' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Progress/VoidProgress.php', + 'Psalm\\Report' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report.php', + 'Psalm\\Report\\CheckstyleReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/CheckstyleReport.php', + 'Psalm\\Report\\CompactReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/CompactReport.php', + 'Psalm\\Report\\ConsoleReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/ConsoleReport.php', + 'Psalm\\Report\\EmacsReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/EmacsReport.php', + 'Psalm\\Report\\GithubActionsReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/GithubActionsReport.php', + 'Psalm\\Report\\JsonReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/JsonReport.php', + 'Psalm\\Report\\JsonSummaryReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/JsonSummaryReport.php', + 'Psalm\\Report\\JunitReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/JunitReport.php', + 'Psalm\\Report\\PhpStormReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/PhpStormReport.php', + 'Psalm\\Report\\PylintReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/PylintReport.php', + 'Psalm\\Report\\ReportOptions' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/ReportOptions.php', + 'Psalm\\Report\\SonarqubeReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/SonarqubeReport.php', + 'Psalm\\Report\\TextReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/TextReport.php', + 'Psalm\\Report\\XmlReport' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Report/XmlReport.php', + 'Psalm\\SourceControl\\Git\\CommitInfo' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/SourceControl/Git/CommitInfo.php', + 'Psalm\\SourceControl\\Git\\GitInfo' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/SourceControl/Git/GitInfo.php', + 'Psalm\\SourceControl\\Git\\RemoteInfo' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/SourceControl/Git/RemoteInfo.php', + 'Psalm\\SourceControl\\SourceControlInfo' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/SourceControl/SourceControlInfo.php', + 'Psalm\\StatementsSource' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/StatementsSource.php', + 'Psalm\\Storage\\Assertion' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Storage/Assertion.php', + 'Psalm\\Storage\\ClassConstantStorage' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Storage/ClassConstantStorage.php', + 'Psalm\\Storage\\ClassLikeStorage' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Storage/ClassLikeStorage.php', + 'Psalm\\Storage\\CustomMetadataTrait' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Storage/CustomMetadataTrait.php', + 'Psalm\\Storage\\FileStorage' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Storage/FileStorage.php', + 'Psalm\\Storage\\FunctionLikeParameter' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Storage/FunctionLikeParameter.php', + 'Psalm\\Storage\\FunctionLikeStorage' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Storage/FunctionLikeStorage.php', + 'Psalm\\Storage\\FunctionStorage' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Storage/FunctionStorage.php', + 'Psalm\\Storage\\MethodStorage' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Storage/MethodStorage.php', + 'Psalm\\Storage\\PropertyStorage' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Storage/PropertyStorage.php', + 'Psalm\\Type' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type.php', + 'Psalm\\Type\\Algebra' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Algebra.php', + 'Psalm\\Type\\Atomic' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic.php', + 'Psalm\\Type\\Atomic\\CallableTrait' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/CallableTrait.php', + 'Psalm\\Type\\Atomic\\GenericTrait' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/GenericTrait.php', + 'Psalm\\Type\\Atomic\\HasIntersectionTrait' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/HasIntersectionTrait.php', + 'Psalm\\Type\\Atomic\\Scalar' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/Scalar.php', + 'Psalm\\Type\\Atomic\\TAnonymousClassInstance' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TAnonymousClassInstance.php', + 'Psalm\\Type\\Atomic\\TArray' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TArray.php', + 'Psalm\\Type\\Atomic\\TArrayKey' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TArrayKey.php', + 'Psalm\\Type\\Atomic\\TAssertionFalsy' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TAssertionFalsy.php', + 'Psalm\\Type\\Atomic\\TBool' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TBool.php', + 'Psalm\\Type\\Atomic\\TCallable' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallable.php', + 'Psalm\\Type\\Atomic\\TCallableArray' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallableArray.php', + 'Psalm\\Type\\Atomic\\TCallableKeyedArray' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallableKeyedArray.php', + 'Psalm\\Type\\Atomic\\TCallableList' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallableList.php', + 'Psalm\\Type\\Atomic\\TCallableObject' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallableObject.php', + 'Psalm\\Type\\Atomic\\TCallableString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TCallableString.php', + 'Psalm\\Type\\Atomic\\TClassString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TClassString.php', + 'Psalm\\Type\\Atomic\\TClassStringMap' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TClassStringMap.php', + 'Psalm\\Type\\Atomic\\TClosedResource' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TClosedResource.php', + 'Psalm\\Type\\Atomic\\TClosure' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TClosure.php', + 'Psalm\\Type\\Atomic\\TConditional' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TConditional.php', + 'Psalm\\Type\\Atomic\\TDependentGetClass' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetClass.php', + 'Psalm\\Type\\Atomic\\TDependentGetDebugType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetDebugType.php', + 'Psalm\\Type\\Atomic\\TDependentGetType' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetType.php', + 'Psalm\\Type\\Atomic\\TEmpty' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TEmpty.php', + 'Psalm\\Type\\Atomic\\TEmptyMixed' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TEmptyMixed.php', + 'Psalm\\Type\\Atomic\\TEmptyNumeric' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TEmptyNumeric.php', + 'Psalm\\Type\\Atomic\\TEmptyScalar' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TEmptyScalar.php', + 'Psalm\\Type\\Atomic\\TFalse' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TFalse.php', + 'Psalm\\Type\\Atomic\\TFloat' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TFloat.php', + 'Psalm\\Type\\Atomic\\TGenericObject' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TGenericObject.php', + 'Psalm\\Type\\Atomic\\THtmlEscapedString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/THtmlEscapedString.php', + 'Psalm\\Type\\Atomic\\TInt' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TInt.php', + 'Psalm\\Type\\Atomic\\TIterable' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TIterable.php', + 'Psalm\\Type\\Atomic\\TKeyOfClassConstant' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TKeyOfClassConstant.php', + 'Psalm\\Type\\Atomic\\TKeyedArray' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TKeyedArray.php', + 'Psalm\\Type\\Atomic\\TList' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TList.php', + 'Psalm\\Type\\Atomic\\TLiteralClassString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralClassString.php', + 'Psalm\\Type\\Atomic\\TLiteralFloat' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralFloat.php', + 'Psalm\\Type\\Atomic\\TLiteralInt' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralInt.php', + 'Psalm\\Type\\Atomic\\TLiteralString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralString.php', + 'Psalm\\Type\\Atomic\\TLowercaseString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TLowercaseString.php', + 'Psalm\\Type\\Atomic\\TMixed' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TMixed.php', + 'Psalm\\Type\\Atomic\\TNamedObject' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TNamedObject.php', + 'Psalm\\Type\\Atomic\\TNever' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TNever.php', + 'Psalm\\Type\\Atomic\\TNonEmptyArray' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyArray.php', + 'Psalm\\Type\\Atomic\\TNonEmptyList' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyList.php', + 'Psalm\\Type\\Atomic\\TNonEmptyLowercaseString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyLowercaseString.php', + 'Psalm\\Type\\Atomic\\TNonEmptyMixed' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyMixed.php', + 'Psalm\\Type\\Atomic\\TNonEmptyScalar' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyScalar.php', + 'Psalm\\Type\\Atomic\\TNonEmptyString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyString.php', + 'Psalm\\Type\\Atomic\\TNull' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TNull.php', + 'Psalm\\Type\\Atomic\\TNumeric' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TNumeric.php', + 'Psalm\\Type\\Atomic\\TNumericString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TNumericString.php', + 'Psalm\\Type\\Atomic\\TObject' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TObject.php', + 'Psalm\\Type\\Atomic\\TObjectWithProperties' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TObjectWithProperties.php', + 'Psalm\\Type\\Atomic\\TPositiveInt' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TPositiveInt.php', + 'Psalm\\Type\\Atomic\\TResource' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TResource.php', + 'Psalm\\Type\\Atomic\\TScalar' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TScalar.php', + 'Psalm\\Type\\Atomic\\TScalarClassConstant' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TScalarClassConstant.php', + 'Psalm\\Type\\Atomic\\TSingleLetter' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TSingleLetter.php', + 'Psalm\\Type\\Atomic\\TString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TString.php', + 'Psalm\\Type\\Atomic\\TTemplateIndexedAccess' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php', + 'Psalm\\Type\\Atomic\\TTemplateKeyOf' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateKeyOf.php', + 'Psalm\\Type\\Atomic\\TTemplateParam' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateParam.php', + 'Psalm\\Type\\Atomic\\TTemplateParamClass' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateParamClass.php', + 'Psalm\\Type\\Atomic\\TTraitString' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TTraitString.php', + 'Psalm\\Type\\Atomic\\TTrue' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TTrue.php', + 'Psalm\\Type\\Atomic\\TTypeAlias' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TTypeAlias.php', + 'Psalm\\Type\\Atomic\\TValueOfClassConstant' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TValueOfClassConstant.php', + 'Psalm\\Type\\Atomic\\TVoid' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Atomic/TVoid.php', + 'Psalm\\Type\\NodeVisitor' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/NodeVisitor.php', + 'Psalm\\Type\\Reconciler' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Reconciler.php', + 'Psalm\\Type\\TaintKind' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/TaintKind.php', + 'Psalm\\Type\\TaintKindGroup' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/TaintKindGroup.php', + 'Psalm\\Type\\TypeNode' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/TypeNode.php', + 'Psalm\\Type\\Union' => __DIR__ . '/..' . '/vimeo/psalm/src/Psalm/Type/Union.php', + 'Psr\\Container\\ContainerExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerExceptionInterface.php', + 'Psr\\Container\\ContainerInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerInterface.php', + 'Psr\\Container\\NotFoundExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/psr/event-dispatcher/src/EventDispatcherInterface.php', + 'Psr\\EventDispatcher\\ListenerProviderInterface' => __DIR__ . '/..' . '/psr/event-dispatcher/src/ListenerProviderInterface.php', + 'Psr\\EventDispatcher\\StoppableEventInterface' => __DIR__ . '/..' . '/psr/event-dispatcher/src/StoppableEventInterface.php', + 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/DummyTest.php', + 'Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\TestLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/TestLogger.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', + 'SessionUpdateTimestampHandlerInterface' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php', + 'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => __DIR__ . '/..' . '/symfony/console/CommandLoader/CommandLoaderInterface.php', + 'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/ContainerCommandLoader.php', + 'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/FactoryCommandLoader.php', + 'Symfony\\Component\\Console\\Command\\Command' => __DIR__ . '/..' . '/symfony/console/Command/Command.php', + 'Symfony\\Component\\Console\\Command\\HelpCommand' => __DIR__ . '/..' . '/symfony/console/Command/HelpCommand.php', + 'Symfony\\Component\\Console\\Command\\ListCommand' => __DIR__ . '/..' . '/symfony/console/Command/ListCommand.php', + 'Symfony\\Component\\Console\\Command\\LockableTrait' => __DIR__ . '/..' . '/symfony/console/Command/LockableTrait.php', + 'Symfony\\Component\\Console\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.php', + 'Symfony\\Component\\Console\\Cursor' => __DIR__ . '/..' . '/symfony/console/Cursor.php', + 'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => __DIR__ . '/..' . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', + 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => __DIR__ . '/..' . '/symfony/console/Descriptor/ApplicationDescription.php', + 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/Descriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php', + 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php', + 'Symfony\\Component\\Console\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/console/EventListener/ErrorListener.php', + 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleCommandEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleErrorEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleTerminateEvent.php', + 'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/CommandNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/console/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidOptionException.php', + 'Symfony\\Component\\Console\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/console/Exception/LogicException.php', + 'Symfony\\Component\\Console\\Exception\\MissingInputException' => __DIR__ . '/..' . '/symfony/console/Exception/MissingInputException.php', + 'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/NamespaceNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/console/Exception/RuntimeException.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleStack.php', + 'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\Dumper' => __DIR__ . '/..' . '/symfony/console/Helper/Dumper.php', + 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/FormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\Helper' => __DIR__ . '/..' . '/symfony/console/Helper/Helper.php', + 'Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php', + 'Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php', + 'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php', + 'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php', + 'Symfony\\Component\\Console\\Helper\\QuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/QuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/SymfonyQuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\Table' => __DIR__ . '/..' . '/symfony/console/Helper/Table.php', + 'Symfony\\Component\\Console\\Helper\\TableCell' => __DIR__ . '/..' . '/symfony/console/Helper/TableCell.php', + 'Symfony\\Component\\Console\\Helper\\TableRows' => __DIR__ . '/..' . '/symfony/console/Helper/TableRows.php', + 'Symfony\\Component\\Console\\Helper\\TableSeparator' => __DIR__ . '/..' . '/symfony/console/Helper/TableSeparator.php', + 'Symfony\\Component\\Console\\Helper\\TableStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableStyle.php', + 'Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php', + 'Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php', + 'Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php', + 'Symfony\\Component\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/symfony/console/Input/InputArgument.php', + 'Symfony\\Component\\Console\\Input\\InputAwareInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputAwareInterface.php', + 'Symfony\\Component\\Console\\Input\\InputDefinition' => __DIR__ . '/..' . '/symfony/console/Input/InputDefinition.php', + 'Symfony\\Component\\Console\\Input\\InputInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputInterface.php', + 'Symfony\\Component\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/symfony/console/Input/InputOption.php', + 'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => __DIR__ . '/..' . '/symfony/console/Input/StreamableInputInterface.php', + 'Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php', + 'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php', + 'Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php', + 'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleSectionOutput.php', + 'Symfony\\Component\\Console\\Output\\NullOutput' => __DIR__ . '/..' . '/symfony/console/Output/NullOutput.php', + 'Symfony\\Component\\Console\\Output\\Output' => __DIR__ . '/..' . '/symfony/console/Output/Output.php', + 'Symfony\\Component\\Console\\Output\\OutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/OutputInterface.php', + 'Symfony\\Component\\Console\\Output\\StreamOutput' => __DIR__ . '/..' . '/symfony/console/Output/StreamOutput.php', + 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ChoiceQuestion.php', + 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ConfirmationQuestion.php', + 'Symfony\\Component\\Console\\Question\\Question' => __DIR__ . '/..' . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\SingleCommandApplication' => __DIR__ . '/..' . '/symfony/console/SingleCommandApplication.php', + 'Symfony\\Component\\Console\\Style\\OutputStyle' => __DIR__ . '/..' . '/symfony/console/Style/OutputStyle.php', + 'Symfony\\Component\\Console\\Style\\StyleInterface' => __DIR__ . '/..' . '/symfony/console/Style/StyleInterface.php', + 'Symfony\\Component\\Console\\Style\\SymfonyStyle' => __DIR__ . '/..' . '/symfony/console/Style/SymfonyStyle.php', + 'Symfony\\Component\\Console\\Terminal' => __DIR__ . '/..' . '/symfony/console/Terminal.php', + 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php', + 'Symfony\\Component\\Console\\Tester\\TesterTrait' => __DIR__ . '/..' . '/symfony/console/Tester/TesterTrait.php', + 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/WrappedListener.php', + 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\AddEventAliasesPass' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php', + 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php', + 'Symfony\\Component\\EventDispatcher\\EventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventDispatcherInterface.php', + 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventSubscriberInterface.php', + 'Symfony\\Component\\EventDispatcher\\GenericEvent' => __DIR__ . '/..' . '/symfony/event-dispatcher/GenericEvent.php', + 'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => __DIR__ . '/..' . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php', + 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/symfony/filesystem/Filesystem.php', + 'Symfony\\Component\\Finder\\Comparator\\Comparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/Comparator.php', + 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/DateComparator.php', + 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/NumberComparator.php', + 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/finder/Exception/AccessDeniedException.php', + 'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => __DIR__ . '/..' . '/symfony/finder/Exception/DirectoryNotFoundException.php', + 'Symfony\\Component\\Finder\\Finder' => __DIR__ . '/..' . '/symfony/finder/Finder.php', + 'Symfony\\Component\\Finder\\Gitignore' => __DIR__ . '/..' . '/symfony/finder/Gitignore.php', + 'Symfony\\Component\\Finder\\Glob' => __DIR__ . '/..' . '/symfony/finder/Glob.php', + 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/CustomFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DateRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DepthRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FileTypeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilecontentFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilenameFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/PathFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SizeRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SortableIterator.php', + 'Symfony\\Component\\Finder\\SplFileInfo' => __DIR__ . '/..' . '/symfony/finder/SplFileInfo.php', + 'Symfony\\Component\\OptionsResolver\\Debug\\OptionsResolverIntrospector' => __DIR__ . '/..' . '/symfony/options-resolver/Debug/OptionsResolverIntrospector.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\AccessException' => __DIR__ . '/..' . '/symfony/options-resolver/Exception/AccessException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/options-resolver/Exception/ExceptionInterface.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/options-resolver/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException' => __DIR__ . '/..' . '/symfony/options-resolver/Exception/InvalidOptionsException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\MissingOptionsException' => __DIR__ . '/..' . '/symfony/options-resolver/Exception/MissingOptionsException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\NoConfigurationException' => __DIR__ . '/..' . '/symfony/options-resolver/Exception/NoConfigurationException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\NoSuchOptionException' => __DIR__ . '/..' . '/symfony/options-resolver/Exception/NoSuchOptionException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\OptionDefinitionException' => __DIR__ . '/..' . '/symfony/options-resolver/Exception/OptionDefinitionException.php', + 'Symfony\\Component\\OptionsResolver\\Exception\\UndefinedOptionsException' => __DIR__ . '/..' . '/symfony/options-resolver/Exception/UndefinedOptionsException.php', + 'Symfony\\Component\\OptionsResolver\\OptionConfigurator' => __DIR__ . '/..' . '/symfony/options-resolver/OptionConfigurator.php', + 'Symfony\\Component\\OptionsResolver\\Options' => __DIR__ . '/..' . '/symfony/options-resolver/Options.php', + 'Symfony\\Component\\OptionsResolver\\OptionsResolver' => __DIR__ . '/..' . '/symfony/options-resolver/OptionsResolver.php', + 'Symfony\\Component\\Process\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/process/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/process/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Process\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/process/Exception/LogicException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessFailedException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessSignaledException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessTimedOutException.php', + 'Symfony\\Component\\Process\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/process/Exception/RuntimeException.php', + 'Symfony\\Component\\Process\\ExecutableFinder' => __DIR__ . '/..' . '/symfony/process/ExecutableFinder.php', + 'Symfony\\Component\\Process\\InputStream' => __DIR__ . '/..' . '/symfony/process/InputStream.php', + 'Symfony\\Component\\Process\\PhpExecutableFinder' => __DIR__ . '/..' . '/symfony/process/PhpExecutableFinder.php', + 'Symfony\\Component\\Process\\PhpProcess' => __DIR__ . '/..' . '/symfony/process/PhpProcess.php', + 'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/AbstractPipes.php', + 'Symfony\\Component\\Process\\Pipes\\PipesInterface' => __DIR__ . '/..' . '/symfony/process/Pipes/PipesInterface.php', + 'Symfony\\Component\\Process\\Pipes\\UnixPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/UnixPipes.php', + 'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/WindowsPipes.php', + 'Symfony\\Component\\Process\\Process' => __DIR__ . '/..' . '/symfony/process/Process.php', + 'Symfony\\Component\\Process\\ProcessUtils' => __DIR__ . '/..' . '/symfony/process/ProcessUtils.php', + 'Symfony\\Component\\Stopwatch\\Section' => __DIR__ . '/..' . '/symfony/stopwatch/Section.php', + 'Symfony\\Component\\Stopwatch\\Stopwatch' => __DIR__ . '/..' . '/symfony/stopwatch/Stopwatch.php', + 'Symfony\\Component\\Stopwatch\\StopwatchEvent' => __DIR__ . '/..' . '/symfony/stopwatch/StopwatchEvent.php', + 'Symfony\\Component\\Stopwatch\\StopwatchPeriod' => __DIR__ . '/..' . '/symfony/stopwatch/StopwatchPeriod.php', + 'Symfony\\Component\\String\\AbstractString' => __DIR__ . '/..' . '/symfony/string/AbstractString.php', + 'Symfony\\Component\\String\\AbstractUnicodeString' => __DIR__ . '/..' . '/symfony/string/AbstractUnicodeString.php', + 'Symfony\\Component\\String\\ByteString' => __DIR__ . '/..' . '/symfony/string/ByteString.php', + 'Symfony\\Component\\String\\CodePointString' => __DIR__ . '/..' . '/symfony/string/CodePointString.php', + 'Symfony\\Component\\String\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/string/Exception/ExceptionInterface.php', + 'Symfony\\Component\\String\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/string/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\String\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/string/Exception/RuntimeException.php', + 'Symfony\\Component\\String\\Inflector\\EnglishInflector' => __DIR__ . '/..' . '/symfony/string/Inflector/EnglishInflector.php', + 'Symfony\\Component\\String\\Inflector\\InflectorInterface' => __DIR__ . '/..' . '/symfony/string/Inflector/InflectorInterface.php', + 'Symfony\\Component\\String\\LazyString' => __DIR__ . '/..' . '/symfony/string/LazyString.php', + 'Symfony\\Component\\String\\Slugger\\AsciiSlugger' => __DIR__ . '/..' . '/symfony/string/Slugger/AsciiSlugger.php', + 'Symfony\\Component\\String\\Slugger\\SluggerInterface' => __DIR__ . '/..' . '/symfony/string/Slugger/SluggerInterface.php', + 'Symfony\\Component\\String\\UnicodeString' => __DIR__ . '/..' . '/symfony/string/UnicodeString.php', + 'Symfony\\Contracts\\EventDispatcher\\Event' => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts/Event.php', + 'Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts/EventDispatcherInterface.php', + 'Symfony\\Contracts\\Service\\Attribute\\Required' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/Required.php', + 'Symfony\\Contracts\\Service\\ResetInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ResetInterface.php', + 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceLocatorTrait.php', + 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceProviderInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberTrait.php', + 'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => __DIR__ . '/..' . '/symfony/service-contracts/Test/ServiceLocatorTest.php', + 'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/Grapheme.php', + 'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Normalizer.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php70\\Php70' => __DIR__ . '/..' . '/symfony/polyfill-php70/Php70.php', + 'Symfony\\Polyfill\\Php72\\Php72' => __DIR__ . '/..' . '/symfony/polyfill-php72/Php72.php', + 'Symfony\\Polyfill\\Php73\\Php73' => __DIR__ . '/..' . '/symfony/polyfill-php73/Php73.php', + 'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php', + 'TypeError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/TypeError.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'Webmozart\\Assert\\Assert' => __DIR__ . '/..' . '/webmozart/assert/src/Assert.php', + 'Webmozart\\Assert\\Mixin' => __DIR__ . '/..' . '/webmozart/assert/src/Mixin.php', + 'Webmozart\\Glob\\Glob' => __DIR__ . '/..' . '/webmozart/glob/src/Glob.php', + 'Webmozart\\Glob\\Iterator\\GlobFilterIterator' => __DIR__ . '/..' . '/webmozart/glob/src/Iterator/GlobFilterIterator.php', + 'Webmozart\\Glob\\Iterator\\GlobIterator' => __DIR__ . '/..' . '/webmozart/glob/src/Iterator/GlobIterator.php', + 'Webmozart\\Glob\\Iterator\\RecursiveDirectoryIterator' => __DIR__ . '/..' . '/webmozart/glob/src/Iterator/RecursiveDirectoryIterator.php', + 'Webmozart\\Glob\\Iterator\\RegexFilterIterator' => __DIR__ . '/..' . '/webmozart/glob/src/Iterator/RegexFilterIterator.php', + 'Webmozart\\Glob\\Test\\TestUtil' => __DIR__ . '/..' . '/webmozart/glob/src/Test/TestUtil.php', + 'Webmozart\\PathUtil\\Path' => __DIR__ . '/..' . '/webmozart/path-util/src/Path.php', + 'Webmozart\\PathUtil\\Url' => __DIR__ . '/..' . '/webmozart/path-util/src/Url.php', + 'XdgBaseDir\\Xdg' => __DIR__ . '/..' . '/dnoegel/php-xdg-base-dir/src/Xdg.php', + 'phpDocumentor\\Reflection\\DocBlock' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock.php', + 'phpDocumentor\\Reflection\\DocBlockFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlockFactory.php', + 'phpDocumentor\\Reflection\\DocBlockFactoryInterface' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php', + 'phpDocumentor\\Reflection\\DocBlock\\Description' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Description.php', + 'phpDocumentor\\Reflection\\DocBlock\\DescriptionFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\ExampleFinder' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php', + 'phpDocumentor\\Reflection\\DocBlock\\Serializer' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php', + 'phpDocumentor\\Reflection\\DocBlock\\StandardTagFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tag' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php', + 'phpDocumentor\\Reflection\\DocBlock\\TagFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Author' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\BaseTag' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Covers' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Deprecated' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Example' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\StaticMethod' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\AlignFormatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\PassthroughFormatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Generic' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\InvalidTag' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Link' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Method' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Param' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Property' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyRead' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyWrite' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Fqsen' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Reference' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Url' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Return_' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\See' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Since' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Source' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\TagWithType' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Throws' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Uses' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Var_' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Version' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php', + 'phpDocumentor\\Reflection\\Element' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Element.php', + 'phpDocumentor\\Reflection\\Exception\\PcreException' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/Exception/PcreException.php', + 'phpDocumentor\\Reflection\\File' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/File.php', + 'phpDocumentor\\Reflection\\Fqsen' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Fqsen.php', + 'phpDocumentor\\Reflection\\FqsenResolver' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/FqsenResolver.php', + 'phpDocumentor\\Reflection\\Location' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Location.php', + 'phpDocumentor\\Reflection\\Project' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Project.php', + 'phpDocumentor\\Reflection\\ProjectFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/ProjectFactory.php', + 'phpDocumentor\\Reflection\\PseudoType' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/PseudoType.php', + 'phpDocumentor\\Reflection\\PseudoTypes\\False_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/PseudoTypes/False_.php', + 'phpDocumentor\\Reflection\\PseudoTypes\\True_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/PseudoTypes/True_.php', + 'phpDocumentor\\Reflection\\Type' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Type.php', + 'phpDocumentor\\Reflection\\TypeResolver' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/TypeResolver.php', + 'phpDocumentor\\Reflection\\Types\\AbstractList' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/AbstractList.php', + 'phpDocumentor\\Reflection\\Types\\AggregatedType' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/AggregatedType.php', + 'phpDocumentor\\Reflection\\Types\\Array_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Array_.php', + 'phpDocumentor\\Reflection\\Types\\Boolean' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Boolean.php', + 'phpDocumentor\\Reflection\\Types\\Callable_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Callable_.php', + 'phpDocumentor\\Reflection\\Types\\ClassString' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/ClassString.php', + 'phpDocumentor\\Reflection\\Types\\Collection' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Collection.php', + 'phpDocumentor\\Reflection\\Types\\Compound' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Compound.php', + 'phpDocumentor\\Reflection\\Types\\Context' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Context.php', + 'phpDocumentor\\Reflection\\Types\\ContextFactory' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/ContextFactory.php', + 'phpDocumentor\\Reflection\\Types\\Expression' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Expression.php', + 'phpDocumentor\\Reflection\\Types\\Float_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Float_.php', + 'phpDocumentor\\Reflection\\Types\\Integer' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Integer.php', + 'phpDocumentor\\Reflection\\Types\\Intersection' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Intersection.php', + 'phpDocumentor\\Reflection\\Types\\Iterable_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Iterable_.php', + 'phpDocumentor\\Reflection\\Types\\Mixed_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Mixed_.php', + 'phpDocumentor\\Reflection\\Types\\Null_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Null_.php', + 'phpDocumentor\\Reflection\\Types\\Nullable' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Nullable.php', + 'phpDocumentor\\Reflection\\Types\\Object_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Object_.php', + 'phpDocumentor\\Reflection\\Types\\Parent_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Parent_.php', + 'phpDocumentor\\Reflection\\Types\\Resource_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Resource_.php', + 'phpDocumentor\\Reflection\\Types\\Scalar' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Scalar.php', + 'phpDocumentor\\Reflection\\Types\\Self_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Self_.php', + 'phpDocumentor\\Reflection\\Types\\Static_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Static_.php', + 'phpDocumentor\\Reflection\\Types\\String_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/String_.php', + 'phpDocumentor\\Reflection\\Types\\This' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/This.php', + 'phpDocumentor\\Reflection\\Types\\Void_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Void_.php', + 'phpDocumentor\\Reflection\\Utils' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/Utils.php', ); public static function getInitializer(ClassLoader $loader) @@ -1425,6 +3599,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c $loader->prefixLengthsPsr4 = ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::$prefixDirsPsr4; $loader->fallbackDirsPsr4 = ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::$fallbackDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::$prefixesPsr0; $loader->classMap = ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::$classMap; }, null, ClassLoader::class); diff --git a/lib/composer/composer/installed.json b/lib/composer/composer/installed.json index fe51488c7066f6687ef680d6bfaa4f7768ef205c..738c1d4cb5b8caa58f274d1c808fdd9b0406da66 100644 --- a/lib/composer/composer/installed.json +++ b/lib/composer/composer/installed.json @@ -1 +1,3145 @@ -[] +{ + "packages": [ + { + "name": "amphp/amp", + "version": "v2.5.0", + "version_normalized": "2.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "f220a51458bf4dd0dedebb171ac3457813c72bbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/f220a51458bf4dd0dedebb171ac3457813c72bbc", + "reference": "f220a51458bf4dd0dedebb171ac3457813c72bbc", + "shasum": "" + }, + "require": { + "php": ">=7" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6.0.9 | ^7", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "time": "2020-07-14T21:47:18+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Amp\\": "lib" + }, + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "http://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "install-path": "../amphp/amp" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.0", + "version_normalized": "1.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/f0c20cf598a958ba2aa8c6e5a71c697d652c7088", + "reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "time": "2020-06-29T18:35:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Amp\\ByteStream\\": "lib" + }, + "files": [ + "lib/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "install-path": "../amphp/byte-stream" + }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99", + "version_normalized": "1.11.99.0", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855", + "reference": "c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "time": "2020-08-25T05:50:16+00:00", + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "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": "./package-versions-deprecated" + }, + { + "name": "composer/semver", + "version": "1.7.1", + "version_normalized": "1.7.1.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "38276325bd896f90dfcfe30029aa5db40df387a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/38276325bd896f90dfcfe30029aa5db40df387a7", + "reference": "38276325bd896f90dfcfe30029aa5db40df387a7", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5" + }, + "time": "2020-09-27T13:13:07+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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" + ], + "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": "1.4.3", + "version_normalized": "1.4.3.0", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ebd27a9866ae8254e873866f795491f02418c5a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ebd27a9866ae8254e873866f795491f02418c5a5", + "reference": "ebd27a9866ae8254e873866f795491f02418c5a5", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + }, + "time": "2020-08-19T10:27:58+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" + ], + "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": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "version_normalized": "0.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "time": "2019-12-04T15:06:13+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "install-path": "../dnoegel/php-xdg-base-dir" + }, + { + "name": "doctrine/annotations", + "version": "1.10.3", + "version_normalized": "1.10.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/5db60a4969eba0e0c197a19c077780aadbc43c5d", + "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^7.5" + }, + "time": "2020-05-25T17:24:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "install-path": "../doctrine/annotations" + }, + { + "name": "doctrine/lexer", + "version": "1.2.1", + "version_normalized": "1.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "time": "2020-05-25T17:44:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "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%2Flexer", + "type": "tidelift" + } + ], + "install-path": "../doctrine/lexer" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.1.1", + "version_normalized": "3.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "0ed363f8de17d284d479ec813c9ad3f6834b5c40" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/0ed363f8de17d284d479ec813c9ad3f6834b5c40", + "reference": "0ed363f8de17d284d479ec813c9ad3f6834b5c40", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0", + "php": ">=7.0", + "phpdocumentor/reflection-docblock": "^4.0.0 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0.0" + }, + "time": "2020-03-11T15:21:41+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "install-path": "../felixfbecker/advanced-json-rpc" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.4.0", + "version_normalized": "1.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "378801f6139bb74ac215d81cca1272af61df9a9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/378801f6139bb74ac215d81cca1272af61df9a9f", + "reference": "378801f6139bb74ac215d81cca1272af61df9a9f", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpstan/phpstan": "*", + "phpunit/phpunit": "^6.3", + "squizlabs/php_codesniffer": "^3.1" + }, + "time": "2019-06-23T21:03:50+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "install-path": "../felixfbecker/language-server-protocol" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v2.16.3", + "version_normalized": "2.16.3.0", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "83baf823a33a1cbd5416c8626935cf3f843c10b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/83baf823a33a1cbd5416c8626935cf3f843c10b0", + "reference": "83baf823a33a1cbd5416c8626935cf3f843c10b0", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4", + "composer/xdebug-handler": "^1.2", + "doctrine/annotations": "^1.2", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^5.6 || ^7.0", + "php-cs-fixer/diff": "^1.3", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", + "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", + "symfony/finder": "^3.0 || ^4.0 || ^5.0", + "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", + "symfony/polyfill-php70": "^1.0", + "symfony/polyfill-php72": "^1.4", + "symfony/process": "^3.0 || ^4.0 || ^5.0", + "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", + "justinrainbow/json-schema": "^5.0", + "keradus/cli-executor": "^1.2", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.1", + "php-cs-fixer/accessible-object": "^1.0", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.1", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.1", + "phpunitgoodpractices/traits": "^1.8", + "symfony/phpunit-bridge": "^4.3 || ^5.0", + "symfony/yaml": "^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters in cache signature.", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", + "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + }, + "time": "2020-04-15T18:51:10+00:00", + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "classmap": [ + "tests/Test/AbstractFixerTestCase.php", + "tests/Test/AbstractIntegrationCaseFactory.php", + "tests/Test/AbstractIntegrationTestCase.php", + "tests/Test/Assert/AssertTokensTrait.php", + "tests/Test/IntegrationCase.php", + "tests/Test/IntegrationCaseFactory.php", + "tests/Test/IntegrationCaseFactoryInterface.php", + "tests/Test/InternalIntegrationCaseFactory.php", + "tests/Test/IsIdenticalConstraint.php", + "tests/TestCase.php" + ] + }, + "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", + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "install-path": "../friendsofphp/php-cs-fixer" + }, + { + "name": "netresearch/jsonmapper", + "version": "v2.1.0", + "version_normalized": "2.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/e0f1e33a71587aca81be5cffbb9746510e1fe04e", + "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4 || ~7.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "time": "2020-04-16T18:48:43+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "install-path": "../netresearch/jsonmapper" + }, + { + "name": "nextcloud/coding-standard", + "version": "v0.3.0", + "version_normalized": "0.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/nextcloud/coding-standard.git", + "reference": "4f5cd012760f8293e19e602651a0ecaa265e4db9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nextcloud/coding-standard/zipball/4f5cd012760f8293e19e602651a0ecaa265e4db9", + "reference": "4f5cd012760f8293e19e602651a0ecaa265e4db9", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": "^2.16", + "php": "^7.2" + }, + "time": "2020-04-10T14:57:18+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Nextcloud\\CodingStandard\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christoph Wurst", + "email": "christoph@winzerhof-wurst.at" + } + ], + "description": "Nextcloud coding standards for the php cs fixer", + "install-path": "../nextcloud/coding-standard" + }, + { + "name": "nikic/php-parser", + "version": "v4.10.2", + "version_normalized": "4.10.2.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "658f1be311a230e0907f5dfe0213742aff0596de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/658f1be311a230e0907f5dfe0213742aff0596de", + "reference": "658f1be311a230e0907f5dfe0213742aff0596de", + "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" + }, + "time": "2020-09-26T10:30:38+00:00", + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-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" + ], + "install-path": "../nikic/php-parser" + }, + { + "name": "openlss/lib-array2xml", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/nullivex/lib-array2xml.git", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "time": "2019-03-29T20:06:56+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "LSS": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Bryan Tong", + "email": "bryan@nullivex.com", + "homepage": "https://www.nullivex.com" + }, + { + "name": "Tony Butler", + "email": "spudz76@gmail.com", + "homepage": "https://www.nullivex.com" + } + ], + "description": "Array2XML conversion library credit to lalit.org", + "homepage": "https://www.nullivex.com", + "keywords": [ + "array", + "array conversion", + "xml", + "xml conversion" + ], + "install-path": "../openlss/lib-array2xml" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "version_normalized": "9.99.99.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "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": "2018-07-02T15:55:56+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" + ], + "install-path": "../paragonie/random_compat" + }, + { + "name": "php-cs-fixer/diff", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/diff.git", + "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/78bb099e9c16361126c86ce82ec4405ebab8e756", + "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "symfony/process": "^3.3" + }, + "time": "2018-02-15T16:58:55+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "SpacePossum" + } + ], + "description": "sebastian/diff v2 backport support for PHP5.6", + "homepage": "https://github.com/PHP-CS-Fixer", + "keywords": [ + "diff" + ], + "install-path": "../php-cs-fixer/diff" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "time": "2020-06-27T09:03:43+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "install-path": "../phpdocumentor/reflection-common" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "version_normalized": "5.2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "time": "2020-09-03T19:13:55+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "install-path": "../phpdocumentor/reflection-docblock" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "version_normalized": "1.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "time": "2020-09-17T18:55:26+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "install-path": "../phpdocumentor/type-resolver" + }, + { + "name": "psr/container", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-02-14T16:28:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "install-path": "../psr/container" + }, + { + "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" + ], + "install-path": "../psr/event-dispatcher" + }, + { + "name": "psr/log", + "version": "1.1.3", + "version_normalized": "1.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2020-03-23T09:12:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "install-path": "../psr/log" + }, + { + "name": "sebastian/diff", + "version": "4.0.3", + "version_normalized": "4.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ffc949a1a2aae270ea064453d7535b82e4c32092" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ffc949a1a2aae270ea064453d7535b82e4c32092", + "reference": "ffc949a1a2aae270ea064453d7535b82e4c32092", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "time": "2020-09-28T05:32:55+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" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/diff" + }, + { + "name": "symfony/console", + "version": "v5.1.7", + "version_normalized": "5.1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "ae789a8a2ad189ce7e8216942cdb9b77319f5eb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/ae789a8a2ad189ce7e8216942cdb9b77319f5eb8", + "reference": "ae789a8a2ad189ce7e8216942cdb9b77319f5eb8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "time": "2020-10-07T15:23:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony Console Component", + "homepage": "https://symfony.com", + "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": "v2.1.2", + "version_normalized": "2.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", + "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2020-05-27T08:34:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "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", + "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": "v5.1.2", + "version_normalized": "5.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/cc0d059e2e997e79ca34125a52f3e33de4424ac7", + "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/event-dispatcher-contracts": "^2", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^4.4|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2020-05-20T17:43:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "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": "v2.1.2", + "version_normalized": "2.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "405952c4e90941a17e52ef7489a2bd94870bb290" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/405952c4e90941a17e52ef7489a2bd94870bb290", + "reference": "405952c4e90941a17e52ef7489a2bd94870bb290", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "time": "2020-05-20T17:43:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "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" + ], + "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": "v5.1.2", + "version_normalized": "5.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "6e4320f06d5f2cce0d96530162491f4465179157" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/6e4320f06d5f2cce0d96530162491f4465179157", + "reference": "6e4320f06d5f2cce0d96530162491f4465179157", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "time": "2020-05-30T20:35:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "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": "v5.1.2", + "version_normalized": "5.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/4298870062bfc667cb78d2b379be4bf5dec5f187", + "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "time": "2020-05-20T17:43:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony Finder Component", + "homepage": "https://symfony.com", + "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": "v5.1.2", + "version_normalized": "5.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "663f5dd5e14057d1954fe721f9709d35837f2447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/663f5dd5e14057d1954fe721f9709d35837f2447", + "reference": "663f5dd5e14057d1954fe721f9709d35837f2447", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php80": "^1.15" + }, + "time": "2020-05-23T13:08:13+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "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.18.1", + "version_normalized": "1.18.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2020-07-14T12:35:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "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" + ], + "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.18.1", + "version_normalized": "1.18.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b740103edbdcc39602239ee8860f0f45a8eb9aa5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b740103edbdcc39602239ee8860f0f45a8eb9aa5", + "reference": "b740103edbdcc39602239ee8860f0f45a8eb9aa5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2020-07-14T12:35:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.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": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "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.18.1", + "version_normalized": "1.18.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e", + "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2020-07-14T12:35:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "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" + ], + "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.18.1", + "version_normalized": "1.18.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2020-07-14T12:35:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.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": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "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-php70", + "version": "v1.17.1", + "version_normalized": "1.17.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "471b096aede7025bace8eb356b9ac801aaba7e2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/471b096aede7025bace8eb356b9ac801aaba7e2d", + "reference": "471b096aede7025bace8eb356b9ac801aaba7e2d", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0|~9.99", + "php": ">=5.3.3" + }, + "time": "2020-06-06T08:46:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "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 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "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-php70" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.17.0", + "version_normalized": "1.17.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "f048e612a3905f34931127360bdd2def19a5e582" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582", + "reference": "f048e612a3905f34931127360bdd2def19a5e582", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2020-05-12T16:47:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.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": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "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-php72" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.18.1", + "version_normalized": "1.18.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2020-07-14T12:35:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "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 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "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-php73" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.18.1", + "version_normalized": "1.18.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "shasum": "" + }, + "require": { + "php": ">=7.0.8" + }, + "time": "2020-07-14T12:35:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "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" + ], + "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/process", + "version": "v5.1.2", + "version_normalized": "5.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", + "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" + }, + "time": "2020-05-30T20:35:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony Process Component", + "homepage": "https://symfony.com", + "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": "v2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "time": "2020-09-07T11:33:47+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "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" + ], + "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": "v5.1.2", + "version_normalized": "5.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/0f7c58cf81dbb5dd67d423a89d577524a2ec0323", + "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/service-contracts": "^1.0|^2" + }, + "time": "2020-05-20T17:43:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "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": "v5.1.7", + "version_normalized": "5.1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "4a9afe9d07bac506f75bcee8ed3ce76da5a9343e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/4a9afe9d07bac506f75bcee8ed3ce76da5a9343e", + "reference": "4a9afe9d07bac506f75bcee8ed3ce76da5a9343e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "time": "2020-09-15T12:23:47+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "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": "Symfony String component", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "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": "vimeo/psalm", + "version": "4.0.1", + "version_normalized": "4.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "b1e2e30026936ef8d5bf6a354d1c3959b6231f44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/b1e2e30026936ef8d5bf6a354d1c3959b6231f44", + "reference": "b1e2e30026936ef8d5bf6a354d1c3959b6231f44", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.1", + "amphp/byte-stream": "^1.5", + "composer/package-versions-deprecated": "^1.8.0", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^1.1", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.0.3", + "felixfbecker/language-server-protocol": "^1.4", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0", + "nikic/php-parser": "^4.10.1", + "openlss/lib-array2xml": "^1.0", + "php": "^7.3|^8", + "sebastian/diff": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "webmozart/glob": "^4.1", + "webmozart/path-util": "^2.3" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/amp": "^2.4.2", + "bamarni/composer-bin-plugin": "^1.2", + "brianium/paratest": "^4.0.0", + "ext-curl": "*", + "phpdocumentor/reflection-docblock": "^5", + "phpmyadmin/sql-parser": "5.1.0", + "phpspec/prophecy": ">=1.9.0", + "phpunit/phpunit": "^9.0", + "psalm/plugin-phpunit": "^0.13", + "slevomat/coding-standard": "^5.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.3", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "suggest": { + "ext-igbinary": "^2.0.5" + }, + "time": "2020-10-20T13:40:17+00:00", + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + }, + "files": [ + "src/functions.php", + "src/spl_object_id.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php" + ], + "install-path": "../vimeo/psalm" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "version_normalized": "1.9.1.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "time": "2020-07-08T17:02:28+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "install-path": "../webmozart/assert" + }, + { + "name": "webmozart/glob", + "version": "4.1.0", + "version_normalized": "4.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/glob.git", + "reference": "3cbf63d4973cf9d780b93d2da8eec7e4a9e63bbe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/glob/zipball/3cbf63d4973cf9d780b93d2da8eec7e4a9e63bbe", + "reference": "3cbf63d4973cf9d780b93d2da8eec7e4a9e63bbe", + "shasum": "" + }, + "require": { + "php": "^5.3.3|^7.0", + "webmozart/path-util": "^2.2" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1", + "symfony/filesystem": "^2.5" + }, + "time": "2015-12-29T11:14:33+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webmozart\\Glob\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A PHP implementation of Ant's glob.", + "install-path": "../webmozart/glob" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "version_normalized": "2.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "time": "2015-12-17T08:42:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "install-path": "../webmozart/path-util" + } + ], + "dev": true +} diff --git a/lib/composer/composer/installed.php b/lib/composer/composer/installed.php new file mode 100644 index 0000000000000000000000000000000000000000..d8a394c4b8eb499b9df66675df9c31c033d3f719 --- /dev/null +++ b/lib/composer/composer/installed.php @@ -0,0 +1,482 @@ + + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => '11fca45e4c9ed5bc53436b6232a656a51f4984fa', + 'name' => '__root__', + ), + 'versions' => + array ( + '__root__' => + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => '11fca45e4c9ed5bc53436b6232a656a51f4984fa', + ), + 'amphp/amp' => + array ( + 'pretty_version' => 'v2.5.0', + 'version' => '2.5.0.0', + 'aliases' => + array ( + ), + 'reference' => 'f220a51458bf4dd0dedebb171ac3457813c72bbc', + ), + 'amphp/byte-stream' => + array ( + 'pretty_version' => 'v1.8.0', + 'version' => '1.8.0.0', + 'aliases' => + array ( + ), + 'reference' => 'f0c20cf598a958ba2aa8c6e5a71c697d652c7088', + ), + 'composer/package-versions-deprecated' => + array ( + 'pretty_version' => '1.11.99', + 'version' => '1.11.99.0', + 'aliases' => + array ( + ), + 'reference' => 'c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855', + ), + 'composer/semver' => + array ( + 'pretty_version' => '1.7.1', + 'version' => '1.7.1.0', + 'aliases' => + array ( + ), + 'reference' => '38276325bd896f90dfcfe30029aa5db40df387a7', + ), + 'composer/xdebug-handler' => + array ( + 'pretty_version' => '1.4.3', + 'version' => '1.4.3.0', + 'aliases' => + array ( + ), + 'reference' => 'ebd27a9866ae8254e873866f795491f02418c5a5', + ), + 'dnoegel/php-xdg-base-dir' => + array ( + 'pretty_version' => 'v0.1.1', + 'version' => '0.1.1.0', + 'aliases' => + array ( + ), + 'reference' => '8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd', + ), + 'doctrine/annotations' => + array ( + 'pretty_version' => '1.10.3', + 'version' => '1.10.3.0', + 'aliases' => + array ( + ), + 'reference' => '5db60a4969eba0e0c197a19c077780aadbc43c5d', + ), + 'doctrine/lexer' => + array ( + 'pretty_version' => '1.2.1', + 'version' => '1.2.1.0', + 'aliases' => + array ( + ), + 'reference' => 'e864bbf5904cb8f5bb334f99209b48018522f042', + ), + 'felixfbecker/advanced-json-rpc' => + array ( + 'pretty_version' => 'v3.1.1', + 'version' => '3.1.1.0', + 'aliases' => + array ( + ), + 'reference' => '0ed363f8de17d284d479ec813c9ad3f6834b5c40', + ), + 'felixfbecker/language-server-protocol' => + array ( + 'pretty_version' => 'v1.4.0', + 'version' => '1.4.0.0', + 'aliases' => + array ( + ), + 'reference' => '378801f6139bb74ac215d81cca1272af61df9a9f', + ), + 'friendsofphp/php-cs-fixer' => + array ( + 'pretty_version' => 'v2.16.3', + 'version' => '2.16.3.0', + 'aliases' => + array ( + ), + 'reference' => '83baf823a33a1cbd5416c8626935cf3f843c10b0', + ), + 'netresearch/jsonmapper' => + array ( + 'pretty_version' => 'v2.1.0', + 'version' => '2.1.0.0', + 'aliases' => + array ( + ), + 'reference' => 'e0f1e33a71587aca81be5cffbb9746510e1fe04e', + ), + 'nextcloud/coding-standard' => + array ( + 'pretty_version' => 'v0.3.0', + 'version' => '0.3.0.0', + 'aliases' => + array ( + ), + 'reference' => '4f5cd012760f8293e19e602651a0ecaa265e4db9', + ), + 'nikic/php-parser' => + array ( + 'pretty_version' => 'v4.10.2', + 'version' => '4.10.2.0', + 'aliases' => + array ( + ), + 'reference' => '658f1be311a230e0907f5dfe0213742aff0596de', + ), + 'ocramius/package-versions' => + array ( + 'replaced' => + array ( + 0 => '1.11.99', + ), + ), + 'openlss/lib-array2xml' => + array ( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'a91f18a8dfc69ffabe5f9b068bc39bb202c81d90', + ), + 'paragonie/random_compat' => + array ( + 'pretty_version' => 'v9.99.99', + 'version' => '9.99.99.0', + 'aliases' => + array ( + ), + 'reference' => '84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95', + ), + 'php-cs-fixer/diff' => + array ( + 'pretty_version' => 'v1.3.0', + 'version' => '1.3.0.0', + 'aliases' => + array ( + ), + 'reference' => '78bb099e9c16361126c86ce82ec4405ebab8e756', + ), + 'phpdocumentor/reflection-common' => + array ( + 'pretty_version' => '2.2.0', + 'version' => '2.2.0.0', + 'aliases' => + array ( + ), + 'reference' => '1d01c49d4ed62f25aa84a747ad35d5a16924662b', + ), + 'phpdocumentor/reflection-docblock' => + array ( + 'pretty_version' => '5.2.2', + 'version' => '5.2.2.0', + 'aliases' => + array ( + ), + 'reference' => '069a785b2141f5bcf49f3e353548dc1cce6df556', + ), + 'phpdocumentor/type-resolver' => + array ( + 'pretty_version' => '1.4.0', + 'version' => '1.4.0.0', + 'aliases' => + array ( + ), + 'reference' => '6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0', + ), + 'psalm/psalm' => + array ( + 'provided' => + array ( + 0 => '4.0.1', + ), + ), + 'psr/container' => + array ( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f', + ), + 'psr/event-dispatcher' => + array ( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', + ), + 'psr/event-dispatcher-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'psr/log' => + array ( + 'pretty_version' => '1.1.3', + 'version' => '1.1.3.0', + 'aliases' => + array ( + ), + 'reference' => '0f73288fd15629204f9d42b7055f72dacbe811fc', + ), + 'psr/log-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'sebastian/diff' => + array ( + 'pretty_version' => '4.0.3', + 'version' => '4.0.3.0', + 'aliases' => + array ( + ), + 'reference' => 'ffc949a1a2aae270ea064453d7535b82e4c32092', + ), + 'symfony/console' => + array ( + 'pretty_version' => 'v5.1.7', + 'version' => '5.1.7.0', + 'aliases' => + array ( + ), + 'reference' => 'ae789a8a2ad189ce7e8216942cdb9b77319f5eb8', + ), + 'symfony/deprecation-contracts' => + array ( + 'pretty_version' => 'v2.1.2', + 'version' => '2.1.2.0', + 'aliases' => + array ( + ), + 'reference' => 'dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337', + ), + 'symfony/event-dispatcher' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => 'cc0d059e2e997e79ca34125a52f3e33de4424ac7', + ), + 'symfony/event-dispatcher-contracts' => + array ( + 'pretty_version' => 'v2.1.2', + 'version' => '2.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '405952c4e90941a17e52ef7489a2bd94870bb290', + ), + 'symfony/event-dispatcher-implementation' => + array ( + 'provided' => + array ( + 0 => '2.0', + ), + ), + 'symfony/filesystem' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '6e4320f06d5f2cce0d96530162491f4465179157', + ), + 'symfony/finder' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '4298870062bfc667cb78d2b379be4bf5dec5f187', + ), + 'symfony/options-resolver' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '663f5dd5e14057d1954fe721f9709d35837f2447', + ), + 'symfony/polyfill-ctype' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => '1c302646f6efc070cd46856e600e5e0684d6b454', + ), + 'symfony/polyfill-intl-grapheme' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => 'b740103edbdcc39602239ee8860f0f45a8eb9aa5', + ), + 'symfony/polyfill-intl-normalizer' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => '37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e', + ), + 'symfony/polyfill-mbstring' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => 'a6977d63bf9a0ad4c65cd352709e230876f9904a', + ), + 'symfony/polyfill-php70' => + array ( + 'pretty_version' => 'v1.17.1', + 'version' => '1.17.1.0', + 'aliases' => + array ( + ), + 'reference' => '471b096aede7025bace8eb356b9ac801aaba7e2d', + ), + 'symfony/polyfill-php72' => + array ( + 'pretty_version' => 'v1.17.0', + 'version' => '1.17.0.0', + 'aliases' => + array ( + ), + 'reference' => 'f048e612a3905f34931127360bdd2def19a5e582', + ), + 'symfony/polyfill-php73' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => 'fffa1a52a023e782cdcc221d781fe1ec8f87fcca', + ), + 'symfony/polyfill-php80' => + array ( + 'pretty_version' => 'v1.18.1', + 'version' => '1.18.1.0', + 'aliases' => + array ( + ), + 'reference' => 'd87d5766cbf48d72388a9f6b85f280c8ad51f981', + ), + 'symfony/process' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1', + ), + 'symfony/service-contracts' => + array ( + 'pretty_version' => 'v2.2.0', + 'version' => '2.2.0.0', + 'aliases' => + array ( + ), + 'reference' => 'd15da7ba4957ffb8f1747218be9e1a121fd298a1', + ), + 'symfony/stopwatch' => + array ( + 'pretty_version' => 'v5.1.2', + 'version' => '5.1.2.0', + 'aliases' => + array ( + ), + 'reference' => '0f7c58cf81dbb5dd67d423a89d577524a2ec0323', + ), + 'symfony/string' => + array ( + 'pretty_version' => 'v5.1.7', + 'version' => '5.1.7.0', + 'aliases' => + array ( + ), + 'reference' => '4a9afe9d07bac506f75bcee8ed3ce76da5a9343e', + ), + 'vimeo/psalm' => + array ( + 'pretty_version' => '4.0.1', + 'version' => '4.0.1.0', + 'aliases' => + array ( + ), + 'reference' => 'b1e2e30026936ef8d5bf6a354d1c3959b6231f44', + ), + 'webmozart/assert' => + array ( + 'pretty_version' => '1.9.1', + 'version' => '1.9.1.0', + 'aliases' => + array ( + ), + 'reference' => 'bafc69caeb4d49c39fd0779086c03a3738cbb389', + ), + 'webmozart/glob' => + array ( + 'pretty_version' => '4.1.0', + 'version' => '4.1.0.0', + 'aliases' => + array ( + ), + 'reference' => '3cbf63d4973cf9d780b93d2da8eec7e4a9e63bbe', + ), + 'webmozart/path-util' => + array ( + 'pretty_version' => '2.3.0', + 'version' => '2.3.0.0', + 'aliases' => + array ( + ), + 'reference' => 'd939f7edc24c9a1bb9c0dee5cb05d8e859490725', + ), + ), +); diff --git a/lib/composer/composer/package-versions-deprecated/CHANGELOG.md b/lib/composer/composer/package-versions-deprecated/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..a838c56ad346fd2cc433f0a73ab178975f74d796 --- /dev/null +++ b/lib/composer/composer/package-versions-deprecated/CHANGELOG.md @@ -0,0 +1,120 @@ +# CHANGELOG + +## 1.1.3 - 2017-09-06 + +This release fixes a bug that caused PackageVersions to prevent +the `composer remove` and `composer update` commands to fail when +this package is removed. + +In addition to that, mutation testing has been added to the suite, +ensuring that the package is accurately and extensively tested. + +Total issues resolved: **3** + +- [40: Mutation testing, PHP 7.1 testing](https://github.com/Ocramius/PackageVersions/pull/40) thanks to @Ocramius +- [41: Removing this package on install results in file access error](https://github.com/Ocramius/PackageVersions/issues/41) thanks to @Xerkus +- [46: #41 Avoid issues when the package is scheduled for removal](https://github.com/Ocramius/PackageVersions/pull/46) thanks to @Jean85 + +## 1.1.2 - 2016-12-30 + +This release fixes a bug that caused PackageVersions to be enabled +even when it was part of a globally installed package. + +Total issues resolved: **3** + +- [35: remove all temp directories](https://github.com/Ocramius/PackageVersions/pull/35) +- [38: Interferes with other projects when installed globally](https://github.com/Ocramius/PackageVersions/issues/38) +- [39: Ignore the global plugin when updating local projects](https://github.com/Ocramius/PackageVersions/pull/39) + +## 1.1.1 - 2016-07-25 + +This release removes the [`"files"`](https://getcomposer.org/doc/04-schema.md#files) directive from +[`composer.json`](https://github.com/Ocramius/PackageVersions/commit/86f2636f7c5e7b56fa035fa3826d5fcf80b6dc72), +as it is no longer needed for `composer install --classmap-authoritative`. +Also, that directive was causing issues with HHVM installations, since +PackageVersions is not compatible with it. + +Total issues resolved: **1** + +- [34: Fatal error during travis build after update to 1.1.0](https://github.com/Ocramius/PackageVersions/issues/34) + +## 1.1.0 - 2016-07-22 + +This release introduces support for running `composer install --classmap-authoritative` +and `composer install --no-scripts`. Please note that performance +while using these modes may be degraded, but the package will +still work. + +Additionally, the package was tuned to prevent the plugin from +running twice at installation. + +Total issues resolved: **10** + +- [18: Fails when using composer install --no-scripts](https://github.com/Ocramius/PackageVersions/issues/18) +- [20: CS (spacing)](https://github.com/Ocramius/PackageVersions/pull/20) +- [22: Document the way the require-dev section is treated](https://github.com/Ocramius/PackageVersions/issues/22) +- [23: Underline that composer.lock is used as source of information](https://github.com/Ocramius/PackageVersions/pull/23) +- [27: Fix incompatibility with --classmap-authoritative](https://github.com/Ocramius/PackageVersions/pull/27) +- [29: mention optimize-autoloader composer.json config option in README](https://github.com/Ocramius/PackageVersions/pull/29) +- [30: The version class is generated twice during composer update](https://github.com/Ocramius/PackageVersions/issues/30) +- [31: Remove double registration of the event listeners](https://github.com/Ocramius/PackageVersions/pull/31) +- [32: Update the usage of mock APIs to use the new API](https://github.com/Ocramius/PackageVersions/pull/32) +- [33: Fix for #18 - support running with --no-scripts flag](https://github.com/Ocramius/PackageVersions/pull/33) + +## 1.0.4 - 2016-04-23 + +This release includes a fix/workaround for composer/composer#5237, +which causes `ocramius/package-versions` to sometimes generate a +`Versions` class with malformed name (something like +`Versions_composer_tmp0`) when running `composer require `. + +Total issues resolved: **2** + +- [16: Workaround for composer/composer#5237 - class parsing](https://github.com/Ocramius/PackageVersions/pull/16) +- [17: Weird Class name being generated](https://github.com/Ocramius/PackageVersions/issues/17) + +## 1.0.3 - 2016-02-26 + +This release fixes an issue related to concurrent autoloader +re-generation caused by multiple composer plugins being installed. +The issue was solved by removing autoloader re-generation from this +package, but it may still affect other packages. + +It is now recommended that you run `composer dump-autoload --optimize` +after installation when using this particular package. +Please note that `composer (install|update) -o` is not sufficient +to avoid autoload overhead when using this particular package. + +Total issues resolved: **1** + +- [15: Remove autoload re-dump optimization](https://github.com/Ocramius/PackageVersions/pull/15) + +## 1.0.2 - 2016-02-24 + +This release fixes issues related to installing the component without +any dev dependencies or with packages that don't have a source or dist +reference, which is usual with packages defined directly in the +`composer.json`. + +Total issues resolved: **3** + +- [11: fix composer install --no-dev PHP7](https://github.com/Ocramius/PackageVersions/pull/11) +- [12: Packages don't always have a source/reference](https://github.com/Ocramius/PackageVersions/issues/12) +- [13: Fix #12 - support dist and missing package version references](https://github.com/Ocramius/PackageVersions/pull/13) + +## 1.0.1 - 2016-02-01 + +This release fixes an issue related with composer updates to +already installed versions. +Using `composer require` within a package that already used +`ocramius/package-versions` caused the installation to be unable +to write the `PackageVersions\Versions` class to a file. + +Total issues resolved: **6** + +- [2: remove unused use statement](https://github.com/Ocramius/PackageVersions/pull/2) +- [3: Remove useless files from dist package](https://github.com/Ocramius/PackageVersions/pull/3) +- [5: failed to open stream: phar error: write operations disabled by the php.ini setting phar.readonly](https://github.com/Ocramius/PackageVersions/issues/5) +- [6: Fix/#5 use composer vendor dir](https://github.com/Ocramius/PackageVersions/pull/6) +- [7: Hotfix - #5 generate package versions also when in phar context](https://github.com/Ocramius/PackageVersions/pull/7) +- [8: Versions class should be ignored by VCS, as it is an install-time artifact](https://github.com/Ocramius/PackageVersions/pull/8) diff --git a/lib/composer/composer/package-versions-deprecated/CONTRIBUTING.md b/lib/composer/composer/package-versions-deprecated/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..71806175833b052ebb4bb2ee35bb1e3ae74b0877 --- /dev/null +++ b/lib/composer/composer/package-versions-deprecated/CONTRIBUTING.md @@ -0,0 +1,39 @@ +--- +title: Contributing +--- + +# Contributing + + * Coding standard for the project is [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) + * 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/Ocramius/PackageVersions.git +``` + +You will then need to run a composer installation: + +```sh +$ cd PackageVersions +$ 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/lib/composer/composer/package-versions-deprecated/LICENSE b/lib/composer/composer/package-versions-deprecated/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..a90b0792cc78b1eb3746f93f9266a5842b7b8f3d --- /dev/null +++ b/lib/composer/composer/package-versions-deprecated/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016 Marco Pivetta + +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/lib/composer/composer/package-versions-deprecated/README.md b/lib/composer/composer/package-versions-deprecated/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c5f5bba103f1d0e6423fe7629bbfc0040a5d8ea4 --- /dev/null +++ b/lib/composer/composer/package-versions-deprecated/README.md @@ -0,0 +1,5 @@ +# Package Versions + +**`composer/package-versions-deprecated` is a fully-compatible fork of [`ocramius/package-versions`](https://github.com/Ocramius/PackageVersions)** which provides compatibility with Composer 1 and 2 on PHP 7+. It replaces ocramius/package-versions so if you have a dependency requiring it and you want to use Composer v2 but can not upgrade to PHP 7.4 just yet, you can require this package instead. + +If you have a direct dependency on ocramius/package-versions, we recommend instead that once you migrated to Composer 2 you also migrate to use the `Composer\Versions` class which offers the functionality present here out of the box. diff --git a/lib/composer/composer/package-versions-deprecated/SECURITY.md b/lib/composer/composer/package-versions-deprecated/SECURITY.md new file mode 100644 index 0000000000000000000000000000000000000000..da9c516dd744b6c88dd8c91f58f7bcf8f7e8341b --- /dev/null +++ b/lib/composer/composer/package-versions-deprecated/SECURITY.md @@ -0,0 +1,5 @@ +## Security contact information + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. diff --git a/lib/composer/composer/package-versions-deprecated/composer.json b/lib/composer/composer/package-versions-deprecated/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..d5a40daacf364f07910fd903f1fa0edc98be6992 --- /dev/null +++ b/lib/composer/composer/package-versions-deprecated/composer.json @@ -0,0 +1,48 @@ +{ + "name": "composer/package-versions-deprecated", + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "type": "composer-plugin", + "license": "MIT", + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "require": { + "php": "^7 || ^8", + "composer-plugin-api": "^1.1.0 || ^2.0" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "phpunit/phpunit": "^6.5 || ^7", + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13" + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "autoload-dev": { + "psr-4": { + "PackageVersionsTest\\": "test/PackageVersionsTest" + } + }, + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "scripts": { + "post-update-cmd": "PackageVersions\\Installer::dumpVersionsClass", + "post-install-cmd": "PackageVersions\\Installer::dumpVersionsClass" + } +} diff --git a/lib/composer/composer/package-versions-deprecated/composer.lock b/lib/composer/composer/package-versions-deprecated/composer.lock new file mode 100644 index 0000000000000000000000000000000000000000..b711f6b13841bfe8b87ad0ce4103e2da44ed5e1b --- /dev/null +++ b/lib/composer/composer/package-versions-deprecated/composer.lock @@ -0,0 +1,2603 @@ +{ + "_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": "6bfe0a7d7a51c4bdf14a2d7ea1d22d11", + "packages": [], + "packages-dev": [ + { + "name": "composer/ca-bundle", + "version": "1.2.7", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/95c63ab2117a72f48f5a55da9740a3273d45b7fd", + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-04-08T08:27:21+00:00" + }, + { + "name": "composer/composer", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "a8c105da344dd84ebd5d11be7943a45b09dc076f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/a8c105da344dd84ebd5d11be7943a45b09dc076f", + "reference": "a8c105da344dd84ebd5d11be7943a45b09dc076f", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "composer/semver": "^1.0", + "composer/spdx-licenses": "^1.2", + "composer/xdebug-handler": "^1.1", + "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.0", + "symfony/console": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/filesystem": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/finder": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0" + }, + "conflict": { + "symfony/console": "2.8.38" + }, + "require-dev": { + "phpspec/prophecy": "^1.10", + "symfony/phpunit-bridge": "^3.4" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer" + } + }, + "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" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/composer/issues", + "source": "https://github.com/composer/composer/tree/master" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-03-29T14:59:26+00:00" + }, + { + "name": "composer/semver", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de", + "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "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": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/1.5.1" + }, + "time": "2020-01-13T12:06:48+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.5.3", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "0c3e51e1880ca149682332770e25977c70cf9dae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/0c3e51e1880ca149682332770e25977c70cf9dae", + "reference": "0c3e51e1880ca149682332770e25977c70cf9dae", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "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": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "time": "2020-02-14T07:44:31+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1ab9842d69e64fb3a01be6b656501032d1b78cb7", + "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + }, + "type": "library", + "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/master" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + } + ], + "time": "2020-03-01T12:26:26+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "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": "http://ocramius.github.com/" + } + ], + "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/master" + }, + "time": "2019-10-21T16:45:58+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "5.2.9", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "44c6787311242a979fa15c704327c20e7221a0e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/44c6787311242a979fa15c704327c20e7221a0e4", + "reference": "44c6787311242a979fa15c704327c20e7221a0e4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/justinrainbow/json-schema/issues", + "source": "https://github.com/justinrainbow/json-schema/tree/5.2.9" + }, + "time": "2019-09-25T14:49:45+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "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.9.5" + }, + "time": "2020-01-17T21:11:47+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^2.0", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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)", + "time": "2018-07-08T19:23:20+00:00" + }, + { + "name": "phar-io/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.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", + "time": "2018-07-08T19:19:57+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "time": "2018-08-07T13:53:10+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "shasum": "" + }, + "require": { + "ext-filter": "^7.1", + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0", + "phpdocumentor/type-resolver": "^1.0", + "webmozart/assert": "^1" + }, + "require-dev": { + "doctrine/instantiator": "^1", + "mockery/mockery": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2020-02-22T12:28:44+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", + "shasum": "" + }, + "require": { + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "^7.2", + "mockery/mockery": "~1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/master" + }, + "time": "2020-02-18T18:59:58+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" + }, + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "6.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.1", + "phpunit/php-file-iterator": "^2.0", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.1 || ^4.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "suggest": { + "ext-xdebug": "^2.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.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": "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" + ], + "time": "2018-10-31T16:06:48+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "050bedf145a257b1ff02746c31894800e5122946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "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": "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" + ], + "time": "2018-09-13T20:33:42+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "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" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.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": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2019-06-07T04:22:29+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "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" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2019-09-17T06:23:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "7.5.20", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", + "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.7", + "phar-io/manifest": "^1.0.2", + "phar-io/version": "^2.0", + "php": "^7.1", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^6.0.7", + "phpunit/php-file-iterator": "^2.0.1", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1", + "sebastian/comparator": "^3.0", + "sebastian/diff": "^3.0", + "sebastian/environment": "^4.0", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpunit/phpunit-mock-objects": "*" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.5-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": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2020-01-08T08:45:45+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-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/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "shasum": "" + }, + "require": { + "php": "^7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "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": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-07-12T15:12:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "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": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "time": "2019-02-04T06:01:07+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-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" + ], + "time": "2019-11-20T08:46:58+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-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": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2019-09-14T09:02:43+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "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": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-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/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-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/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "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": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2018-10-04T04:07:39+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "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": "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", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.7.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/e2e5d290e4d2a4f0eb449f510071392e00e10d19", + "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.7.2" + }, + "time": "2019-10-24T14:27:39+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/8800503d56b9867d43d9c303b9cbcc26016e82f0", + "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.1.0" + }, + "time": "2020-02-14T15:25:33+00:00" + }, + { + "name": "symfony/console", + "version": "v5.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "5fa1caadc8cdaa17bcfb25219f3b53fe294a9935" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/5fa1caadc8cdaa17bcfb25219f3b53fe294a9935", + "reference": "5fa1caadc8cdaa17bcfb25219f3b53fe294a9935", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "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": "Symfony Console Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/console/tree/v5.0.7" + }, + "time": "2020-03-30T11:42:42+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "ca3b87dd09fff9b771731637f5379965fbfab420" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ca3b87dd09fff9b771731637f5379965fbfab420", + "reference": "ca3b87dd09fff9b771731637f5379965fbfab420", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "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": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.0.7" + }, + "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" + } + ], + "time": "2020-03-27T16:56:45+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "600a52c29afc0d1caa74acbec8d3095ca7e9910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/600a52c29afc0d1caa74acbec8d3095ca7e9910d", + "reference": "600a52c29afc0d1caa74acbec8d3095ca7e9910d", + "shasum": "" + }, + "require": { + "php": "^7.2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "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": "Symfony Finder Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/5.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" + } + ], + "time": "2020-03-27T16:56:45+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "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.15.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" + } + ], + "time": "2020-02-27T09:26:54+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.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": "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.15.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" + } + ], + "time": "2020-03-09T19:04:49+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", + "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "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 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.15.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" + } + ], + "time": "2020-02-27T09:26:54+00:00" + }, + { + "name": "symfony/process", + "version": "v5.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e", + "reference": "c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e", + "shasum": "" + }, + "require": { + "php": "^7.2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "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": "Symfony Process Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.0.7" + }, + "time": "2020-03-27T16:56:45+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "144c5e51266b281231e947b51223ba14acf1a749" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", + "reference": "144c5e51266b281231e947b51223ba14acf1a749", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "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/v2.0.1" + }, + "time": "2019-11-18T17:27:11+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.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", + "time": "2019-06-13T22:48:21+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6", + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozart/assert/issues", + "source": "https://github.com/webmozart/assert/tree/master" + }, + "time": "2020-04-18T12:12:48+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "composer/composer": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7", + "composer-plugin-api": "^1.1.0 || ^2.0" + }, + "platform-dev": { + "ext-zip": "^1.13" + }, + "plugin-api-version": "1.1.0" +} diff --git a/lib/composer/composer/package-versions-deprecated/phpcs.xml.dist b/lib/composer/composer/package-versions-deprecated/phpcs.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..e169c61679d95b61b80c65f8d2670515d5b15201 --- /dev/null +++ b/lib/composer/composer/package-versions-deprecated/phpcs.xml.dist @@ -0,0 +1,21 @@ + + + + + + + + + + + src + test + + + src/PackageVersions/Versions.php + + + + src/PackageVersions/Installer.php + + diff --git a/lib/composer/composer/package-versions-deprecated/src/PackageVersions/FallbackVersions.php b/lib/composer/composer/package-versions-deprecated/src/PackageVersions/FallbackVersions.php new file mode 100644 index 0000000000000000000000000000000000000000..18e5fe64f627d1506b69904a01443ce3116354ae --- /dev/null +++ b/lib/composer/composer/package-versions-deprecated/src/PackageVersions/FallbackVersions.php @@ -0,0 +1,128 @@ + + */ + private static function getVersions(array $packageData): Generator + { + foreach ($packageData as $package) { + yield $package['name'] => $package['version'] . '@' . ( + $package['source']['reference'] ?? $package['dist']['reference'] ?? '' + ); + } + + yield self::ROOT_PACKAGE_NAME => self::ROOT_PACKAGE_NAME; + } +} diff --git a/lib/composer/composer/package-versions-deprecated/src/PackageVersions/Installer.php b/lib/composer/composer/package-versions-deprecated/src/PackageVersions/Installer.php new file mode 100644 index 0000000000000000000000000000000000000000..b87a7b1529f85ce2c8afbd7a2c3164c2a0a3f033 --- /dev/null +++ b/lib/composer/composer/package-versions-deprecated/src/PackageVersions/Installer.php @@ -0,0 +1,265 @@ + + * @internal + */ + const VERSIONS = %s; + + private function __construct() + { + } + + /** + * @psalm-pure + * + * @psalm-suppress ImpureMethodCall we know that {@see InstalledVersions} interaction does not + * cause any side effects here. + */ + public static function rootPackageName() : string + { + if (!class_exists(InstalledVersions::class, false) || !InstalledVersions::getRawData()) { + return self::ROOT_PACKAGE_NAME; + } + + return InstalledVersions::getRootPackage()['name']; + } + + /** + * @throws OutOfBoundsException If a version cannot be located. + * + * @psalm-param key-of $packageName + * @psalm-pure + * + * @psalm-suppress ImpureMethodCall we know that {@see InstalledVersions} interaction does not + * cause any side effects here. + */ + public static function getVersion(string $packageName): string + { + if (class_exists(InstalledVersions::class, false) && InstalledVersions::getRawData()) { + return InstalledVersions::getPrettyVersion($packageName) + . '@' . InstalledVersions::getReference($packageName); + } + + if (isset(self::VERSIONS[$packageName])) { + return self::VERSIONS[$packageName]; + } + + throw new OutOfBoundsException( + 'Required package "' . $packageName . '" is not installed: check your ./vendor/composer/installed.json and/or ./composer.lock files' + ); + } +} + +PHP; + + public function activate(Composer $composer, IOInterface $io) + { + // Nothing to do here, as all features are provided through event listeners + } + + public function deactivate(Composer $composer, IOInterface $io) + { + // Nothing to do here, as all features are provided through event listeners + } + + public function uninstall(Composer $composer, IOInterface $io) + { + // Nothing to do here, as all features are provided through event listeners + } + + /** + * {@inheritDoc} + */ + public static function getSubscribedEvents(): array + { + return [ScriptEvents::POST_AUTOLOAD_DUMP => 'dumpVersionsClass']; + } + + /** + * @throws RuntimeException + */ + public static function dumpVersionsClass(Event $composerEvent) + { + $composer = $composerEvent->getComposer(); + $rootPackage = $composer->getPackage(); + $versions = iterator_to_array(self::getVersions($composer->getLocker(), $rootPackage)); + + if (! array_key_exists('composer/package-versions-deprecated', $versions)) { + //plugin must be globally installed - we only want to generate versions for projects which specifically + //require composer/package-versions-deprecated + return; + } + + $versionClass = self::generateVersionsClass($rootPackage->getName(), $versions); + + self::writeVersionClassToFile($versionClass, $composer, $composerEvent->getIO()); + } + + /** + * @param string[] $versions + */ + private static function generateVersionsClass(string $rootPackageName, array $versions): string + { + return sprintf( + self::$generatedClassTemplate, + 'fin' . 'al ' . 'cla' . 'ss ' . 'Versions', // note: workaround for regex-based code parsers :-( + $rootPackageName, + var_export($versions, true) + ); + } + + /** + * @throws RuntimeException + */ + private static function writeVersionClassToFile(string $versionClassSource, Composer $composer, IOInterface $io) + { + $installPath = self::locateRootPackageInstallPath($composer->getConfig(), $composer->getPackage()) + . '/src/PackageVersions/Versions.php'; + + $installDir = dirname($installPath); + if (! file_exists($installDir)) { + $io->write('composer/package-versions-deprecated: Package not found (probably scheduled for removal); generation of version class skipped.'); + + return; + } + + if (! is_writable($installDir)) { + $io->write( + sprintf( + 'composer/package-versions-deprecated: %s is not writable; generation of version class skipped.', + $installDir + ) + ); + + return; + } + + $io->write('composer/package-versions-deprecated: Generating version class...'); + + $installPathTmp = $installPath . '_' . uniqid('tmp', true); + file_put_contents($installPathTmp, $versionClassSource); + chmod($installPathTmp, 0664); + rename($installPathTmp, $installPath); + + $io->write('composer/package-versions-deprecated: ...done generating version class'); + } + + /** + * @throws RuntimeException + */ + private static function locateRootPackageInstallPath( + Config $composerConfig, + RootPackageInterface $rootPackage + ): string { + if (self::getRootPackageAlias($rootPackage)->getName() === 'composer/package-versions-deprecated') { + return dirname($composerConfig->get('vendor-dir')); + } + + return $composerConfig->get('vendor-dir') . '/composer/package-versions-deprecated'; + } + + private static function getRootPackageAlias(RootPackageInterface $rootPackage): PackageInterface + { + $package = $rootPackage; + + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + return $package; + } + + /** + * @return Generator&string[] + * + * @psalm-return Generator + */ + private static function getVersions(Locker $locker, RootPackageInterface $rootPackage): Generator + { + $lockData = $locker->getLockData(); + + $lockData['packages-dev'] = $lockData['packages-dev'] ?? []; + + foreach (array_merge($lockData['packages'], $lockData['packages-dev']) as $package) { + yield $package['name'] => $package['version'] . '@' . ( + $package['source']['reference'] ?? $package['dist']['reference'] ?? '' + ); + } + + foreach ($rootPackage->getReplaces() as $replace) { + $version = $replace->getPrettyConstraint(); + if ($version === 'self.version') { + $version = $rootPackage->getPrettyVersion(); + } + + yield $replace->getTarget() => $version . '@' . $rootPackage->getSourceReference(); + } + + yield $rootPackage->getName() => $rootPackage->getPrettyVersion() . '@' . $rootPackage->getSourceReference(); + } +} diff --git a/lib/composer/composer/package-versions-deprecated/src/PackageVersions/Versions.php b/lib/composer/composer/package-versions-deprecated/src/PackageVersions/Versions.php new file mode 100644 index 0000000000000000000000000000000000000000..67e8d3d1495f55ec6a7a4b80a6d6b559ea3bde84 --- /dev/null +++ b/lib/composer/composer/package-versions-deprecated/src/PackageVersions/Versions.php @@ -0,0 +1,129 @@ + + * @internal + */ + const VERSIONS = array ( + 'amphp/amp' => 'v2.5.0@f220a51458bf4dd0dedebb171ac3457813c72bbc', + 'amphp/byte-stream' => 'v1.8.0@f0c20cf598a958ba2aa8c6e5a71c697d652c7088', + 'composer/package-versions-deprecated' => '1.11.99@c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855', + 'composer/semver' => '1.7.1@38276325bd896f90dfcfe30029aa5db40df387a7', + 'composer/xdebug-handler' => '1.4.3@ebd27a9866ae8254e873866f795491f02418c5a5', + 'dnoegel/php-xdg-base-dir' => 'v0.1.1@8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd', + 'doctrine/annotations' => '1.10.3@5db60a4969eba0e0c197a19c077780aadbc43c5d', + 'doctrine/lexer' => '1.2.1@e864bbf5904cb8f5bb334f99209b48018522f042', + 'felixfbecker/advanced-json-rpc' => 'v3.1.1@0ed363f8de17d284d479ec813c9ad3f6834b5c40', + 'felixfbecker/language-server-protocol' => 'v1.4.0@378801f6139bb74ac215d81cca1272af61df9a9f', + 'friendsofphp/php-cs-fixer' => 'v2.16.3@83baf823a33a1cbd5416c8626935cf3f843c10b0', + 'netresearch/jsonmapper' => 'v2.1.0@e0f1e33a71587aca81be5cffbb9746510e1fe04e', + 'nextcloud/coding-standard' => 'v0.3.0@4f5cd012760f8293e19e602651a0ecaa265e4db9', + 'nikic/php-parser' => 'v4.10.2@658f1be311a230e0907f5dfe0213742aff0596de', + 'openlss/lib-array2xml' => '1.0.0@a91f18a8dfc69ffabe5f9b068bc39bb202c81d90', + 'paragonie/random_compat' => 'v9.99.99@84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95', + 'php-cs-fixer/diff' => 'v1.3.0@78bb099e9c16361126c86ce82ec4405ebab8e756', + 'phpdocumentor/reflection-common' => '2.2.0@1d01c49d4ed62f25aa84a747ad35d5a16924662b', + 'phpdocumentor/reflection-docblock' => '5.2.2@069a785b2141f5bcf49f3e353548dc1cce6df556', + 'phpdocumentor/type-resolver' => '1.4.0@6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0', + 'psr/container' => '1.0.0@b7ce3b176482dbbc1245ebf52b181af44c2cf55f', + 'psr/event-dispatcher' => '1.0.0@dbefd12671e8a14ec7f180cab83036ed26714bb0', + 'psr/log' => '1.1.3@0f73288fd15629204f9d42b7055f72dacbe811fc', + 'sebastian/diff' => '4.0.3@ffc949a1a2aae270ea064453d7535b82e4c32092', + 'symfony/console' => 'v5.1.7@ae789a8a2ad189ce7e8216942cdb9b77319f5eb8', + 'symfony/deprecation-contracts' => 'v2.1.2@dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337', + 'symfony/event-dispatcher' => 'v5.1.2@cc0d059e2e997e79ca34125a52f3e33de4424ac7', + 'symfony/event-dispatcher-contracts' => 'v2.1.2@405952c4e90941a17e52ef7489a2bd94870bb290', + 'symfony/filesystem' => 'v5.1.2@6e4320f06d5f2cce0d96530162491f4465179157', + 'symfony/finder' => 'v5.1.2@4298870062bfc667cb78d2b379be4bf5dec5f187', + 'symfony/options-resolver' => 'v5.1.2@663f5dd5e14057d1954fe721f9709d35837f2447', + 'symfony/polyfill-ctype' => 'v1.18.1@1c302646f6efc070cd46856e600e5e0684d6b454', + 'symfony/polyfill-intl-grapheme' => 'v1.18.1@b740103edbdcc39602239ee8860f0f45a8eb9aa5', + 'symfony/polyfill-intl-normalizer' => 'v1.18.1@37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e', + 'symfony/polyfill-mbstring' => 'v1.18.1@a6977d63bf9a0ad4c65cd352709e230876f9904a', + 'symfony/polyfill-php70' => 'v1.17.1@471b096aede7025bace8eb356b9ac801aaba7e2d', + 'symfony/polyfill-php72' => 'v1.17.0@f048e612a3905f34931127360bdd2def19a5e582', + 'symfony/polyfill-php73' => 'v1.18.1@fffa1a52a023e782cdcc221d781fe1ec8f87fcca', + 'symfony/polyfill-php80' => 'v1.18.1@d87d5766cbf48d72388a9f6b85f280c8ad51f981', + 'symfony/process' => 'v5.1.2@7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1', + 'symfony/service-contracts' => 'v2.2.0@d15da7ba4957ffb8f1747218be9e1a121fd298a1', + 'symfony/stopwatch' => 'v5.1.2@0f7c58cf81dbb5dd67d423a89d577524a2ec0323', + 'symfony/string' => 'v5.1.7@4a9afe9d07bac506f75bcee8ed3ce76da5a9343e', + 'vimeo/psalm' => '4.0.1@b1e2e30026936ef8d5bf6a354d1c3959b6231f44', + 'webmozart/assert' => '1.9.1@bafc69caeb4d49c39fd0779086c03a3738cbb389', + 'webmozart/glob' => '4.1.0@3cbf63d4973cf9d780b93d2da8eec7e4a9e63bbe', + 'webmozart/path-util' => '2.3.0@d939f7edc24c9a1bb9c0dee5cb05d8e859490725', + '__root__' => 'dev-master@11fca45e4c9ed5bc53436b6232a656a51f4984fa', +); + + private function __construct() + { + } + + /** + * @psalm-pure + * + * @psalm-suppress ImpureMethodCall we know that {@see InstalledVersions} interaction does not + * cause any side effects here. + */ + public static function rootPackageName() : string + { + if (!class_exists(InstalledVersions::class, false) || !InstalledVersions::getRawData()) { + return self::ROOT_PACKAGE_NAME; + } + + return InstalledVersions::getRootPackage()['name']; + } + + /** + * @throws OutOfBoundsException If a version cannot be located. + * + * @psalm-param key-of $packageName + * @psalm-pure + * + * @psalm-suppress ImpureMethodCall we know that {@see InstalledVersions} interaction does not + * cause any side effects here. + */ + public static function getVersion(string $packageName): string + { + if (class_exists(InstalledVersions::class, false) && InstalledVersions::getRawData()) { + return InstalledVersions::getPrettyVersion($packageName) + . '@' . InstalledVersions::getReference($packageName); + } + + if (isset(self::VERSIONS[$packageName])) { + return self::VERSIONS[$packageName]; + } + + throw new OutOfBoundsException( + 'Required package "' . $packageName . '" is not installed: check your ./vendor/composer/installed.json and/or ./composer.lock files' + ); + } +} diff --git a/lib/composer/composer/platform_check.php b/lib/composer/composer/platform_check.php new file mode 100644 index 0000000000000000000000000000000000000000..7bee0cee6314a8238dcda5af318200116e8d69ef --- /dev/null +++ b/lib/composer/composer/platform_check.php @@ -0,0 +1,31 @@ += 70300)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.3.0". You are running ' . PHP_VERSION . '.'; +} + +$missingExtensions = array(); +extension_loaded('dom') || $missingExtensions[] = 'dom'; +extension_loaded('filter') || $missingExtensions[] = 'filter'; +extension_loaded('json') || $missingExtensions[] = 'json'; +extension_loaded('libxml') || $missingExtensions[] = 'libxml'; +extension_loaded('mbstring') || $missingExtensions[] = 'mbstring'; +extension_loaded('pcre') || $missingExtensions[] = 'pcre'; +extension_loaded('pdo') || $missingExtensions[] = 'pdo'; +extension_loaded('reflection') || $missingExtensions[] = 'reflection'; +extension_loaded('simplexml') || $missingExtensions[] = 'simplexml'; +extension_loaded('spl') || $missingExtensions[] = 'spl'; +extension_loaded('tokenizer') || $missingExtensions[] = 'tokenizer'; + +if ($missingExtensions) { + $issues[] = 'Your Composer dependencies require the following PHP extensions to be installed: ' . implode(', ', $missingExtensions); +} + +if ($issues) { + echo 'Composer detected issues in your platform:' . "\n\n" . implode("\n", $issues); + exit(104); +} diff --git a/lib/composer/composer/semver/CHANGELOG.md b/lib/composer/composer/semver/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..c2dbd3fbfb21e8d19c38fd37db63aa1583d0aa6c --- /dev/null +++ b/lib/composer/composer/semver/CHANGELOG.md @@ -0,0 +1,102 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +### [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. + +[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/lib/composer/composer/semver/LICENSE b/lib/composer/composer/semver/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..466975862624c73fa7ba3d55e0e05f7be6d6e0a6 --- /dev/null +++ b/lib/composer/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/lib/composer/composer/semver/README.md b/lib/composer/composer/semver/README.md new file mode 100644 index 0000000000000000000000000000000000000000..409b9dcbaeb41d18be6c13ba9f379588016311ae --- /dev/null +++ b/lib/composer/composer/semver/README.md @@ -0,0 +1,70 @@ +composer/semver +=============== + +Semver 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. + +[![Build Status](https://travis-ci.org/composer/semver.svg?branch=master)](https://travis-ci.org/composer/semver) + + +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` 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` class provides the following methods: + +* satisfies($version, $constraints) +* satisfiedBy(array $versions, $constraint) +* sort($versions) +* rsort($versions) + + +License +------- + +composer/semver is licensed under the MIT License, see the LICENSE file for details. diff --git a/lib/composer/composer/semver/composer.json b/lib/composer/composer/semver/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..981e7d1552f083415d913ab9f127b7a9698346bd --- /dev/null +++ b/lib/composer/composer/semver/composer.json @@ -0,0 +1,57 @@ +{ + "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": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5" + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Semver\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "scripts": { + "test": "phpunit" + } +} diff --git a/lib/composer/composer/semver/src/Comparator.php b/lib/composer/composer/semver/src/Comparator.php new file mode 100644 index 0000000000000000000000000000000000000000..a9d758f1c0ceea21c7a8fc6ffb865339cce6b12f --- /dev/null +++ b/lib/composer/composer/semver/src/Comparator.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 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 + */ + public static function compare($version1, $operator, $version2) + { + $constraint = new Constraint($operator, $version2); + + return $constraint->matches(new Constraint('==', $version1)); + } +} diff --git a/lib/composer/composer/semver/src/Constraint/AbstractConstraint.php b/lib/composer/composer/semver/src/Constraint/AbstractConstraint.php new file mode 100644 index 0000000000000000000000000000000000000000..7b5270fa350acaf0b5cc57abd807b6af46410474 --- /dev/null +++ b/lib/composer/composer/semver/src/Constraint/AbstractConstraint.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 Composer\Semver\Constraint; + +trigger_error('The ' . __NAMESPACE__ . '\AbstractConstraint abstract class is deprecated, there is no replacement for it, it will be removed in the next major version.', E_USER_DEPRECATED); + +/** + * Base constraint class. + */ +abstract class AbstractConstraint implements ConstraintInterface +{ + /** @var string */ + protected $prettyString; + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + if ($provider instanceof $this) { + // see note at bottom of this class declaration + return $this->matchSpecific($provider); + } + + // turn matching around to find a match + return $provider->matches($this); + } + + /** + * @param string $prettyString + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * @return string + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return $this->__toString(); + } + + // implementations must implement a method of this format: + // not declared abstract here because type hinting violates parameter coherence (TODO right word?) + // public function matchSpecific( $provider); +} diff --git a/lib/composer/composer/semver/src/Constraint/Constraint.php b/lib/composer/composer/semver/src/Constraint/Constraint.php new file mode 100644 index 0000000000000000000000000000000000000000..0f28d64357c8dbf22c81065dd456aa73bc7817e4 --- /dev/null +++ b/lib/composer/composer/semver/src/Constraint/Constraint.php @@ -0,0 +1,215 @@ + + * + * 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 to integer translation table. + * + * @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 + */ + private static $transOpInt = array( + self::OP_EQ => '==', + self::OP_LT => '<', + self::OP_LE => '<=', + self::OP_GT => '>', + self::OP_GE => '>=', + self::OP_NE => '!=', + ); + + /** @var int */ + protected $operator; + + /** @var string */ + protected $version; + + /** @var string */ + protected $prettyString; + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + if ($provider instanceof $this) { + return $this->matchSpecific($provider); + } + + // turn matching around to find a match + return $provider->matches($this); + } + + /** + * @param string $prettyString + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * @return string + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return $this->__toString(); + } + + /** + * Get all supported comparison operators. + * + * @return array + */ + public static function getSupportedOperators() + { + return array_keys(self::$transOpStr); + } + + /** + * Sets operator and version to compare with. + * + * @param string $operator + * @param string $version + * + * @throws \InvalidArgumentException if invalid operator is given. + */ + 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; + } + + /** + * @param string $a + * @param string $b + * @param string $operator + * @param bool $compareBranches + * + * @throws \InvalidArgumentException if invalid operator is given. + * + * @return bool + */ + 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 = 'dev-' === substr($a, 0, 4); + $bIsBranch = 'dev-' === substr($b, 0, 4); + + 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); + } + + /** + * @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) { + return (!$isEqualOp && !$isProviderEqualOp) + || $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 true; + } + + if ($this->versionCompare($provider->version, $this->version, self::$transOpInt[$this->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 !($provider->version === $this->version + && self::$transOpInt[$provider->operator] === $providerNoEqualOp + && self::$transOpInt[$this->operator] !== $noEqualOp); + } + + return false; + } + + /** + * @return string + */ + public function __toString() + { + return self::$transOpInt[$this->operator] . ' ' . $this->version; + } +} diff --git a/lib/composer/composer/semver/src/Constraint/ConstraintInterface.php b/lib/composer/composer/semver/src/Constraint/ConstraintInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..7cb13b6a86e4f09e4b296c24b1c3e83eff644164 --- /dev/null +++ b/lib/composer/composer/semver/src/Constraint/ConstraintInterface.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 Composer\Semver\Constraint; + +interface ConstraintInterface +{ + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider); + + /** + * @return string + */ + public function getPrettyString(); + + /** + * @return string + */ + public function __toString(); +} diff --git a/lib/composer/composer/semver/src/Constraint/EmptyConstraint.php b/lib/composer/composer/semver/src/Constraint/EmptyConstraint.php new file mode 100644 index 0000000000000000000000000000000000000000..a082b80928666170c3dd84e36aaef29ee57417f0 --- /dev/null +++ b/lib/composer/composer/semver/src/Constraint/EmptyConstraint.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 Composer\Semver\Constraint; + +/** + * Defines the absence of a constraint. + */ +class EmptyConstraint implements ConstraintInterface +{ + /** @var string */ + protected $prettyString; + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + return true; + } + + /** + * @param string $prettyString + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * @return string + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return (string) $this; + } + + /** + * @return string + */ + public function __toString() + { + return '[]'; + } +} diff --git a/lib/composer/composer/semver/src/Constraint/MultiConstraint.php b/lib/composer/composer/semver/src/Constraint/MultiConstraint.php new file mode 100644 index 0000000000000000000000000000000000000000..911285302dabd3d1745c9534ed5501630a345a4b --- /dev/null +++ b/lib/composer/composer/semver/src/Constraint/MultiConstraint.php @@ -0,0 +1,120 @@ + + * + * 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[] */ + protected $constraints; + + /** @var string|null */ + protected $prettyString; + + /** @var bool */ + protected $conjunctive; + + /** + * @param ConstraintInterface[] $constraints A set of constraints + * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive + */ + public function __construct(array $constraints, $conjunctive = true) + { + $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; + } + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + if (false === $this->conjunctive) { + foreach ($this->constraints as $constraint) { + if ($constraint->matches($provider)) { + return true; + } + } + + return false; + } + + foreach ($this->constraints as $constraint) { + if (!$constraint->matches($provider)) { + return false; + } + } + + return true; + } + + /** + * @param string|null $prettyString + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * @return string + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return (string) $this; + } + + /** + * @return string + */ + public function __toString() + { + $constraints = array(); + foreach ($this->constraints as $constraint) { + $constraints[] = (string) $constraint; + } + + return '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']'; + } +} diff --git a/lib/composer/composer/semver/src/Semver.php b/lib/composer/composer/semver/src/Semver.php new file mode 100644 index 0000000000000000000000000000000000000000..4f312d73d32efa5b44776409d93cdbff43489263 --- /dev/null +++ b/lib/composer/composer/semver/src/Semver.php @@ -0,0 +1,127 @@ + + * + * 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 array $versions + * @param string $constraints + * + * @return array + */ + 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 array $versions + * + * @return array + */ + public static function sort(array $versions) + { + return self::usort($versions, self::SORT_ASC); + } + + /** + * Sort given array of versions in reverse. + * + * @param array $versions + * + * @return array + */ + public static function rsort(array $versions) + { + return self::usort($versions, self::SORT_DESC); + } + + /** + * @param array $versions + * @param int $direction + * + * @return array + */ + 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) { + $normalized[] = array($versionParser->normalize($version), $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/lib/composer/composer/semver/src/VersionParser.php b/lib/composer/composer/semver/src/VersionParser.php new file mode 100644 index 0000000000000000000000000000000000000000..5af0fa307fb912f1b6a5bb8fa6f6d7297592a49c --- /dev/null +++ b/lib/composer/composer/semver/src/VersionParser.php @@ -0,0 +1,578 @@ + + * + * 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\EmptyConstraint; +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 + */ + public static function parseStability($version) + { + $version = preg_replace('{#.+$}i', '', $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($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($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])); + } + + // match master-like branches + if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) { + return '9999999-dev'; + } + + // 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})?)' . self::$modifierRegex . '$}i', $version, $matches)) { + $version = 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', $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($name); + + if (in_array($name, array('master', 'trunk', 'default'))) { + return $this->normalize($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; + } + + /** + * Parses a constraint string into MultiConstraint and/or Constraint objects. + * + * @param string $constraints + * + * @return ConstraintInterface + */ + public function parseConstraints($constraints) + { + $prettyConstraint = $constraints; + + $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints)); + $orGroups = array(); + + foreach ($orConstraints as $constraints) { + $andConstraints = preg_split('{(?< ,]) *(? 1) { + $constraintObjects = array(); + foreach ($andConstraints as $constraint) { + foreach ($this->parseConstraint($constraint) as $parsedConstraint) { + $constraintObjects[] = $parsedConstraint; + } + } + } else { + $constraintObjects = $this->parseConstraint($andConstraints[0]); + } + + if (1 === count($constraintObjects)) { + $constraint = $constraintObjects[0]; + } else { + $constraint = new MultiConstraint($constraintObjects); + } + + $orGroups[] = $constraint; + } + + if (1 === count($orGroups)) { + $constraint = $orGroups[0]; + } elseif (2 === count($orGroups) + // parse the two OR groups and if they are contiguous we collapse + // them into one constraint + && $orGroups[0] instanceof MultiConstraint + && $orGroups[1] instanceof MultiConstraint + && 2 === count($orGroups[0]->getConstraints()) + && 2 === count($orGroups[1]->getConstraints()) + && ($a = (string) $orGroups[0]) + && strpos($a, '[>=') === 0 && (false !== ($posA = strpos($a, '<', 4))) + && ($b = (string) $orGroups[1]) + && strpos($b, '[>=') === 0 && (false !== ($posB = strpos($b, '<', 4))) + && substr($a, $posA + 2, -1) === substr($b, 4, $posB - 5) + ) { + $constraint = new MultiConstraint(array( + new Constraint('>=', substr($a, 4, $posA - 5)), + new Constraint('<', substr($b, $posB + 2, -1)), + )); + } else { + $constraint = new MultiConstraint($orGroups, false); + } + + $constraint->setPrettyString($prettyConstraint); + + return $constraint; + } + + /** + * @param string $constraint + * + * @throws \UnexpectedValueException + * + * @return 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)) { + return array(new EmptyConstraint()); + } + + $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 + */ + private function manipulateVersionString($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/lib/composer/composer/xdebug-handler/CHANGELOG.md b/lib/composer/composer/xdebug-handler/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..e580779359a121aa42f64bee419380c1ae8b73ca --- /dev/null +++ b/lib/composer/composer/xdebug-handler/CHANGELOG.md @@ -0,0 +1,78 @@ +## [Unreleased] + +## [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/1.4.3...HEAD +[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/lib/composer/composer/xdebug-handler/LICENSE b/lib/composer/composer/xdebug-handler/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..963618a14e82573e7293f29667a42548bdf90d6b --- /dev/null +++ b/lib/composer/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/lib/composer/composer/xdebug-handler/README.md b/lib/composer/composer/xdebug-handler/README.md new file mode 100644 index 0000000000000000000000000000000000000000..57fb9adc0d793730848b2db27514ccab7dcf0265 --- /dev/null +++ b/lib/composer/composer/xdebug-handler/README.md @@ -0,0 +1,293 @@ +# composer/xdebug-handler + +[![packagist](https://img.shields.io/packagist/v/composer/xdebug-handler.svg)](https://packagist.org/packages/composer/xdebug-handler) +[![linux build](https://img.shields.io/travis/composer/xdebug-handler/master.svg?label=linux+build)](https://travis-ci.org/composer/xdebug-handler) +[![windows build](https://img.shields.io/appveyor/ci/Seldaek/xdebug-handler/master.svg?label=windows+build)](https://ci.appveyor.com/project/Seldaek/xdebug-handler) +![license](https://img.shields.io/github/license/composer/xdebug-handler.svg) +![php](https://img.shields.io/packagist/php-v/composer/xdebug-handler.svg?colorB=8892BF&label=php) + +Restart a CLI process without loading the Xdebug extension. + +Originally written as part of [composer/composer](https://github.com/composer/composer), +now extracted and made available as a stand-alone library. + +## Installation + +Install the latest version with: + +```bash +$ composer require composer/xdebug-handler +``` + +## Requirements + +* PHP 5.3.2 minimum, although functionality is disabled below PHP 5.4.0. 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 two parameters: + +#### _$envPrefix_ +This is used to create distinct environment variables and is upper-cased and prepended to default base values. 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 + +#### _$colorOption_ +This optional value is added to the restart command-line and is needed to force color output in a piped child process. Only long-options are supported, for example `--ansi` or `--colors=always` etc. + +If the original command-line contains an argument that pattern matches this value (for example `--no-ansi`, `--colors=never`) then _$colorOption_ is ignored. + +If the pattern match ends with `=auto` (for example `--colors=auto`), the argument is replaced by _$colorOption_. Otherwise it is added at either the end of the command-line, or preceding the first double-dash `--` delimiter. + +## 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 using `passthru`. + * 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 +From PHP 7.1 with the pcntl extension loaded, asynchronous signal handling is automatically enabled. `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). + +### 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). +* On Windows `sapi_windows_set_ctrl_handler` handlers will not receive CTRL events. + +### Helper methods +These static methods provide information from the current process, regardless of whether it has been restarted or not. + +#### _getAllIniFiles()_ +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()_ +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()_ +Returns the Xdebug version string that was skipped by the restart, or an empty value 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: '2.6.0' (for example), or an empty string +``` + +### Setter methods +These methods implement a fluent interface and must be called before the main `check()` method. + +#### _setLogger($logger)_ +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): + +``` +// Restart overridden +DEBUG Checking MYAPP_ALLOW_XDEBUG +DEBUG The Xdebug extension is loaded (2.5.0) +DEBUG No restart (MYAPP_ALLOW_XDEBUG=1) + +// Failed restart +DEBUG Checking MYAPP_ALLOW_XDEBUG +DEBUG The Xdebug extension is loaded (2.5.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($script)_ +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()_ +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($isLoaded)_ +By default the process will restart if Xdebug is loaded. 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($command)_ +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. + +Note that the `$command` parameter is the escaped command-line string that will be used for the new process and must be treated accordingly. + +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($isLoaded) + { + if (Command::isHelp()) { + # No need to disable Xdebug for this + return false; + } + + $this->required = (bool) ini_get('phar.readonly'); + return $isLoaded || $this->required; + } + + protected function restart($command) + { + 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/lib/composer/composer/xdebug-handler/composer.json b/lib/composer/composer/xdebug-handler/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..ea40a2f21eff85e0f3b0ddd833338351d637fd39 --- /dev/null +++ b/lib/composer/composer/xdebug-handler/composer.json @@ -0,0 +1,40 @@ +{ + "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": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + }, + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\XdebugHandler\\": "tests" + } + }, + "scripts": { + "test": "phpunit" + } +} diff --git a/lib/composer/composer/xdebug-handler/src/PhpConfig.php b/lib/composer/composer/xdebug-handler/src/PhpConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..5535eca56e4b3b497c3fd3b262b58fa925fc619f --- /dev/null +++ b/lib/composer/composer/xdebug-handler/src/PhpConfig.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 Composer\XdebugHandler; + +/** + * @author John Stevenson + */ +class PhpConfig +{ + /** + * Use the original PHP configuration + * + * @return array PHP cli options + */ + public function useOriginal() + { + $this->getDataAndReset(); + return array(); + } + + /** + * Use standard restart settings + * + * @return array PHP cli options + */ + public function useStandard() + { + if ($data = $this->getDataAndReset()) { + return array('-n', '-c', $data['tmpIni']); + } + + return array(); + } + + /** + * Use environment variables to persist settings + * + * @return array PHP cli options + */ + public function usePersistent() + { + if ($data = $this->getDataAndReset()) { + Process::setEnv('PHPRC', $data['tmpIni']); + Process::setEnv('PHP_INI_SCAN_DIR', ''); + } + + return array(); + } + + /** + * Returns restart data if available and resets the environment + * + * @return array|null + */ + private function getDataAndReset() + { + if ($data = XdebugHandler::getRestartSettings()) { + Process::setEnv('PHPRC', $data['phprc']); + Process::setEnv('PHP_INI_SCAN_DIR', $data['scanDir']); + } + + return $data; + } +} diff --git a/lib/composer/composer/xdebug-handler/src/Process.php b/lib/composer/composer/xdebug-handler/src/Process.php new file mode 100644 index 0000000000000000000000000000000000000000..eb2ad2b4ed1f13ad510d36495587cca420a6b2ff --- /dev/null +++ b/lib/composer/composer/xdebug-handler/src/Process.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\XdebugHandler; + +/** + * Provides utility functions to prepare a child process command-line and set + * environment variables in that process. + * + * @author John Stevenson + * @internal + */ +class Process +{ + /** + * Returns an array of parameters, including a color option if required + * + * A color option is needed because child process output is piped. + * + * @param array $args The script parameters + * @param string $colorOption The long option to force color output + * + * @return array + */ + public static function addColorOption(array $args, $colorOption) + { + if (!$colorOption + || in_array($colorOption, $args) + || !preg_match('/^--([a-z]+$)|(^--[a-z]+=)/', $colorOption, $matches)) { + return $args; + } + + if (isset($matches[2])) { + // Handle --color(s)= options + if (false !== ($index = array_search($matches[2].'auto', $args))) { + $args[$index] = $colorOption; + return $args; + } elseif (preg_grep('/^'.$matches[2].'/', $args)) { + return $args; + } + } elseif (in_array('--no-'.$matches[1], $args)) { + return $args; + } + + // Check for NO_COLOR variable (https://no-color.org/) + if (false !== getenv('NO_COLOR')) { + return $args; + } + + if (false !== ($index = array_search('--', $args))) { + // Position option before double-dash delimiter + array_splice($args, $index, 0, $colorOption); + } else { + $args[] = $colorOption; + } + + return $args; + } + + /** + * 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 + * + * @return string The escaped argument + */ + public static function escape($arg, $meta = true, $module = false) + { + 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_match('/%[^%]+%/', $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; + } + + /** + * Returns true if the output stream supports colors + * + * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo + * terminals via named pipes, so we can only check the environment. + * + * @param mixed $output A valid CLI output stream + * + * @return bool + */ + public static function supportsColor($output) + { + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + return (function_exists('sapi_windows_vt100_support') + && sapi_windows_vt100_support($output)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + if (function_exists('stream_isatty')) { + return stream_isatty($output); + } + + if (function_exists('posix_isatty')) { + return posix_isatty($output); + } + + $stat = fstat($output); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } + + /** + * Makes putenv environment changes available in $_SERVER and $_ENV + * + * @param string $name + * @param string|false $value A false value unsets the variable + * + * @return bool Whether the environment variable was set + */ + public static function setEnv($name, $value = false) + { + $unset = false === $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/lib/composer/composer/xdebug-handler/src/Status.php b/lib/composer/composer/xdebug-handler/src/Status.php new file mode 100644 index 0000000000000000000000000000000000000000..e714b1c2c665b8bf14c2f399ce44aa6b6ed1f548 --- /dev/null +++ b/lib/composer/composer/xdebug-handler/src/Status.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +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'; + + private $debug; + private $envAllowXdebug; + private $loaded; + private $logger; + private $time; + + /** + * Constructor + * + * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name + * @param bool $debug Whether debug output is required + */ + public function __construct($envAllowXdebug, $debug) + { + $start = getenv(self::ENV_RESTART); + Process::setEnv(self::ENV_RESTART); + $this->time = $start ? round((microtime(true) - $start) * 1000) : 0; + + $this->envAllowXdebug = $envAllowXdebug; + $this->debug = $debug && defined('STDERR'); + } + + /** + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Calls a handler method to report a message + * + * @param string $op The handler constant + * @param null|string $data Data required by the handler + */ + public function report($op, $data) + { + if ($this->logger || $this->debug) { + call_user_func(array($this, 'report'.$op), $data); + } + } + + /** + * Outputs a status message + * + * @param string $text + * @param string $level + */ + private function output($text, $level = null) + { + if ($this->logger) { + $this->logger->log($level ?: LogLevel::DEBUG, $text); + } + + if ($this->debug) { + fwrite(STDERR, sprintf('xdebug-handler[%d] %s', getmypid(), $text.PHP_EOL)); + } + } + + private function reportCheck($loaded) + { + $this->loaded = $loaded; + $this->output('Checking '.$this->envAllowXdebug); + } + + private function reportError($error) + { + $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING); + } + + private function reportInfo($info) + { + $this->output($info); + } + + private function reportNoRestart() + { + $this->output($this->getLoadedMessage()); + + if ($this->loaded) { + $text = sprintf('No restart (%s)', $this->getEnvAllow()); + if (!getenv($this->envAllowXdebug)) { + $text .= ' Allowed by application'; + } + $this->output($text); + } + } + + private function reportRestart() + { + $this->output($this->getLoadedMessage()); + Process::setEnv(self::ENV_RESTART, (string) microtime(true)); + } + + private function reportRestarted() + { + $loaded = $this->getLoadedMessage(); + $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded); + $level = $this->loaded ? LogLevel::WARNING : null; + $this->output($text, $level); + } + + private function reportRestarting($command) + { + $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 + * + * @return string + */ + private function getEnvAllow() + { + return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug); + } + + /** + * Returns the Xdebug status and version + * + * @return string + */ + private function getLoadedMessage() + { + $loaded = $this->loaded ? sprintf('loaded (%s)', $this->loaded) : 'not loaded'; + return 'The Xdebug extension is '.$loaded; + } +} diff --git a/lib/composer/composer/xdebug-handler/src/XdebugHandler.php b/lib/composer/composer/xdebug-handler/src/XdebugHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..ed5107ef00cd9dc7d2febd463ac613cca075cd9a --- /dev/null +++ b/lib/composer/composer/xdebug-handler/src/XdebugHandler.php @@ -0,0 +1,597 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\XdebugHandler; + +use Psr\Log\LoggerInterface; + +/** + * @author John Stevenson + */ +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; + + private static $inRestart; + private static $name; + private static $skipped; + + private $cli; + private $colorOption; + private $debug; + private $envAllowXdebug; + private $envOriginalInis; + private $loaded; + private $persistent; + private $script; + /** @var Status|null */ + 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 + * @param string $colorOption Command-line long option to force color output + * @throws \RuntimeException If a parameter is invalid + */ + public function __construct($envPrefix, $colorOption = '') + { + if (!is_string($envPrefix) || empty($envPrefix) || !is_string($colorOption)) { + throw new \RuntimeException('Invalid constructor parameter'); + } + + self::$name = strtoupper($envPrefix); + $this->envAllowXdebug = self::$name.self::SUFFIX_ALLOW; + $this->envOriginalInis = self::$name.self::SUFFIX_INIS; + + $this->colorOption = $colorOption; + + if (extension_loaded('xdebug')) { + $ext = new \ReflectionExtension('xdebug'); + $this->loaded = $ext->getVersion() ?: 'unknown'; + } + + if ($this->cli = PHP_SAPI === 'cli') { + $this->debug = getenv(self::DEBUG); + } + + $this->statusWriter = new Status($this->envAllowXdebug, (bool) $this->debug); + } + + /** + * Activates status message output to a PSR3 logger + * + * @param LoggerInterface $logger + * + * @return $this + */ + public function setLogger(LoggerInterface $logger) + { + $this->statusWriter->setLogger($logger); + return $this; + } + + /** + * Sets the main script location if it cannot be called from argv + * + * @param string $script + * + * @return $this + */ + public function setMainScript($script) + { + $this->script = $script; + return $this; + } + + /** + * Persist the settings to keep Xdebug out of sub-processes + * + * @return $this + */ + public function setPersistent() + { + $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() + { + $this->notify(Status::CHECK, $this->loaded); + $envArgs = explode('|', (string) getenv($this->envAllowXdebug)); + + if (empty($envArgs[0]) && $this->requiresRestart((bool) $this->loaded)) { + // 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 (!$this->loaded) { + // 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); + + if ($settings = self::getRestartSettings()) { + // 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 array + */ + public static function getAllIniFiles() + { + if (!empty(self::$name)) { + $env = getenv(self::$name.self::SUFFIX_INIS); + + if (false !== $env) { + return explode(PATH_SEPARATOR, $env); + } + } + + $paths = array((string) php_ini_loaded_file()); + + if ($scanned = php_ini_scanned_files()) { + $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. + * + * @return array|null + */ + public static function getRestartSettings() + { + $envArgs = explode('|', (string) getenv(self::RESTART_SETTINGS)); + + if (count($envArgs) !== 6 + || (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) { + return; + } + + return array( + '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 + * + * @return string + */ + public static function getSkippedVersion() + { + return (string) self::$skipped; + } + + /** + * Returns true if Xdebug is loaded, or as directed by an extending class + * + * @param bool $isLoaded Whether Xdebug is loaded + * + * @return bool + */ + protected function requiresRestart($isLoaded) + { + return $isLoaded; + } + + /** + * Allows an extending class to access the tmpIni + * + * @param string $command + */ + protected function restart($command) + { + $this->doRestart($command); + } + + /** + * Executes the restarted command then deletes the tmp ini + * + * @param string $command + */ + private function doRestart($command) + { + $this->tryEnableSignals(); + $this->notify(Status::RESTARTING, $command); + + passthru($command, $exitCode); + $this->notify(Status::INFO, 'Restarted process exited '.$exitCode); + + if ($this->debug === '2') { + $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni); + } else { + @unlink($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 + * + * @return bool + */ + private function prepareRestart() + { + $error = ''; + $iniFiles = self::getAllIniFiles(); + $scannedInis = count($iniFiles) > 1; + $tmpDir = sys_get_temp_dir(); + + if (!$this->cli) { + $error = 'Unsupported SAPI: '.PHP_SAPI; + } elseif (!defined('PHP_BINARY')) { + $error = 'PHP version is too old: '.PHP_VERSION; + } elseif (!$this->checkConfiguration($info)) { + $error = $info; + } elseif (!$this->checkScanDirConfig()) { + $error = 'PHP version does not report scanned inis: '.PHP_VERSION; + } elseif (!$this->checkMainScript()) { + $error = 'Unable to access main script: '.$this->script; + } elseif (!$this->writeTmpIni($iniFiles, $tmpDir, $error)) { + $error = $error ?: 'Unable to create temp ini file at: '.$tmpDir; + } elseif (!$this->setEnvironment($scannedInis, $iniFiles)) { + $error = 'Unable to set environment variables'; + } + + if ($error) { + $this->notify(Status::ERROR, $error); + } + + return empty($error); + } + + /** + * Returns true if the tmp ini file was written + * + * @param array $iniFiles All ini files used in the current process + * @param string $tmpDir The system temporary directory + * @param string $error Set by method if ini file cannot be read + * + * @return bool + */ + private function writeTmpIni(array $iniFiles, $tmpDir, &$error) + { + if (!$this->tmpIni = @tempnam($tmpDir, '')) { + return false; + } + + // $iniFiles has at least one item and it may be empty + if (empty($iniFiles[0])) { + array_shift($iniFiles); + } + + $content = ''; + $regex = '/^\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; + } + $content .= preg_replace($regex, ';$1', $data).PHP_EOL; + } + + // Merge loaded settings into our ini content, if it is valid + if ($config = parse_ini_string($content)) { + $loaded = ini_get_all(null, 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 @file_put_contents($this->tmpIni, $content); + } + + /** + * Returns the restart command line + * + * @return string + */ + private function getCommand() + { + $php = array(PHP_BINARY); + $args = array_slice($_SERVER['argv'], 1); + + if (!$this->persistent) { + // Use command-line options + array_push($php, '-n', '-c', $this->tmpIni); + } + + if (defined('STDOUT') && Process::supportsColor(STDOUT)) { + $args = Process::addColorOption($args, $this->colorOption); + } + + $args = array_merge($php, array($this->script), $args); + + $cmd = Process::escape(array_shift($args), true, true); + foreach ($args as $arg) { + $cmd .= ' '.Process::escape($arg); + } + + return $cmd; + } + + /** + * Returns true if the restart environment variables were set + * + * No need to update $_SERVER since this is set in the restarted process. + * + * @param bool $scannedInis Whether there were scanned ini files + * @param array $iniFiles All ini files used in the current process + * + * @return bool + */ + private function setEnvironment($scannedInis, array $iniFiles) + { + $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 = array( + self::RESTART_ID, + $this->loaded, + (int) $scannedInis, + false === $scanDir ? '*' : $scanDir, + false === $phprc ? '*' : $phprc, + ); + + return putenv($this->envAllowXdebug.'='.implode('|', $envArgs)); + } + + /** + * Logs status messages + * + * @param string $op Status handler constant + * @param null|string $data Optional data + */ + private function notify($op, $data = null) + { + $this->statusWriter->report($op, $data); + } + + /** + * Returns default, changed and command-line ini settings + * + * @param array $loadedConfig All current ini settings + * @param array $iniConfig Settings from user ini files + * + * @return string + */ + private function mergeLoadedConfig(array $loadedConfig, array $iniConfig) + { + $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 + * + * @return bool + */ + private function checkMainScript() + { + if (null !== $this->script) { + // 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 + $options = PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : false; + $trace = debug_backtrace($options); + + if (($main = end($trace)) && isset($main['file'])) { + return file_exists($this->script = $main['file']); + } + + return false; + } + + /** + * Adds restart settings to the environment + * + * @param string $envArgs + */ + private function setEnvRestartSettings($envArgs) + { + $settings = array( + 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 + * + * @param array $settings + */ + private function syncSettings(array $settings) + { + 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 scanned inis and PHP is able to report them + * + * php_ini_scanned_files will fail when PHP_CONFIG_FILE_SCAN_DIR is empty. + * Fixed in 7.1.13 and 7.2.1 + * + * @return bool + */ + private function checkScanDirConfig() + { + return !(getenv('PHP_INI_SCAN_DIR') + && !PHP_CONFIG_FILE_SCAN_DIR + && (PHP_VERSION_ID < 70113 + || PHP_VERSION_ID === 70200)); + } + + /** + * Returns true if there are no known configuration issues + * + * @param string $info Set by method + */ + private function checkConfiguration(&$info) + { + if (false !== strpos(ini_get('disable_functions'), 'passthru')) { + $info = 'passthru function is disabled'; + return false; + } + + if (extension_loaded('uopz') && !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; + } + } + + return true; + } + + /** + * Enables async signals and control interrupts in the restarted process + * + * Only available on Unix PHP 7.1+ with the pcntl extension. To replicate on + * Windows would require PHP 7.4+ using proc_open rather than passthru. + */ + private function tryEnableSignals() + { + if (!function_exists('pcntl_async_signals')) { + return; + } + + pcntl_async_signals(true); + $message = 'Async signals enabled'; + + if (!self::$inRestart) { + // Restarting, so ignore SIGINT in parent + pcntl_signal(SIGINT, SIG_IGN); + $message .= ' (SIGINT = SIG_IGN)'; + } elseif (is_int(pcntl_signal_get_handler(SIGINT))) { + // Restarted, no handler set so force default action + pcntl_signal(SIGINT, SIG_DFL); + $message .= ' (SIGINT = SIG_DFL)'; + } + + $this->notify(Status::INFO, $message); + } +} diff --git a/lib/composer/dnoegel/php-xdg-base-dir/LICENSE b/lib/composer/dnoegel/php-xdg-base-dir/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..029a00ab57fd8206cf1257d01a384919b247a67c --- /dev/null +++ b/lib/composer/dnoegel/php-xdg-base-dir/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Daniel Nögel + +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/lib/composer/dnoegel/php-xdg-base-dir/README.md b/lib/composer/dnoegel/php-xdg-base-dir/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ee06b2d646a9d71cf60f775dd7966a25e7d634a5 --- /dev/null +++ b/lib/composer/dnoegel/php-xdg-base-dir/README.md @@ -0,0 +1,41 @@ +# XDG Base Directory + +[![Latest Stable Version](https://img.shields.io/packagist/v/dnoegel/php-xdg-base-dir.svg?style=flat-square)](https://packagist.org/packages/dnoegel/php-xdg-base-dir) +[![Total Downloads](https://img.shields.io/packagist/dt/dnoegel/php-xdg-base-dir.svg?style=flat-square)](https://packagist.org/packages/dnoegel/php-xdg-base-dir) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) +[![Build Status](https://img.shields.io/travis/dnoegel/php-xdg-base-dir/master.svg?style=flat-square)](https://travis-ci.org/dnoegel/php-xdg-base-dir) + +Implementation of XDG Base Directory specification for php + +## Install + +Via Composer + +``` bash +$ composer require dnoegel/php-xdg-base-dir +``` + +## Usage + +``` php +$xdg = new \XdgBaseDir\Xdg(); + +echo $xdg->getHomeDir(); +echo $xdg->getHomeConfigDir(); +echo $xdg->getHomeDataDir(); +echo $xdg->getHomeCacheDir(); +echo $xdg->getRuntimeDir(); + +print_r($xdg->getDataDirs()); // returns array +print_r($xdg->getConfigDirs()); // returns array +``` + +## Testing + +``` bash +$ phpunit +``` + +## License + +The MIT License (MIT). Please see [License File](https://github.com/dnoegel/php-xdg-base-dir/blob/master/LICENSE) for more information. diff --git a/lib/composer/dnoegel/php-xdg-base-dir/composer.json b/lib/composer/dnoegel/php-xdg-base-dir/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..94c463745bfe36c605d219c715981468b74c60e1 --- /dev/null +++ b/lib/composer/dnoegel/php-xdg-base-dir/composer.json @@ -0,0 +1,17 @@ +{ + "name": "dnoegel/php-xdg-base-dir", + "description": "implementation of xdg base directory specification for php", + "type": "library", + "license": "MIT", + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + } +} diff --git a/lib/composer/dnoegel/php-xdg-base-dir/src/Xdg.php b/lib/composer/dnoegel/php-xdg-base-dir/src/Xdg.php new file mode 100644 index 0000000000000000000000000000000000000000..2dd314d0bca4f6f62bac0ca6ccd07943e6ca712f --- /dev/null +++ b/lib/composer/dnoegel/php-xdg-base-dir/src/Xdg.php @@ -0,0 +1,132 @@ +getHomeDir(); + + $path = DIRECTORY_SEPARATOR === $homeDir ? $homeDir.'.config' : $homeDir . DIRECTORY_SEPARATOR . '.config'; + + return $path; + } + + /** + * @return string + */ + public function getHomeDataDir() + { + $path = getenv('XDG_DATA_HOME') ?: $this->getHomeDir() . DIRECTORY_SEPARATOR . '.local' . DIRECTORY_SEPARATOR . 'share'; + + return $path; + } + + /** + * @return array + */ + public function getConfigDirs() + { + $configDirs = getenv('XDG_CONFIG_DIRS') ? explode(':', getenv('XDG_CONFIG_DIRS')) : array('/etc/xdg'); + + $paths = array_merge(array($this->getHomeConfigDir()), $configDirs); + + return $paths; + } + + /** + * @return array + */ + public function getDataDirs() + { + $dataDirs = getenv('XDG_DATA_DIRS') ? explode(':', getenv('XDG_DATA_DIRS')) : array('/usr/local/share', '/usr/share'); + + $paths = array_merge(array($this->getHomeDataDir()), $dataDirs); + + return $paths; + } + + /** + * @return string + */ + public function getHomeCacheDir() + { + $path = getenv('XDG_CACHE_HOME') ?: $this->getHomeDir() . DIRECTORY_SEPARATOR . '.cache'; + + return $path; + + } + + public function getRuntimeDir($strict=true) + { + if ($runtimeDir = getenv('XDG_RUNTIME_DIR')) { + return $runtimeDir; + } + + if ($strict) { + throw new \RuntimeException('XDG_RUNTIME_DIR was not set'); + } + + $fallback = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::RUNTIME_DIR_FALLBACK . getenv('USER'); + + $create = false; + + if (!is_dir($fallback)) { + mkdir($fallback, 0700, true); + } + + $st = lstat($fallback); + + # The fallback must be a directory + if (!$st['mode'] & self::S_IFDIR) { + rmdir($fallback); + $create = true; + } elseif ($st['uid'] != $this->getUid() || + $st['mode'] & (self::S_IRWXG | self::S_IRWXO) + ) { + rmdir($fallback); + $create = true; + } + + if ($create) { + mkdir($fallback, 0700, true); + } + + return $fallback; + } + + private function getUid() + { + if (function_exists('posix_getuid')) { + return posix_getuid(); + } + + return getmyuid(); + } +} diff --git a/lib/composer/doctrine/annotations/.doctrine-project.json b/lib/composer/doctrine/annotations/.doctrine-project.json new file mode 100644 index 0000000000000000000000000000000000000000..25d470860a67a32a3cb9d98bd72ffe97814cbce3 --- /dev/null +++ b/lib/composer/doctrine/annotations/.doctrine-project.json @@ -0,0 +1,40 @@ +{ + "active": true, + "name": "Annotations", + "slug": "annotations", + "docsSlug": "doctrine-annotations", + "versions": [ + { + "name": "1.9", + "branchName": "1.9", + "slug": "1.9", + "aliases": [ + "latest" + ], + "upcoming": true + }, + { + "name": "1.8", + "branchName": "1.8", + "slug": "1.8", + "current": true, + "aliases": [ + "current", + "stable" + ], + "maintained": true + }, + { + "name": "1.7", + "branchName": "1.7", + "slug": "1.7", + "maintained": false + }, + { + "name": "1.6", + "branchName": "1.6", + "slug": "1.6", + "maintained": false + } + ] +} diff --git a/lib/composer/doctrine/annotations/CHANGELOG.md b/lib/composer/doctrine/annotations/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..0b0ba1a71d8c725fa394861e87542b0e612cf738 --- /dev/null +++ b/lib/composer/doctrine/annotations/CHANGELOG.md @@ -0,0 +1,162 @@ +## Changelog + +### 1.6.1 + +This release fixes an issue in which annotations such as `@foo-bar` +and `@foo-` were incorrectly recognised as valid, and both erroneously +parsed as `@foo`. + +Any annotation with `@name-*` format will now silently be ignored, +allowing vendor-specific annotations to be prefixed with the tool +name. + +Total issues resolved: **3** + +- [165: Update the composer branch alias](https://github.com/doctrine/annotations/pull/165) thanks to @mikeSimonson +- [209: Change Annotation::value typehint to mixed](https://github.com/doctrine/annotations/pull/209) thanks to @malarzm +- [257: Skip parsing annotations containing dashes, such as `@Foo-bar`, or `@Foo-`](https://github.com/doctrine/annotations/pull/257) thanks to @Ocramius + +### 1.6.0 + +This release brings a new endpoint that make sure that you can't shoot yourself in the foot by calling ```registerLoader``` multiple times and a few tests improvements. + +Total issues resolved: **7** + +- [145: Memory leak in AnnotationRegistry::registerLoader() when called multiple times](https://github.com/doctrine/annotations/issues/145) thanks to @TriAnMan +- [146: Import error on @experimental Annotation](https://github.com/doctrine/annotations/issues/146) thanks to @aturki +- [147: Ignoring @experimental annotation used by Symfony 3.3 CacheAdapter](https://github.com/doctrine/annotations/pull/147) thanks to @aturki +- [151: Remove duplicate code in `DCOM58Test`](https://github.com/doctrine/annotations/pull/151) thanks to @tuanphpvn +- [161: Prevent loading class_exists multiple times](https://github.com/doctrine/annotations/pull/161) thanks to @jrjohnson +- [162: Add registerUniqueLoader to AnnotationRegistry](https://github.com/doctrine/annotations/pull/162) thanks to @jrjohnson +- [163: Use assertDirectoryExists and assertDirectoryNotExists](https://github.com/doctrine/annotations/pull/163) thanks to @carusogabriel + +Thanks to everyone involved in this release. + +### 1.5.0 + +This release increments the minimum supported PHP version to 7.1.0. + +Also, HHVM official support has been dropped. + +Some noticeable performance improvements to annotation autoloading +have been applied, making failed annotation autoloading less heavy +on the filesystem access. + +- [133: Add @throws annotation in AnnotationReader#__construct()](https://github.com/doctrine/annotations/issues/133) thanks to @SenseException +- [134: Require PHP 7.1, drop HHVM support](https://github.com/doctrine/annotations/issues/134) thanks to @lcobucci +- [135: Prevent the same loader from being registered twice](https://github.com/doctrine/annotations/issues/135) thanks to @jrjohnson +- [137: #135 optimise multiple class load attempts in AnnotationRegistry](https://github.com/doctrine/annotations/issues/137) thanks to @Ocramius + + +### 1.4.0 + +This release fix an issue were some annotations could be not loaded if the namespace in the use statement started with a backslash. +It also update the tests and drop the support for php 5.X + +- [115: Missing annotations with the latest composer version](https://github.com/doctrine/annotations/issues/115) thanks to @pascalporedda +- [120: Missing annotations with the latest composer version](https://github.com/doctrine/annotations/pull/120) thanks to @gnat42 +- [121: Adding a more detailed explanation of the test](https://github.com/doctrine/annotations/pull/121) thanks to @mikeSimonson +- [101: Test annotation parameters containing space](https://github.com/doctrine/annotations/pull/101) thanks to @mikeSimonson +- [111: Cleanup: move to correct phpunit assertions](https://github.com/doctrine/annotations/pull/111) thanks to @Ocramius +- [112: Removes support for PHP 5.x](https://github.com/doctrine/annotations/pull/112) thanks to @railto +- [113: bumped phpunit version to 5.7](https://github.com/doctrine/annotations/pull/113) thanks to @gabbydgab +- [114: Enhancement: Use SVG Travis build badge](https://github.com/doctrine/annotations/pull/114) thanks to @localheinz +- [118: Integrating PHPStan](https://github.com/doctrine/annotations/pull/118) thanks to @ondrejmirtes + +### 1.3.1 - 2016-12-30 + +This release fixes an issue with ignored annotations that were already +autoloaded, causing the `SimpleAnnotationReader` to pick them up +anyway. [#110](https://github.com/doctrine/annotations/pull/110) + +Additionally, an issue was fixed in the `CachedReader`, which was +not correctly checking the freshness of cached annotations when +traits were defined on a class. [#105](https://github.com/doctrine/annotations/pull/105) + +Total issues resolved: **2** + +- [105: Return single max timestamp](https://github.com/doctrine/annotations/pull/105) +- [110: setIgnoreNotImportedAnnotations(true) didn’t work for existing classes](https://github.com/doctrine/annotations/pull/110) + +### 1.3.0 + +This release introduces a PHP version bump. `doctrine/annotations` now requires PHP +5.6 or later to be installed. + +A series of additional improvements have been introduced: + + * support for PHP 7 "grouped use statements" + * support for ignoring entire namespace names + via `Doctrine\Common\Annotations\AnnotationReader::addGlobalIgnoredNamespace()` and + `Doctrine\Common\Annotations\DocParser::setIgnoredAnnotationNamespaces()`. This will + allow you to ignore annotations from namespaces that you cannot autoload + * testing all parent classes and interfaces when checking if the annotation cache + in the `CachedReader` is fresh + * simplifying the cache keys used by the `CachedReader`: keys are no longer artificially + namespaced, since `Doctrine\Common\Cache` already supports that + * corrected parsing of multibyte strings when `mbstring.func_overload` is enabled + * corrected parsing of annotations when `"\t"` is put before the first annotation + in a docblock + * allow skipping non-imported annotations when a custom `DocParser` is passed to + the `AnnotationReader` constructor + +Total issues resolved: **15** + +- [45: DocParser can now ignore whole namespaces](https://github.com/doctrine/annotations/pull/45) +- [57: Switch to the docker-based infrastructure on Travis](https://github.com/doctrine/annotations/pull/57) +- [59: opcache.load_comments has been removed from PHP 7](https://github.com/doctrine/annotations/pull/59) +- [62: [CachedReader\ Test traits and parent class to see if cache is fresh](https://github.com/doctrine/annotations/pull/62) +- [65: Remove cache salt making key unnecessarily long](https://github.com/doctrine/annotations/pull/65) +- [66: Fix of incorrect parsing multibyte strings](https://github.com/doctrine/annotations/pull/66) +- [68: Annotations that are indented by tab are not processed.](https://github.com/doctrine/annotations/issues/68) +- [69: Support for Group Use Statements](https://github.com/doctrine/annotations/pull/69) +- [70: Allow tab character before first annotation in DocBlock](https://github.com/doctrine/annotations/pull/70) +- [74: Ignore not registered annotations fix](https://github.com/doctrine/annotations/pull/74) +- [92: Added tests for AnnotationRegistry class.](https://github.com/doctrine/annotations/pull/92) +- [96: Fix/#62 check trait and parent class ttl in annotations](https://github.com/doctrine/annotations/pull/96) +- [97: Feature - #45 - allow ignoring entire namespaces](https://github.com/doctrine/annotations/pull/97) +- [98: Enhancement/#65 remove cache salt from cached reader](https://github.com/doctrine/annotations/pull/98) +- [99: Fix - #70 - allow tab character before first annotation in docblock](https://github.com/doctrine/annotations/pull/99) + +### 1.2.4 + +Total issues resolved: **1** + +- [51: FileCacheReader::saveCacheFile::unlink fix](https://github.com/doctrine/annotations/pull/51) + +### 1.2.3 + +Total issues resolved: [**2**](https://github.com/doctrine/annotations/milestones/v1.2.3) + +- [49: #46 - applying correct `chmod()` to generated cache file](https://github.com/doctrine/annotations/pull/49) +- [50: Hotfix: match escaped quotes (revert #44)](https://github.com/doctrine/annotations/pull/50) + +### 1.2.2 + +Total issues resolved: **4** + +- [43: Exclude files from distribution with .gitattributes](https://github.com/doctrine/annotations/pull/43) +- [44: Update DocLexer.php](https://github.com/doctrine/annotations/pull/44) +- [46: A plain "file_put_contents" can cause havoc](https://github.com/doctrine/annotations/pull/46) +- [48: Deprecating the `FileCacheReader` in 1.2.2: will be removed in 2.0.0](https://github.com/doctrine/annotations/pull/48) + +### 1.2.1 + +Total issues resolved: **4** + +- [38: fixes doctrine/common#326](https://github.com/doctrine/annotations/pull/38) +- [39: Remove superfluous NS](https://github.com/doctrine/annotations/pull/39) +- [41: Warn if load_comments is not enabled.](https://github.com/doctrine/annotations/pull/41) +- [42: Clean up unused uses](https://github.com/doctrine/annotations/pull/42) + +### 1.2.0 + + * HHVM support + * Allowing dangling comma in annotations + * Excluded annotations are no longer autoloaded + * Importing namespaces also in traits + * Added support for `::class` 5.5-style constant, works also in 5.3 and 5.4 + +### 1.1.0 + + * Add Exception when ZendOptimizer+ or Opcache is configured to drop comments diff --git a/lib/composer/doctrine/annotations/LICENSE b/lib/composer/doctrine/annotations/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..5e781fce4bb504715ba0ec0188715b18a198ca6b --- /dev/null +++ b/lib/composer/doctrine/annotations/LICENSE @@ -0,0 +1,19 @@ +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. + +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/lib/composer/doctrine/annotations/README.md b/lib/composer/doctrine/annotations/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a53b91f2b0d852a9ee33a9fb543d7a8688e05cb9 --- /dev/null +++ b/lib/composer/doctrine/annotations/README.md @@ -0,0 +1,17 @@ +# Doctrine Annotations + +[![Build Status](https://travis-ci.org/doctrine/annotations.svg?branch=master)](https://travis-ci.org/doctrine/annotations) +[![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations) +[![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references) +[![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations) +[![Latest Stable Version](https://poser.pugx.org/doctrine/annotations/v/stable.png)](https://packagist.org/packages/doctrine/annotations) + +Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)). + +## Documentation + +See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html). + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md). diff --git a/lib/composer/doctrine/annotations/composer.json b/lib/composer/doctrine/annotations/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..040e71732c4d40f0d2df052ca9603d622973fd17 --- /dev/null +++ b/lib/composer/doctrine/annotations/composer.json @@ -0,0 +1,44 @@ +{ + "name": "doctrine/annotations", + "type": "library", + "description": "Docblock Annotations Parser", + "keywords": ["annotations", "docblock", "parser"], + "homepage": "http://www.doctrine-project.org", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": "^7.1 || ^8.0", + "ext-tokenizer": "*", + "doctrine/lexer": "1.*" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^7.5" + }, + "config": { + "sort-packages": true + }, + "autoload": { + "psr-4": { "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Performance\\Common\\Annotations\\": "tests/Doctrine/Performance/Common/Annotations", + "Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations" + }, + "files": [ + "tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + } +} diff --git a/lib/composer/doctrine/annotations/docs/en/annotations.rst b/lib/composer/doctrine/annotations/docs/en/annotations.rst new file mode 100644 index 0000000000000000000000000000000000000000..1984dba5f7c2dd7ce5bab495244a03c49df6df02 --- /dev/null +++ b/lib/composer/doctrine/annotations/docs/en/annotations.rst @@ -0,0 +1,271 @@ +Handling Annotations +==================== + +There are several different approaches to handling annotations in PHP. +Doctrine Annotations maps docblock annotations to PHP classes. Because +not all docblock annotations are used for metadata purposes a filter is +applied to ignore or skip classes that are not Doctrine annotations. + +Take a look at the following code snippet: + +.. code-block:: php + + namespace MyProject\Entities; + + use Doctrine\ORM\Mapping AS ORM; + use Symfony\Component\Validation\Constraints AS Assert; + + /** + * @author Benjamin Eberlei + * @ORM\Entity + * @MyProject\Annotations\Foobarable + */ + class User + { + /** + * @ORM\Id @ORM\Column @ORM\GeneratedValue + * @dummy + * @var int + */ + private $id; + + /** + * @ORM\Column(type="string") + * @Assert\NotEmpty + * @Assert\Email + * @var string + */ + private $email; + } + +In this snippet you can see a variety of different docblock annotations: + +- Documentation annotations such as ``@var`` and ``@author``. These + annotations are on a blacklist and never considered for throwing an + exception due to wrongly used annotations. +- Annotations imported through use statements. The statement ``use + Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace + available as ``@ORM\ClassName``. Same goes for the import of + ``@Assert``. +- The ``@dummy`` annotation. It is not a documentation annotation and + not blacklisted. For Doctrine Annotations it is not entirely clear how + to handle this annotation. Depending on the configuration an exception + (unknown annotation) will be thrown when parsing this annotation. +- The fully qualified annotation ``@MyProject\Annotations\Foobarable``. + This is transformed directly into the given class name. + +How are these annotations loaded? From looking at the code you could +guess that the ORM Mapping, Assert Validation and the fully qualified +annotation can just be loaded using +the defined PHP autoloaders. This is not the case however: For error +handling reasons every check for class existence inside the +``AnnotationReader`` sets the second parameter $autoload +of ``class_exists($name, $autoload)`` to false. To work flawlessly the +``AnnotationReader`` requires silent autoloaders which many autoloaders are +not. Silent autoloading is NOT part of the `PSR-0 specification +`_ +for autoloading. + +This is why Doctrine Annotations uses its own autoloading mechanism +through a global registry. If you are wondering about the annotation +registry being global, there is no other way to solve the architectural +problems of autoloading annotation classes in a straightforward fashion. +Additionally if you think about PHP autoloading then you recognize it is +a global as well. + +To anticipate the configuration section, making the above PHP class work +with Doctrine Annotations requires this setup: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\AnnotationRegistry; + + AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php"); + AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src"); + AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src"); + + $reader = new AnnotationReader(); + AnnotationReader::addGlobalIgnoredName('dummy'); + +The second block with the annotation registry calls registers all the +three different annotation namespaces that are used. +Doctrine Annotations saves all its annotations in a single file, that is +why ``AnnotationRegistry#registerFile`` is used in contrast to +``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0 +compatible loading mechanism for class to file names. + +In the third block, we create the actual ``AnnotationReader`` instance. +Note that we also add ``dummy`` to the global list of ignored +annotations for which we do not throw exceptions. Setting this is +necessary in our example case, otherwise ``@dummy`` would trigger an +exception to be thrown during the parsing of the docblock of +``MyProject\Entities\User#id``. + +Setup and Configuration +----------------------- + +To use the annotations library is simple, you just need to create a new +``AnnotationReader`` instance: + +.. code-block:: php + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + +This creates a simple annotation reader with no caching other than in +memory (in php arrays). Since parsing docblocks can be expensive you +should cache this process by using a caching reader. + +You can use a file caching reader, but please note it is deprecated to +do so: + +.. code-block:: php + + use Doctrine\Common\Annotations\FileCacheReader; + use Doctrine\Common\Annotations\AnnotationReader; + + $reader = new FileCacheReader( + new AnnotationReader(), + "/path/to/cache", + $debug = true + ); + +If you set the ``debug`` flag to ``true`` the cache reader will check +for changes in the original files, which is very important during +development. If you don't set it to ``true`` you have to delete the +directory to clear the cache. This gives faster performance, however +should only be used in production, because of its inconvenience during +development. + +You can also use one of the ``Doctrine\Common\Cache\Cache`` cache +implementations to cache the annotations: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\CachedReader; + use Doctrine\Common\Cache\ApcCache; + + $reader = new CachedReader( + new AnnotationReader(), + new ApcCache(), + $debug = true + ); + +The ``debug`` flag is used here as well to invalidate the cache files +when the PHP class with annotations changed and should be used during +development. + +.. warning :: + + The ``AnnotationReader`` works and caches under the + assumption that all annotations of a doc-block are processed at + once. That means that annotation classes that do not exist and + aren't loaded and cannot be autoloaded (using the + AnnotationRegistry) would never be visible and not accessible if a + cache is used unless the cache is cleared and the annotations + requested again, this time with all annotations defined. + +By default the annotation reader returns a list of annotations with +numeric indexes. If you want your annotations to be indexed by their +class name you can wrap the reader in an ``IndexedReader``: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\IndexedReader; + + $reader = new IndexedReader(new AnnotationReader()); + +.. warning:: + + You should never wrap the indexed reader inside a cached reader, + only the other way around. This way you can re-use the cache with + indexed or numeric keys, otherwise your code may experience failures + due to caching in a numerical or indexed format. + +Registering Annotations +~~~~~~~~~~~~~~~~~~~~~~~ + +As explained in the introduction, Doctrine Annotations uses its own +autoloading mechanism to determine if a given annotation has a +corresponding PHP class that can be autoloaded. For annotation +autoloading you have to configure the +``Doctrine\Common\Annotations\AnnotationRegistry``. There are three +different mechanisms to configure annotation autoloading: + +- Calling ``AnnotationRegistry#registerFile($file)`` to register a file + that contains one or more annotation classes. +- Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs = + null)`` to register that the given namespace contains annotations and + that their base directory is located at the given $dirs or in the + include path if ``NULL`` is passed. The given directories should *NOT* + be the directory where classes of the namespace are in, but the base + directory of the root namespace. The AnnotationRegistry uses a + namespace to directory separator approach to resolve the correct path. +- Calling ``AnnotationRegistry#registerLoader($callable)`` to register + an autoloader callback. The callback accepts the class as first and + only parameter and has to return ``true`` if the corresponding file + was found and included. + +.. note:: + + Loaders have to fail silently, if a class is not found even if it + matches for example the namespace prefix of that loader. Never is a + loader to throw a warning or exception if the loading failed + otherwise parsing doc block annotations will become a huge pain. + +A sample loader callback could look like: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationRegistry; + use Symfony\Component\ClassLoader\UniversalClassLoader; + + AnnotationRegistry::registerLoader(function($class) { + $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; + + if (file_exists("/my/base/path/" . $file)) { + // file_exists() makes sure that the loader fails silently + require "/my/base/path/" . $file; + } + }); + + $loader = new UniversalClassLoader(); + AnnotationRegistry::registerLoader(array($loader, "loadClass")); + + +Ignoring missing exceptions +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default an exception is thrown from the ``AnnotationReader`` if an +annotation was found that: + +- is not part of the blacklist of ignored "documentation annotations"; +- was not imported through a use statement; +- is not a fully qualified class that exists. + +You can disable this behavior for specific names if your docblocks do +not follow strict requirements: + +.. code-block:: php + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + AnnotationReader::addGlobalIgnoredName('foo'); + +PHP Imports +~~~~~~~~~~~ + +By default the annotation reader parses the use-statement of a php file +to gain access to the import rules and register them for the annotation +processing. Only if you are using PHP Imports can you validate the +correct usage of annotations and throw exceptions if you misspelled an +annotation. This mechanism is enabled by default. + +To ease the upgrade path, we still allow you to disable this mechanism. +Note however that we will remove this in future versions: + +.. code-block:: php + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + $reader->setEnabledPhpImports(false); diff --git a/lib/composer/doctrine/annotations/docs/en/custom.rst b/lib/composer/doctrine/annotations/docs/en/custom.rst new file mode 100644 index 0000000000000000000000000000000000000000..e589a5432a1bb1a1da83adc52f43865e88344cbd --- /dev/null +++ b/lib/composer/doctrine/annotations/docs/en/custom.rst @@ -0,0 +1,353 @@ +Custom Annotation Classes +========================= + +If you want to define your own annotations, you just have to group them +in a namespace and register this namespace in the ``AnnotationRegistry``. +Annotation classes have to contain a class-level docblock with the text +``@Annotation``: + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** @Annotation */ + class Bar + { + // some code + } + +Inject annotation values +------------------------ + +The annotation parser checks if the annotation constructor has arguments, +if so then it will pass the value array, otherwise it will try to inject +values into public properties directly: + + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * + * Some Annotation using a constructor + */ + class Bar + { + private $foo; + + public function __construct(array $values) + { + $this->foo = $values['foo']; + } + } + + /** + * @Annotation + * + * Some Annotation without a constructor + */ + class Foo + { + public $bar; + } + +Annotation Target +----------------- + +``@Target`` indicates the kinds of class elements to which an annotation +type is applicable. Then you could define one or more targets: + +- ``CLASS`` Allowed in class docblocks +- ``PROPERTY`` Allowed in property docblocks +- ``METHOD`` Allowed in the method docblocks +- ``ALL`` Allowed in class, property and method docblocks +- ``ANNOTATION`` Allowed inside other annotations + +If the annotations is not allowed in the current context, an +``AnnotationException`` is thrown. + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * @Target({"METHOD","PROPERTY"}) + */ + class Bar + { + // some code + } + + /** + * @Annotation + * @Target("CLASS") + */ + class Foo + { + // some code + } + +Attribute types +--------------- + +The annotation parser checks the given parameters using the phpdoc +annotation ``@var``, The data type could be validated using the ``@var`` +annotation on the annotation properties or using the ``@Attributes`` and +``@Attribute`` annotations. + +If the data type does not match you get an ``AnnotationException`` + +.. code-block:: php + + namespace MyCompany\Annotations; + + /** + * @Annotation + * @Target({"METHOD","PROPERTY"}) + */ + class Bar + { + /** @var mixed */ + public $mixed; + + /** @var boolean */ + public $boolean; + + /** @var bool */ + public $bool; + + /** @var float */ + public $float; + + /** @var string */ + public $string; + + /** @var integer */ + public $integer; + + /** @var array */ + public $array; + + /** @var SomeAnnotationClass */ + public $annotation; + + /** @var array */ + public $arrayOfIntegers; + + /** @var array */ + public $arrayOfAnnotations; + } + + /** + * @Annotation + * @Target({"METHOD","PROPERTY"}) + * @Attributes({ + * @Attribute("stringProperty", type = "string"), + * @Attribute("annotProperty", type = "SomeAnnotationClass"), + * }) + */ + class Foo + { + public function __construct(array $values) + { + $this->stringProperty = $values['stringProperty']; + $this->annotProperty = $values['annotProperty']; + } + + // some code + } + +Annotation Required +------------------- + +``@Required`` indicates that the field must be specified when the +annotation is used. If it is not used you get an ``AnnotationException`` +stating that this value can not be null. + +Declaring a required field: + +.. code-block:: php + + /** + * @Annotation + * @Target("ALL") + */ + class Foo + { + /** @Required */ + public $requiredField; + } + +Usage: + +.. code-block:: php + + /** @Foo(requiredField="value") */ + public $direction; // Valid + + /** @Foo */ + public $direction; // Required field missing, throws an AnnotationException + + +Enumerated values +----------------- + +- An annotation property marked with ``@Enum`` is a field that accepts a + fixed set of scalar values. +- You should use ``@Enum`` fields any time you need to represent fixed + values. +- The annotation parser checks the given value and throws an + ``AnnotationException`` if the value does not match. + + +Declaring an enumerated property: + +.. code-block:: php + + /** + * @Annotation + * @Target("ALL") + */ + class Direction + { + /** + * @Enum({"NORTH", "SOUTH", "EAST", "WEST"}) + */ + public $value; + } + +Annotation usage: + +.. code-block:: php + + /** @Direction("NORTH") */ + public $direction; // Valid value + + /** @Direction("NORTHEAST") */ + public $direction; // Invalid value, throws an AnnotationException + + +Constants +--------- + +The use of constants and class constants is available on the annotations +parser. + +The following usages are allowed: + +.. code-block:: php + + namespace MyCompany\Entity; + + use MyCompany\Annotations\Foo; + use MyCompany\Annotations\Bar; + use MyCompany\Entity\SomeClass; + + /** + * @Foo(PHP_EOL) + * @Bar(Bar::FOO) + * @Foo({SomeClass::FOO, SomeClass::BAR}) + * @Bar({SomeClass::FOO_KEY = SomeClass::BAR_VALUE}) + */ + class User + { + } + + +Be careful with constants and the cache ! + +.. note:: + + The cached reader will not re-evaluate each time an annotation is + loaded from cache. When a constant is changed the cache must be + cleaned. + + +Usage +----- + +Using the library API is simple. Using the annotations described in the +previous section, you can now annotate other classes with your +annotations: + +.. code-block:: php + + namespace MyCompany\Entity; + + use MyCompany\Annotations\Foo; + use MyCompany\Annotations\Bar; + + /** + * @Foo(bar="foo") + * @Bar(foo="bar") + */ + class User + { + } + +Now we can write a script to get the annotations above: + +.. code-block:: php + + $reflClass = new ReflectionClass('MyCompany\Entity\User'); + $classAnnotations = $reader->getClassAnnotations($reflClass); + + foreach ($classAnnotations AS $annot) { + if ($annot instanceof \MyCompany\Annotations\Foo) { + echo $annot->bar; // prints "foo"; + } else if ($annot instanceof \MyCompany\Annotations\Bar) { + echo $annot->foo; // prints "bar"; + } + } + +You have a complete API for retrieving annotation class instances from a +class, property or method docblock: + + +Reader API +~~~~~~~~~~ + +Access all annotations of a class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getClassAnnotations(\ReflectionClass $class); + +Access one annotation of a class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getClassAnnotation(\ReflectionClass $class, $annotationName); + +Access all annotations of a method +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getMethodAnnotations(\ReflectionMethod $method); + +Access one annotation of a method +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName); + +Access all annotations of a property +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getPropertyAnnotations(\ReflectionProperty $property); + +Access one annotation of a property +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: php + + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName); diff --git a/lib/composer/doctrine/annotations/docs/en/index.rst b/lib/composer/doctrine/annotations/docs/en/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..3b3368c96271476f4ddffa34ff04960827bf9918 --- /dev/null +++ b/lib/composer/doctrine/annotations/docs/en/index.rst @@ -0,0 +1,97 @@ +Introduction +============ + +Doctrine Annotations allows to implement custom annotation +functionality for PHP classes. + +.. code-block:: php + + class Foo + { + /** + * @MyAnnotation(myProperty="value") + */ + private $bar; + } + +Annotations aren't implemented in PHP itself which is why this component +offers a way to use the PHP doc-blocks as a place for the well known +annotation syntax using the ``@`` char. + +Annotations in Doctrine are used for the ORM configuration to build the +class mapping, but it can be used in other projects for other purposes +too. + +Installation +============ + +You can install the Annotation component with composer: + +.. code-block:: + +   $ composer require doctrine/annotations + +Create an annotation class +========================== + +An annotation class is a representation of the later used annotation +configuration in classes. The annotation class of the previous example +looks like this: + +.. code-block:: php + + /** + * @Annotation + */ + final class MyAnnotation + { + public $myProperty; + } + +The annotation class is declared as an annotation by ``@Annotation``. + +:ref:`Read more about custom annotations. ` + +Reading annotations +=================== + +The access to the annotations happens by reflection of the class +containing them. There are multiple reader-classes implementing the +``Doctrine\Common\Annotations\Reader`` interface, that can access the +annotations of a class. A common one is +``Doctrine\Common\Annotations\AnnotationReader``: + +.. code-block:: php + + use Doctrine\Common\Annotations\AnnotationReader; + use Doctrine\Common\Annotations\AnnotationRegistry; + + // Deprecated and will be removed in 2.0 but currently needed + AnnotationRegistry::registerLoader('class_exists'); + + $reflectionClass = new ReflectionClass(Foo::class); + $property = $reflectionClass->getProperty('bar'); + + $reader = new AnnotationReader(); + $myAnnotation = $reader->getPropertyAnnotation($property, MyAnnotation::class); + + echo $myAnnotation->myProperty; // result: "value" + +Note that ``AnnotationRegistry::registerLoader('class_exists')`` only works +if you already have an autoloader configured (i.e. composer autoloader). +Otherwise, :ref:`please take a look to the other annotation autoload mechanisms `. + +A reader has multiple methods to access the annotations of a class. + +:ref:`Read more about handling annotations. ` + +IDE Support +----------- + +Some IDEs already provide support for annotations: + +- Eclipse via the `Symfony2 Plugin `_ +- PHPStorm via the `PHP Annotations Plugin `_ or the `Symfony2 Plugin `_ + +.. _Read more about handling annotations.: annotations +.. _Read more about custom annotations.: custom diff --git a/lib/composer/doctrine/annotations/docs/en/sidebar.rst b/lib/composer/doctrine/annotations/docs/en/sidebar.rst new file mode 100644 index 0000000000000000000000000000000000000000..6f5d13c46ab0747286070d4b223048a17c502e10 --- /dev/null +++ b/lib/composer/doctrine/annotations/docs/en/sidebar.rst @@ -0,0 +1,6 @@ +.. toctree:: + :depth: 3 + + index + annotations + custom diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php new file mode 100644 index 0000000000000000000000000000000000000000..a79a0f8f0a18da1d0066c94bac1b605031e27be0 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php @@ -0,0 +1,79 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Annotations class. + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Annotation +{ + /** + * Value property. Common among all derived classes. + * + * @var string + */ + public $value; + + /** + * Constructor. + * + * @param array $data Key-value for properties to be defined in this class. + */ + public final function __construct(array $data) + { + foreach ($data as $key => $value) { + $this->$key = $value; + } + } + + /** + * Error handler for unknown property accessor in Annotation class. + * + * @param string $name Unknown property name. + * + * @throws \BadMethodCallException + */ + public function __get($name) + { + throw new \BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) + ); + } + + /** + * Error handler for unknown property mutator in Annotation class. + * + * @param string $name Unknown property name. + * @param mixed $value Property value. + * + * @throws \BadMethodCallException + */ + public function __set($name, $value) + { + throw new \BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) + ); + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php new file mode 100644 index 0000000000000000000000000000000000000000..dbef6df087497cecf043dcdac40f95ffb5551fa0 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php @@ -0,0 +1,47 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the attribute type during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Attribute +{ + /** + * @var string + */ + public $name; + + /** + * @var string + */ + public $type; + + /** + * @var boolean + */ + public $required = false; +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php new file mode 100644 index 0000000000000000000000000000000000000000..53134e3097af6c5dc95695b478d53282f2455604 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php @@ -0,0 +1,37 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the types of all declared attributes during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Attributes +{ + /** + * @var array + */ + public $value; +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php new file mode 100644 index 0000000000000000000000000000000000000000..82f6241ec96c44f751526d821afa598244f415fd --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php @@ -0,0 +1,84 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the available values during the parsing process. + * + * @since 2.4 + * @author Fabio B. Silva + * + * @Annotation + * @Attributes({ + * @Attribute("value", required = true, type = "array"), + * @Attribute("literal", required = false, type = "array") + * }) + */ +final class Enum +{ + /** + * @var array + */ + public $value; + + /** + * Literal target declaration. + * + * @var array + */ + public $literal; + + /** + * Annotation constructor. + * + * @param array $values + * + * @throws \InvalidArgumentException + */ + public function __construct(array $values) + { + if ( ! isset($values['literal'])) { + $values['literal'] = []; + } + + foreach ($values['value'] as $var) { + if( ! is_scalar($var)) { + throw new \InvalidArgumentException(sprintf( + '@Enum supports only scalar values "%s" given.', + is_object($var) ? get_class($var) : gettype($var) + )); + } + } + + foreach ($values['literal'] as $key => $var) { + if( ! in_array($key, $values['value'])) { + throw new \InvalidArgumentException(sprintf( + 'Undefined enumerator value "%s" for literal "%s".', + $key , $var + )); + } + } + + $this->value = $values['value']; + $this->literal = $values['literal']; + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php new file mode 100644 index 0000000000000000000000000000000000000000..85ec3d6dd1d25cbe9e3c4cdc345927d9cbd7cd73 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser to ignore specific + * annotations during the parsing process. + * + * @Annotation + * @author Johannes M. Schmitt + */ +final class IgnoreAnnotation +{ + /** + * @var array + */ + public $names; + + /** + * Constructor. + * + * @param array $values + * + * @throws \RuntimeException + */ + public function __construct(array $values) + { + if (is_string($values['value'])) { + $values['value'] = [$values['value']]; + } + if (!is_array($values['value'])) { + throw new \RuntimeException(sprintf('@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.', json_encode($values['value']))); + } + + $this->names = $values['value']; + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php new file mode 100644 index 0000000000000000000000000000000000000000..d67f9606879fdaa1bf1f9181ee90081a839b6510 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php @@ -0,0 +1,33 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check if that attribute is required during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Required +{ +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php new file mode 100644 index 0000000000000000000000000000000000000000..a52972b8b47825dd757a46f9416fe8bf87484ee4 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php @@ -0,0 +1,107 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the annotation target during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Target +{ + const TARGET_CLASS = 1; + const TARGET_METHOD = 2; + const TARGET_PROPERTY = 4; + const TARGET_ANNOTATION = 8; + const TARGET_ALL = 15; + + /** + * @var array + */ + private static $map = [ + 'ALL' => self::TARGET_ALL, + 'CLASS' => self::TARGET_CLASS, + 'METHOD' => self::TARGET_METHOD, + 'PROPERTY' => self::TARGET_PROPERTY, + 'ANNOTATION' => self::TARGET_ANNOTATION, + ]; + + /** + * @var array + */ + public $value; + + /** + * Targets as bitmask. + * + * @var integer + */ + public $targets; + + /** + * Literal target declaration. + * + * @var integer + */ + public $literal; + + /** + * Annotation constructor. + * + * @param array $values + * + * @throws \InvalidArgumentException + */ + public function __construct(array $values) + { + if (!isset($values['value'])){ + $values['value'] = null; + } + if (is_string($values['value'])){ + $values['value'] = [$values['value']]; + } + if (!is_array($values['value'])){ + throw new \InvalidArgumentException( + sprintf('@Target expects either a string value, or an array of strings, "%s" given.', + is_object($values['value']) ? get_class($values['value']) : gettype($values['value']) + ) + ); + } + + $bitmask = 0; + foreach ($values['value'] as $literal) { + if(!isset(self::$map[$literal])){ + throw new \InvalidArgumentException( + sprintf('Invalid Target "%s". Available targets: [%s]', + $literal, implode(', ', array_keys(self::$map))) + ); + } + $bitmask |= self::$map[$literal]; + } + + $this->targets = $bitmask; + $this->value = $values['value']; + $this->literal = implode(', ', $this->value); + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php new file mode 100644 index 0000000000000000000000000000000000000000..d06fe663c26964be98aba634890638e246eada84 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php @@ -0,0 +1,197 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Description of AnnotationException + * + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class AnnotationException extends \Exception +{ + /** + * Creates a new AnnotationException describing a Syntax error. + * + * @param string $message Exception message + * + * @return AnnotationException + */ + public static function syntaxError($message) + { + return new self('[Syntax Error] ' . $message); + } + + /** + * Creates a new AnnotationException describing a Semantical error. + * + * @param string $message Exception message + * + * @return AnnotationException + */ + public static function semanticalError($message) + { + return new self('[Semantical Error] ' . $message); + } + + /** + * Creates a new AnnotationException describing an error which occurred during + * the creation of the annotation. + * + * @since 2.2 + * + * @param string $message + * + * @return AnnotationException + */ + public static function creationError($message) + { + return new self('[Creation Error] ' . $message); + } + + /** + * Creates a new AnnotationException describing a type error. + * + * @since 1.1 + * + * @param string $message + * + * @return AnnotationException + */ + public static function typeError($message) + { + return new self('[Type Error] ' . $message); + } + + /** + * Creates a new AnnotationException describing a constant semantical error. + * + * @since 2.3 + * + * @param string $identifier + * @param string $context + * + * @return AnnotationException + */ + public static function semanticalErrorConstants($identifier, $context = null) + { + return self::semanticalError(sprintf( + "Couldn't find constant %s%s.", + $identifier, + $context ? ', ' . $context : '' + )); + } + + /** + * Creates a new AnnotationException describing an type error of an attribute. + * + * @since 2.2 + * + * @param string $attributeName + * @param string $annotationName + * @param string $context + * @param string $expected + * @param mixed $actual + * + * @return AnnotationException + */ + public static function attributeTypeError($attributeName, $annotationName, $context, $expected, $actual) + { + return self::typeError(sprintf( + 'Attribute "%s" of @%s declared on %s expects %s, but got %s.', + $attributeName, + $annotationName, + $context, + $expected, + is_object($actual) ? 'an instance of ' . get_class($actual) : gettype($actual) + )); + } + + /** + * Creates a new AnnotationException describing an required error of an attribute. + * + * @since 2.2 + * + * @param string $attributeName + * @param string $annotationName + * @param string $context + * @param string $expected + * + * @return AnnotationException + */ + public static function requiredError($attributeName, $annotationName, $context, $expected) + { + return self::typeError(sprintf( + 'Attribute "%s" of @%s declared on %s expects %s. This value should not be null.', + $attributeName, + $annotationName, + $context, + $expected + )); + } + + /** + * Creates a new AnnotationException describing a invalid enummerator. + * + * @since 2.4 + * + * @param string $attributeName + * @param string $annotationName + * @param string $context + * @param array $available + * @param mixed $given + * + * @return AnnotationException + */ + public static function enumeratorError($attributeName, $annotationName, $context, $available, $given) + { + return new self(sprintf( + '[Enum Error] Attribute "%s" of @%s declared on %s accept only [%s], but got %s.', + $attributeName, + $annotationName, + $context, + implode(', ', $available), + is_object($given) ? get_class($given) : $given + )); + } + + /** + * @return AnnotationException + */ + public static function optimizerPlusSaveComments() + { + return new self( + "You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1." + ); + } + + /** + * @return AnnotationException + */ + public static function optimizerPlusLoadComments() + { + return new self( + "You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1." + ); + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php new file mode 100644 index 0000000000000000000000000000000000000000..8811c2951d230fdaa41c6bb35dc640b76c227175 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -0,0 +1,418 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation; +use Doctrine\Common\Annotations\Annotation\Target; +use ReflectionClass; +use ReflectionMethod; +use ReflectionProperty; + +/** + * A reader for docblock annotations. + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Johannes M. Schmitt + */ +class AnnotationReader implements Reader +{ + /** + * Global map for imports. + * + * @var array + */ + private static $globalImports = [ + 'ignoreannotation' => 'Doctrine\Common\Annotations\Annotation\IgnoreAnnotation', + ]; + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names are case sensitive. + * + * @var array + */ + private static $globalIgnoredNames = [ + // Annotation tags + 'Annotation' => true, 'Attribute' => true, 'Attributes' => true, + /* Can we enable this? 'Enum' => true, */ + 'Required' => true, + 'Target' => true, + // Widely used tags (but not existent in phpdoc) + 'fix' => true , 'fixme' => true, + 'override' => true, + // PHPDocumentor 1 tags + 'abstract'=> true, 'access'=> true, + 'code' => true, + 'deprec'=> true, + 'endcode' => true, 'exception'=> true, + 'final'=> true, + 'ingroup' => true, 'inheritdoc'=> true, 'inheritDoc'=> true, + 'magic' => true, + 'name'=> true, + 'toc' => true, 'tutorial'=> true, + 'private' => true, + 'static'=> true, 'staticvar'=> true, 'staticVar'=> true, + 'throw' => true, + // PHPDocumentor 2 tags. + 'api' => true, 'author'=> true, + 'category'=> true, 'copyright'=> true, + 'deprecated'=> true, + 'example'=> true, + 'filesource'=> true, + 'global'=> true, + 'ignore'=> true, /* Can we enable this? 'index' => true, */ 'internal'=> true, + 'license'=> true, 'link'=> true, + 'method' => true, + 'package'=> true, 'param'=> true, 'property' => true, 'property-read' => true, 'property-write' => true, + 'return'=> true, + 'see'=> true, 'since'=> true, 'source' => true, 'subpackage'=> true, + 'throws'=> true, 'todo'=> true, 'TODO'=> true, + 'usedby'=> true, 'uses' => true, + 'var'=> true, 'version'=> true, + // PHPUnit tags + 'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true, + // PHPCheckStyle + 'SuppressWarnings' => true, + // PHPStorm + 'noinspection' => true, + // PEAR + 'package_version' => true, + // PlantUML + 'startuml' => true, 'enduml' => true, + // Symfony 3.3 Cache Adapter + 'experimental' => true, + // Slevomat Coding Standard + 'phpcsSuppress' => true, + // PHP CodeSniffer + 'codingStandardsIgnoreStart' => true, + 'codingStandardsIgnoreEnd' => true, + // PHPStan + 'template' => true, 'implements' => true, 'extends' => true, 'use' => true, + ]; + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names are case sensitive. + * + * @var array + */ + private static $globalIgnoredNamespaces = []; + + /** + * Add a new annotation to the globally ignored annotation names with regard to exception handling. + * + * @param string $name + */ + static public function addGlobalIgnoredName($name) + { + self::$globalIgnoredNames[$name] = true; + } + + /** + * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling. + * + * @param string $namespace + */ + static public function addGlobalIgnoredNamespace($namespace) + { + self::$globalIgnoredNamespaces[$namespace] = true; + } + + /** + * Annotations parser. + * + * @var \Doctrine\Common\Annotations\DocParser + */ + private $parser; + + /** + * Annotations parser used to collect parsing metadata. + * + * @var \Doctrine\Common\Annotations\DocParser + */ + private $preParser; + + /** + * PHP parser used to collect imports. + * + * @var \Doctrine\Common\Annotations\PhpParser + */ + private $phpParser; + + /** + * In-memory cache mechanism to store imported annotations per class. + * + * @var array + */ + private $imports = []; + + /** + * In-memory cache mechanism to store ignored annotations per class. + * + * @var array + */ + private $ignoredAnnotationNames = []; + + /** + * Constructor. + * + * Initializes a new AnnotationReader. + * + * @param DocParser $parser + * + * @throws AnnotationException + */ + public function __construct(DocParser $parser = null) + { + if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) { + throw AnnotationException::optimizerPlusSaveComments(); + } + + if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') == 0) { + throw AnnotationException::optimizerPlusSaveComments(); + } + + // Make sure that the IgnoreAnnotation annotation is loaded + class_exists(IgnoreAnnotation::class); + + $this->parser = $parser ?: new DocParser(); + + $this->preParser = new DocParser; + + $this->preParser->setImports(self::$globalImports); + $this->preParser->setIgnoreNotImportedAnnotations(true); + $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames); + + $this->phpParser = new PhpParser; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $this->parser->setTarget(Target::TARGET_CLASS); + $this->parser->setImports($this->getClassImports($class)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + $annotations = $this->getClassAnnotations($class); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $context = 'property ' . $class->getName() . "::\$" . $property->getName(); + + $this->parser->setTarget(Target::TARGET_PROPERTY); + $this->parser->setImports($this->getPropertyImports($property)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($property->getDocComment(), $context); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + $annotations = $this->getPropertyAnnotations($property); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; + + $this->parser->setTarget(Target::TARGET_METHOD); + $this->parser->setImports($this->getMethodImports($method)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($method->getDocComment(), $context); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + $annotations = $this->getMethodAnnotations($method); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Returns the ignored annotations for the given class. + * + * @param \ReflectionClass $class + * + * @return array + */ + private function getIgnoredAnnotationNames(ReflectionClass $class) + { + $name = $class->getName(); + if (isset($this->ignoredAnnotationNames[$name])) { + return $this->ignoredAnnotationNames[$name]; + } + + $this->collectParsingMetadata($class); + + return $this->ignoredAnnotationNames[$name]; + } + + /** + * Retrieves imports. + * + * @param \ReflectionClass $class + * + * @return array + */ + private function getClassImports(ReflectionClass $class) + { + $name = $class->getName(); + if (isset($this->imports[$name])) { + return $this->imports[$name]; + } + + $this->collectParsingMetadata($class); + + return $this->imports[$name]; + } + + /** + * Retrieves imports for methods. + * + * @param \ReflectionMethod $method + * + * @return array + */ + private function getMethodImports(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $classImports = $this->getClassImports($class); + + $traitImports = []; + + foreach ($class->getTraits() as $trait) { + if ($trait->hasMethod($method->getName()) + && $trait->getFileName() === $method->getFileName() + ) { + $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait)); + } + } + + return array_merge($classImports, $traitImports); + } + + /** + * Retrieves imports for properties. + * + * @param \ReflectionProperty $property + * + * @return array + */ + private function getPropertyImports(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $classImports = $this->getClassImports($class); + + $traitImports = []; + + foreach ($class->getTraits() as $trait) { + if ($trait->hasProperty($property->getName())) { + $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait)); + } + } + + return array_merge($classImports, $traitImports); + } + + /** + * Collects parsing metadata for a given class. + * + * @param \ReflectionClass $class + */ + private function collectParsingMetadata(ReflectionClass $class) + { + $ignoredAnnotationNames = self::$globalIgnoredNames; + $annotations = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name); + + foreach ($annotations as $annotation) { + if ($annotation instanceof IgnoreAnnotation) { + foreach ($annotation->names AS $annot) { + $ignoredAnnotationNames[$annot] = true; + } + } + } + + $name = $class->getName(); + + $this->imports[$name] = array_merge( + self::$globalImports, + $this->phpParser->parseClass($class), + ['__NAMESPACE__' => $class->getNamespaceName()] + ); + + $this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames; + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php new file mode 100644 index 0000000000000000000000000000000000000000..ceb7eb7e097ad41e6e3d7ed8308bda9f9a1e4939 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php @@ -0,0 +1,180 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +final class AnnotationRegistry +{ + /** + * A map of namespaces to use for autoloading purposes based on a PSR-0 convention. + * + * Contains the namespace as key and an array of directories as value. If the value is NULL + * the include path is used for checking for the corresponding file. + * + * This autoloading mechanism does not utilize the PHP autoloading but implements autoloading on its own. + * + * @var string[][]|string[]|null[] + */ + static private $autoloadNamespaces = []; + + /** + * A map of autoloader callables. + * + * @var callable[] + */ + static private $loaders = []; + + /** + * An array of classes which cannot be found + * + * @var null[] indexed by class name + */ + static private $failedToAutoload = []; + + /** + * Whenever registerFile() was used. Disables use of standard autoloader. + * + * @var bool + */ + static private $registerFileUsed = false; + + public static function reset() : void + { + self::$autoloadNamespaces = []; + self::$loaders = []; + self::$failedToAutoload = []; + self::$registerFileUsed = false; + } + + /** + * Registers file. + * + * @deprecated This method is deprecated and will be removed in doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. + */ + public static function registerFile(string $file) : void + { + self::$registerFileUsed = true; + + require_once $file; + } + + /** + * Adds a namespace with one or many directories to look for files or null for the include path. + * + * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. + * + * @param string $namespace + * @param string|array|null $dirs + * + * @deprecated This method is deprecated and will be removed in doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. + */ + public static function registerAutoloadNamespace(string $namespace, $dirs = null) : void + { + self::$autoloadNamespaces[$namespace] = $dirs; + } + + /** + * Registers multiple namespaces. + * + * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. + * + * @param string[][]|string[]|null[] $namespaces indexed by namespace name + * + * @deprecated This method is deprecated and will be removed in doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. + */ + public static function registerAutoloadNamespaces(array $namespaces) : void + { + self::$autoloadNamespaces = \array_merge(self::$autoloadNamespaces, $namespaces); + } + + /** + * Registers an autoloading callable for annotations, much like spl_autoload_register(). + * + * NOTE: These class loaders HAVE to be silent when a class was not found! + * IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class. + * + * @deprecated This method is deprecated and will be removed in doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. + */ + public static function registerLoader(callable $callable) : void + { + // Reset our static cache now that we have a new loader to work with + self::$failedToAutoload = []; + self::$loaders[] = $callable; + } + + /** + * Registers an autoloading callable for annotations, if it is not already registered + * + * @deprecated This method is deprecated and will be removed in doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. + */ + public static function registerUniqueLoader(callable $callable) : void + { + if ( ! in_array($callable, self::$loaders, true) ) { + self::registerLoader($callable); + } + } + + /** + * Autoloads an annotation class silently. + */ + public static function loadAnnotationClass(string $class) : bool + { + if (\class_exists($class, false)) { + return true; + } + + if (\array_key_exists($class, self::$failedToAutoload)) { + return false; + } + + foreach (self::$autoloadNamespaces AS $namespace => $dirs) { + if (\strpos($class, $namespace) === 0) { + $file = \str_replace('\\', \DIRECTORY_SEPARATOR, $class) . '.php'; + + if ($dirs === null) { + if ($path = stream_resolve_include_path($file)) { + require $path; + return true; + } + } else { + foreach((array) $dirs AS $dir) { + if (is_file($dir . \DIRECTORY_SEPARATOR . $file)) { + require $dir . \DIRECTORY_SEPARATOR . $file; + return true; + } + } + } + } + } + + foreach (self::$loaders AS $loader) { + if ($loader($class) === true) { + return true; + } + } + + if (self::$loaders === [] && self::$autoloadNamespaces === [] && self::$registerFileUsed === false && \class_exists($class)) { + return true; + } + + self::$failedToAutoload[$class] = null; + + return false; + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php new file mode 100644 index 0000000000000000000000000000000000000000..8ed16f1a0d77a14181afd906b506d7cf85187b8b --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php @@ -0,0 +1,278 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Cache\Cache; +use ReflectionClass; + +/** + * A cache aware annotation reader. + * + * @author Johannes M. Schmitt + * @author Benjamin Eberlei + */ +final class CachedReader implements Reader +{ + /** + * @var Reader + */ + private $delegate; + + /** + * @var Cache + */ + private $cache; + + /** + * @var boolean + */ + private $debug; + + /** + * @var array + */ + private $loadedAnnotations = []; + + /** + * @var int[] + */ + private $loadedFilemtimes = []; + + /** + * @param bool $debug + */ + public function __construct(Reader $reader, Cache $cache, $debug = false) + { + $this->delegate = $reader; + $this->cache = $cache; + $this->debug = (boolean) $debug; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $cacheKey = $class->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { + $annots = $this->delegate->getClassAnnotations($class); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $cacheKey = $class->getName().'$'.$property->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { + $annots = $this->delegate->getPropertyAnnotations($property); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $cacheKey = $class->getName().'#'.$method->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { + $annots = $this->delegate->getMethodAnnotations($method); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * Clears loaded annotations. + * + * @return void + */ + public function clearLoadedAnnotations() + { + $this->loadedAnnotations = []; + $this->loadedFilemtimes = []; + } + + /** + * Fetches a value from the cache. + * + * @param string $cacheKey The cache key. + * + * @return mixed The cached value or false when the value is not in cache. + */ + private function fetchFromCache($cacheKey, ReflectionClass $class) + { + if (($data = $this->cache->fetch($cacheKey)) !== false) { + if (!$this->debug || $this->isCacheFresh($cacheKey, $class)) { + return $data; + } + } + + return false; + } + + /** + * Saves a value to the cache. + * + * @param string $cacheKey The cache key. + * @param mixed $value The value. + * + * @return void + */ + private function saveToCache($cacheKey, $value) + { + $this->cache->save($cacheKey, $value); + if ($this->debug) { + $this->cache->save('[C]'.$cacheKey, time()); + } + } + + /** + * Checks if the cache is fresh. + * + * @param string $cacheKey + * + * @return boolean + */ + private function isCacheFresh($cacheKey, ReflectionClass $class) + { + $lastModification = $this->getLastModification($class); + if ($lastModification === 0) { + return true; + } + + return $this->cache->fetch('[C]'.$cacheKey) >= $lastModification; + } + + /** + * Returns the time the class was last modified, testing traits and parents + * + * @return int + */ + private function getLastModification(ReflectionClass $class) + { + $filename = $class->getFileName(); + + if (isset($this->loadedFilemtimes[$filename])) { + return $this->loadedFilemtimes[$filename]; + } + + $parent = $class->getParentClass(); + + $lastModification = max(array_merge( + [$filename ? filemtime($filename) : 0], + array_map([$this, 'getTraitLastModificationTime'], $class->getTraits()), + array_map([$this, 'getLastModification'], $class->getInterfaces()), + $parent ? [$this->getLastModification($parent)] : [] + )); + + assert($lastModification !== false); + + return $this->loadedFilemtimes[$filename] = $lastModification; + } + + /** + * @return int + */ + private function getTraitLastModificationTime(ReflectionClass $reflectionTrait) + { + $fileName = $reflectionTrait->getFileName(); + + if (isset($this->loadedFilemtimes[$fileName])) { + return $this->loadedFilemtimes[$fileName]; + } + + $lastModificationTime = max(array_merge( + [$fileName ? filemtime($fileName) : 0], + array_map([$this, 'getTraitLastModificationTime'], $reflectionTrait->getTraits()) + )); + + assert($lastModificationTime !== false); + + return $this->loadedFilemtimes[$fileName] = $lastModificationTime; + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php new file mode 100644 index 0000000000000000000000000000000000000000..8182f6c6e0c5a4c27739e08b51f3414614b421e1 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php @@ -0,0 +1,147 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Lexer\AbstractLexer; + +/** + * Simple lexer for docblock annotations. + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Johannes M. Schmitt + */ +final class DocLexer extends AbstractLexer +{ + const T_NONE = 1; + const T_INTEGER = 2; + const T_STRING = 3; + const T_FLOAT = 4; + + // All tokens that are also identifiers should be >= 100 + const T_IDENTIFIER = 100; + const T_AT = 101; + const T_CLOSE_CURLY_BRACES = 102; + const T_CLOSE_PARENTHESIS = 103; + const T_COMMA = 104; + const T_EQUALS = 105; + const T_FALSE = 106; + const T_NAMESPACE_SEPARATOR = 107; + const T_OPEN_CURLY_BRACES = 108; + const T_OPEN_PARENTHESIS = 109; + const T_TRUE = 110; + const T_NULL = 111; + const T_COLON = 112; + const T_MINUS = 113; + + /** + * @var array + */ + protected $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 array + */ + protected $withCase = [ + 'true' => self::T_TRUE, + 'false' => self::T_FALSE, + 'null' => self::T_NULL + ]; + + /** + * Whether the next token starts immediately, or if there were + * non-captured symbols before that + */ + public function nextTokenIsAdjacent() : bool + { + return $this->token === null + || ($this->lookahead !== null + && ($this->lookahead['position'] - $this->token['position']) === strlen($this->token['value'])); + } + + /** + * {@inheritdoc} + */ + protected function getCatchablePatterns() + { + return [ + '[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*', + '(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', + '"(?:""|[^"])*+"', + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNonCatchablePatterns() + { + return ['\s+', '\*+', '(.)']; + } + + /** + * {@inheritdoc} + */ + protected function getType(&$value) + { + $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] === '\\' || ctype_alpha($value[0])) { + return self::T_IDENTIFIER; + } + + $lowerValue = strtolower($value); + + if (isset($this->withCase[$lowerValue])) { + return $this->withCase[$lowerValue]; + } + + // Checking numeric value + if (is_numeric($value)) { + return (strpos($value, '.') !== false || stripos($value, 'e') !== false) + ? self::T_FLOAT : self::T_INTEGER; + } + + return $type; + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php new file mode 100644 index 0000000000000000000000000000000000000000..741149ae24ae053ee70ab94f9399e1441a4a4c63 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php @@ -0,0 +1,1221 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Annotations\Annotation\Attribute; +use ReflectionClass; +use Doctrine\Common\Annotations\Annotation\Enum; +use Doctrine\Common\Annotations\Annotation\Target; +use Doctrine\Common\Annotations\Annotation\Attributes; + +/** + * A parser for docblock annotations. + * + * It is strongly discouraged to change the default annotation parsing process. + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Johannes M. Schmitt + * @author Fabio B. Silva + */ +final class DocParser +{ + /** + * An array of all valid tokens for a class name. + * + * @var array + */ + private static $classIdentifiers = [ + DocLexer::T_IDENTIFIER, + DocLexer::T_TRUE, + DocLexer::T_FALSE, + DocLexer::T_NULL + ]; + + /** + * The lexer. + * + * @var \Doctrine\Common\Annotations\DocLexer + */ + private $lexer; + + /** + * Current target context. + * + * @var integer + */ + private $target; + + /** + * Doc parser used to collect annotation target. + * + * @var \Doctrine\Common\Annotations\DocParser + */ + private static $metadataParser; + + /** + * Flag to control if the current annotation is nested or not. + * + * @var boolean + */ + private $isNestedAnnotation = false; + + /** + * Hashmap containing all use-statements that are to be used when parsing + * the given doc block. + * + * @var array + */ + private $imports = []; + + /** + * This hashmap is used internally to cache results of class_exists() + * look-ups. + * + * @var array + */ + private $classExists = []; + + /** + * Whether annotations that have not been imported should be ignored. + * + * @var boolean + */ + private $ignoreNotImportedAnnotations = false; + + /** + * An array of default namespaces if operating in simple mode. + * + * @var string[] + */ + private $namespaces = []; + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names must be the raw names as used in the class, not the fully qualified + * class names. + * + * @var bool[] indexed by annotation name + */ + private $ignoredAnnotationNames = []; + + /** + * A list with annotations in namespaced format + * that are not causing exceptions when not resolved to an annotation class. + * + * @var bool[] indexed by namespace name + */ + private $ignoredAnnotationNamespaces = []; + + /** + * @var string + */ + private $context = ''; + + /** + * Hash-map for caching annotation metadata. + * + * @var array + */ + private static $annotationMetadata = [ + 'Doctrine\Common\Annotations\Annotation\Target' => [ + 'is_annotation' => true, + 'has_constructor' => true, + 'properties' => [], + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => 'value', + 'attribute_types' => [ + 'value' => [ + 'required' => false, + 'type' =>'array', + 'array_type'=>'string', + 'value' =>'array' + ] + ], + ], + 'Doctrine\Common\Annotations\Annotation\Attribute' => [ + 'is_annotation' => true, + 'has_constructor' => false, + 'targets_literal' => 'ANNOTATION_ANNOTATION', + 'targets' => Target::TARGET_ANNOTATION, + 'default_property' => 'name', + 'properties' => [ + 'name' => 'name', + 'type' => 'type', + 'required' => 'required' + ], + 'attribute_types' => [ + 'value' => [ + 'required' => true, + 'type' =>'string', + 'value' =>'string' + ], + 'type' => [ + 'required' =>true, + 'type' =>'string', + 'value' =>'string' + ], + 'required' => [ + 'required' =>false, + 'type' =>'boolean', + 'value' =>'boolean' + ] + ], + ], + 'Doctrine\Common\Annotations\Annotation\Attributes' => [ + 'is_annotation' => true, + 'has_constructor' => false, + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => 'value', + 'properties' => [ + 'value' => 'value' + ], + 'attribute_types' => [ + 'value' => [ + 'type' =>'array', + 'required' =>true, + 'array_type'=>'Doctrine\Common\Annotations\Annotation\Attribute', + 'value' =>'array' + ] + ], + ], + 'Doctrine\Common\Annotations\Annotation\Enum' => [ + 'is_annotation' => true, + 'has_constructor' => true, + 'targets_literal' => 'ANNOTATION_PROPERTY', + 'targets' => Target::TARGET_PROPERTY, + 'default_property' => 'value', + 'properties' => [ + 'value' => 'value' + ], + 'attribute_types' => [ + 'value' => [ + 'type' => 'array', + 'required' => true, + ], + 'literal' => [ + 'type' => 'array', + 'required' => false, + ], + ], + ], + ]; + + /** + * Hash-map for handle types declaration. + * + * @var array + */ + private static $typeMap = [ + 'float' => 'double', + 'bool' => 'boolean', + // allow uppercase Boolean in honor of George Boole + 'Boolean' => 'boolean', + 'int' => 'integer', + ]; + + /** + * Constructs a new DocParser. + */ + public function __construct() + { + $this->lexer = new DocLexer; + } + + /** + * Sets the annotation names that are ignored during the parsing process. + * + * The names are supposed to be the raw names as used in the class, not the + * fully qualified class names. + * + * @param bool[] $names indexed by annotation name + * + * @return void + */ + public function setIgnoredAnnotationNames(array $names) + { + $this->ignoredAnnotationNames = $names; + } + + /** + * Sets the annotation namespaces that are ignored during the parsing process. + * + * @param bool[] $ignoredAnnotationNamespaces indexed by annotation namespace name + * + * @return void + */ + public function setIgnoredAnnotationNamespaces($ignoredAnnotationNamespaces) + { + $this->ignoredAnnotationNamespaces = $ignoredAnnotationNamespaces; + } + + /** + * Sets ignore on not-imported annotations. + * + * @param boolean $bool + * + * @return void + */ + public function setIgnoreNotImportedAnnotations($bool) + { + $this->ignoreNotImportedAnnotations = (boolean) $bool; + } + + /** + * Sets the default namespaces. + * + * @param string $namespace + * + * @return void + * + * @throws \RuntimeException + */ + public function addNamespace($namespace) + { + if ($this->imports) { + throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); + } + + $this->namespaces[] = $namespace; + } + + /** + * Sets the imports. + * + * @param array $imports + * + * @return void + * + * @throws \RuntimeException + */ + public function setImports(array $imports) + { + if ($this->namespaces) { + throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); + } + + $this->imports = $imports; + } + + /** + * Sets current target context as bitmask. + * + * @param integer $target + * + * @return void + */ + public function setTarget($target) + { + $this->target = $target; + } + + /** + * Parses the given docblock string for annotations. + * + * @param string $input The docblock string to parse. + * @param string $context The parsing context. + * + * @return array Array of annotations. If no annotations are found, an empty array is returned. + */ + public function parse($input, $context = '') + { + $pos = $this->findInitialTokenPosition($input); + if ($pos === null) { + return []; + } + + $this->context = $context; + + $this->lexer->setInput(trim(substr($input, $pos), '* /')); + $this->lexer->moveNext(); + + return $this->Annotations(); + } + + /** + * Finds the first valid annotation + * + * @param string $input The docblock string to parse + * + * @return int|null + */ + private function findInitialTokenPosition($input) + { + $pos = 0; + + // search for first valid annotation + while (($pos = strpos($input, '@', $pos)) !== false) { + $preceding = substr($input, $pos - 1, 1); + + // if the @ is preceded by a space, a tab or * it is valid + if ($pos === 0 || $preceding === ' ' || $preceding === '*' || $preceding === "\t") { + return $pos; + } + + $pos++; + } + + return null; + } + + /** + * Attempts to match the given token with the current lookahead token. + * If they match, updates the lookahead token; otherwise raises a syntax error. + * + * @param integer $token Type of token. + * + * @return boolean True if tokens match; false otherwise. + */ + private function match($token) + { + if ( ! $this->lexer->isNextToken($token) ) { + $this->syntaxError($this->lexer->getLiteral($token)); + } + + return $this->lexer->moveNext(); + } + + /** + * Attempts to match the current lookahead token with any of the given tokens. + * + * If any of them matches, this method updates the lookahead token; otherwise + * a syntax error is raised. + * + * @param array $tokens + * + * @return boolean + */ + private function matchAny(array $tokens) + { + if ( ! $this->lexer->isNextTokenAny($tokens)) { + $this->syntaxError(implode(' or ', array_map([$this->lexer, 'getLiteral'], $tokens))); + } + + return $this->lexer->moveNext(); + } + + /** + * Generates a new syntax error. + * + * @param string $expected Expected string. + * @param array|null $token Optional token. + * + * @return void + * + * @throws AnnotationException + */ + private function syntaxError($expected, $token = null) + { + if ($token === null) { + $token = $this->lexer->lookahead; + } + + $message = sprintf('Expected %s, got ', $expected); + $message .= ($this->lexer->lookahead === null) + ? 'end of string' + : sprintf("'%s' at position %s", $token['value'], $token['position']); + + if (strlen($this->context)) { + $message .= ' in ' . $this->context; + } + + $message .= '.'; + + throw AnnotationException::syntaxError($message); + } + + /** + * Attempts to check if a class exists or not. This never goes through the PHP autoloading mechanism + * but uses the {@link AnnotationRegistry} to load classes. + * + * @param string $fqcn + * + * @return boolean + */ + private function classExists($fqcn) + { + if (isset($this->classExists[$fqcn])) { + return $this->classExists[$fqcn]; + } + + // first check if the class already exists, maybe loaded through another AnnotationReader + if (class_exists($fqcn, false)) { + return $this->classExists[$fqcn] = true; + } + + // final check, does this class exist? + return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn); + } + + /** + * Collects parsing metadata for a given annotation class + * + * @param string $name The annotation name + * + * @return void + */ + private function collectAnnotationMetadata($name) + { + if (self::$metadataParser === null) { + self::$metadataParser = new self(); + + self::$metadataParser->setIgnoreNotImportedAnnotations(true); + self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames); + self::$metadataParser->setImports([ + 'enum' => 'Doctrine\Common\Annotations\Annotation\Enum', + 'target' => 'Doctrine\Common\Annotations\Annotation\Target', + 'attribute' => 'Doctrine\Common\Annotations\Annotation\Attribute', + 'attributes' => 'Doctrine\Common\Annotations\Annotation\Attributes' + ]); + + // Make sure that annotations from metadata are loaded + class_exists(Enum::class); + class_exists(Target::class); + class_exists(Attribute::class); + class_exists(Attributes::class); + } + + $class = new \ReflectionClass($name); + $docComment = $class->getDocComment(); + + // Sets default values for annotation metadata + $metadata = [ + 'default_property' => null, + 'has_constructor' => (null !== $constructor = $class->getConstructor()) && $constructor->getNumberOfParameters() > 0, + 'properties' => [], + 'property_types' => [], + 'attribute_types' => [], + 'targets_literal' => null, + 'targets' => Target::TARGET_ALL, + 'is_annotation' => false !== strpos($docComment, '@Annotation'), + ]; + + // verify that the class is really meant to be an annotation + if ($metadata['is_annotation']) { + self::$metadataParser->setTarget(Target::TARGET_CLASS); + + foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) { + if ($annotation instanceof Target) { + $metadata['targets'] = $annotation->targets; + $metadata['targets_literal'] = $annotation->literal; + + continue; + } + + if ($annotation instanceof Attributes) { + foreach ($annotation->value as $attribute) { + $this->collectAttributeTypeMetadata($metadata, $attribute); + } + } + } + + // if not has a constructor will inject values into public properties + if (false === $metadata['has_constructor']) { + // collect all public properties + foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $metadata['properties'][$property->name] = $property->name; + + if (false === ($propertyComment = $property->getDocComment())) { + continue; + } + + $attribute = new Attribute(); + + $attribute->required = (false !== strpos($propertyComment, '@Required')); + $attribute->name = $property->name; + $attribute->type = (false !== strpos($propertyComment, '@var') && preg_match('/@var\s+([^\s]+)/',$propertyComment, $matches)) + ? $matches[1] + : 'mixed'; + + $this->collectAttributeTypeMetadata($metadata, $attribute); + + // checks if the property has @Enum + if (false !== strpos($propertyComment, '@Enum')) { + $context = 'property ' . $class->name . "::\$" . $property->name; + + self::$metadataParser->setTarget(Target::TARGET_PROPERTY); + + foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) { + if ( ! $annotation instanceof Enum) { + continue; + } + + $metadata['enum'][$property->name]['value'] = $annotation->value; + $metadata['enum'][$property->name]['literal'] = ( ! empty($annotation->literal)) + ? $annotation->literal + : $annotation->value; + } + } + } + + // choose the first property as default property + $metadata['default_property'] = reset($metadata['properties']); + } + } + + self::$annotationMetadata[$name] = $metadata; + } + + /** + * Collects parsing metadata for a given attribute. + * + * @param array $metadata + * @param Attribute $attribute + * + * @return void + */ + private function collectAttributeTypeMetadata(&$metadata, Attribute $attribute) + { + // handle internal type declaration + $type = self::$typeMap[$attribute->type] ?? $attribute->type; + + // handle the case if the property type is mixed + if ('mixed' === $type) { + return; + } + + // Evaluate type + switch (true) { + // Checks if the property has array + case (false !== $pos = strpos($type, '<')): + $arrayType = substr($type, $pos + 1, -1); + $type = 'array'; + + if (isset(self::$typeMap[$arrayType])) { + $arrayType = self::$typeMap[$arrayType]; + } + + $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; + break; + + // Checks if the property has type[] + case (false !== $pos = strrpos($type, '[')): + $arrayType = substr($type, 0, $pos); + $type = 'array'; + + if (isset(self::$typeMap[$arrayType])) { + $arrayType = self::$typeMap[$arrayType]; + } + + $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; + break; + } + + $metadata['attribute_types'][$attribute->name]['type'] = $type; + $metadata['attribute_types'][$attribute->name]['value'] = $attribute->type; + $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required; + } + + /** + * Annotations ::= Annotation {[ "*" ]* [Annotation]}* + * + * @return array + */ + private function Annotations() + { + $annotations = []; + + while (null !== $this->lexer->lookahead) { + if (DocLexer::T_AT !== $this->lexer->lookahead['type']) { + $this->lexer->moveNext(); + continue; + } + + // make sure the @ is preceded by non-catchable pattern + if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) { + $this->lexer->moveNext(); + continue; + } + + // make sure the @ is followed by either a namespace separator, or + // an identifier token + if ((null === $peek = $this->lexer->glimpse()) + || (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true)) + || $peek['position'] !== $this->lexer->lookahead['position'] + 1) { + $this->lexer->moveNext(); + continue; + } + + $this->isNestedAnnotation = false; + if (false !== $annot = $this->Annotation()) { + $annotations[] = $annot; + } + } + + return $annotations; + } + + /** + * Annotation ::= "@" AnnotationName MethodCall + * AnnotationName ::= QualifiedName | SimpleName + * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName + * NameSpacePart ::= identifier | null | false | true + * SimpleName ::= identifier | null | false | true + * + * @return mixed False if it is not a valid annotation. + * + * @throws AnnotationException + */ + private function Annotation() + { + $this->match(DocLexer::T_AT); + + // check if we have an annotation + $name = $this->Identifier(); + + if ($this->lexer->isNextToken(DocLexer::T_MINUS) + && $this->lexer->nextTokenIsAdjacent() + ) { + // Annotations with dashes, such as "@foo-" or "@foo-bar", are to be discarded + return false; + } + + // only process names which are not fully qualified, yet + // fully qualified names must start with a \ + $originalName = $name; + + if ('\\' !== $name[0]) { + $pos = strpos($name, '\\'); + $alias = (false === $pos)? $name : substr($name, 0, $pos); + $found = false; + $loweredAlias = strtolower($alias); + + if ($this->namespaces) { + foreach ($this->namespaces as $namespace) { + if ($this->classExists($namespace.'\\'.$name)) { + $name = $namespace.'\\'.$name; + $found = true; + break; + } + } + } elseif (isset($this->imports[$loweredAlias])) { + $namespace = ltrim($this->imports[$loweredAlias], '\\'); + $name = (false !== $pos) + ? $namespace . substr($name, $pos) + : $namespace; + $found = $this->classExists($name); + } elseif ( ! isset($this->ignoredAnnotationNames[$name]) + && isset($this->imports['__NAMESPACE__']) + && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name) + ) { + $name = $this->imports['__NAMESPACE__'].'\\'.$name; + $found = true; + } elseif (! isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) { + $found = true; + } + + if ( ! $found) { + if ($this->isIgnoredAnnotation($name)) { + return false; + } + + throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation?', $name, $this->context)); + } + } + + $name = ltrim($name,'\\'); + + if ( ! $this->classExists($name)) { + throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context)); + } + + // at this point, $name contains the fully qualified class name of the + // annotation, and it is also guaranteed that this class exists, and + // that it is loaded + + + // collects the metadata annotation only if there is not yet + if ( ! isset(self::$annotationMetadata[$name])) { + $this->collectAnnotationMetadata($name); + } + + // verify that the class is really meant to be an annotation and not just any ordinary class + if (self::$annotationMetadata[$name]['is_annotation'] === false) { + if ($this->isIgnoredAnnotation($originalName) || $this->isIgnoredAnnotation($name)) { + return false; + } + + throw AnnotationException::semanticalError(sprintf('The class "%s" is not annotated with @Annotation. Are you sure this class can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "%s". If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s.', $name, $name, $originalName, $this->context)); + } + + //if target is nested annotation + $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target; + + // Next will be nested + $this->isNestedAnnotation = true; + + //if annotation does not support current target + if (0 === (self::$annotationMetadata[$name]['targets'] & $target) && $target) { + throw AnnotationException::semanticalError( + sprintf('Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s.', + $originalName, $this->context, self::$annotationMetadata[$name]['targets_literal']) + ); + } + + $values = $this->MethodCall(); + + if (isset(self::$annotationMetadata[$name]['enum'])) { + // checks all declared attributes + foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) { + // checks if the attribute is a valid enumerator + if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) { + throw AnnotationException::enumeratorError($property, $name, $this->context, $enum['literal'], $values[$property]); + } + } + } + + // checks all declared attributes + foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) { + if ($property === self::$annotationMetadata[$name]['default_property'] + && !isset($values[$property]) && isset($values['value'])) { + $property = 'value'; + } + + // handle a not given attribute or null value + if (!isset($values[$property])) { + if ($type['required']) { + throw AnnotationException::requiredError($property, $originalName, $this->context, 'a(n) '.$type['value']); + } + + continue; + } + + if ($type['type'] === 'array') { + // handle the case of a single value + if ( ! is_array($values[$property])) { + $values[$property] = [$values[$property]]; + } + + // checks if the attribute has array type declaration, such as "array" + if (isset($type['array_type'])) { + foreach ($values[$property] as $item) { + if (gettype($item) !== $type['array_type'] && !$item instanceof $type['array_type']) { + throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'either a(n) '.$type['array_type'].', or an array of '.$type['array_type'].'s', $item); + } + } + } + } elseif (gettype($values[$property]) !== $type['type'] && !$values[$property] instanceof $type['type']) { + throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'a(n) '.$type['value'], $values[$property]); + } + } + + // check if the annotation expects values via the constructor, + // or directly injected into public properties + if (self::$annotationMetadata[$name]['has_constructor'] === true) { + return new $name($values); + } + + $instance = new $name(); + + foreach ($values as $property => $value) { + if (!isset(self::$annotationMetadata[$name]['properties'][$property])) { + if ('value' !== $property) { + throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not have a property named "%s". Available properties: %s', $originalName, $this->context, $property, implode(', ', self::$annotationMetadata[$name]['properties']))); + } + + // handle the case if the property has no annotations + if ( ! $property = self::$annotationMetadata[$name]['default_property']) { + throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not accept any values, but got %s.', $originalName, $this->context, json_encode($values))); + } + } + + $instance->{$property} = $value; + } + + return $instance; + } + + /** + * MethodCall ::= ["(" [Values] ")"] + * + * @return array + */ + private function MethodCall() + { + $values = []; + + if ( ! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { + return $values; + } + + $this->match(DocLexer::T_OPEN_PARENTHESIS); + + if ( ! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { + $values = $this->Values(); + } + + $this->match(DocLexer::T_CLOSE_PARENTHESIS); + + return $values; + } + + /** + * Values ::= Array | Value {"," Value}* [","] + * + * @return array + */ + private function Values() + { + $values = [$this->Value()]; + + while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { + $this->match(DocLexer::T_COMMA); + + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { + break; + } + + $token = $this->lexer->lookahead; + $value = $this->Value(); + + if ( ! is_object($value) && ! is_array($value)) { + $this->syntaxError('Value', $token); + } + + $values[] = $value; + } + + foreach ($values as $k => $value) { + if (is_object($value) && $value instanceof \stdClass) { + $values[$value->name] = $value->value; + } else if ( ! isset($values['value'])){ + $values['value'] = $value; + } else { + if ( ! is_array($values['value'])) { + $values['value'] = [$values['value']]; + } + + $values['value'][] = $value; + } + + unset($values[$k]); + } + + return $values; + } + + /** + * Constant ::= integer | string | float | boolean + * + * @return mixed + * + * @throws AnnotationException + */ + private function Constant() + { + $identifier = $this->Identifier(); + + if ( ! defined($identifier) && false !== strpos($identifier, '::') && '\\' !== $identifier[0]) { + list($className, $const) = explode('::', $identifier); + + $pos = strpos($className, '\\'); + $alias = (false === $pos) ? $className : substr($className, 0, $pos); + $found = false; + $loweredAlias = strtolower($alias); + + switch (true) { + case !empty ($this->namespaces): + foreach ($this->namespaces as $ns) { + if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) { + $className = $ns.'\\'.$className; + $found = true; + break; + } + } + break; + + case isset($this->imports[$loweredAlias]): + $found = true; + $className = (false !== $pos) + ? $this->imports[$loweredAlias] . substr($className, $pos) + : $this->imports[$loweredAlias]; + break; + + default: + if(isset($this->imports['__NAMESPACE__'])) { + $ns = $this->imports['__NAMESPACE__']; + + if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) { + $className = $ns.'\\'.$className; + $found = true; + } + } + break; + } + + if ($found) { + $identifier = $className . '::' . $const; + } + } + + /** + * Checks if identifier ends with ::class and remove the leading backslash if it exists. + */ + if ($this->identifierEndsWithClassConstant($identifier) && ! $this->identifierStartsWithBackslash($identifier)) { + return substr($identifier, 0, $this->getClassConstantPositionInIdentifier($identifier)); + } + if ($this->identifierEndsWithClassConstant($identifier) && $this->identifierStartsWithBackslash($identifier)) { + return substr($identifier, 1, $this->getClassConstantPositionInIdentifier($identifier) - 1); + } + + if (!defined($identifier)) { + throw AnnotationException::semanticalErrorConstants($identifier, $this->context); + } + + return constant($identifier); + } + + private function identifierStartsWithBackslash(string $identifier) : bool + { + return '\\' === $identifier[0]; + } + + private function identifierEndsWithClassConstant(string $identifier) : bool + { + return $this->getClassConstantPositionInIdentifier($identifier) === strlen($identifier) - strlen('::class'); + } + + /** + * @return int|false + */ + private function getClassConstantPositionInIdentifier(string $identifier) + { + return stripos($identifier, '::class'); + } + + /** + * Identifier ::= string + * + * @return string + */ + private function Identifier() + { + // check if we have an annotation + if ( ! $this->lexer->isNextTokenAny(self::$classIdentifiers)) { + $this->syntaxError('namespace separator or identifier'); + } + + $this->lexer->moveNext(); + + $className = $this->lexer->token['value']; + + while ( + null !== $this->lexer->lookahead && + $this->lexer->lookahead['position'] === ($this->lexer->token['position'] + strlen($this->lexer->token['value'])) && + $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR) + ) { + $this->match(DocLexer::T_NAMESPACE_SEPARATOR); + $this->matchAny(self::$classIdentifiers); + + $className .= '\\' . $this->lexer->token['value']; + } + + return $className; + } + + /** + * Value ::= PlainValue | FieldAssignment + * + * @return mixed + */ + private function Value() + { + $peek = $this->lexer->glimpse(); + + if (DocLexer::T_EQUALS === $peek['type']) { + return $this->FieldAssignment(); + } + + return $this->PlainValue(); + } + + /** + * PlainValue ::= integer | string | float | boolean | Array | Annotation + * + * @return mixed + */ + private function PlainValue() + { + if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) { + return $this->Arrayx(); + } + + if ($this->lexer->isNextToken(DocLexer::T_AT)) { + return $this->Annotation(); + } + + if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { + return $this->Constant(); + } + + switch ($this->lexer->lookahead['type']) { + case DocLexer::T_STRING: + $this->match(DocLexer::T_STRING); + return $this->lexer->token['value']; + + case DocLexer::T_INTEGER: + $this->match(DocLexer::T_INTEGER); + return (int)$this->lexer->token['value']; + + case DocLexer::T_FLOAT: + $this->match(DocLexer::T_FLOAT); + return (float)$this->lexer->token['value']; + + case DocLexer::T_TRUE: + $this->match(DocLexer::T_TRUE); + return true; + + case DocLexer::T_FALSE: + $this->match(DocLexer::T_FALSE); + return false; + + case DocLexer::T_NULL: + $this->match(DocLexer::T_NULL); + return null; + + default: + $this->syntaxError('PlainValue'); + } + } + + /** + * FieldAssignment ::= FieldName "=" PlainValue + * FieldName ::= identifier + * + * @return \stdClass + */ + private function FieldAssignment() + { + $this->match(DocLexer::T_IDENTIFIER); + $fieldName = $this->lexer->token['value']; + + $this->match(DocLexer::T_EQUALS); + + $item = new \stdClass(); + $item->name = $fieldName; + $item->value = $this->PlainValue(); + + return $item; + } + + /** + * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}" + * + * @return array + */ + private function Arrayx() + { + $array = $values = []; + + $this->match(DocLexer::T_OPEN_CURLY_BRACES); + + // If the array is empty, stop parsing and return. + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { + $this->match(DocLexer::T_CLOSE_CURLY_BRACES); + + return $array; + } + + $values[] = $this->ArrayEntry(); + + while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { + $this->match(DocLexer::T_COMMA); + + // optional trailing comma + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { + break; + } + + $values[] = $this->ArrayEntry(); + } + + $this->match(DocLexer::T_CLOSE_CURLY_BRACES); + + foreach ($values as $value) { + list ($key, $val) = $value; + + if ($key !== null) { + $array[$key] = $val; + } else { + $array[] = $val; + } + } + + return $array; + } + + /** + * ArrayEntry ::= Value | KeyValuePair + * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant + * Key ::= string | integer | Constant + * + * @return array + */ + private function ArrayEntry() + { + $peek = $this->lexer->glimpse(); + + if (DocLexer::T_EQUALS === $peek['type'] + || DocLexer::T_COLON === $peek['type']) { + + if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { + $key = $this->Constant(); + } else { + $this->matchAny([DocLexer::T_INTEGER, DocLexer::T_STRING]); + $key = $this->lexer->token['value']; + } + + $this->matchAny([DocLexer::T_EQUALS, DocLexer::T_COLON]); + + return [$key, $this->PlainValue()]; + } + + return [null, $this->Value()]; + } + + /** + * Checks whether the given $name matches any ignored annotation name or namespace + * + * @param string $name + * + * @return bool + */ + private function isIgnoredAnnotation($name) + { + if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { + return true; + } + + foreach (array_keys($this->ignoredAnnotationNamespaces) as $ignoredAnnotationNamespace) { + $ignoredAnnotationNamespace = rtrim($ignoredAnnotationNamespace, '\\') . '\\'; + + if (0 === stripos(rtrim($name, '\\') . '\\', $ignoredAnnotationNamespace)) { + return true; + } + } + + return false; + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php new file mode 100644 index 0000000000000000000000000000000000000000..40141af28eaeb839788ee999756a0cbd885aa2b2 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php @@ -0,0 +1,290 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * File cache reader for annotations. + * + * @author Johannes M. Schmitt + * @author Benjamin Eberlei + * + * @deprecated the FileCacheReader is deprecated and will be removed + * in version 2.0.0 of doctrine/annotations. Please use the + * {@see \Doctrine\Common\Annotations\CachedReader} instead. + */ +class FileCacheReader implements Reader +{ + /** + * @var Reader + */ + private $reader; + + /** + * @var string + */ + private $dir; + + /** + * @var bool + */ + private $debug; + + /** + * @var array + */ + private $loadedAnnotations = []; + + /** + * @var array + */ + private $classNameHashes = []; + + /** + * @var int + */ + private $umask; + + /** + * Constructor. + * + * @param Reader $reader + * @param string $cacheDir + * @param boolean $debug + * + * @throws \InvalidArgumentException + */ + public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002) + { + if ( ! is_int($umask)) { + throw new \InvalidArgumentException(sprintf( + 'The parameter umask must be an integer, was: %s', + gettype($umask) + )); + } + + $this->reader = $reader; + $this->umask = $umask; + + if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777 & (~$this->umask), true)) { + throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $cacheDir)); + } + + $this->dir = rtrim($cacheDir, '\\/'); + $this->debug = $debug; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(\ReflectionClass $class) + { + if ( ! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + $key = $this->classNameHashes[$class->name]; + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; + if (!is_file($path)) { + $annot = $this->reader->getClassAnnotations($class); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + if ($this->debug + && (false !== $filename = $class->getFileName()) + && filemtime($path) < filemtime($filename)) { + @unlink($path); + + $annot = $this->reader->getClassAnnotations($class); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + if ( ! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + $key = $this->classNameHashes[$class->name].'$'.$property->getName(); + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; + if (!is_file($path)) { + $annot = $this->reader->getPropertyAnnotations($property); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + if ($this->debug + && (false !== $filename = $class->getFilename()) + && filemtime($path) < filemtime($filename)) { + @unlink($path); + + $annot = $this->reader->getPropertyAnnotations($property); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + if ( ! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + $key = $this->classNameHashes[$class->name].'#'.$method->getName(); + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; + if (!is_file($path)) { + $annot = $this->reader->getMethodAnnotations($method); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + if ($this->debug + && (false !== $filename = $class->getFilename()) + && filemtime($path) < filemtime($filename)) { + @unlink($path); + + $annot = $this->reader->getMethodAnnotations($method); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * Saves the cache file. + * + * @param string $path + * @param mixed $data + * + * @return void + */ + private function saveCacheFile($path, $data) + { + if (!is_writable($this->dir)) { + throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable. Both, the webserver and the console user need access. You can manage access rights for multiple users with "chmod +a". If your system does not support this, check out the acl package.', $this->dir)); + } + + $tempfile = tempnam($this->dir, uniqid('', true)); + + if (false === $tempfile) { + throw new \RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir)); + } + + @chmod($tempfile, 0666 & (~$this->umask)); + + $written = file_put_contents($tempfile, 'umask)); + + if (false === rename($tempfile, $path)) { + @unlink($tempfile); + throw new \RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path)); + } + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(\ReflectionClass $class, $annotationName) + { + $annotations = $this->getClassAnnotations($class); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) + { + $annotations = $this->getMethodAnnotations($method); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) + { + $annotations = $this->getPropertyAnnotations($property); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Clears loaded annotations. + * + * @return void + */ + public function clearLoadedAnnotations() + { + $this->loadedAnnotations = []; + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php new file mode 100644 index 0000000000000000000000000000000000000000..4e8c3c8c3cb54890103d7811e472d270b17c21ff --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php @@ -0,0 +1,119 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Allows the reader to be used in-place of Doctrine's reader. + * + * @author Johannes M. Schmitt + */ +class IndexedReader implements Reader +{ + /** + * @var Reader + */ + private $delegate; + + /** + * Constructor. + * + * @param Reader $reader + */ + public function __construct(Reader $reader) + { + $this->delegate = $reader; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(\ReflectionClass $class) + { + $annotations = []; + foreach ($this->delegate->getClassAnnotations($class) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(\ReflectionClass $class, $annotation) + { + return $this->delegate->getClassAnnotation($class, $annotation); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + $annotations = []; + foreach ($this->delegate->getMethodAnnotations($method) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotation) + { + return $this->delegate->getMethodAnnotation($method, $annotation); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + $annotations = []; + foreach ($this->delegate->getPropertyAnnotations($property) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotation) + { + return $this->delegate->getPropertyAnnotation($property, $annotation); + } + + /** + * Proxies all methods to the delegate. + * + * @param string $method + * @param array $args + * + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array([$this->delegate, $method], $args); + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php new file mode 100644 index 0000000000000000000000000000000000000000..ec871813ba6763ffa7149d726365f4e983da6340 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php @@ -0,0 +1,91 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use SplFileObject; + +/** + * Parses a file for namespaces/use/class declarations. + * + * @author Fabien Potencier + * @author Christian Kaps + */ +final class PhpParser +{ + /** + * Parses a class. + * + * @param \ReflectionClass $class A ReflectionClass object. + * + * @return array A list with use statements in the form (Alias => FQN). + */ + public function parseClass(\ReflectionClass $class) + { + if (method_exists($class, 'getUseStatements')) { + return $class->getUseStatements(); + } + + if (false === $filename = $class->getFileName()) { + return []; + } + + $content = $this->getFileContent($filename, $class->getStartLine()); + + if (null === $content) { + return []; + } + + $namespace = preg_quote($class->getNamespaceName()); + $content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content); + $tokenizer = new TokenParser('parseUseStatements($class->getNamespaceName()); + + return $statements; + } + + /** + * Gets the content of the file right up to the given line number. + * + * @param string $filename The name of the file to load. + * @param integer $lineNumber The number of lines to read from file. + * + * @return string|null The content of the file or null if the file does not exist. + */ + private function getFileContent($filename, $lineNumber) + { + if ( ! is_file($filename)) { + return null; + } + + $content = ''; + $lineCnt = 0; + $file = new SplFileObject($filename); + while (!$file->eof()) { + if ($lineCnt++ == $lineNumber) { + break; + } + + $content .= $file->fgets(); + } + + return $content; + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php new file mode 100644 index 0000000000000000000000000000000000000000..4774f87312bfc6e8526ea5b6c508fceef7ffdc91 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php @@ -0,0 +1,89 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Interface for annotation readers. + * + * @author Johannes M. Schmitt + */ +interface Reader +{ + /** + * Gets the annotations applied to a class. + * + * @param \ReflectionClass $class The ReflectionClass of the class from which + * the class annotations should be read. + * + * @return array An array of Annotations. + */ + function getClassAnnotations(\ReflectionClass $class); + + /** + * Gets a class annotation. + * + * @param \ReflectionClass $class The ReflectionClass of the class from which + * the class annotations should be read. + * @param string $annotationName The name of the annotation. + * + * @return object|null The Annotation or NULL, if the requested annotation does not exist. + */ + function getClassAnnotation(\ReflectionClass $class, $annotationName); + + /** + * Gets the annotations applied to a method. + * + * @param \ReflectionMethod $method The ReflectionMethod of the method from which + * the annotations should be read. + * + * @return array An array of Annotations. + */ + function getMethodAnnotations(\ReflectionMethod $method); + + /** + * Gets a method annotation. + * + * @param \ReflectionMethod $method The ReflectionMethod to read the annotations from. + * @param string $annotationName The name of the annotation. + * + * @return object|null The Annotation or NULL, if the requested annotation does not exist. + */ + function getMethodAnnotation(\ReflectionMethod $method, $annotationName); + + /** + * Gets the annotations applied to a property. + * + * @param \ReflectionProperty $property The ReflectionProperty of the property + * from which the annotations should be read. + * + * @return array An array of Annotations. + */ + function getPropertyAnnotations(\ReflectionProperty $property); + + /** + * Gets a property annotation. + * + * @param \ReflectionProperty $property The ReflectionProperty to read the annotations from. + * @param string $annotationName The name of the annotation. + * + * @return object|null The Annotation or NULL, if the requested annotation does not exist. + */ + function getPropertyAnnotation(\ReflectionProperty $property, $annotationName); +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php new file mode 100644 index 0000000000000000000000000000000000000000..d4757eea2fb59a064b25c3869d453d803dc03a91 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php @@ -0,0 +1,127 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Simple Annotation Reader. + * + * This annotation reader is intended to be used in projects where you have + * full-control over all annotations that are available. + * + * @since 2.2 + * @author Johannes M. Schmitt + * @author Fabio B. Silva + */ +class SimpleAnnotationReader implements Reader +{ + /** + * @var DocParser + */ + private $parser; + + /** + * Constructor. + * + * Initializes a new SimpleAnnotationReader. + */ + public function __construct() + { + $this->parser = new DocParser(); + $this->parser->setIgnoreNotImportedAnnotations(true); + } + + /** + * Adds a namespace in which we will look for annotations. + * + * @param string $namespace + * + * @return void + */ + public function addNamespace($namespace) + { + $this->parser->addNamespace($namespace); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(\ReflectionClass $class) + { + return $this->parser->parse($class->getDocComment(), 'class '.$class->getName()); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + return $this->parser->parse($method->getDocComment(), 'method '.$method->getDeclaringClass()->name.'::'.$method->getName().'()'); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + return $this->parser->parse($property->getDocComment(), 'property '.$property->getDeclaringClass()->name.'::$'.$property->getName()); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(\ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } +} diff --git a/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php new file mode 100644 index 0000000000000000000000000000000000000000..03d9320ab0b0419222ef6e9b9c7291fd71bd5235 --- /dev/null +++ b/lib/composer/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php @@ -0,0 +1,194 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Parses a file for namespaces/use/class declarations. + * + * @author Fabien Potencier + * @author Christian Kaps + */ +class TokenParser +{ + /** + * The token list. + * + * @var array + */ + private $tokens; + + /** + * The number of tokens. + * + * @var int + */ + private $numTokens; + + /** + * The current array pointer. + * + * @var int + */ + private $pointer = 0; + + /** + * @param string $contents + */ + public function __construct($contents) + { + $this->tokens = token_get_all($contents); + + // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it + // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored + // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a + // docblock. If the first thing in the file is a class without a doc block this would cause calls to + // getDocBlock() on said class to return our long lost doc_comment. Argh. + // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least + // it's harmless to us. + token_get_all("numTokens = count($this->tokens); + } + + /** + * Gets the next non whitespace and non comment token. + * + * @param boolean $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped. + * If FALSE then only whitespace and normal comments are skipped. + * + * @return array|null The token if exists, null otherwise. + */ + public function next($docCommentIsComment = TRUE) + { + for ($i = $this->pointer; $i < $this->numTokens; $i++) { + $this->pointer++; + if ($this->tokens[$i][0] === T_WHITESPACE || + $this->tokens[$i][0] === T_COMMENT || + ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)) { + + continue; + } + + return $this->tokens[$i]; + } + + return null; + } + + /** + * Parses a single use statement. + * + * @return array A list with all found class names for a use statement. + */ + public function parseUseStatement() + { + + $groupRoot = ''; + $class = ''; + $alias = ''; + $statements = []; + $explicitAlias = false; + while (($token = $this->next())) { + $isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR; + if (!$explicitAlias && $isNameToken) { + $class .= $token[1]; + $alias = $token[1]; + } else if ($explicitAlias && $isNameToken) { + $alias .= $token[1]; + } else if ($token[0] === T_AS) { + $explicitAlias = true; + $alias = ''; + } else if ($token === ',') { + $statements[strtolower($alias)] = $groupRoot . $class; + $class = ''; + $alias = ''; + $explicitAlias = false; + } else if ($token === ';') { + $statements[strtolower($alias)] = $groupRoot . $class; + break; + } else if ($token === '{' ) { + $groupRoot = $class; + $class = ''; + } else if ($token === '}' ) { + continue; + } else { + break; + } + } + + return $statements; + } + + /** + * Gets all use statements. + * + * @param string $namespaceName The namespace name of the reflected class. + * + * @return array A list with all found use statements. + */ + public function parseUseStatements($namespaceName) + { + $statements = []; + while (($token = $this->next())) { + if ($token[0] === T_USE) { + $statements = array_merge($statements, $this->parseUseStatement()); + continue; + } + if ($token[0] !== T_NAMESPACE || $this->parseNamespace() != $namespaceName) { + continue; + } + + // Get fresh array for new namespace. This is to prevent the parser to collect the use statements + // for a previous namespace with the same name. This is the case if a namespace is defined twice + // or if a namespace with the same name is commented out. + $statements = []; + } + + return $statements; + } + + /** + * Gets the namespace. + * + * @return string The found namespace. + */ + public function parseNamespace() + { + $name = ''; + while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) { + $name .= $token[1]; + } + + return $name; + } + + /** + * Gets the class name. + * + * @return string The found class name. + */ + public function parseClass() + { + // Namespaces and class names are tokenized the same: T_STRINGs + // separated by T_NS_SEPARATOR so we can use one function to provide + // both. + return $this->parseNamespace(); + } +} diff --git a/lib/composer/doctrine/annotations/phpbench.json.dist b/lib/composer/doctrine/annotations/phpbench.json.dist new file mode 100644 index 0000000000000000000000000000000000000000..35edde95006504fc379c8963685e8185d6869b57 --- /dev/null +++ b/lib/composer/doctrine/annotations/phpbench.json.dist @@ -0,0 +1,4 @@ +{ + "bootstrap": "tests/Doctrine/Performance/Common/bootstrap.php", + "path": "tests/Doctrine/Performance/Common/Annotations" +} diff --git a/lib/composer/doctrine/annotations/phpstan.neon b/lib/composer/doctrine/annotations/phpstan.neon new file mode 100644 index 0000000000000000000000000000000000000000..bac7f83c4d6fa2a44724ab31c3b8dbc08ed1cd02 --- /dev/null +++ b/lib/composer/doctrine/annotations/phpstan.neon @@ -0,0 +1,14 @@ +parameters: + autoload_files: + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Annotations/DocParserTest.php + excludes_analyse: + - %currentWorkingDirectory%/tests/*/Fixtures/* + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Annotations/ReservedKeywordsClasses.php + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM58Entity.php + - %currentWorkingDirectory%/tests/Doctrine/Tests/DoctrineTestCase.php + polluteScopeWithLoopInitialAssignments: true + ignoreErrors: + - '#Class Doctrine_Tests_Common_Annotations_Fixtures_ClassNoNamespaceNoComment not found#' + - '#Instantiated class Doctrine_Tests_Common_Annotations_Fixtures_ClassNoNamespaceNoComment not found#' + - '#Property Doctrine\\Tests\\Common\\Annotations\\DummyClassNonAnnotationProblem::\$foo has unknown class#' + - '#Call to an undefined method ReflectionClass::getUseStatements\(\)#' diff --git a/lib/composer/doctrine/lexer/LICENSE b/lib/composer/doctrine/lexer/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e8fdec4afb76e7d7e140a1d71ceb19a97f71855b --- /dev/null +++ b/lib/composer/doctrine/lexer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2018 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/lib/composer/doctrine/lexer/README.md b/lib/composer/doctrine/lexer/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e1b419a696ad0438e846c7db1202485b5496273f --- /dev/null +++ b/lib/composer/doctrine/lexer/README.md @@ -0,0 +1,9 @@ +# Doctrine Lexer + +Build Status: [![Build Status](https://travis-ci.org/doctrine/lexer.svg?branch=master)](https://travis-ci.org/doctrine/lexer) + +Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers. + +This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL). + +https://www.doctrine-project.org/projects/lexer.html diff --git a/lib/composer/doctrine/lexer/composer.json b/lib/composer/doctrine/lexer/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..3432bae4af49a33b6c8e65cd1142f07b30f5617f --- /dev/null +++ b/lib/composer/doctrine/lexer/composer.json @@ -0,0 +1,41 @@ +{ + "name": "doctrine/lexer", + "type": "library", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "keywords": [ + "php", + "parser", + "lexer", + "annotations", + "docblock" + ], + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "autoload": { + "psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" } + }, + "autoload-dev": { + "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "config": { + "sort-packages": true + } +} diff --git a/lib/composer/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php b/lib/composer/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php new file mode 100644 index 0000000000000000000000000000000000000000..385643a4acea53118e2cb6f35dc62326a0ef9735 --- /dev/null +++ b/lib/composer/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php @@ -0,0 +1,328 @@ +input = $input; + $this->tokens = []; + + $this->reset(); + $this->scan($input); + } + + /** + * Resets the lexer. + * + * @return void + */ + public function reset() + { + $this->lookahead = null; + $this->token = null; + $this->peek = 0; + $this->position = 0; + } + + /** + * Resets the peek pointer to 0. + * + * @return void + */ + public function resetPeek() + { + $this->peek = 0; + } + + /** + * Resets the lexer position on the input to the given position. + * + * @param int $position Position to place the lexical scanner. + * + * @return void + */ + public function resetPosition($position = 0) + { + $this->position = $position; + } + + /** + * Retrieve the original lexer's input until a given position. + * + * @param int $position + * + * @return string + */ + public function getInputUntilPosition($position) + { + return substr($this->input, 0, $position); + } + + /** + * Checks whether a given token matches the current lookahead. + * + * @param int|string $token + * + * @return bool + */ + public function isNextToken($token) + { + return $this->lookahead !== null && $this->lookahead['type'] === $token; + } + + /** + * Checks whether any of the given tokens matches the current lookahead. + * + * @param array $tokens + * + * @return bool + */ + public function isNextTokenAny(array $tokens) + { + return $this->lookahead !== null && in_array($this->lookahead['type'], $tokens, true); + } + + /** + * Moves to the next token in the input string. + * + * @return bool + */ + public function moveNext() + { + $this->peek = 0; + $this->token = $this->lookahead; + $this->lookahead = isset($this->tokens[$this->position]) + ? $this->tokens[$this->position++] : null; + + return $this->lookahead !== null; + } + + /** + * Tells the lexer to skip input tokens until it sees a token with the given value. + * + * @param string $type The token type to skip until. + * + * @return void + */ + public function skipUntil($type) + { + while ($this->lookahead !== null && $this->lookahead['type'] !== $type) { + $this->moveNext(); + } + } + + /** + * Checks if given value is identical to the given token. + * + * @param mixed $value + * @param int|string $token + * + * @return bool + */ + public function isA($value, $token) + { + return $this->getType($value) === $token; + } + + /** + * Moves the lookahead token forward. + * + * @return array|null The next token or NULL if there are no more tokens ahead. + */ + public function peek() + { + if (isset($this->tokens[$this->position + $this->peek])) { + return $this->tokens[$this->position + $this->peek++]; + } + + return null; + } + + /** + * Peeks at the next token, returns it and immediately resets the peek. + * + * @return array|null The next token or NULL if there are no more tokens ahead. + */ + public function glimpse() + { + $peek = $this->peek(); + $this->peek = 0; + + return $peek; + } + + /** + * Scans the input string for tokens. + * + * @param string $input A query string. + * + * @return void + */ + protected function scan($input) + { + if (! isset($this->regex)) { + $this->regex = sprintf( + '/(%s)|%s/%s', + implode(')|(', $this->getCatchablePatterns()), + implode('|', $this->getNonCatchablePatterns()), + $this->getModifiers() + ); + } + + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; + $matches = preg_split($this->regex, $input, -1, $flags); + + if ($matches === false) { + // Work around https://bugs.php.net/78122 + $matches = [[$input, 0]]; + } + + foreach ($matches as $match) { + // Must remain before 'value' assignment since it can change content + $type = $this->getType($match[0]); + + $this->tokens[] = [ + 'value' => $match[0], + 'type' => $type, + 'position' => $match[1], + ]; + } + } + + /** + * Gets the literal for a given token. + * + * @param int|string $token + * + * @return int|string + */ + public function getLiteral($token) + { + $className = static::class; + $reflClass = new ReflectionClass($className); + $constants = $reflClass->getConstants(); + + foreach ($constants as $name => $value) { + if ($value === $token) { + return $className . '::' . $name; + } + } + + return $token; + } + + /** + * Regex modifiers + * + * @return string + */ + protected function getModifiers() + { + return 'iu'; + } + + /** + * Lexical catchable patterns. + * + * @return array + */ + abstract protected function getCatchablePatterns(); + + /** + * Lexical non-catchable patterns. + * + * @return array + */ + abstract protected function getNonCatchablePatterns(); + + /** + * Retrieve token type. Also processes the token value if necessary. + * + * @param string $value + * + * @return int|string|null + */ + abstract protected function getType(&$value); +} diff --git a/lib/composer/felixfbecker/advanced-json-rpc/.travis.yml b/lib/composer/felixfbecker/advanced-json-rpc/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..3fb59a191a25301ca986f6c0d1080344472eb60a --- /dev/null +++ b/lib/composer/felixfbecker/advanced-json-rpc/.travis.yml @@ -0,0 +1,43 @@ +language: php + +php: + - '7.0' + - '7.2' + +env: + global: + - FORCE_COLOR=1 + +cache: + directories: + - $HOME/.composer/cache + - $HOME/.npm + +install: + - composer install --prefer-dist + +script: + - vendor/bin/phpunit --coverage-clover=coverage.xml --whitelist lib --bootstrap vendor/autoload.php tests + +after_success: + - bash <(curl -s https://codecov.io/bash) + +jobs: + include: + - stage: release + language: node_js + node_js: '8' + install: + - npm ci + script: + - npm run semantic-release + after_success: false + +stages: + - test + - name: release + if: branch = master AND type = push AND fork = false + +branches: + except: + - /^v\d+\.\d+\.\d+$/ diff --git a/lib/composer/felixfbecker/advanced-json-rpc/LICENSE b/lib/composer/felixfbecker/advanced-json-rpc/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..fc354170c54a5cfaa38c939bb48d363d5307e043 --- /dev/null +++ b/lib/composer/felixfbecker/advanced-json-rpc/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2016, Felix Frederick Becker + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/lib/composer/felixfbecker/advanced-json-rpc/composer.json b/lib/composer/felixfbecker/advanced-json-rpc/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..a7e1d7bbee10e34f5194a97e66dc210b28809674 --- /dev/null +++ b/lib/composer/felixfbecker/advanced-json-rpc/composer.json @@ -0,0 +1,32 @@ +{ + "name": "felixfbecker/advanced-json-rpc", + "description": "A more advanced JSONRPC implementation", + "type": "library", + "license": "ISC", + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "autoload-dev": { + "psr-4": { + "AdvancedJsonRpc\\Tests\\": "tests/" + } + }, + "require": { + "php": ">=7.0", + "netresearch/jsonmapper": "^1.0 || ^2.0", + "phpdocumentor/reflection-docblock": "^4.0.0 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0.0" + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/lib/composer/felixfbecker/advanced-json-rpc/lib/Dispatcher.php b/lib/composer/felixfbecker/advanced-json-rpc/lib/Dispatcher.php new file mode 100644 index 0000000000000000000000000000000000000000..a2ad10fc9480defd85ba37a1df44f53cf30c63c3 --- /dev/null +++ b/lib/composer/felixfbecker/advanced-json-rpc/lib/Dispatcher.php @@ -0,0 +1,172 @@ + ReflectionMethod[] + * + * @var ReflectionMethod + */ + private $methods; + + /** + * @var \phpDocumentor\Reflection\DocBlockFactory + */ + private $docBlockFactory; + + /** + * @var \phpDocumentor\Reflection\Types\ContextFactory + */ + private $contextFactory; + + /** + * @param object $target The target object that should receive the method calls + * @param string $delimiter A delimiter for method calls on properties, for example someProperty->someMethod + */ + public function __construct($target, $delimiter = '->') + { + $this->target = $target; + $this->delimiter = $delimiter; + $this->docBlockFactory = DocBlockFactory::createInstance(); + $this->contextFactory = new Types\ContextFactory(); + $this->mapper = new JsonMapper(); + } + + /** + * Calls the appropriate method handler for an incoming Message + * + * @param string|object $msg The incoming message + * @return mixed + */ + public function dispatch($msg) + { + if (is_string($msg)) { + $msg = json_decode($msg); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new Error(json_last_error_msg(), ErrorCode::PARSE_ERROR); + } + } + // Find out the object and function that should be called + $obj = $this->target; + $parts = explode($this->delimiter, $msg->method); + // The function to call is always the last part of the method + $fn = array_pop($parts); + // For namespaced methods like textDocument/didOpen, call the didOpen method on the $textDocument property + // For simple methods like initialize, shutdown, exit, this loop will simply not be entered and $obj will be + // the target + foreach ($parts as $part) { + if (!isset($obj->$part)) { + throw new Error("Method {$msg->method} is not implemented", ErrorCode::METHOD_NOT_FOUND); + } + $obj = $obj->$part; + } + if (!isset($this->methods[$msg->method])) { + try { + $method = new ReflectionMethod($obj, $fn); + $this->methods[$msg->method] = $method; + } catch (ReflectionException $e) { + throw new Error($e->getMessage(), ErrorCode::METHOD_NOT_FOUND, null, $e); + } + } + $method = $this->methods[$msg->method]; + $parameters = $method->getParameters(); + if ($method->getDocComment()) { + $docBlock = $this->docBlockFactory->create( + $method->getDocComment(), + $this->contextFactory->createFromReflector($method->getDeclaringClass()) + ); + $paramTags = $docBlock->getTagsByName('param'); + } + $args = []; + if (isset($msg->params)) { + // Find out the position + if (is_array($msg->params)) { + $args = $msg->params; + } else if (is_object($msg->params)) { + foreach ($parameters as $pos => $parameter) { + $value = null; + foreach(get_object_vars($msg->params) as $key => $val) { + if ($parameter->name === $key) { + $value = $val; + break; + } + } + $args[$pos] = $value; + } + } else { + throw new Error('Params must be structured or omitted', ErrorCode::INVALID_REQUEST); + } + foreach ($args as $position => $value) { + try { + // If the type is structured (array or object), map it with JsonMapper + if (is_object($value)) { + // Does the parameter have a type hint? + $param = $parameters[$position]; + if ($param->hasType()) { + $paramType = $param->getType(); + if ($paramType instanceof ReflectionNamedType) { + // We have object data to map and want the class name. + // This should not include the `?` if the type was nullable. + $class = $paramType->getName(); + } else { + // Fallback for php 7.0, which is still supported (and doesn't have nullable). + $class = (string)$paramType; + } + $value = $this->mapper->map($value, new $class()); + } + } else if (is_array($value) && isset($docBlock)) { + // Get the array type from the DocBlock + $type = $paramTags[$position]->getType(); + // For union types, use the first one that is a class array (often it is SomeClass[]|null) + if ($type instanceof Types\Compound) { + for ($i = 0; $t = $type->get($i); $i++) { + if ( + $t instanceof Types\Array_ + && $t->getValueType() instanceof Types\Object_ + && (string)$t->getValueType() !== 'object' + ) { + $class = (string)$t->getValueType()->getFqsen(); + $value = $this->mapper->mapArray($value, [], $class); + break; + } + } + } else if ($type instanceof Types\Array_) { + $class = (string)$type->getValueType()->getFqsen(); + $value = $this->mapper->mapArray($value, [], $class); + } else { + throw new Error('Type is not matching @param tag', ErrorCode::INVALID_PARAMS); + } + } + } catch (JsonMapper_Exception $e) { + throw new Error($e->getMessage(), ErrorCode::INVALID_PARAMS, null, $e); + } + $args[$position] = $value; + } + } + ksort($args); + $result = $obj->$fn(...$args); + return $result; + } +} diff --git a/lib/composer/felixfbecker/advanced-json-rpc/lib/Error.php b/lib/composer/felixfbecker/advanced-json-rpc/lib/Error.php new file mode 100644 index 0000000000000000000000000000000000000000..b2801918b7d0be97dc47a87308ebf6cac0299fb3 --- /dev/null +++ b/lib/composer/felixfbecker/advanced-json-rpc/lib/Error.php @@ -0,0 +1,38 @@ +data = $data; + } +} diff --git a/lib/composer/felixfbecker/advanced-json-rpc/lib/ErrorCode.php b/lib/composer/felixfbecker/advanced-json-rpc/lib/ErrorCode.php new file mode 100644 index 0000000000000000000000000000000000000000..f0ef47927c86b19b570e7016de885aeefb02f81b --- /dev/null +++ b/lib/composer/felixfbecker/advanced-json-rpc/lib/ErrorCode.php @@ -0,0 +1,48 @@ +id) && isset($msg->error); + } + + /** + * @param int|string $id + * @param \AdvancedJsonRpc\Error $error + */ + public function __construct($id, Error $error) + { + parent::__construct($id); + $this->error = $error; + } +} diff --git a/lib/composer/felixfbecker/advanced-json-rpc/lib/Message.php b/lib/composer/felixfbecker/advanced-json-rpc/lib/Message.php new file mode 100644 index 0000000000000000000000000000000000000000..e2231dc538e2281abbc4144b4f57ce347559d095 --- /dev/null +++ b/lib/composer/felixfbecker/advanced-json-rpc/lib/Message.php @@ -0,0 +1,52 @@ +method, $decoded->params ?? null); + } else if (Request::isRequest($decoded)) { + $obj = new Request($decoded->id, $decoded->method, $decoded->params ?? null); + } else if (SuccessResponse::isSuccessResponse($decoded)) { + $obj = new SuccessResponse($decoded->id, $decoded->result); + } else if (ErrorResponse::isErrorResponse($decoded)) { + $obj = new ErrorResponse($decoded->id, new Error($decoded->error->message, $decoded->error->code, $decoded->error->data ?? null)); + } else { + throw new Error('Invalid message', ErrorCode::INVALID_REQUEST); + } + return $obj; + } + + public function __toString(): string + { + $encoded = json_encode($this); + if ($encoded === false) { + throw new Error(json_last_error_msg(), ErrorCode::INTERNAL_ERROR); + } + return $encoded; + } +} diff --git a/lib/composer/felixfbecker/advanced-json-rpc/lib/Notification.php b/lib/composer/felixfbecker/advanced-json-rpc/lib/Notification.php new file mode 100644 index 0000000000000000000000000000000000000000..3440164d9c24081db597274d1be6edea3dcda525 --- /dev/null +++ b/lib/composer/felixfbecker/advanced-json-rpc/lib/Notification.php @@ -0,0 +1,56 @@ +method); + } + + /** + * @param string $method + * @param mixed $params + */ + public function __construct(string $method, $params = null) + { + $this->method = $method; + $this->params = $params; + } +} diff --git a/lib/composer/felixfbecker/advanced-json-rpc/lib/Request.php b/lib/composer/felixfbecker/advanced-json-rpc/lib/Request.php new file mode 100644 index 0000000000000000000000000000000000000000..14290082584103397117378b4302d6598c0e7f33 --- /dev/null +++ b/lib/composer/felixfbecker/advanced-json-rpc/lib/Request.php @@ -0,0 +1,63 @@ +method); + } + + /** + * @param string|int $id + * @param string $method + * @param object|array $params + */ + public function __construct($id, string $method, $params = null) + { + $this->id = $id; + $this->method = $method; + $this->params = $params; + } +} diff --git a/lib/composer/felixfbecker/advanced-json-rpc/lib/Response.php b/lib/composer/felixfbecker/advanced-json-rpc/lib/Response.php new file mode 100644 index 0000000000000000000000000000000000000000..a871eeac26a04777920fbf25eae7f38581e39bba --- /dev/null +++ b/lib/composer/felixfbecker/advanced-json-rpc/lib/Response.php @@ -0,0 +1,40 @@ +error)); + } + + /** + * @param int|string $id + * @param mixed $result + * @param ResponseError $error + */ + public function __construct($id) + { + $this->id = $id; + } +} diff --git a/lib/composer/felixfbecker/advanced-json-rpc/lib/SuccessResponse.php b/lib/composer/felixfbecker/advanced-json-rpc/lib/SuccessResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..222fd46e777ff9fb9c38e46272312b3c371d33ee --- /dev/null +++ b/lib/composer/felixfbecker/advanced-json-rpc/lib/SuccessResponse.php @@ -0,0 +1,40 @@ +result = $result; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/.editorconfig b/lib/composer/felixfbecker/language-server-protocol/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..b5f0c5dfe7dda91a78ae4de8cf3ae1d011e2db13 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/.editorconfig @@ -0,0 +1,17 @@ + +[*] +insert_final_newline = true +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{json,yml}] +indent_size = 2 + +[composer.json] +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/lib/composer/felixfbecker/language-server-protocol/LICENSE b/lib/composer/felixfbecker/language-server-protocol/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..fc354170c54a5cfaa38c939bb48d363d5307e043 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2016, Felix Frederick Becker + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/lib/composer/felixfbecker/language-server-protocol/README.md b/lib/composer/felixfbecker/language-server-protocol/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1e169d1e6ed2ab8a795514e49ba8ae8fb8f8276e --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/README.md @@ -0,0 +1,19 @@ +# Language Server Protocol for PHP + +[![packagist](https://img.shields.io/packagist/v/felixfbecker/language-server-protocol.svg)](https://packagist.org/packages/felixfbecker/language-server-protocol) +[![build](https://travis-ci.org/felixfbecker/php-language-server-protocol.svg?branch=master)](https://travis-ci.org/felixfbecker/php-language-server-protocol) +[![php](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg)](https://php.net/) +[![license](https://img.shields.io/packagist/l/felixfbecker/language-server-protocol.svg)](https://github.com/felixfbecker/php-language-server-protocol/blob/master/LICENSE) + +Protocol classes for the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) in PHP + +## Installation + +``` +composer require felixfbecker/language-server-protocol +``` + +## Releases + +Releases are done automatically in CI by analyzing commit messages. +Make sure to follow the [Conventional Commits Convention](https://www.conventionalcommits.org/en/v1.0.0-beta.2/). diff --git a/lib/composer/felixfbecker/language-server-protocol/composer.json b/lib/composer/felixfbecker/language-server-protocol/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..6890e8b48d97393132c1cb2d5c6de5c46afd9f01 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/composer.json @@ -0,0 +1,36 @@ +{ + "name": "felixfbecker/language-server-protocol", + "description": "PHP classes for the Language Server Protocol", + "license": "ISC", + "keywords": [ + "php", + "language", + "server", + "microsoft" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.3", + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1" + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": + { + "phpstan": "./vendor/bin/phpstan analyse -c phpstan.neon --ansi --level=7 -vvv src" + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/package-lock.json b/lib/composer/felixfbecker/language-server-protocol/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..7fcdd3b4e054bd43911ecdf89350a7f9f67c4613 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/package-lock.json @@ -0,0 +1,6489 @@ +{ + "name": "php-language-server-protocol", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.stat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.2.tgz", + "integrity": "sha512-yprFYuno9FtNsSHVlSWd+nRlmGoAbqbeCwOryP6sC/zoCjhpArcRMYp19EvpSUSizJAlsXEwJv+wcWS9XaXdMw==", + "dev": true + }, + "@octokit/rest": { + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.12.0.tgz", + "integrity": "sha512-5wRag4kHRkp0JDo++L9x9FkDlHEALbLnbSede16D8u+a2/t+gX32uhDs8cukVLyyrZR79nmh1lNpxZmffwoNoQ==", + "dev": true, + "requires": { + "before-after-hook": "^1.1.0", + "btoa-lite": "^1.0.0", + "debug": "^3.1.0", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.0", + "lodash": "^4.17.4", + "node-fetch": "^2.1.1", + "universal-user-agent": "^2.0.0", + "url-template": "^2.0.8" + }, + "dependencies": { + "debug": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@semantic-release/commit-analyzer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-6.0.1.tgz", + "integrity": "sha512-ENCRn1tm1D08CCBnIPsID8GjboWT6E97s0Lk3XrpAh+IMx615uAU1X2FoXyOGGc6zmqp9Ff4s8KECd/GjMcodQ==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.0.0", + "debug": "^4.0.0", + "import-from": "^2.1.0", + "lodash": "^4.17.4" + } + }, + "@semantic-release/error": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-2.2.0.tgz", + "integrity": "sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg==", + "dev": true + }, + "@semantic-release/github": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-5.0.5.tgz", + "integrity": "sha512-Hdt6b8ST2pg6pl151PlcsnTcdsC2UJhA5FdbmunbZG/TVmlnKCZ4WUzpji7YqJtDLjbQTuFm/vhM6atW3XjMWg==", + "dev": true, + "requires": { + "@octokit/rest": "^15.2.0", + "@semantic-release/error": "^2.2.0", + "aggregate-error": "^1.0.0", + "bottleneck": "^2.0.1", + "debug": "^4.0.0", + "dir-glob": "^2.0.0", + "fs-extra": "^7.0.0", + "globby": "^8.0.0", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "issue-parser": "^3.0.0", + "lodash": "^4.17.4", + "mime": "^2.0.3", + "p-filter": "^1.0.0", + "p-retry": "^2.0.0", + "parse-github-url": "^1.0.1", + "url-join": "^4.0.0" + } + }, + "@semantic-release/npm": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-5.0.4.tgz", + "integrity": "sha512-ExGXP9GnM2hqUIgTnp6sXKB1G0Yh+fuLftmIopq5KHBWj34Wd2YbM/3iLkXXnAP1YZ9YCp7hsAdsR014ctbwHg==", + "dev": true, + "requires": { + "@semantic-release/error": "^2.2.0", + "aggregate-error": "^1.0.0", + "detect-indent": "^5.0.0", + "detect-newline": "^2.1.0", + "execa": "^1.0.0", + "fs-extra": "^7.0.0", + "lodash": "^4.17.4", + "nerf-dart": "^1.0.0", + "normalize-url": "^3.0.0", + "npm": "^6.3.0", + "parse-json": "^4.0.0", + "rc": "^1.2.8", + "read-pkg": "^4.0.0", + "registry-auth-token": "^3.3.1" + }, + "dependencies": { + "read-pkg": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "dev": true, + "requires": { + "normalize-package-data": "^2.3.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0" + } + } + } + }, + "@semantic-release/release-notes-generator": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-7.0.2.tgz", + "integrity": "sha512-fomHrGq/gfZIAQYZk0MLRwfQ8d+DbTcI3kuO1hU2L0fDJYKHZHuPmKnsfVa5KoNdVVPHx878D/ojgyStRqhc9g==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-changelog-writer": "^4.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.0.0", + "debug": "^4.0.0", + "get-stream": "^4.0.0", + "git-url-parse": "^10.0.1", + "import-from": "^2.1.0", + "into-stream": "^3.1.0", + "lodash": "^4.17.4" + } + }, + "JSONStream": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.4.tgz", + "integrity": "sha512-Y7vfi3I5oMOYIr+WxV8NZxDSwcbNgzdKYsTNInmycOq9bUYwGg9ryu57Wg5NLmCjqdFPNUmpMBo3kSJN9tCbXg==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "aggregate-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-1.0.0.tgz", + "integrity": "sha1-iINE2tAiCnLjr1CQYRf0h3GSX6w=", + "dev": true, + "requires": { + "clean-stack": "^1.0.0", + "indent-string": "^3.0.0" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "before-after-hook": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-1.1.0.tgz", + "integrity": "sha512-VOMDtYPwLbIncTxNoSzRyvaMxtXmLWLUqr8k5AfC1BzLk34HvBXaQX8snOwQZ4c0aX8aSERqtJSiI9/m2u5kuA==", + "dev": true + }, + "bottleneck": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.11.0.tgz", + "integrity": "sha512-DvKiYR1kG1qRVoLBUtPlmJffktoBZIz3qtdUbINlwzQXDhlhZdF8gWesPjwp05xqr5QZ7wXA2k1w78/COCweTg==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "btoa-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", + "integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", + "dev": true, + "requires": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-stack": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-1.3.0.tgz", + "integrity": "sha1-noIVAa6XmYbEax1m0tQy2y/UrjE=", + "dev": true + }, + "cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "dev": true, + "requires": { + "colors": "1.0.3" + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": true + }, + "compare-func": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", + "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^3.0.0" + } + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "conventional-changelog-angular": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.1.tgz", + "integrity": "sha512-q4ylJ68fWZDdrFC9z4zKcf97HW6hp7Mo2YlqD4owfXhecFKy/PJCU/1oVFF4TqochchChqmZ0Vb0e0g8/MKNlA==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "q": "^1.5.1" + } + }, + "conventional-changelog-writer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.0.tgz", + "integrity": "sha512-hMZPe0AQ6Bi05epeK/7hz80xxk59nPA5z/b63TOHq2wigM0/akreOc8N4Jam5b9nFgKWX1e9PdPv2ewgW6bcfg==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "conventional-commits-filter": "^2.0.0", + "dateformat": "^3.0.0", + "handlebars": "^4.0.2", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.2.1", + "meow": "^4.0.0", + "semver": "^5.5.0", + "split": "^1.0.0", + "through2": "^2.0.0" + } + }, + "conventional-commits-filter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.0.tgz", + "integrity": "sha512-Cfl0j1/NquB/TMVx7Wrmyq7uRM+/rPQbtVVGwzfkhZ6/yH6fcMmP0Q/9044TBZPTNdGzm46vXFXL14wbET0/Mg==", + "dev": true, + "requires": { + "is-subset": "^0.1.1", + "modify-values": "^1.0.0" + } + }, + "conventional-commits-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.0.tgz", + "integrity": "sha512-GWh71U26BLWgMykCp+VghZ4s64wVbtseECcKQ/PvcPZR2cUnz+FUc2J9KjxNl7/ZbCxST8R03c9fc+Vi0umS9Q==", + "dev": true, + "requires": { + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.0", + "lodash": "^4.2.1", + "meow": "^4.0.0", + "split2": "^2.0.0", + "through2": "^2.0.0", + "trim-off-newlines": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz", + "integrity": "sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, + "debug": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.0.1.tgz", + "integrity": "sha512-K23FHJ/Mt404FSlp6gSZCevIbTMLX0j3fmHhUEhQ3Wq0FMODW3+cUSoLdy1Gx4polAf4t/lphhmHH35BB8cLYw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + } + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + } + }, + "dot-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", + "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-ci": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-3.0.0.tgz", + "integrity": "sha512-3Xt4Cfjdy9MTTrg/eWTnJNQIrtU1DDV0KyuWOGlrR2oa9dOdzoOMbQBFbfrTiv+GypdiWWIw5HdmtakZO+rzWA==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "java-properties": "^0.2.9" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fast-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.2.tgz", + "integrity": "sha512-TR6zxCKftDQnUAPvkrCWdBgDq/gbqx8A3ApnBrR5rMvpp6+KMJI0Igw7fkWPgeVK0uhRXTXdvO3O+YP0CaUX2g==", + "dev": true, + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.0.1", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.1", + "micromatch": "^3.1.10" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "find-versions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-2.0.0.tgz", + "integrity": "sha1-KtkNSQ9oKMGqQCks9wmsMxghDDw=", + "dev": true, + "requires": { + "array-uniq": "^1.0.0", + "semver-regex": "^1.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-extra": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz", + "integrity": "sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.0.0.tgz", + "integrity": "sha512-FneLKMENeOR7wOK0/ZXCh+lwqtnPwkeunJjRN28LPqzGvNAhYvrTAhXv6xDm4vsJ0M7lcRbIYHQudKsSy2RtSQ==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "git-log-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", + "integrity": "sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo=", + "dev": true, + "requires": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "~0.6.6" + }, + "dependencies": { + "split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ=", + "dev": true, + "requires": { + "through2": "~2.0.0" + } + } + } + }, + "git-up": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-2.0.10.tgz", + "integrity": "sha512-2v4UN3qV2RGypD9QpmUjpk+4+RlYpW8GFuiZqQnKmvei08HsFPd0RfbDvEhnE4wBvnYs8ORVtYpOFuuCEmBVBw==", + "dev": true, + "requires": { + "is-ssh": "^1.3.0", + "parse-url": "^1.3.0" + } + }, + "git-url-parse": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-10.0.1.tgz", + "integrity": "sha512-Tq2u8UPXc/FawC/dO8bvh8jcck0Lkor5OhuZvmVSeyJGRucDBfw9y2zy/GNCx28lMYh1N12IzPwDexjUNFyAeg==", + "dev": true, + "requires": { + "git-up": "^2.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "dev": true + }, + "globby": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", + "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "handlebars": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", + "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hook-std": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-1.1.0.tgz", + "integrity": "sha512-aIyBZbZl3NS8XoSwIDQ+ZaiBuPOhhPWoBFA3QX0Q8hOMO8Tx4xGRTDnn/nl/LAtZWdieXzFC9ohAtTSnWrlHCQ==", + "dev": true + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", + "dev": true, + "requires": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-ssh": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.0.tgz", + "integrity": "sha1-6+oRaaJhTaOSpjdANmw84EnY3/Y=", + "dev": true, + "requires": { + "protocols": "^1.1.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, + "is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "dev": true, + "requires": { + "text-extensions": "^1.0.0" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "issue-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-3.0.0.tgz", + "integrity": "sha512-VWIhBdy0eOhlvpxOOMecBCHMpjx7lWVZcYpSzjD4dSdxptzI9TBR/cQEh057HL8+7jQKTLs+uCtezY/9VoveCA==", + "dev": true, + "requires": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + } + }, + "java-properties": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-0.2.10.tgz", + "integrity": "sha512-CpKJh9VRNhS+XqZtg1UMejETGEiqwCGDC/uwPEEQwc2nfdbSm73SIE29TplG2gLYuBOOTNDqxzG6A9NtEPLt0w==", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + } + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, + "lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", + "dev": true + }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, + "lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", + "dev": true + }, + "lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", + "dev": true + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "macos-release": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-1.1.0.tgz", + "integrity": "sha512-mmLbumEYMi5nXReB9js3WGsB8UE6cDBWyIO62Z4DNx6GbRhDxHNjA1MlzSpJ2S2KM1wyiPRA0d19uHWYYvMHjA==", + "dev": true + }, + "map-age-cleaner": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz", + "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "marked": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.5.0.tgz", + "integrity": "sha512-UhjmkCWKu1SS/BIePL2a59BMJ7V42EYtTfksodPRXzPEGEph3Inp5dylseqt+KbU9Jglsx8xcMKmlumfJMBXAA==", + "dev": true + }, + "marked-terminal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-3.1.1.tgz", + "integrity": "sha512-7UBFww1rdx0w9HehLMCVYa8/AxXaiDigDfMsJcj82/wgLQG9cj+oiMAVlJpeWD57VFJY2OYY+bKeEVIjIlxi+w==", + "dev": true, + "requires": { + "cardinal": "^2.1.1", + "chalk": "^2.4.1", + "cli-table": "^0.3.1", + "lodash.assign": "^4.2.0", + "node-emoji": "^1.4.1" + } + }, + "mem": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", + "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^1.1.0" + } + }, + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + }, + "dependencies": { + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + } + } + }, + "merge2": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz", + "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "dev": true + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-emoji": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.8.1.tgz", + "integrity": "sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg==", + "dev": true, + "requires": { + "lodash.toarray": "^4.4.0" + } + }, + "node-fetch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz", + "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA==", + "dev": true + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true + }, + "npm": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.4.1.tgz", + "integrity": "sha512-mXJL1NTVU136PtuopXCUQaNWuHlXCTp4McwlSW8S9/Aj8OEPAlSBgo8og7kJ01MjCDrkmqFQTvN5tTEhBMhXQg==", + "dev": true, + "requires": { + "JSONStream": "^1.3.4", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "~1.2.0", + "archy": "~1.0.0", + "bin-links": "^1.1.2", + "bluebird": "~3.5.1", + "byte-size": "^4.0.3", + "cacache": "^11.2.0", + "call-limit": "~1.1.0", + "chownr": "~1.0.1", + "ci-info": "^1.4.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.5.0", + "cmd-shim": "~2.0.2", + "columnify": "~1.5.4", + "config-chain": "~1.1.11", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "figgy-pudding": "^3.4.1", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.0.1", + "glob": "~7.1.2", + "graceful-fs": "~4.1.11", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.7.1", + "iferr": "^1.0.2", + "imurmurhash": "*", + "inflight": "~1.0.6", + "inherits": "~2.0.3", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "^2.0.6", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^2.0.2", + "libnpmhook": "^4.0.1", + "libnpx": "^10.2.0", + "lock-verify": "^2.0.2", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^4.1.3", + "meant": "~1.0.1", + "mississippi": "^3.0.0", + "mkdirp": "~0.5.1", + "move-concurrently": "^1.0.1", + "node-gyp": "^3.8.0", + "nopt": "~4.0.1", + "normalize-package-data": "~2.4.0", + "npm-audit-report": "^1.3.1", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "~3.0.0", + "npm-lifecycle": "^2.1.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.11", + "npm-pick-manifest": "^2.1.0", + "npm-profile": "^3.0.2", + "npm-registry-client": "^8.6.0", + "npm-registry-fetch": "^1.1.0", + "npm-user-validate": "~1.0.0", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "^1.5.0", + "osenv": "^0.1.5", + "pacote": "^8.1.6", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.1.0", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "~1.0.1", + "read-installed": "~4.0.3", + "read-package-json": "^2.0.13", + "read-package-tree": "^5.2.1", + "readable-stream": "^2.3.6", + "readdir-scoped-modules": "*", + "request": "^2.88.0", + "retry": "^0.12.0", + "rimraf": "~2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.5.0", + "sha": "~2.0.1", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^6.0.0", + "stringify-package": "^1.0.0", + "tar": "^4.4.6", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "uid-number": "0.0.6", + "umask": "~1.1.0", + "unique-filename": "~1.1.0", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.3.2", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^1.3.1", + "worker-farm": "^1.6.0", + "write-file-atomic": "^2.3.0" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "agent-base": { + "version": "4.2.0", + "bundled": true, + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.4.1", + "bundled": true, + "dev": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "asap": { + "version": "2.0.6", + "bundled": true, + "dev": true + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true, + "dev": true + }, + "aws4": { + "version": "1.8.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-links": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "cmd-shim": "^2.0.2", + "gentle-fs": "^2.0.0", + "graceful-fs": "^4.1.11", + "write-file-atomic": "^2.3.0" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.5.1", + "bundled": true, + "dev": true + }, + "boxen": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "builtins": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "byline": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "byte-size": { + "version": "4.0.3", + "bundled": true, + "dev": true + }, + "cacache": { + "version": "11.2.0", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "figgy-pudding": "^3.1.0", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.0", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "call-limit": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "ci-info": { + "version": "1.4.0", + "bundled": true, + "dev": true + }, + "cidr-regex": { + "version": "2.0.9", + "bundled": true, + "dev": true, + "requires": { + "ip-regex": "^2.1.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + } + }, + "cli-table3": { + "version": "0.5.0", + "bundled": true, + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "cmd-shim": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "color-convert": { + "version": "1.9.1", + "bundled": true, + "dev": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "colors": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "dev": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "bundled": true, + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "config-chain": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "cyclist": { + "version": "0.2.2", + "bundled": true, + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "bundled": true, + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "duplexify": { + "version": "3.6.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "editor": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "dev": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "err-code": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "errno": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es6-promise": { + "version": "4.2.4", + "bundled": true, + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "extend": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "figgy-pudding": { + "version": "3.4.1", + "bundled": true, + "dev": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "genfun": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "gentle-fs": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.2", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-dirs": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "bundled": true, + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "har-validator": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "2.7.1", + "bundled": true, + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true, + "dev": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "iferr": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "ip": { + "version": "1.1.5", + "bundled": true, + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-ci": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "ci-info": "^1.0.0" + } + }, + "is-cidr": { + "version": "2.0.6", + "bundled": true, + "dev": true, + "requires": { + "cidr-regex": "^2.0.8" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-obj": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-retry-allowed": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true, + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true, + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "libcipm": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^2.0.3", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^8.1.6", + "protoduck": "^5.0.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" + } + }, + "libnpmhook": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.1.0", + "npm-registry-fetch": "^3.0.0" + }, + "dependencies": { + "npm-registry-fetch": { + "version": "3.1.1", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "figgy-pudding": "^3.1.0", + "lru-cache": "^4.1.2", + "make-fetch-happen": "^4.0.0", + "npm-package-arg": "^6.0.0" + } + } + } + }, + "libnpx": { + "version": "10.2.0", + "bundled": true, + "dev": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lock-verify": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "npm-package-arg": "^5.1.2 || 6", + "semver": "^5.4.1" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true, + "dev": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "bundled": true, + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true, + "dev": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true, + "dev": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true, + "dev": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true, + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true, + "dev": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true, + "dev": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "lru-cache": { + "version": "4.1.3", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-fetch-happen": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^11.0.1", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + } + }, + "meant": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "mime-db": { + "version": "1.35.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.19", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "~1.35.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.3.3", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "bundled": true, + "dev": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-gyp": { + "version": "3.8.0", + "bundled": true, + "dev": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "nopt": { + "version": "3.0.6", + "bundled": true, + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + } + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-audit-report": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "npm-install-checks": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + } + }, + "npm-lifecycle": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.11", + "node-gyp": "^3.8.0", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0", + "which": "^1.3.1" + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true, + "dev": true + }, + "npm-package-arg": { + "version": "6.1.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-profile": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.2 || 2", + "make-fetch-happen": "^2.5.0 || 3 || 4" + } + }, + "npm-registry-client": { + "version": "8.6.0", + "bundled": true, + "dev": true, + "requires": { + "concat-stream": "^1.5.2", + "graceful-fs": "^4.1.6", + "normalize-package-data": "~1.0.1 || ^2.0.0", + "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "npmlog": "2 || ^3.1.0 || ^4.0.0", + "once": "^1.3.3", + "request": "^2.74.0", + "retry": "^0.10.0", + "safe-buffer": "^5.1.1", + "semver": "2 >=2.2.1 || 3.x || 4 || 5", + "slide": "^1.1.3", + "ssri": "^5.2.4" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true, + "dev": true + }, + "ssri": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + } + } + }, + "npm-registry-fetch": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "figgy-pudding": "^2.0.1", + "lru-cache": "^4.1.2", + "make-fetch-happen": "^3.0.0", + "npm-package-arg": "^6.0.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "cacache": { + "version": "10.0.4", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + }, + "dependencies": { + "mississippi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + } + } + }, + "figgy-pudding": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "make-fetch-happen": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^10.0.4", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.0", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^3.0.1", + "ssri": "^5.2.4" + } + }, + "pump": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true, + "dev": true + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "requires": { + "ip": "^1.1.4", + "smart-buffer": "^1.0.13" + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "socks": "^1.1.10" + } + }, + "ssri": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.0", + "bundled": true, + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "package-json": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pacote": { + "version": "8.1.6", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "cacache": "^11.0.2", + "get-stream": "^3.0.0", + "glob": "^7.1.2", + "lru-cache": "^4.1.3", + "make-fetch-happen": "^4.0.1", + "minimatch": "^3.0.4", + "minipass": "^2.3.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.10", + "npm-pick-manifest": "^2.1.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.5.0", + "ssri": "^6.0.0", + "tar": "^4.4.3", + "unique-filename": "^1.1.0", + "which": "^1.3.0" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "pify": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true, + "dev": true + } + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "read": "1" + } + }, + "proto-list": { + "version": "1.2.4", + "bundled": true, + "dev": true + }, + "protoduck": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "genfun": "^4.0.1" + } + }, + "prr": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "psl": { + "version": "1.1.29", + "bundled": true, + "dev": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "bundled": true, + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "qs": { + "version": "6.5.2", + "bundled": true, + "dev": true + }, + "query-string": { + "version": "6.1.0", + "bundled": true, + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "strict-uri-encode": "^2.0.0" + } + }, + "qw": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true + } + } + }, + "read": { + "version": "1.0.7", + "bundled": true, + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "read-installed": { + "version": "4.0.3", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + } + }, + "read-package-json": { + "version": "2.0.13", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "slash": "^1.0.0" + } + }, + "read-package-tree": { + "version": "5.2.1", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "once": "^1.3.0", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "registry-auth-token": { + "version": "3.3.2", + "bundled": true, + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, + "request": { + "version": "2.88.0", + "bundled": true, + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "retry": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true + }, + "semver-diff": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "sha": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "readable-stream": "^2.0.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slash": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "smart-buffer": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "socks": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.0.1" + } + }, + "socks-proxy-agent": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "~4.2.0", + "socks": "~2.2.0" + } + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "dev": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + } + }, + "isarray": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true, + "dev": true + } + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "sshpk": { + "version": "1.14.2", + "bundled": true, + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.0", + "bundled": true, + "dev": true + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "stringify-package": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "4.4.6", + "bundled": true, + "dev": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.3", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + }, + "dependencies": { + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "through": { + "version": "2.3.8", + "bundled": true, + "dev": true + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, + "timed-out": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "bundled": true, + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "bundled": true, + "dev": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true + }, + "umask": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "unique-filename": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "util-extend": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.3.2", + "bundled": true, + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.2" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "widest-line": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "11.0.0", + "bundled": true, + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + } + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "os-locale": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", + "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", + "dev": true, + "requires": { + "execa": "^0.10.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "dependencies": { + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + } + } + }, + "os-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-2.0.1.tgz", + "integrity": "sha1-uaOGNhwXrjohc27wWZQFyajF3F4=", + "dev": true, + "requires": { + "macos-release": "^1.0.0", + "win-release": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-1.0.0.tgz", + "integrity": "sha1-Yp0xcVAgnI/VCLoTdxPvS7kg6ds=", + "dev": true, + "requires": { + "p-map": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + }, + "dependencies": { + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + } + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true + }, + "p-retry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-2.0.0.tgz", + "integrity": "sha512-ZbCuzAmiwJ45q4evp/IG9D+5MUllGSUeCWwPt3j/tdYSi1KPkSD+46uqmAA1LhccDhOXv8kYZKNb8x78VflzfA==", + "dev": true, + "requires": { + "retry": "^0.12.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-github-url": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", + "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse-url": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-1.3.11.tgz", + "integrity": "sha1-V8FUKKuKiSsfQ4aWRccR0OFEtVQ=", + "dev": true, + "requires": { + "is-ssh": "^1.3.0", + "protocols": "^1.4.0" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "protocols": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.6.tgz", + "integrity": "sha1-+LsmPqG1/Xp2BNJri+Ob13Z4v4o=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", + "dev": true, + "requires": { + "esprima": "~4.0.0" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "semantic-release": { + "version": "15.9.16", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-15.9.16.tgz", + "integrity": "sha512-5RWqMFwDBXzIaNGUdnJxI4aCd4DtKtdc+5ZNjNWXABEmkimZVuuzZhMaTVNhHYfSuVUqWG9GuATEKhjlVoTzfQ==", + "dev": true, + "requires": { + "@semantic-release/commit-analyzer": "^6.0.0", + "@semantic-release/error": "^2.2.0", + "@semantic-release/github": "^5.0.0", + "@semantic-release/npm": "^5.0.1", + "@semantic-release/release-notes-generator": "^7.0.0", + "aggregate-error": "^1.0.0", + "cosmiconfig": "^5.0.1", + "debug": "^4.0.0", + "env-ci": "^3.0.0", + "execa": "^1.0.0", + "figures": "^2.0.0", + "find-versions": "^2.0.0", + "get-stream": "^4.0.0", + "git-log-parser": "^1.2.0", + "git-url-parse": "^10.0.1", + "hook-std": "^1.1.0", + "hosted-git-info": "^2.7.1", + "lodash": "^4.17.4", + "marked": "^0.5.0", + "marked-terminal": "^3.0.0", + "p-locate": "^3.0.0", + "p-reduce": "^1.0.0", + "read-pkg-up": "^4.0.0", + "resolve-from": "^4.0.0", + "semver": "^5.4.1", + "signale": "^1.2.1", + "yargs": "^12.0.0" + } + }, + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true + }, + "semver-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-1.0.0.tgz", + "integrity": "sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "signale": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.3.0.tgz", + "integrity": "sha512-TyFhsQ9wZDYDfsPqWMyjCxsDoMwfpsT0130Mce7wDiVCSDdtWSg83dOqoj8aGpGCs3n1YPcam6sT1OFPuGT/OQ==", + "dev": true, + "requires": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha1-Gv2Uc46ZmwNG17n8NzvlXgdXcCk=", + "dev": true + }, + "spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", + "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", + "dev": true + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "dev": true, + "requires": { + "through2": "^2.0.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "text-extensions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.8.0.tgz", + "integrity": "sha512-mVzjRxuWnDKs/qH1rbOJEVHLlSX9kty9lpi7lMvLgU9S74mQ8/Ozg9UPcKxShh0qG2NZ+NyPOPpcZU4C1Eld9A==", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + }, + "trim-off-newlines": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", + "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "universal-user-agent": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-2.0.1.tgz", + "integrity": "sha512-vz+heWVydO0iyYAa65VHD7WZkYzhl7BeNVy4i54p4TF8OMiLSXdbuQe4hm+fmWAsL+rVibaQHXfhvkw3c1Ws2w==", + "dev": true, + "requires": { + "os-name": "^2.0.1" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url-join": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz", + "integrity": "sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=", + "dev": true + }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "win-release": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/win-release/-/win-release-1.1.1.tgz", + "integrity": "sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk=", + "dev": true, + "requires": { + "semver": "^5.0.1" + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" + }, + "dependencies": { + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "dev": true, + "requires": { + "xregexp": "4.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + } + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/package.json b/lib/composer/felixfbecker/language-server-protocol/package.json new file mode 100644 index 0000000000000000000000000000000000000000..314301e0aee95d00f92dbc38cfffb64b30166e67 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/package.json @@ -0,0 +1,15 @@ +{ + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol" + }, + "release": { + "verifyConditions": "@semantic-release/github", + "prepare": [], + "publish": "@semantic-release/github" + }, + "devDependencies": { + "semantic-release": "^15.9.16" + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/ClientCapabilities.php b/lib/composer/felixfbecker/language-server-protocol/src/ClientCapabilities.php new file mode 100644 index 0000000000000000000000000000000000000000..335bf68aa3771d600ed3a43d41661c19b30f9ec4 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/ClientCapabilities.php @@ -0,0 +1,34 @@ +xfilesProvider = $xfilesProvider; + $this->xcontentProvider = $xcontentProvider; + $this->xcacheProvider = $xcacheProvider; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/CodeActionContext.php b/lib/composer/felixfbecker/language-server-protocol/src/CodeActionContext.php new file mode 100644 index 0000000000000000000000000000000000000000..ec87380158bc078f32cf23a00f396ab12ef1a117 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/CodeActionContext.php @@ -0,0 +1,25 @@ +diagnostics = $diagnostics; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/CodeLens.php b/lib/composer/felixfbecker/language-server-protocol/src/CodeLens.php new file mode 100644 index 0000000000000000000000000000000000000000..6a863ad2f5a8aa74d60c9e80221ea0788ddbe3e9 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/CodeLens.php @@ -0,0 +1,42 @@ +range = $range; + $this->command = $command; + $this->data = $data; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/CodeLensOptions.php b/lib/composer/felixfbecker/language-server-protocol/src/CodeLensOptions.php new file mode 100644 index 0000000000000000000000000000000000000000..7db0d4df1f86228cb174df255e7a0eb8b0f2be35 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/CodeLensOptions.php @@ -0,0 +1,21 @@ +resolveProvider = $resolveProvider; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/Command.php b/lib/composer/felixfbecker/language-server-protocol/src/Command.php new file mode 100644 index 0000000000000000000000000000000000000000..01664f0a8a695db9147061917fc939ea7dc7899a --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/Command.php @@ -0,0 +1,39 @@ +title = $title; + $this->command = $command; + $this->arguments = $arguments; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/CompletionContext.php b/lib/composer/felixfbecker/language-server-protocol/src/CompletionContext.php new file mode 100644 index 0000000000000000000000000000000000000000..4cda4eb28c32f7faad0e8ea3e665916fe6ad1ac4 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/CompletionContext.php @@ -0,0 +1,30 @@ +triggerKind = $triggerKind; + $this->triggerCharacter = $triggerCharacter; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/CompletionItem.php b/lib/composer/felixfbecker/language-server-protocol/src/CompletionItem.php new file mode 100644 index 0000000000000000000000000000000000000000..0798fd50ec862db794f0e190b4f70298c81551eb --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/CompletionItem.php @@ -0,0 +1,149 @@ +label = $label; + $this->kind = $kind; + $this->detail = $detail; + $this->documentation = $documentation; + $this->sortText = $sortText; + $this->filterText = $filterText; + $this->insertText = $insertText; + $this->textEdit = $textEdit; + $this->additionalTextEdits = $additionalTextEdits; + $this->command = $command; + $this->data = $data; + $this->insertTextFormat = $insertTextFormat; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/CompletionItemKind.php b/lib/composer/felixfbecker/language-server-protocol/src/CompletionItemKind.php new file mode 100644 index 0000000000000000000000000000000000000000..7de7a59c4e35b54c5f4d489c70e13ecbfd32920c --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/CompletionItemKind.php @@ -0,0 +1,70 @@ +items = $items; + $this->isIncomplete = $isIncomplete; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/CompletionOptions.php b/lib/composer/felixfbecker/language-server-protocol/src/CompletionOptions.php new file mode 100644 index 0000000000000000000000000000000000000000..43d44fe21e346b89fb33d71174a5c494741f8550 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/CompletionOptions.php @@ -0,0 +1,30 @@ +resolveProvider = $resolveProvider; + $this->triggerCharacters = $triggerCharacters; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/CompletionTriggerKind.php b/lib/composer/felixfbecker/language-server-protocol/src/CompletionTriggerKind.php new file mode 100644 index 0000000000000000000000000000000000000000..f84c48b5d177b8081eb93d16ed1499f23f20471d --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/CompletionTriggerKind.php @@ -0,0 +1,16 @@ +range = $range; + $this->rangeLength = $rangeLength; + $this->text = $text; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/DependencyReference.php b/lib/composer/felixfbecker/language-server-protocol/src/DependencyReference.php new file mode 100644 index 0000000000000000000000000000000000000000..afb6d30d4b8c1d7c1de1000f8fb405dded80d2ea --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/DependencyReference.php @@ -0,0 +1,27 @@ +attributes = $attributes ?? new \stdClass; + $this->hints = $hints; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/Diagnostic.php b/lib/composer/felixfbecker/language-server-protocol/src/Diagnostic.php new file mode 100644 index 0000000000000000000000000000000000000000..41615fa4b3a709701751b0e3f2345c11436b1e21 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/Diagnostic.php @@ -0,0 +1,63 @@ +message = $message; + $this->range = $range; + $this->code = $code; + $this->severity = $severity; + $this->source = $source; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/DiagnosticSeverity.php b/lib/composer/felixfbecker/language-server-protocol/src/DiagnosticSeverity.php new file mode 100644 index 0000000000000000000000000000000000000000..d11ed95d4cc20a3c1ec780082cfc24e6998cd601 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/DiagnosticSeverity.php @@ -0,0 +1,26 @@ +range = $range; + $this->kind = $kind; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/DocumentHighlightKind.php b/lib/composer/felixfbecker/language-server-protocol/src/DocumentHighlightKind.php new file mode 100644 index 0000000000000000000000000000000000000000..21c5001e9fa02a6253b58f6a5ce806de3b83e54b --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/DocumentHighlightKind.php @@ -0,0 +1,24 @@ +firstTriggerCharacter = $firstTriggerCharacter; + $this->moreTriggerCharacter = $moreTriggerCharacter; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/ErrorCode.php b/lib/composer/felixfbecker/language-server-protocol/src/ErrorCode.php new file mode 100644 index 0000000000000000000000000000000000000000..ffbc075550eda9543f576e774a4f5c1614aec4be --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/ErrorCode.php @@ -0,0 +1,17 @@ +uri = $uri; + $this->type = $type; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/FormattingOptions.php b/lib/composer/felixfbecker/language-server-protocol/src/FormattingOptions.php new file mode 100644 index 0000000000000000000000000000000000000000..fe77dce5db232aee29e7ac34b1dfeeb8e362db1d --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/FormattingOptions.php @@ -0,0 +1,31 @@ +tabSize = $tabSize; + $this->insertSpaces = $insertSpaces; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/Hover.php b/lib/composer/felixfbecker/language-server-protocol/src/Hover.php new file mode 100644 index 0000000000000000000000000000000000000000..d7b5a65363caef99279d087234a5e3064d4c0b3b --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/Hover.php @@ -0,0 +1,33 @@ +contents = $contents; + $this->range = $range; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/InitializeResult.php b/lib/composer/felixfbecker/language-server-protocol/src/InitializeResult.php new file mode 100644 index 0000000000000000000000000000000000000000..557b16683bfe49ef83c735a8b9cf23dee44eb174 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/InitializeResult.php @@ -0,0 +1,21 @@ +capabilities = $capabilities ?? new ServerCapabilities(); + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/InsertTextFormat.php b/lib/composer/felixfbecker/language-server-protocol/src/InsertTextFormat.php new file mode 100644 index 0000000000000000000000000000000000000000..e3c7ae11054d94cb077f0149a817768f23334e0c --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/InsertTextFormat.php @@ -0,0 +1,25 @@ +uri = $uri; + $this->range = $range; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/MarkedString.php b/lib/composer/felixfbecker/language-server-protocol/src/MarkedString.php new file mode 100644 index 0000000000000000000000000000000000000000..c3ac254f04b1e7c6c1125e747879557d338409b3 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/MarkedString.php @@ -0,0 +1,22 @@ +language = $language; + $this->value = $value; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/MarkupContent.php b/lib/composer/felixfbecker/language-server-protocol/src/MarkupContent.php new file mode 100644 index 0000000000000000000000000000000000000000..f7d6dc8221062316a1aa71a9f56e0049e5420a0d --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/MarkupContent.php @@ -0,0 +1,50 @@ +kind = $kind; + $this->value = $value; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/MarkupKind.php b/lib/composer/felixfbecker/language-server-protocol/src/MarkupKind.php new file mode 100644 index 0000000000000000000000000000000000000000..962fb0318465e2cf08aad613ca3f7d4ec6d58612 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/MarkupKind.php @@ -0,0 +1,23 @@ +title = $title; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/MessageType.php b/lib/composer/felixfbecker/language-server-protocol/src/MessageType.php new file mode 100644 index 0000000000000000000000000000000000000000..1d01540bdc187de5264755c2e93f440e2589cb39 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/MessageType.php @@ -0,0 +1,29 @@ +name = $name; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/ParameterInformation.php b/lib/composer/felixfbecker/language-server-protocol/src/ParameterInformation.php new file mode 100644 index 0000000000000000000000000000000000000000..bb3db396b0a9d491a239c997d333df9a5af01428 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/ParameterInformation.php @@ -0,0 +1,45 @@ +label = $label; + $this->documentation = $documentation; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/Position.php b/lib/composer/felixfbecker/language-server-protocol/src/Position.php new file mode 100644 index 0000000000000000000000000000000000000000..399f3fa67a1e2c8d461e1d0a8a6efeb4ba20a253 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/Position.php @@ -0,0 +1,65 @@ +line = $line; + $this->character = $character; + } + + /** + * Compares this position to another position + * Returns + * - 0 if the positions match + * - a negative number if $this is before $position + * - a positive number otherwise + * + * @param Position $position + * @return int + */ + public function compare(Position $position): int + { + if ($this->line === $position->line && $this->character === $position->character) { + return 0; + } + + if ($this->line !== $position->line) { + return $this->line - $position->line; + } + + return $this->character - $position->character; + } + + /** + * Returns the offset of the position in a string + * + * @param string $content + * @return int + */ + public function toOffset(string $content): int + { + $lines = explode("\n", $content); + $slice = array_slice($lines, 0, $this->line); + return (int) array_sum(array_map('strlen', $slice)) + count($slice) + $this->character; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/Range.php b/lib/composer/felixfbecker/language-server-protocol/src/Range.php new file mode 100644 index 0000000000000000000000000000000000000000..74f23935181f6adc817ef8fb79a62f011232e737 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/Range.php @@ -0,0 +1,40 @@ +start = $start; + $this->end = $end; + } + + /** + * Checks if a position is within the range + * + * @param Position $position + * @return bool + */ + public function includes(Position $position): bool + { + return $this->start->compare($position) <= 0 && $this->end->compare($position) >= 0; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/ReferenceContext.php b/lib/composer/felixfbecker/language-server-protocol/src/ReferenceContext.php new file mode 100644 index 0000000000000000000000000000000000000000..caeb04119e43c695dcf1602c429a29f983d8be72 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/ReferenceContext.php @@ -0,0 +1,18 @@ +includeDeclaration = $includeDeclaration; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/ReferenceInformation.php b/lib/composer/felixfbecker/language-server-protocol/src/ReferenceInformation.php new file mode 100644 index 0000000000000000000000000000000000000000..f5f76b7de5e0bb1da2b8025ef9ca4ca265152e8e --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/ReferenceInformation.php @@ -0,0 +1,36 @@ +reference = $reference; + $this->symbol = $symbol; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/SaveOptions.php b/lib/composer/felixfbecker/language-server-protocol/src/SaveOptions.php new file mode 100644 index 0000000000000000000000000000000000000000..356252840a329024ffd0f67e3023a0eee28fc59c --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/SaveOptions.php @@ -0,0 +1,15 @@ +signatures = $signatures; + $this->activeSignature = $activeSignature; + $this->activeParameter = $activeParameter; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/SignatureHelpOptions.php b/lib/composer/felixfbecker/language-server-protocol/src/SignatureHelpOptions.php new file mode 100644 index 0000000000000000000000000000000000000000..7247452cacfa33ce28df27101d950e4a68230f43 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/SignatureHelpOptions.php @@ -0,0 +1,21 @@ +triggerCharacters = $triggerCharacters; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/SignatureInformation.php b/lib/composer/felixfbecker/language-server-protocol/src/SignatureInformation.php new file mode 100644 index 0000000000000000000000000000000000000000..f6c6e0f7bda27466644d3427cca52c7810fb7d87 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/SignatureInformation.php @@ -0,0 +1,49 @@ +label = $label; + $this->parameters = $parameters; + $this->documentation = $documentation; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/SymbolDescriptor.php b/lib/composer/felixfbecker/language-server-protocol/src/SymbolDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..7415ed15c594f04e4b5219b5adf34be366885fd4 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/SymbolDescriptor.php @@ -0,0 +1,34 @@ +fqsen = $fqsen; + $this->package = $package; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/SymbolInformation.php b/lib/composer/felixfbecker/language-server-protocol/src/SymbolInformation.php new file mode 100644 index 0000000000000000000000000000000000000000..4cf6654346d6522ee10d354d8514a23db7a1a72b --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/SymbolInformation.php @@ -0,0 +1,52 @@ +name = $name; + $this->kind = $kind; + $this->location = $location; + $this->containerName = $containerName; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/SymbolKind.php b/lib/composer/felixfbecker/language-server-protocol/src/SymbolKind.php new file mode 100644 index 0000000000000000000000000000000000000000..b59eecae11beff0a089cc79f8688d1ddaef08909 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/SymbolKind.php @@ -0,0 +1,28 @@ +symbol = $symbol; + $this->location = $location; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentContentChangeEvent.php b/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentContentChangeEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..0336c733b84ce7c64ac86e036d777c93fd589542 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentContentChangeEvent.php @@ -0,0 +1,38 @@ +range = $range; + $this->rangeLength = $rangeLength; + $this->text = $text; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentIdentifier.php b/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentIdentifier.php new file mode 100644 index 0000000000000000000000000000000000000000..7478f7f6fa2a324f3f2602a01fdd1775d6e9d5ea --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentIdentifier.php @@ -0,0 +1,21 @@ +uri = $uri; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentItem.php b/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentItem.php new file mode 100644 index 0000000000000000000000000000000000000000..a5f1024dfd5b68a60e59e2648140f61ae7f031f4 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentItem.php @@ -0,0 +1,46 @@ +uri = $uri; + $this->languageId = $languageId; + $this->version = $version; + $this->text = $text; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentSyncKind.php b/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentSyncKind.php new file mode 100644 index 0000000000000000000000000000000000000000..0adf634599a1cc8091c3ebf8775c628d6ec1baba --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/TextDocumentSyncKind.php @@ -0,0 +1,25 @@ +range = $range; + $this->newText = $newText; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/VersionedTextDocumentIdentifier.php b/lib/composer/felixfbecker/language-server-protocol/src/VersionedTextDocumentIdentifier.php new file mode 100644 index 0000000000000000000000000000000000000000..6232713ff83620d36cc441de677f483cf9d2386e --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/VersionedTextDocumentIdentifier.php @@ -0,0 +1,19 @@ +version = $version; + } +} diff --git a/lib/composer/felixfbecker/language-server-protocol/src/WorkspaceEdit.php b/lib/composer/felixfbecker/language-server-protocol/src/WorkspaceEdit.php new file mode 100644 index 0000000000000000000000000000000000000000..4a918d82b4b6487b7b0f12f0c764c800a48d5a40 --- /dev/null +++ b/lib/composer/felixfbecker/language-server-protocol/src/WorkspaceEdit.php @@ -0,0 +1,24 @@ +changes = $changes; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/CHANGELOG.md b/lib/composer/friendsofphp/php-cs-fixer/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..40461ad0e139c1e711eee5bf27220e9e36c9bbf2 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/CHANGELOG.md @@ -0,0 +1,3153 @@ +CHANGELOG for PHP CS Fixer +========================== + +This file contains changelogs for stable releases only. + +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.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 suport 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 unneccesary loop (Sijun Zhu) +* minor #4366 Unset the auxillary 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 aliasses (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 unneccesary loop (Sijun Zhu) +* minor #4366 Unset the auxillary 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 EspaceImplicitBackslashesFixer - 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 incorect (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 SelfAcessorFixer - 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 SelfAcessorFixer - 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 NoSpacesInsideOfssetFixer (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: Ofsset 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 varaible decalration : 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 inalid 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 cousing 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 unneded 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 invalide 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 aling => (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/lib/composer/friendsofphp/php-cs-fixer/CONTRIBUTING.md b/lib/composer/friendsofphp/php-cs-fixer/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..4b4f5e3919d4dcc491ffc70368292cb05753d708 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributions Are Welcome! + +If you need any help, don't hesitate to ask the community on [Gitter](https://gitter.im/PHP-CS-Fixer/Lobby). + +## Quick Guide + +* [Fork](https://help.github.com/articles/fork-a-repo/) the repo. +* [Checkout](https://git-scm.com/docs/git-checkout) the branch you want to make changes on: + * If you are fixing a bug or typo, improving tests or for any small tweak: the lowest branch where the changes can be applied. Once your Pull Request is accepted, the changes will get merged up to highest branches. + * `master` in other cases (new feature, deprecation, or backwards compatibility breaking changes). Note that most of the time, `master` represents the next minor release of PHP CS Fixer, so Pull Requests that break backwards compatibility might be postponed. +* Install dependencies: `composer install`. +* Create a new branch, e.g. `feature-foo` or `bugfix-bar`. +* Make changes. +* If you are adding functionality or fixing a bug - add a test! Prefer adding new test cases over modifying existing ones. +* Make sure there is no trailing spaces in code: `./check_trailing_spaces.sh`. +* Regenerate README: `php php-cs-fixer readme > README.rst`. Do not modify `README.rst` manually! +* Check if tests pass: `vendor/bin/phpunit`. +* Fix project itself: `php php-cs-fixer 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 the first time: + +* Submit one Pull Request per fix or feature. +* If your changes are not up to date, [rebase](https://git-scm.com/docs/git-rebase) your branch onto the parent branch. +* Follow the conventions used in the project. +* Remember about tests and documentation. +* Don't bump version. + +## Making New Fixers + +There is a [cookbook](doc/COOKBOOK-FIXERS.md) 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/lib/composer/friendsofphp/php-cs-fixer/LICENSE b/lib/composer/friendsofphp/php-cs-fixer/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e060f2ac162c950b2b3ac10ca0c05762b830c3a6 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012-2020 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/lib/composer/friendsofphp/php-cs-fixer/README.rst b/lib/composer/friendsofphp/php-cs-fixer/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..cc6b17ff58f88f337d54b150cebaf508da231949 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/README.rst @@ -0,0 +1,2188 @@ +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. + +The PHP CS Fixer is maintained on GitHub at https://github.com/FriendsOfPHP/PHP-CS-Fixer +bug reports and ideas about new features are welcome there. + +You can talk to us at https://gitter.im/PHP-CS-Fixer/Lobby about the project, +configuration, possible improvements, ideas and questions, please visit us! + +Requirements +------------ + +PHP needs to be a minimum version of PHP 5.6.0. + +Installation +------------ + +Locally +~~~~~~~ + +Download the `php-cs-fixer.phar`_ file and store it somewhere on your computer. + +Globally (manual) +~~~~~~~~~~~~~~~~~ + +You can run these commands to easily access latest ``php-cs-fixer`` from anywhere on +your system: + +.. code-block:: bash + + $ wget https://cs.symfony.com/download/php-cs-fixer-v2.phar -O php-cs-fixer + +or with specified version: + +.. code-block:: bash + + $ wget https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v2.16.3/php-cs-fixer.phar -O php-cs-fixer + +or with curl: + +.. code-block:: bash + + $ curl -L https://cs.symfony.com/download/php-cs-fixer-v2.phar -o php-cs-fixer + +then: + +.. code-block:: bash + + $ sudo chmod a+x php-cs-fixer + $ sudo mv php-cs-fixer /usr/local/bin/php-cs-fixer + +Then, just run ``php-cs-fixer``. + +Globally (Composer) +~~~~~~~~~~~~~~~~~~~ + +To install PHP CS Fixer, `install Composer `_ and issue the following command: + +.. code-block:: bash + + $ composer global require friendsofphp/php-cs-fixer + +Then make sure you have the global Composer binaries directory in your ``PATH``. This directory is platform-dependent, see `Composer documentation `_ for details. Example for some Unix systems: + +.. code-block:: bash + + $ export PATH="$PATH:$HOME/.composer/vendor/bin" + +Globally (homebrew) +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + $ brew install php-cs-fixer + +Locally (PHIVE) +~~~~~~~~~~~~~~~ + +Install `PHIVE `_ and issue the following command: + +.. code-block:: bash + + $ phive install php-cs-fixer # use `--global` for global install + +Update +------ + +Locally +~~~~~~~ + +The ``self-update`` command tries to update ``php-cs-fixer`` itself: + +.. code-block:: bash + + $ php php-cs-fixer.phar self-update + +Globally (manual) +~~~~~~~~~~~~~~~~~ + +You can update ``php-cs-fixer`` through this command: + +.. code-block:: bash + + $ sudo php-cs-fixer self-update + +Globally (Composer) +~~~~~~~~~~~~~~~~~~~ + +You can update ``php-cs-fixer`` through this command: + +.. code-block:: bash + + $ ./composer.phar global update friendsofphp/php-cs-fixer + +Globally (homebrew) +~~~~~~~~~~~~~~~~~~~ + +You can update ``php-cs-fixer`` through this command: + +.. code-block:: bash + + $ brew upgrade php-cs-fixer + +Locally (PHIVE) +~~~~~~~~~~~~~~~ + +.. code-block:: bash + + $ phive update php-cs-fixer + +Usage +----- + +The ``fix`` command tries to fix as much coding standards +problems as possible on a given file or files in a given directory and its subdirectories: + +.. code-block:: bash + + $ php php-cs-fixer.phar fix /path/to/dir + $ php php-cs-fixer.phar fix /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: + +.. code-block:: bash + + $ php php-cs-fixer.phar fix --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 XML schemas + +* ``junit`` follows the `JUnit xml schema from Jenkins `_ +* ``checkstyle`` follows the common `"checkstyle" 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 + +* ``--verbose=0`` or no option: normal +* ``--verbose``, ``--verbose=1``, ``-v``: verbose +* ``--verbose=2``, ``-vv``: very verbose +* ``--verbose=3``, ``-vvv``: debug + +The ``--rules`` option limits the rules to apply to the +project: + +.. code-block:: bash + + $ php php-cs-fixer.phar fix /path/to/project --rules=@PSR2 + +By default the PSR1 and PSR2 rules are used. + +The ``--rules`` option lets you choose the exact rules to +apply (the rule names must be separated by a comma): + +.. code-block:: bash + + $ php php-cs-fixer.phar fix /path/to/dir --rules=line_ending,full_opening_tag,indentation_type + +You can also blacklist 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``: + +.. code-block:: bash + + $ php php-cs-fixer.phar fix /path/to/dir --rules=-full_opening_tag,-indentation_type + +When using combinations of exact and blacklist rules, applying exact rules along with above blacklisted results: + +.. code-block:: bash + + $ php php-cs-fixer.phar fix /path/to/project --rules=@Symfony,-@PSR1,-blank_line_before_statement,strict_comparison + +Complete configuration for rules can be supplied using a ``json`` formatted string. + +.. code-block:: bash + + $ php php-cs-fixer.phar fix /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 ``--diff-format`` option allows to specify in which format the fixer should output the changes it makes: + +* ``udiff``: unified diff format; +* ``sbd``: Sebastianbergmann/diff format (default when using `--diff` without specifying `diff-format`). + +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; +* ``run-in``: [deprecated] simple single-line progress output; +* ``estimating``: [deprecated] multiline progress output with number of files and percentage on each line. Note that with this option, the files list is evaluated before processing to get the total number of files and then kept in memory to avoid using the file iterator twice. This has an impact on memory usage so using this option is not recommended on very large projects; +* ``estimating-max``: [deprecated] same as ``dots``; +* ``dots``: same as ``estimating`` but using all terminal columns instead of default 80. + +If the option is not provided, it defaults to ``run-in`` 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``. + +.. code-block:: bash + + $ php php-cs-fixer.phar fix --verbose --show-progress=estimating + +The command can also read from standard input, in which case it won't +automatically fix anything: + +.. code-block:: bash + + $ cat foo.php | php php-cs-fixer.phar fix --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 (unified differ, estimating, full-width progress indicator): + +.. code-block:: bash + + $ PHP_CS_FIXER_FUTURE_MODE=1 php php-cs-fixer.phar fix -v --diff + +Choose from the list of available rules: + +* **align_multiline_comment** [@PhpCsFixer] + + Each line of multi-line DocComments must have an asterisk [PSR-5] and + must be aligned with the first one. + + Configuration options: + + - ``comment_type`` (``'all_multiline'``, ``'phpdocs_like'``, ``'phpdocs_only'``): 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``); defaults to ``'phpdocs_only'`` + +* **array_indentation** [@PhpCsFixer] + + Each element of an array must be indented exactly once. + +* **array_syntax** [@Symfony, @PhpCsFixer] + + PHP arrays should be declared using the configured syntax. + + Configuration options: + + - ``syntax`` (``'long'``, ``'short'``): whether to use the ``long`` or ``short`` array + syntax; defaults to ``'long'`` + +* **backtick_to_shell_exec** + + Converts backtick operators to ``shell_exec`` calls. + +* **binary_operator_spaces** [@Symfony, @PhpCsFixer] + + Binary operators should be surrounded by space as configured. + + Configuration options: + + - ``align_double_arrow`` (``false``, ``null``, ``true``): whether to apply, remove or + ignore double arrows alignment; defaults to ``false``. DEPRECATED: use + options ``operators`` and ``default`` instead + - ``align_equals`` (``false``, ``null``, ``true``): whether to apply, remove or ignore + equals alignment; defaults to ``false``. DEPRECATED: use options + ``operators`` and ``default`` instead + - ``default`` (``'align'``, ``'align_single_space'``, ``'align_single_space_minimal'``, + ``'no_space'``, ``'single_space'``, ``null``): default fix strategy; defaults to + ``'single_space'`` + - ``operators`` (``array``): dictionary of ``binary operator`` => ``fix strategy`` + values that differ from the default strategy; defaults to ``[]`` + +* **blank_line_after_namespace** [@PSR2, @Symfony, @PhpCsFixer] + + There MUST be one blank line after the namespace declaration. + +* **blank_line_after_opening_tag** [@Symfony, @PhpCsFixer] + + Ensure there is no code on the same line as the PHP open tag and it is + followed by a blank line. + +* **blank_line_before_return** + + An empty line feed should precede a return statement. DEPRECATED: use + ``blank_line_before_statement`` instead. + +* **blank_line_before_statement** [@Symfony, @PhpCsFixer] + + An empty line feed must precede any configured statement. + + Configuration options: + + - ``statements`` (a subset of ``['break', 'case', 'continue', 'declare', + 'default', 'die', 'do', 'exit', 'for', 'foreach', 'goto', 'if', + 'include', 'include_once', 'require', 'require_once', 'return', + 'switch', 'throw', 'try', 'while', 'yield']``): list of statements which + must be preceded by an empty line; defaults to ``['break', 'continue', + 'declare', 'return', 'throw', 'try']`` + +* **braces** [@PSR2, @Symfony, @PhpCsFixer] + + The body of each structure MUST be enclosed by braces. Braces should be + properly placed. Body of braces should be properly indented. + + Configuration options: + + - ``allow_single_line_closure`` (``bool``): whether single line lambda notation + should be allowed; defaults to ``false`` + - ``position_after_anonymous_constructs`` (``'next'``, ``'same'``): whether the + opening brace should be placed on "next" or "same" line after anonymous + constructs (anonymous classes and lambda functions); defaults to ``'same'`` + - ``position_after_control_structures`` (``'next'``, ``'same'``): whether the opening + brace should be placed on "next" or "same" line after control + structures; defaults to ``'same'`` + - ``position_after_functions_and_oop_constructs`` (``'next'``, ``'same'``): 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); defaults to ``'next'`` + +* **cast_spaces** [@Symfony, @PhpCsFixer] + + A single space or none should be between cast and variable. + + Configuration options: + + - ``space`` (``'none'``, ``'single'``): spacing to apply between cast and variable; + defaults to ``'single'`` + +* **class_attributes_separation** [@Symfony, @PhpCsFixer] + + Class, trait and interface elements must be separated with one blank + line. + + Configuration options: + + - ``elements`` (a subset of ``['const', 'method', 'property']``): list of classy + elements; 'const', 'method', 'property'; defaults to ``['const', + 'method', 'property']`` + +* **class_definition** [@PSR2, @Symfony, @PhpCsFixer] + + Whitespace around the keywords of a class, trait or interfaces + definition should be one space. + + Configuration options: + + - ``multi_line_extends_each_single_line`` (``bool``): whether definitions should + be multiline; defaults to ``false``; DEPRECATED alias: + ``multiLineExtendsEachSingleLine`` + - ``single_item_single_line`` (``bool``): whether definitions should be single + line when including a single item; defaults to ``false``; DEPRECATED alias: + ``singleItemSingleLine`` + - ``single_line`` (``bool``): whether definitions should be single line; defaults + to ``false``; DEPRECATED alias: ``singleLine`` + +* **class_keyword_remove** + + Converts ``::class`` keywords to FQCN strings. + +* **combine_consecutive_issets** [@PhpCsFixer] + + Using ``isset($var) &&`` multiple times should be done in one call. + +* **combine_consecutive_unsets** [@PhpCsFixer] + + Calling ``unset`` on multiple items should be done in one call. + +* **combine_nested_dirname** [@PHP70Migration:risky, @PHP71Migration:risky] + + Replace multiple nested calls of ``dirname`` by only one call with second + ``$level`` parameter. Requires PHP >= 7.0. + + *Risky rule: risky when the function ``dirname`` is overridden.* + +* **comment_to_phpdoc** [@PhpCsFixer:risky] + + Comments with annotation should be docblock when used on structural + elements. + + *Risky rule: risky as new docblocks might mean more, e.g. a Doctrine entity might have a new column in database.* + + Configuration options: + + - ``ignored_tags`` (``array``): list of ignored tags; defaults to ``[]`` + +* **compact_nullable_typehint** [@PhpCsFixer] + + Remove extra spaces in a nullable typehint. + +* **concat_space** [@Symfony, @PhpCsFixer] + + Concatenation should be spaced according configuration. + + Configuration options: + + - ``spacing`` (``'none'``, ``'one'``): spacing to apply around concatenation operator; + defaults to ``'none'`` + +* **constant_case** [@PSR2, @Symfony, @PhpCsFixer] + + The PHP constants ``true``, ``false``, and ``null`` MUST be written using the + correct casing. + + Configuration options: + + - ``case`` (``'lower'``, ``'upper'``): whether to use the ``upper`` or ``lower`` case + syntax; defaults to ``'lower'`` + +* **date_time_immutable** + + Class ``DateTimeImmutable`` should be used instead of ``DateTime``. + + *Risky rule: risky when the code relies on modifying ``DateTime`` objects or if any of the ``date_create*`` functions are overridden.* + +* **declare_equal_normalize** [@Symfony, @PhpCsFixer] + + Equal sign in declare statement should be surrounded by spaces or not + following configuration. + + Configuration options: + + - ``space`` (``'none'``, ``'single'``): spacing to apply around the equal sign; + defaults to ``'none'`` + +* **declare_strict_types** [@PHP70Migration:risky, @PHP71Migration:risky] + + Force strict types declaration in all files. Requires PHP >= 7.0. + + *Risky rule: forcing strict types will stop non strict code from working.* + +* **dir_constant** [@Symfony:risky, @PhpCsFixer:risky] + + Replaces ``dirname(__FILE__)`` expression with equivalent ``__DIR__`` + constant. + + *Risky rule: risky when the function ``dirname`` is overridden.* + +* **doctrine_annotation_array_assignment** [@DoctrineAnnotation] + + Doctrine annotations must use configured operator for assignment in + arrays. + + Configuration options: + + - ``ignored_tags`` (``array``): list of tags that must not be treated as Doctrine + Annotations; defaults to ``['abstract', 'access', 'code', 'deprec', + 'encode', 'exception', 'final', 'ingroup', 'inheritdoc', 'inheritDoc', + 'magic', 'name', 'toc', 'tutorial', 'private', 'static', 'staticvar', + 'staticVar', 'throw', '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', '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', + 'SuppressWarnings', 'noinspection', 'package_version', 'enduml', + 'startuml', 'fix', 'FIXME', 'fixme', 'override']`` + - ``operator`` (``':'``, ``'='``): the operator to use; defaults to ``'='`` + +* **doctrine_annotation_braces** [@DoctrineAnnotation] + + Doctrine annotations without arguments must use the configured syntax. + + Configuration options: + + - ``ignored_tags`` (``array``): list of tags that must not be treated as Doctrine + Annotations; defaults to ``['abstract', 'access', 'code', 'deprec', + 'encode', 'exception', 'final', 'ingroup', 'inheritdoc', 'inheritDoc', + 'magic', 'name', 'toc', 'tutorial', 'private', 'static', 'staticvar', + 'staticVar', 'throw', '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', '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', + 'SuppressWarnings', 'noinspection', 'package_version', 'enduml', + 'startuml', 'fix', 'FIXME', 'fixme', 'override']`` + - ``syntax`` (``'with_braces'``, ``'without_braces'``): whether to add or remove + braces; defaults to ``'without_braces'`` + +* **doctrine_annotation_indentation** [@DoctrineAnnotation] + + Doctrine annotations must be indented with four spaces. + + Configuration options: + + - ``ignored_tags`` (``array``): list of tags that must not be treated as Doctrine + Annotations; defaults to ``['abstract', 'access', 'code', 'deprec', + 'encode', 'exception', 'final', 'ingroup', 'inheritdoc', 'inheritDoc', + 'magic', 'name', 'toc', 'tutorial', 'private', 'static', 'staticvar', + 'staticVar', 'throw', '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', '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', + 'SuppressWarnings', 'noinspection', 'package_version', 'enduml', + 'startuml', 'fix', 'FIXME', 'fixme', 'override']`` + - ``indent_mixed_lines`` (``bool``): whether to indent lines that have content + before closing parenthesis; defaults to ``false`` + +* **doctrine_annotation_spaces** [@DoctrineAnnotation] + + Fixes spaces in Doctrine annotations. + + Configuration options: + + - ``after_argument_assignments`` (``null``, ``bool``): whether to add, remove or + ignore spaces after argument assignment operator; defaults to ``false`` + - ``after_array_assignments_colon`` (``null``, ``bool``): whether to add, remove or + ignore spaces after array assignment ``:`` operator; defaults to ``true`` + - ``after_array_assignments_equals`` (``null``, ``bool``): whether to add, remove or + ignore spaces after array assignment ``=`` operator; defaults to ``true`` + - ``around_argument_assignments`` (``bool``): whether to fix spaces around + argument assignment operator; defaults to ``true``. DEPRECATED: use options + ``before_argument_assignments`` and ``after_argument_assignments`` instead + - ``around_array_assignments`` (``bool``): whether to fix spaces around array + assignment operators; defaults to ``true``. DEPRECATED: use options + ``before_array_assignments_equals``, ``after_array_assignments_equals``, + ``before_array_assignments_colon`` and ``after_array_assignments_colon`` + instead + - ``around_commas`` (``bool``): whether to fix spaces around commas; defaults to + ``true`` + - ``around_parentheses`` (``bool``): whether to fix spaces around parentheses; + defaults to ``true`` + - ``before_argument_assignments`` (``null``, ``bool``): whether to add, remove or + ignore spaces before argument assignment operator; defaults to ``false`` + - ``before_array_assignments_colon`` (``null``, ``bool``): whether to add, remove or + ignore spaces before array ``:`` assignment operator; defaults to ``true`` + - ``before_array_assignments_equals`` (``null``, ``bool``): whether to add, remove or + ignore spaces before array ``=`` assignment operator; defaults to ``true`` + - ``ignored_tags`` (``array``): list of tags that must not be treated as Doctrine + Annotations; defaults to ``['abstract', 'access', 'code', 'deprec', + 'encode', 'exception', 'final', 'ingroup', 'inheritdoc', 'inheritDoc', + 'magic', 'name', 'toc', 'tutorial', 'private', 'static', 'staticvar', + 'staticVar', 'throw', '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', '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', + 'SuppressWarnings', 'noinspection', 'package_version', 'enduml', + 'startuml', 'fix', 'FIXME', 'fixme', 'override']`` + +* **elseif** [@PSR2, @Symfony, @PhpCsFixer] + + The keyword ``elseif`` should be used instead of ``else if`` so that all + control keywords look like single words. + +* **encoding** [@PSR1, @PSR2, @Symfony, @PhpCsFixer] + + PHP code MUST use only UTF-8 without BOM (remove BOM). + +* **ereg_to_preg** [@Symfony:risky, @PhpCsFixer:risky] + + Replace deprecated ``ereg`` regular expression functions with ``preg``. + + *Risky rule: risky if the ``ereg`` function is overridden.* + +* **error_suppression** [@Symfony:risky, @PhpCsFixer:risky] + + Error control operator should be added to deprecation notices and/or + removed from other cases. + + *Risky rule: risky because adding/removing ``@`` might cause changes to code behaviour or if ``trigger_error`` function is overridden.* + + Configuration options: + + - ``mute_deprecation_error`` (``bool``): whether to add ``@`` in deprecation + notices; defaults to ``true`` + - ``noise_remaining_usages`` (``bool``): whether to remove ``@`` in remaining + usages; defaults to ``false`` + - ``noise_remaining_usages_exclude`` (``array``): list of global functions to + exclude from removing ``@``; defaults to ``[]`` + +* **escape_implicit_backslashes** [@PhpCsFixer] + + Escape implicit backslashes in strings and heredocs to ease the + understanding of which are special chars interpreted by PHP and which + not. + + Configuration options: + + - ``double_quoted`` (``bool``): whether to fix double-quoted strings; defaults to + ``true`` + - ``heredoc_syntax`` (``bool``): whether to fix heredoc syntax; defaults to ``true`` + - ``single_quoted`` (``bool``): whether to fix single-quoted strings; defaults to + ``false`` + +* **explicit_indirect_variable** [@PhpCsFixer] + + Add curly braces to indirect variables to make them clear to understand. + Requires PHP >= 7.0. + +* **explicit_string_variable** [@PhpCsFixer] + + Converts implicit variables into explicit ones in double-quoted strings + or heredoc syntax. + +* **final_class** + + All classes must be final, except abstract ones and Doctrine entities. + + *Risky rule: risky when subclassing non-abstract classes.* + +* **final_internal_class** [@PhpCsFixer:risky] + + Internal classes should be ``final``. + + *Risky rule: changing classes to ``final`` might cause code execution to break.* + + Configuration options: + + - ``annotation-black-list`` (``array``): class level annotations tags that must be + omitted to fix the class, even if all of the white list ones are used + as well. (case insensitive); defaults to ``['@final', '@Entity', + '@ORM\\Entity', '@ORM\\Mapping\\Entity', '@Mapping\\Entity']`` + - ``annotation-white-list`` (``array``): class level annotations tags that must be + set in order to fix the class. (case insensitive); defaults to + ``['@internal']`` + - ``consider-absent-docblock-as-internal-class`` (``bool``): should classes + without any DocBlock be fixed to final?; defaults to ``false`` + +* **final_public_method_for_abstract_class** + + All ``public`` methods of ``abstract`` classes should be ``final``. + + *Risky rule: risky when overriding ``public`` methods of ``abstract`` classes.* + +* **final_static_access** + + Converts ``static`` access to ``self`` access in ``final`` classes. + +* **fopen_flag_order** [@Symfony:risky, @PhpCsFixer:risky] + + Order the flags in ``fopen`` calls, ``b`` and ``t`` must be last. + + *Risky rule: risky when the function ``fopen`` is overridden.* + +* **fopen_flags** [@Symfony:risky, @PhpCsFixer:risky] + + The flags in ``fopen`` calls must omit ``t``, and ``b`` must be omitted or + included consistently. + + *Risky rule: risky when the function ``fopen`` is overridden.* + + Configuration options: + + - ``b_mode`` (``bool``): the ``b`` flag must be used (``true``) or omitted (``false``); + defaults to ``true`` + +* **full_opening_tag** [@PSR1, @PSR2, @Symfony, @PhpCsFixer] + + PHP code must use the long ``= 7.3. + +* **heredoc_to_nowdoc** [@PhpCsFixer] + + Convert ``heredoc`` to ``nowdoc`` where possible. + +* **implode_call** [@Symfony:risky, @PhpCsFixer:risky] + + Function ``implode`` must be called with 2 arguments in the documented + order. + + *Risky rule: risky when the function ``implode`` is overridden.* + +* **include** [@Symfony, @PhpCsFixer] + + Include/Require and file path should be divided with a single space. + File path should not be placed under brackets. + +* **increment_style** [@Symfony, @PhpCsFixer] + + Pre- or post-increment and decrement operators should be used if + possible. + + Configuration options: + + - ``style`` (``'post'``, ``'pre'``): whether to use pre- or post-increment and + decrement operators; defaults to ``'pre'`` + +* **indentation_type** [@PSR2, @Symfony, @PhpCsFixer] + + Code MUST use configured indentation type. + +* **is_null** [@Symfony:risky, @PhpCsFixer:risky] + + Replaces ``is_null($var)`` expression with ``null === $var``. + + *Risky rule: risky when the function ``is_null`` is overridden.* + + Configuration options: + + - ``use_yoda_style`` (``bool``): whether Yoda style conditions should be used; + defaults to ``true``. DEPRECATED: use ``yoda_style`` fixer instead + +* **line_ending** [@PSR2, @Symfony, @PhpCsFixer] + + All PHP files must use same line ending. + +* **linebreak_after_opening_tag** + + Ensure there is no code on the same line as the PHP open tag. + +* **list_syntax** + + List (``array`` destructuring) assignment should be declared using the + configured syntax. Requires PHP >= 7.1. + + Configuration options: + + - ``syntax`` (``'long'``, ``'short'``): whether to use the ``long`` or ``short`` ``list`` + syntax; defaults to ``'long'`` + +* **logical_operators** [@PhpCsFixer:risky] + + Use ``&&`` and ``||`` logical operators instead of ``and`` and ``or``. + + *Risky rule: risky, because you must double-check if using and/or with lower precedence was intentional.* + +* **lowercase_cast** [@Symfony, @PhpCsFixer] + + Cast should be written in lower case. + +* **lowercase_constants** + + The PHP constants ``true``, ``false``, and ``null`` MUST be in lower case. + DEPRECATED: use ``constant_case`` instead. + +* **lowercase_keywords** [@PSR2, @Symfony, @PhpCsFixer] + + PHP keywords MUST be in lower case. + +* **lowercase_static_reference** [@Symfony, @PhpCsFixer] + + Class static references ``self``, ``static`` and ``parent`` MUST be in lower + case. + +* **magic_constant_casing** [@Symfony, @PhpCsFixer] + + Magic constants should be referred to using the correct casing. + +* **magic_method_casing** [@Symfony, @PhpCsFixer] + + Magic method definitions and calls must be using the correct casing. + +* **mb_str_functions** + + Replace non multibyte-safe functions with corresponding mb function. + + *Risky rule: risky when any of the functions are overridden.* + +* **method_argument_space** [@PSR2, @Symfony, @PhpCsFixer] + + 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. + + Configuration options: + + - ``after_heredoc`` (``bool``): whether the whitespace between heredoc end and + comma should be removed; defaults to ``false`` + - ``ensure_fully_multiline`` (``bool``): ensure every argument of a multiline + argument list is on its own line; defaults to ``false``. DEPRECATED: use + option ``on_multiline`` instead + - ``keep_multiple_spaces_after_comma`` (``bool``): whether keep multiple spaces + after comma; defaults to ``false`` + - ``on_multiline`` (``'ensure_fully_multiline'``, ``'ensure_single_line'``, ``'ignore'``): + defines how to handle function arguments lists that contain newlines; + defaults to ``'ignore'`` + +* **method_chaining_indentation** [@PhpCsFixer] + + Method chaining MUST be properly indented. Method chaining with + different levels of indentation is not supported. + +* **method_separation** + + Methods must be separated with one blank line. DEPRECATED: use + ``class_attributes_separation`` instead. + +* **modernize_types_casting** [@Symfony:risky, @PhpCsFixer:risky] + + Replaces ``intval``, ``floatval``, ``doubleval``, ``strval`` and ``boolval`` + function calls with according type casting operator. + + *Risky rule: risky if any of the functions ``intval``, ``floatval``, ``doubleval``, ``strval`` or ``boolval`` are overridden.* + +* **multiline_comment_opening_closing** [@PhpCsFixer] + + 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. + +* **multiline_whitespace_before_semicolons** [@PhpCsFixer] + + Forbid multi-line whitespace before the closing semicolon or move the + semicolon to the new line for chained calls. + + Configuration options: + + - ``strategy`` (``'new_line_for_chained_calls'``, ``'no_multi_line'``): forbid + multi-line whitespace or move the semicolon to the new line for chained + calls; defaults to ``'no_multi_line'`` + +* **native_constant_invocation** [@Symfony:risky, @PhpCsFixer:risky] + + Add leading ``\`` before constant invocation of internal constant to speed + up resolving. Constant name match is case-sensitive, except for ``null``, + ``false`` and ``true``. + + *Risky rule: risky when any of the constants are namespaced or overridden.* + + Configuration options: + + - ``exclude`` (``array``): list of constants to ignore; defaults to ``['null', + 'false', 'true']`` + - ``fix_built_in`` (``bool``): 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; defaults to ``true`` + - ``include`` (``array``): list of additional constants to fix; defaults to ``[]`` + - ``scope`` (``'all'``, ``'namespaced'``): only fix constant invocations that are made + within a namespace or fix all; defaults to ``'all'`` + +* **native_function_casing** [@Symfony, @PhpCsFixer] + + Function defined by PHP should be called using the correct casing. + +* **native_function_invocation** [@Symfony:risky, @PhpCsFixer:risky] + + Add leading ``\`` before function invocation to speed up resolving. + + *Risky rule: risky when any of the functions are overridden.* + + Configuration options: + + - ``exclude`` (``array``): list of functions to ignore; defaults to ``[]`` + - ``include`` (``array``): 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); + defaults to ``['@internal']`` + - ``scope`` (``'all'``, ``'namespaced'``): only fix function calls that are made + within a namespace or fix all; defaults to ``'all'`` + - ``strict`` (``bool``): whether leading ``\`` of function call not meant to have it + should be removed; defaults to ``false`` + +* **native_function_type_declaration_casing** [@Symfony, @PhpCsFixer] + + Native type hints for functions should use the correct case. + +* **new_with_braces** [@Symfony, @PhpCsFixer] + + All instances created with new keyword must be followed by braces. + +* **no_alias_functions** [@Symfony:risky, @PhpCsFixer:risky] + + Master functions shall be used instead of aliases. + + *Risky rule: risky when any of the alias functions are overridden.* + + Configuration options: + + - ``sets`` (a subset of ``['@internal', '@IMAP', '@mbreg', '@all']``): list of + sets to fix. Defined sets are ``@internal`` (native functions), ``@IMAP`` + (IMAP functions), ``@mbreg`` (from ``ext-mbstring``) ``@all`` (all listed + sets); defaults to ``['@internal', '@IMAP']`` + +* **no_alternative_syntax** [@PhpCsFixer] + + Replace control structure alternative syntax to use braces. + +* **no_binary_string** [@PhpCsFixer] + + There should not be a binary flag before strings. + +* **no_blank_lines_after_class_opening** [@Symfony, @PhpCsFixer] + + There should be no empty lines after class opening brace. + +* **no_blank_lines_after_phpdoc** [@Symfony, @PhpCsFixer] + + There should not be blank lines between docblock and the documented + element. + +* **no_blank_lines_before_namespace** + + There should be no blank lines before a namespace declaration. + +* **no_break_comment** [@PSR2, @Symfony, @PhpCsFixer] + + There must be a comment when fall-through is intentional in a non-empty + case body. + + Configuration options: + + - ``comment_text`` (``string``): the text to use in the added comment and to + detect it; defaults to ``'no break'`` + +* **no_closing_tag** [@PSR2, @Symfony, @PhpCsFixer] + + The closing ``?>`` tag MUST be omitted from files containing only PHP. + +* **no_empty_comment** [@Symfony, @PhpCsFixer] + + There should not be any empty comments. + +* **no_empty_phpdoc** [@Symfony, @PhpCsFixer] + + There should not be empty PHPDoc blocks. + +* **no_empty_statement** [@Symfony, @PhpCsFixer] + + Remove useless semicolon statements. + +* **no_extra_blank_lines** [@Symfony, @PhpCsFixer] + + Removes extra blank lines and/or blank lines following configuration. + + Configuration options: + + - ``tokens`` (a subset of ``['break', 'case', 'continue', 'curly_brace_block', + 'default', 'extra', 'parenthesis_brace_block', 'return', + 'square_brace_block', 'switch', 'throw', 'use', 'useTrait', + 'use_trait']``): list of tokens to fix; defaults to ``['extra']`` + +* **no_extra_consecutive_blank_lines** + + Removes extra blank lines and/or blank lines following configuration. + DEPRECATED: use ``no_extra_blank_lines`` instead. + + Configuration options: + + - ``tokens`` (a subset of ``['break', 'case', 'continue', 'curly_brace_block', + 'default', 'extra', 'parenthesis_brace_block', 'return', + 'square_brace_block', 'switch', 'throw', 'use', 'useTrait', + 'use_trait']``): list of tokens to fix; defaults to ``['extra']`` + +* **no_homoglyph_names** [@Symfony:risky, @PhpCsFixer:risky] + + Replace accidental usage of homoglyphs (non ascii characters) in names. + + *Risky rule: renames classes and cannot rename the files. You might have string references to renamed code (``$$name``).* + +* **no_leading_import_slash** [@Symfony, @PhpCsFixer] + + Remove leading slashes in ``use`` clauses. + +* **no_leading_namespace_whitespace** [@Symfony, @PhpCsFixer] + + The namespace declaration line shouldn't contain leading whitespace. + +* **no_mixed_echo_print** [@Symfony, @PhpCsFixer] + + Either language construct ``print`` or ``echo`` should be used. + + Configuration options: + + - ``use`` (``'echo'``, ``'print'``): the desired language construct; defaults to + ``'echo'`` + +* **no_multiline_whitespace_around_double_arrow** [@Symfony, @PhpCsFixer] + + Operator ``=>`` should not be surrounded by multi-line whitespaces. + +* **no_multiline_whitespace_before_semicolons** + + Multi-line whitespace before closing semicolon are prohibited. + DEPRECATED: use ``multiline_whitespace_before_semicolons`` instead. + +* **no_null_property_initialization** [@PhpCsFixer] + + Properties MUST not be explicitly initialized with ``null`` except when + they have a type declaration (PHP 7.4). + +* **no_php4_constructor** + + Convert PHP4-style constructors to ``__construct``. + + *Risky rule: risky when old style constructor being fixed is overridden or overrides parent one.* + +* **no_short_bool_cast** [@Symfony, @PhpCsFixer] + + Short cast ``bool`` using double exclamation mark should not be used. + +* **no_short_echo_tag** [@PhpCsFixer] + + Replace short-echo ````. + +* **ordered_class_elements** [@PhpCsFixer] + + Orders the elements of classes/interfaces/traits. + + Configuration options: + + - ``order`` (a subset of ``['use_trait', 'public', 'protected', 'private', + 'constant', 'constant_public', 'constant_protected', + 'constant_private', 'property', 'property_static', 'property_public', + 'property_protected', 'property_private', 'property_public_static', + 'property_protected_static', 'property_private_static', 'method', + 'method_static', 'method_public', 'method_protected', 'method_private', + 'method_public_static', 'method_protected_static', + 'method_private_static', 'construct', 'destruct', 'magic', 'phpunit']``): + list of strings defining order of elements; defaults to ``['use_trait', + 'constant_public', 'constant_protected', 'constant_private', + 'property_public', 'property_protected', 'property_private', + 'construct', 'destruct', 'magic', 'phpunit', 'method_public', + 'method_protected', 'method_private']`` + - ``sortAlgorithm`` (``'alpha'``, ``'none'``): how multiple occurrences of same type + statements should be sorted; defaults to ``'none'`` + +* **ordered_imports** [@Symfony, @PhpCsFixer] + + Ordering ``use`` statements. + + Configuration options: + + - ``imports_order`` (``array``, ``null``): defines the order of import types; defaults + to ``null``; DEPRECATED alias: ``importsOrder`` + - ``sort_algorithm`` (``'alpha'``, ``'length'``, ``'none'``): whether the statements + should be sorted alphabetically or by length, or not sorted; defaults + to ``'alpha'``; DEPRECATED alias: ``sortAlgorithm`` + +* **ordered_interfaces** + + Orders the interfaces in an ``implements`` or ``interface extends`` clause. + + *Risky rule: risky for ``implements`` when specifying both an interface and its parent interface, because PHP doesn't break on ``parent, child`` but does on ``child, parent``.* + + Configuration options: + + - ``direction`` (``'ascend'``, ``'descend'``): which direction the interfaces should + be ordered; defaults to ``'ascend'`` + - ``order`` (``'alpha'``, ``'length'``): how the interfaces should be ordered; + defaults to ``'alpha'`` + +* **php_unit_construct** [@Symfony:risky, @PhpCsFixer:risky] + + PHPUnit assertion method calls like ``->assertSame(true, $foo)`` should be + written with dedicated method like ``->assertTrue($foo)``. + + *Risky rule: fixer could be risky if one is overriding PHPUnit's native methods.* + + Configuration options: + + - ``assertions`` (a subset of ``['assertSame', 'assertEquals', + 'assertNotEquals', 'assertNotSame']``): list of assertion methods to fix; + defaults to ``['assertEquals', 'assertSame', 'assertNotEquals', + 'assertNotSame']`` + +* **php_unit_dedicate_assert** [@PHPUnit30Migration:risky, @PHPUnit32Migration:risky, @PHPUnit35Migration:risky, @PHPUnit43Migration:risky, @PHPUnit48Migration:risky, @PHPUnit50Migration:risky, @PHPUnit52Migration:risky, @PHPUnit54Migration:risky, @PHPUnit55Migration:risky, @PHPUnit56Migration:risky, @PHPUnit57Migration:risky, @PHPUnit60Migration:risky, @PHPUnit75Migration:risky] + + PHPUnit assertions like ``assertInternalType``, ``assertFileExists``, should + be used over ``assertTrue``. + + *Risky rule: fixer could be risky if one is overriding PHPUnit's native methods.* + + Configuration options: + + - ``functions`` (a subset of ``['array_key_exists', 'empty', 'file_exists', + 'is_array', 'is_bool', 'is_callable', 'is_double', 'is_float', + 'is_infinite', 'is_int', 'is_integer', 'is_long', 'is_nan', 'is_null', + 'is_numeric', 'is_object', 'is_real', 'is_resource', 'is_scalar', + 'is_string']``, ``null``): list of assertions to fix (overrides ``target``); + defaults to ``null``. DEPRECATED: use option ``target`` instead + - ``target`` (``'3.0'``, ``'3.5'``, ``'5.0'``, ``'5.6'``, ``'newest'``): target version of + PHPUnit; defaults to ``'5.0'`` + +* **php_unit_dedicate_assert_internal_type** [@PHPUnit75Migration:risky] + + PHPUnit assertions like ``assertIsArray`` should be used over + ``assertInternalType``. + + *Risky rule: risky when PHPUnit methods are overridden or when project has PHPUnit incompatibilities.* + + Configuration options: + + - ``target`` (``'7.5'``, ``'newest'``): target version of PHPUnit; defaults to + ``'newest'`` + +* **php_unit_expectation** [@PHPUnit52Migration:risky, @PHPUnit54Migration:risky, @PHPUnit55Migration:risky, @PHPUnit56Migration:risky, @PHPUnit57Migration:risky, @PHPUnit60Migration:risky, @PHPUnit75Migration:risky] + + Usages of ``->setExpectedException*`` methods MUST be replaced by + ``->expectException*`` methods. + + *Risky rule: risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.* + + Configuration options: + + - ``target`` (``'5.2'``, ``'5.6'``, ``'newest'``): target version of PHPUnit; defaults to + ``'newest'`` + +* **php_unit_fqcn_annotation** [@Symfony, @PhpCsFixer] + + PHPUnit annotations should be a FQCNs including a root namespace. + +* **php_unit_internal_class** [@PhpCsFixer] + + All PHPUnit test classes should be marked as internal. + + Configuration options: + + - ``types`` (a subset of ``['normal', 'final', 'abstract']``): what types of + classes to mark as internal; defaults to ``['normal', 'final']`` + +* **php_unit_method_casing** [@PhpCsFixer] + + Enforce camel (or snake) case for PHPUnit test methods, following + configuration. + + Configuration options: + + - ``case`` (``'camel_case'``, ``'snake_case'``): apply camel or snake case to test + methods; defaults to ``'camel_case'`` + +* **php_unit_mock** [@PHPUnit54Migration:risky, @PHPUnit55Migration:risky, @PHPUnit56Migration:risky, @PHPUnit57Migration:risky, @PHPUnit60Migration:risky, @PHPUnit75Migration:risky] + + Usages of ``->getMock`` and + ``->getMockWithoutInvokingTheOriginalConstructor`` methods MUST be + replaced by ``->createMock`` or ``->createPartialMock`` methods. + + *Risky rule: risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.* + + Configuration options: + + - ``target`` (``'5.4'``, ``'5.5'``, ``'newest'``): target version of PHPUnit; defaults to + ``'newest'`` + +* **php_unit_mock_short_will_return** [@Symfony:risky, @PhpCsFixer:risky] + + Usage of PHPUnit's mock e.g. ``->will($this->returnValue(..))`` must be + replaced by its shorter equivalent such as ``->willReturn(...)``. + + *Risky rule: risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.* + +* **php_unit_namespaced** [@PHPUnit48Migration:risky, @PHPUnit50Migration:risky, @PHPUnit52Migration:risky, @PHPUnit54Migration:risky, @PHPUnit55Migration:risky, @PHPUnit56Migration:risky, @PHPUnit57Migration:risky, @PHPUnit60Migration:risky, @PHPUnit75Migration:risky] + + PHPUnit classes MUST be used in namespaced version, e.g. + ``\PHPUnit\Framework\TestCase`` instead of ``\PHPUnit_Framework_TestCase``. + + *Risky rule: risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.* + + Configuration options: + + - ``target`` (``'4.8'``, ``'5.7'``, ``'6.0'``, ``'newest'``): target version of PHPUnit; + defaults to ``'newest'`` + +* **php_unit_no_expectation_annotation** [@PHPUnit32Migration:risky, @PHPUnit35Migration:risky, @PHPUnit43Migration:risky, @PHPUnit48Migration:risky, @PHPUnit50Migration:risky, @PHPUnit52Migration:risky, @PHPUnit54Migration:risky, @PHPUnit55Migration:risky, @PHPUnit56Migration:risky, @PHPUnit57Migration:risky, @PHPUnit60Migration:risky, @PHPUnit75Migration:risky] + + Usages of ``@expectedException*`` annotations MUST be replaced by + ``->setExpectedException*`` methods. + + *Risky rule: risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.* + + Configuration options: + + - ``target`` (``'3.2'``, ``'4.3'``, ``'newest'``): target version of PHPUnit; defaults to + ``'newest'`` + - ``use_class_const`` (``bool``): use ::class notation; defaults to ``true`` + +* **php_unit_ordered_covers** [@PhpCsFixer] + + Order ``@covers`` annotation of PHPUnit tests. + +* **php_unit_set_up_tear_down_visibility** [@PhpCsFixer:risky] + + Changes the visibility of the ``setUp()`` and ``tearDown()`` functions of + PHPUnit to ``protected``, to match the PHPUnit TestCase. + + *Risky rule: this fixer may change functions named ``setUp()`` or ``tearDown()`` outside of PHPUnit tests, when a class is wrongly seen as a PHPUnit test.* + +* **php_unit_size_class** + + All PHPUnit test cases should have ``@small``, ``@medium`` or ``@large`` + annotation to enable run time limits. + + Configuration options: + + - ``group`` (``'large'``, ``'medium'``, ``'small'``): define a specific group to be used + in case no group is already in use; defaults to ``'small'`` + +* **php_unit_strict** [@PhpCsFixer:risky] + + PHPUnit methods like ``assertSame`` should be used instead of + ``assertEquals``. + + *Risky rule: risky when any of the functions are overridden or when testing object equality.* + + Configuration options: + + - ``assertions`` (a subset of ``['assertAttributeEquals', + 'assertAttributeNotEquals', 'assertEquals', 'assertNotEquals']``): list + of assertion methods to fix; defaults to ``['assertAttributeEquals', + 'assertAttributeNotEquals', 'assertEquals', 'assertNotEquals']`` + +* **php_unit_test_annotation** [@PhpCsFixer:risky] + + Adds or removes @test annotations from tests, following configuration. + + *Risky rule: this fixer may change the name of your tests, and could cause incompatibility with abstract classes or interfaces.* + + Configuration options: + + - ``case`` (``'camel'``, ``'snake'``): whether to camel or snake case when adding the + test prefix; defaults to ``'camel'``. DEPRECATED: use + ``php_unit_method_casing`` fixer instead + - ``style`` (``'annotation'``, ``'prefix'``): whether to use the @test annotation or + not; defaults to ``'prefix'`` + +* **php_unit_test_case_static_method_calls** [@PhpCsFixer:risky] + + Calls to ``PHPUnit\Framework\TestCase`` static methods must all be of the + same type, either ``$this->``, ``self::`` or ``static::``. + + *Risky rule: risky when PHPUnit methods are overridden or not accessible, or when project has PHPUnit incompatibilities.* + + Configuration options: + + - ``call_type`` (``'self'``, ``'static'``, ``'this'``): the call type to use for referring + to PHPUnit methods; defaults to ``'static'`` + - ``methods`` (``array``): dictionary of ``method`` => ``call_type`` values that + differ from the default strategy; defaults to ``[]`` + +* **php_unit_test_class_requires_covers** [@PhpCsFixer] + + Adds a default ``@coversNothing`` annotation to PHPUnit test classes that + have no ``@covers*`` annotation. + +* **phpdoc_add_missing_param_annotation** [@PhpCsFixer] + + PHPDoc should contain ``@param`` for all params. + + Configuration options: + + - ``only_untyped`` (``bool``): whether to add missing ``@param`` annotations for + untyped parameters only; defaults to ``true`` + +* **phpdoc_align** [@Symfony, @PhpCsFixer] + + All items of the given phpdoc tags must be either left-aligned or (by + default) aligned vertically. + + Configuration options: + + - ``align`` (``'left'``, ``'vertical'``): align comments; defaults to ``'vertical'`` + - ``tags`` (a subset of ``['param', 'property', 'property-read', + 'property-write', 'return', 'throws', 'type', 'var', 'method']``): the + tags that should be aligned; defaults to ``['param', 'return', 'throws', + 'type', 'var']`` + +* **phpdoc_annotation_without_dot** [@Symfony, @PhpCsFixer] + + PHPDoc annotation descriptions should not be a sentence. + +* **phpdoc_indent** [@Symfony, @PhpCsFixer] + + Docblocks should have the same indentation as the documented subject. + +* **phpdoc_inline_tag** [@Symfony, @PhpCsFixer] + + Fix PHPDoc inline tags, make ``@inheritdoc`` always inline. + +* **phpdoc_line_span** + + Changes doc blocks from single to multi line, or reversed. Works for + class constants, properties and methods only. + + Configuration options: + + - ``const`` (``'multi'``, ``'single'``): whether const blocks should be single or + multi line; defaults to ``'multi'`` + - ``method`` (``'multi'``, ``'single'``): whether method doc blocks should be single + or multi line; defaults to ``'multi'`` + - ``property`` (``'multi'``, ``'single'``): whether property doc blocks should be + single or multi line; defaults to ``'multi'`` + +* **phpdoc_no_access** [@Symfony, @PhpCsFixer] + + ``@access`` annotations should be omitted from PHPDoc. + +* **phpdoc_no_alias_tag** [@Symfony, @PhpCsFixer] + + No alias PHPDoc tags should be used. + + Configuration options: + + - ``replacements`` (``array``): mapping between replaced annotations with new + ones; defaults to ``['property-read' => 'property', 'property-write' => + 'property', 'type' => 'var', 'link' => 'see']`` + +* **phpdoc_no_empty_return** [@PhpCsFixer] + + ``@return void`` and ``@return null`` annotations should be omitted from + PHPDoc. + +* **phpdoc_no_package** [@Symfony, @PhpCsFixer] + + ``@package`` and ``@subpackage`` annotations should be omitted from PHPDoc. + +* **phpdoc_no_useless_inheritdoc** [@Symfony, @PhpCsFixer] + + Classy that does not inherit must not have ``@inheritdoc`` tags. + +* **phpdoc_order** [@PhpCsFixer] + + Annotations in PHPDoc should be ordered so that ``@param`` annotations + come first, then ``@throws`` annotations, then ``@return`` annotations. + +* **phpdoc_return_self_reference** [@Symfony, @PhpCsFixer] + + The type of ``@return`` annotations of methods returning a reference to + itself must the configured one. + + Configuration options: + + - ``replacements`` (``array``): mapping between replaced return types with new + ones; defaults to ``['this' => '$this', '@this' => '$this', '$self' => + 'self', '@self' => 'self', '$static' => 'static', '@static' => + 'static']`` + +* **phpdoc_scalar** [@Symfony, @PhpCsFixer] + + Scalar types should always be written in the same form. ``int`` not + ``integer``, ``bool`` not ``boolean``, ``float`` not ``real`` or ``double``. + + Configuration options: + + - ``types`` (a subset of ``['boolean', 'callback', 'double', 'integer', 'real', + 'str']``): a map of types to fix; defaults to ``['boolean', 'double', + 'integer', 'real', 'str']`` + +* **phpdoc_separation** [@Symfony, @PhpCsFixer] + + Annotations in PHPDoc should be grouped together so that annotations of + the same type immediately follow each other, and annotations of a + different type are separated by a single blank line. + +* **phpdoc_single_line_var_spacing** [@Symfony, @PhpCsFixer] + + Single line ``@var`` PHPDoc should have proper spacing. + +* **phpdoc_summary** [@Symfony, @PhpCsFixer] + + PHPDoc summary should end in either a full stop, exclamation mark, or + question mark. + +* **phpdoc_to_comment** [@Symfony, @PhpCsFixer] + + Docblocks should only be used on structural elements. + +* **phpdoc_to_param_type** + + EXPERIMENTAL: Takes ``@param`` annotations of non-mixed types and adjusts + accordingly the function signature. Requires PHP >= 7.0. + + *Risky rule: [1] This rule is EXPERIMENTAL and is not covered with backward compatibility promise. [2] ``@param`` annotation is mandatory for the fixer to make changes, signatures of methods without it (no docblock, inheritdocs) will not be fixed. [3] Manual actions are required if inherited signatures are not properly documented.* + + Configuration options: + + - ``scalar_types`` (``bool``): fix also scalar types; may have unexpected + behaviour due to PHP bad type coercion system; defaults to ``true`` + +* **phpdoc_to_return_type** + + EXPERIMENTAL: Takes ``@return`` annotation of non-mixed types and adjusts + accordingly the function signature. Requires PHP >= 7.0. + + *Risky rule: [1] This rule is EXPERIMENTAL and is not covered with backward compatibility promise. [2] ``@return`` annotation is mandatory for the fixer to make changes, signatures of methods without it (no docblock, inheritdocs) will not be fixed. [3] Manual actions are required if inherited signatures are not properly documented. [4] ``@inheritdocs`` support is under construction.* + + Configuration options: + + - ``scalar_types`` (``bool``): fix also scalar types; may have unexpected + behaviour due to PHP bad type coercion system; defaults to ``true`` + +* **phpdoc_trim** [@Symfony, @PhpCsFixer] + + PHPDoc should start and end with content, excluding the very first and + last line of the docblocks. + +* **phpdoc_trim_consecutive_blank_line_separation** [@Symfony, @PhpCsFixer] + + Removes extra blank lines after summary and after description in PHPDoc. + +* **phpdoc_types** [@Symfony, @PhpCsFixer] + + The correct case must be used for standard PHP types in PHPDoc. + + Configuration options: + + - ``groups`` (a subset of ``['simple', 'alias', 'meta']``): type groups to fix; + defaults to ``['simple', 'alias', 'meta']`` + +* **phpdoc_types_order** [@Symfony, @PhpCsFixer] + + Sorts PHPDoc types. + + Configuration options: + + - ``null_adjustment`` (``'always_first'``, ``'always_last'``, ``'none'``): forces the + position of ``null`` (overrides ``sort_algorithm``); defaults to + ``'always_first'`` + - ``sort_algorithm`` (``'alpha'``, ``'none'``): the sorting algorithm to apply; + defaults to ``'alpha'`` + +* **phpdoc_var_annotation_correct_order** [@PhpCsFixer] + + ``@var`` and ``@type`` annotations must have type and name in the correct + order. + +* **phpdoc_var_without_name** [@Symfony, @PhpCsFixer] + + ``@var`` and ``@type`` annotations of classy properties should not contain + the name. + +* **pow_to_exponentiation** [@PHP56Migration:risky, @PHP70Migration:risky, @PHP71Migration:risky] + + Converts ``pow`` to the ``**`` operator. + + *Risky rule: risky when the function ``pow`` is overridden.* + +* **pre_increment** + + Pre incrementation/decrementation should be used if possible. + DEPRECATED: use ``increment_style`` instead. + +* **protected_to_private** [@PhpCsFixer] + + Converts ``protected`` variables and methods to ``private`` where possible. + +* **psr0** + + 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. + + *Risky rule: this fixer may change your class name, which will break the code that depends on the old name.* + + Configuration options: + + - ``dir`` (``string``): the directory where the project code is placed; defaults + to ``''`` + +* **psr4** [@Symfony:risky, @PhpCsFixer:risky] + + Class names should match the file name. + + *Risky rule: this fixer may change your class name, which will break the code that depends on the old name.* + +* **random_api_migration** [@PHP70Migration:risky, @PHP71Migration:risky] + + Replaces ``rand``, ``srand``, ``getrandmax`` functions calls with their ``mt_*`` + analogs. + + *Risky rule: risky when the configured functions are overridden.* + + Configuration options: + + - ``replacements`` (``array``): mapping between replaced functions with the new + ones; defaults to ``['getrandmax' => 'mt_getrandmax', 'rand' => + 'mt_rand', 'srand' => 'mt_srand']`` + +* **return_assignment** [@PhpCsFixer] + + Local, dynamic and directly referenced variables should not be assigned + and directly returned by a function or method. + +* **return_type_declaration** [@Symfony, @PhpCsFixer] + + There should be one or no space before colon, and one space after it in + return type declarations, according to configuration. + + Configuration options: + + - ``space_before`` (``'none'``, ``'one'``): spacing to apply before colon; defaults to + ``'none'`` + +* **self_accessor** [@Symfony:risky, @PhpCsFixer:risky] + + Inside class or interface element ``self`` should be preferred to the + class name itself. + + *Risky rule: risky when using dynamic calls like get_called_class() or late static binding.* + +* **self_static_accessor** + + Inside a ``final`` class or anonymous class ``self`` should be preferred to + ``static``. + +* **semicolon_after_instruction** [@Symfony, @PhpCsFixer] + + Instructions must be terminated with a semicolon. + +* **set_type_to_cast** [@Symfony:risky, @PhpCsFixer:risky] + + Cast shall be used, not ``settype``. + + *Risky rule: risky when the ``settype`` function is overridden or when used as the 2nd or 3rd expression in a ``for`` loop .* + +* **short_scalar_cast** [@Symfony, @PhpCsFixer] + + Cast ``(boolean)`` and ``(integer)`` should be written as ``(bool)`` and + ``(int)``, ``(double)`` and ``(real)`` as ``(float)``, ``(binary)`` as + ``(string)``. + +* **silenced_deprecation_error** + + Ensures deprecation notices are silenced. DEPRECATED: use + ``error_suppression`` instead. + + *Risky rule: silencing of deprecation errors might cause changes to code behaviour.* + +* **simple_to_complex_string_variable** [@PhpCsFixer] + + Converts explicit variables in double-quoted strings and heredoc syntax + from simple to complex format (``${`` to ``{$``). + +* **simplified_null_return** + + A return statement wishing to return ``void`` should not return ``null``. + +* **single_blank_line_at_eof** [@PSR2, @Symfony, @PhpCsFixer] + + A PHP file without end tag must always end with a single empty line + feed. + +* **single_blank_line_before_namespace** [@Symfony, @PhpCsFixer] + + There should be exactly one blank line before a namespace declaration. + +* **single_class_element_per_statement** [@PSR2, @Symfony, @PhpCsFixer] + + There MUST NOT be more than one property or constant declared per + statement. + + Configuration options: + + - ``elements`` (a subset of ``['const', 'property']``): list of strings which + element should be modified; defaults to ``['const', 'property']`` + +* **single_import_per_statement** [@PSR2, @Symfony, @PhpCsFixer] + + There MUST be one use keyword per declaration. + +* **single_line_after_imports** [@PSR2, @Symfony, @PhpCsFixer] + + Each namespace use MUST go on its own line and there MUST be one blank + line after the use statements block. + +* **single_line_comment_style** [@Symfony, @PhpCsFixer] + + Single-line comments and multi-line comments with only one line of + actual content should use the ``//`` syntax. + + Configuration options: + + - ``comment_types`` (a subset of ``['asterisk', 'hash']``): list of comment types + to fix; defaults to ``['asterisk', 'hash']`` + +* **single_line_throw** [@Symfony] + + Throwing exception must be done in single line. + +* **single_quote** [@Symfony, @PhpCsFixer] + + Convert double quotes to single quotes for simple strings. + + Configuration options: + + - ``strings_containing_single_quote_chars`` (``bool``): whether to fix + double-quoted strings that contains single-quotes; defaults to ``false`` + +* **single_trait_insert_per_statement** [@Symfony, @PhpCsFixer] + + Each trait ``use`` must be done as single statement. + +* **space_after_semicolon** [@Symfony, @PhpCsFixer] + + Fix whitespace after a semicolon. + + Configuration options: + + - ``remove_in_empty_for_expressions`` (``bool``): whether spaces should be removed + for empty ``for`` expressions; defaults to ``false`` + +* **standardize_increment** [@Symfony, @PhpCsFixer] + + Increment and decrement operators should be used if possible. + +* **standardize_not_equals** [@Symfony, @PhpCsFixer] + + Replace all ``<>`` with ``!=``. + +* **static_lambda** + + Lambdas not (indirect) referencing ``$this`` must be declared ``static``. + + *Risky rule: risky when using ``->bindTo`` on lambdas without referencing to ``$this``.* + +* **strict_comparison** [@PhpCsFixer:risky] + + Comparisons should be strict. + + *Risky rule: changing comparisons to strict might change code behavior.* + +* **strict_param** [@PhpCsFixer:risky] + + Functions should be used with ``$strict`` param set to ``true``. + + *Risky rule: risky when the fixed function is overridden or if the code relies on non-strict usage.* + +* **string_line_ending** [@PhpCsFixer:risky] + + All multi-line strings must use correct line ending. + + *Risky rule: changing the line endings of multi-line strings might affect string comparisons and outputs.* + +* **switch_case_semicolon_to_colon** [@PSR2, @Symfony, @PhpCsFixer] + + A case should be followed by a colon and not a semicolon. + +* **switch_case_space** [@PSR2, @Symfony, @PhpCsFixer] + + Removes extra spaces between colon and case value. + +* **ternary_operator_spaces** [@Symfony, @PhpCsFixer] + + Standardize spaces around ternary operator. + +* **ternary_to_null_coalescing** [@PHP70Migration, @PHP71Migration, @PHP73Migration] + + Use ``null`` coalescing operator ``??`` where possible. Requires PHP >= 7.0. + +* **trailing_comma_in_multiline_array** [@Symfony, @PhpCsFixer] + + PHP multi-line arrays should have a trailing comma. + + Configuration options: + + - ``after_heredoc`` (``bool``): whether a trailing comma should also be placed + after heredoc end; defaults to ``false`` + +* **trim_array_spaces** [@Symfony, @PhpCsFixer] + + Arrays should be formatted like function/method arguments, without + leading or trailing single line space. + +* **unary_operator_spaces** [@Symfony, @PhpCsFixer] + + Unary operators should be placed adjacent to their operands. + +* **visibility_required** [@PSR2, @Symfony, @PhpCsFixer, @PHP71Migration, @PHP73Migration] + + 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. + + Configuration options: + + - ``elements`` (a subset of ``['property', 'method', 'const']``): the structural + elements to fix (PHP >= 7.1 required for ``const``); defaults to + ``['property', 'method']`` + +* **void_return** [@PHP71Migration:risky] + + Add ``void`` return type to functions with missing or empty return + statements, but priority is given to ``@return`` annotations. Requires + PHP >= 7.1. + + *Risky rule: modifies the signature of functions.* + +* **whitespace_after_comma_in_array** [@Symfony, @PhpCsFixer] + + In array declaration, there MUST be a whitespace after each comma. + +* **yoda_style** [@Symfony, @PhpCsFixer] + + Write conditions in Yoda style (``true``), non-Yoda style (``false``) or + ignore those conditions (``null``) based on configuration. + + Configuration options: + + - ``always_move_variable`` (``bool``): whether variables should always be on non + assignable side when applying Yoda style; defaults to ``false`` + - ``equal`` (``bool``, ``null``): style for equal (``==``, ``!=``) statements; defaults to + ``true`` + - ``identical`` (``bool``, ``null``): style for identical (``===``, ``!==``) statements; + defaults to ``true`` + - ``less_and_greater`` (``bool``, ``null``): style for less and greater than (``<``, + ``<=``, ``>``, ``>=``) statements; defaults to ``null`` + + +The ``--dry-run`` option displays the files that need to be +fixed but without actually modifying them: + +.. code-block:: bash + + $ php php-cs-fixer.phar fix /path/to/code --dry-run + +Config file +----------- + +Instead of using command line options to customize the rule, you can save the +project configuration in a ``.php_cs.dist`` file in the root directory of your project. +The file must return an instance of `PhpCsFixer\\ConfigInterface `_ +which lets you configure the rules, the files and directories that +need to be analyzed. You may also create ``.php_cs`` file, which is +the local configuration that will be used instead of the project configuration. It +is a good practice to add that file into your ``.gitignore`` file. +With the ``--config`` option you can specify the path to the +``.php_cs`` file. + +The example below will add two rules to the default list of PSR2 set rules: + +.. code-block:: php + + exclude('somedir') + ->notPath('src/Symfony/Component/Translation/Tests/fixtures/resources.php') + ->in(__DIR__) + ; + + return PhpCsFixer\Config::create() + ->setRules([ + '@PSR2' => true, + 'strict_param' => true, + 'array_syntax' => ['syntax' => 'short'], + ]) + ->setFinder($finder) + ; + +**NOTE**: ``exclude`` will work only for directories, so if you need to exclude file, try ``notPath``. +Both ``exclude`` and ``notPath`` methods accept only relative paths to the ones defined with the ``in`` method. + +See `Symfony\\Finder `_ +online documentation for other `Finder` methods. + +You may also use a blacklist for the rules instead of the above shown whitelist approach. +The following example shows how to use all ``Symfony`` rules but the ``full_opening_tag`` rule. + +.. code-block:: php + + exclude('somedir') + ->in(__DIR__) + ; + + return PhpCsFixer\Config::create() + ->setRules([ + '@Symfony' => true, + 'full_opening_tag' => false, + ]) + ->setFinder($finder) + ; + +You may want to use non-linux whitespaces in your project. Then you need to +configure them in your config file. + +.. code-block:: php + + setIndent("\t") + ->setLineEnding("\r\n") + ; + +By using ``--using-cache`` option with ``yes`` or ``no`` you can set if the caching +mechanism should be used. + +Caching +------- + +The caching mechanism is enabled by default. This will speed up further runs by +fixing only files that were modified since the last run. The tool will fix all +files if the tool version has changed or the list of rules has changed. +Cache is supported only for tool downloaded as phar file or installed via +composer. + +Cache can be disabled via ``--using-cache`` option or config file: + +.. code-block:: php + + setUsingCache(false) + ; + +Cache file can be specified via ``--cache-file`` option or config file: + +.. code-block:: php + + setCacheFile(__DIR__.'/.php_cs.cache') + ; + +Using PHP CS Fixer on CI +------------------------ + +Require ``friendsofphp/php-cs-fixer`` as a ``dev`` dependency: + +.. code-block:: bash + + $ ./composer.phar require --dev friendsofphp/php-cs-fixer + +Then, add the following command to your CI: + +.. code-block:: bash + + $ IFS=' + $ ' + $ CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRTUXB "${COMMIT_RANGE}") + $ if ! echo "${CHANGED_FILES}" | grep -qE "^(\\.php_cs(\\.dist)?|composer\\.lock)$"; then EXTRA_ARGS=$(printf -- '--path-mode=intersection\n--\n%s' "${CHANGED_FILES}"); else EXTRA_ARGS=''; fi + $ vendor/bin/php-cs-fixer fix --config=.php_cs.dist -v --dry-run --stop-on-violation --using-cache=no ${EXTRA_ARGS} + +Where ``$COMMIT_RANGE`` is your range of commits, e.g. ``$TRAVIS_COMMIT_RANGE`` or ``HEAD~..HEAD``. + +Exit code +--------- + +Exit code 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. + +(Applies to exit code of the ``fix`` command only) + +Helpers +------- + +Dedicated plugins exist for: + +* `Atom`_ +* `NetBeans`_ +* `PhpStorm`_ +* `Sublime Text`_ +* `Vim`_ +* `VS Code`_ + +Contribute +---------- + +The tool comes with quite a few built-in fixers, but everyone is more than +welcome to `contribute`_ more of them. + +Fixers +~~~~~~ + +A *fixer* is a class that tries to fix one CS issue (a ``Fixer`` class must +implement ``FixerInterface``). + +Configs +~~~~~~~ + +A *config* knows about the CS rules and the files and directories that must be +scanned by the tool when run in the directory of your project. It is useful for +projects that follow a well-known directory structures (like for Symfony +projects for instance). + +.. _php-cs-fixer.phar: https://cs.symfony.com/download/php-cs-fixer-v2.phar +.. _Atom: https://github.com/Glavin001/atom-beautify +.. _NetBeans: http://plugins.netbeans.org/plugin/49042/php-cs-fixer +.. _PhpStorm: https://medium.com/@valeryan/how-to-configure-phpstorm-to-use-php-cs-fixer-1844991e521f +.. _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 +.. _contribute: https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/CONTRIBUTING.md diff --git a/lib/composer/friendsofphp/php-cs-fixer/UPGRADE.md b/lib/composer/friendsofphp/php-cs-fixer/UPGRADE.md new file mode 100644 index 0000000000000000000000000000000000000000..435d016f9a022e6ee0e2939fcae080b90ef608fc --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/UPGRADE.md @@ -0,0 +1,184 @@ +UPGRADE GUIDE FROM 1.x to 2.0 +============================= + +This is guide for upgrade from version 1.x to 2.0 for using the CLI tool. + +Rules and sets +-------------- +To configure which fixers should be used you must now set rules and sets instead of fixers and level. This affects both configuration file and CLI arguments. + +Default ruleset was changed from Symfony standard to more generic PSR2. You can still use Symfony standard, which in fact extends PSR2. + +The term of risky fixers was introduced. Risky fixer is a fixer that may change the meaning of code (like `StrictComparisonFixer` fixer, which will change `==` into `===`). No rules that are followed by risky fixers are run by default. You need to explicitly permit risky fixers to run them. + +Default configuration changes +---------------------------- +By default, PSR2 rules are used instead of Symfony rules. +Files that will be fixed are php/phpt/twig instead of php/twig/xml/yml. +Finally, the caching mechanism is enabled by default. + +CLI options +----------- + +| 1.x | 2.0 | Description | Note | +| --------------- | --------------- | ------------------------------------------------------------------------------ | ------------------------------- | +|     | --allow-risky | Are risky fixers allowed                                                 | | +| | --cache-file | The path to the cache file | option was added | +| --config | | Config class codename | option was removed | +| --config-file | --config | The path to a .php_cs file | option was renamed | +| --diff | --diff | Show diff | | +| --dry-run | --dry-run | Run in dry-run mode | | +| --fixers | | Coding standard fixers | option was removed, see --rules | +| --format | --format | Choose format | | +| --level | | Coding standard level | option was removed, see --rules | +| | --path-mode | Should the finder from config be
overridden or intersected with `path` arg | option was added | +| | --rules | Rules to be used | option was added | +| | --using-cache | Does cache should be used | option was added | + + +CLI argument +------------ + +On 2.x line `path` argument is an array, so you may pass multiple paths. + +Intersection path mode makes the `path` argument a mask for finder you have defined in your configuration file. +Only files pointed by both finder and CLI `path` argument will be fixed. + +Exit codes +---------- + +Exit codes for `fix` command have been changed and are build using the following bit flags: + +| 1.x bit | 2.0 bit | Description | Note | +| -------:| -------:| ----------------------------------------------------------- | ---------------------------------------------------------------- | +| 0 | 0 | OK | | +| 1 | 1 | General error (or PHP/HHVM minimal requirement not matched) | no longer used for other states, never combined with other flags | +| | 4 | Some files have invalid syntax | flag was added, works only in dry-run mode | +| | 8 | Some files need fixing | flag was added, works only in dry-run mode | +| 16 | 16 | Configuration error of the application | | +| 32 | 32 | Configuration error of a Fixer | | +| | 64 | Exception within the application | flag was added | + +Namespace +--------- +`Symfony\CS` namespace was renamed into `PhpCsFixer`. + +Config file +----------- +From now you can create new configuration file: `.php_cs.dist`. This file is used if no `.php_cs` file was found. It is recommended to create `.php_cs.dist` file attached in your repository and add `.php_cs` file to `.gitignore` for allowing your contributors to have theirs own configuration file. + +Config and Finder classes +------------------------- +All off `Symfony\CS\Config\*` and `Symfony\CS\Finder\*` classes have been removed, instead use `PhpCsFixer\Config` and `PhpCsFixer\Finder`. + +For that reason you can not set config class by `--config` CLI argument, from now it is used to set configuration file. Therefor the `--config-file` CLI argument is no longer available. + +Renamed rules +------------- + +Old name | New name | Note +-------- | -------- | ---- +align_double_arrow | binary_operator_spaces | use configuration ['align_double_arrow' => true] +align_equals | binary_operator_spaces | use configuration ['align_equals' => true] +array_element_no_space_before_comma | no_whitespace_before_comma_in_array +array_element_white_space_after_comma | whitespace_after_comma_in_array +blankline_after_open_tag | blank_line_after_opening_tag +concat_with_spaces | concat_space | use configuration ['spacing' => 'one'] +concat_without_spaces | concat_space | use configuration ['spacing' => 'none'] +double_arrow_multiline_whitespaces | no_multiline_whitespace_around_double_arrow +duplicate_semicolon | no_empty_statement | new one fixes more cases +empty_return | simplified_null_return +echo_to_print | no_mixed_echo_print | use configuration ['use' => 'print'] +eof_ending | single_blank_line_at_eof +extra_empty_lines | no_extra_consecutive_blank_lines +function_call_space | no_spaces_after_function_name +general_phpdoc_annotation_rename | phpdoc_no_alias_tag | use configuration ['property-read' => 'property', 'property-write' => 'property'] +indentation | indentation_type +join_function | no_alias_functions | new one fixes more aliases +line_after_namespace | blank_line_after_namespace +linefeed | line_ending | whitespaces type aware +list_commas | no_trailing_comma_in_list_call +logical_not_operators_with_spaces | not_operator_with_space +logical_not_operators_with_successor_space | not_operator_with_successor_space +long_array_syntax | array_syntax | use configuration ['syntax' => 'long'] +method_argument_default_value | no_unreachable_default_argument_value +multiline_array_trailing_comma | trailing_comma_in_multiline_array +multiline_spaces_before_semicolon | no_multiline_whitespace_before_semicolons +multiple_use | single_import_per_statement +namespace_no_leading_whitespace | no_leading_namespace_whitespace +newline_after_open_tag | linebreak_after_opening_tag +no_empty_lines_after_phpdocs | no_blank_lines_after_phpdoc +object_operator | object_operator_without_whitespace +operators_spaces | binary_operator_spaces +ordered_use | ordered_imports +parenthesis | no_spaces_inside_parenthesis +php4_constructor | no_php4_constructor +php_closing_tag | no_closing_tag +phpdoc_params | phpdoc_align +phpdoc_property | phpdoc_no_alias_tag | use configuration ['type' => 'var'] +phpdoc_short_description | phpdoc_summary +phpdoc_type_to_var | phpdoc_no_alias_tag | use configuration ['type' => 'var'] +phpdoc_var_to_type | phpdoc_no_alias_tag | use configuration ['var' => 'type'] +print_to_echo | no_mixed_echo_print | use configuration ['use' => 'echo'] +remove_leading_slash_use | no_leading_import_slash +remove_lines_between_uses | no_extra_consecutive_blank_lines | use configuration ['use'] +return | blank_line_before_return +short_array_syntax | array_syntax | use configuration ['syntax' => 'short'] +short_bool_cast | no_short_bool_cast +short_echo_tag | no_short_echo_tag +short_tag | full_opening_tag +single_array_no_trailing_comma | no_trailing_comma_in_singleline_array +spaces_after_semicolon | space_after_semicolon +spaces_before_semicolon | no_singleline_whitespace_before_semicolons +spaces_cast | cast_spaces +standardize_not_equal | standardize_not_equals +strict | strict_comparison +ternary_spaces | ternary_operator_spaces +trailing_spaces | no_trailing_whitespace +unalign_double_arrow | binary_operator_spaces | use configuration ['align_double_arrow' => false] +unalign_equals | binary_operator_spaces | use configuration ['align_equals' => false] +unary_operators_spaces | unary_operator_spaces +unneeded_control_parentheses | no_unneeded_control_parentheses +unused_use | no_unused_imports +visibility | visibility_required +whitespacy_lines | no_whitespace_in_blank_line + +Changes to Fixers +----------------- + +Fixer | Note +----- | ---- +psr0 | Fixer no longer takes base dir from `ConfigInterface::getDir`, instead you may configure the fixer with `['dir' => 'my/path']`. + +Custom fixers +------------- + +If you have registered custom fixers in your config file `*.php_cs` using `addCustomFixer()` method... +``` +fixers([ + 'blankline_after_open_tag', + // ... + ]) + ->addCustomFixer(new ShopSys\CodingStandards\CsFixer\MissingButtonTypeFixer()) + ->addCustomFixer(new ShopSys\CodingStandards\CsFixer\OrmJoinColumnRequireNullableFixer()); +``` +...now you have to use `registerCustomFixers()` method instead and enable the custom fixers by their names in the `setRules()` method: +``` +registerCustomFixers([ + new ShopSys\CodingStandards\CsFixer\MissingButtonTypeFixer(), + new ShopSys\CodingStandards\CsFixer\OrmJoinColumnRequireNullableFixer(), + ]) + ->setRules([ + 'blankline_after_open_tag', + 'Shopsys/missing_button_type' => true, + 'Shopsys/orm_join_column_require_nullable' => true, + // ... + ]); + +``` diff --git a/lib/composer/friendsofphp/php-cs-fixer/ci-integration.sh b/lib/composer/friendsofphp/php-cs-fixer/ci-integration.sh new file mode 100644 index 0000000000000000000000000000000000000000..489c5d26a9f370a877f5a64b3ce25d0c83d1c9ea --- /dev/null +++ b/lib/composer/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(\\.dist)?|composer\\.lock)$"; then EXTRA_ARGS=$(printf -- '--path-mode=intersection\n--\n%s' "${CHANGED_FILES}"); else EXTRA_ARGS=''; fi +vendor/bin/php-cs-fixer fix --config=.php_cs.dist -v --dry-run --stop-on-violation --using-cache=no ${EXTRA_ARGS} diff --git a/lib/composer/friendsofphp/php-cs-fixer/composer.json b/lib/composer/friendsofphp/php-cs-fixer/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..ee88eef79e7fae06c4bdf870767bf0e55f9fb381 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/composer.json @@ -0,0 +1,83 @@ +{ + "name": "friendsofphp/php-cs-fixer", + "type": "application", + "description": "A tool to automatically fix PHP code style", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "ext-json": "*", + "ext-tokenizer": "*", + "composer/semver": "^1.4", + "composer/xdebug-handler": "^1.2", + "doctrine/annotations": "^1.2", + "php-cs-fixer/diff": "^1.3", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", + "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", + "symfony/finder": "^3.0 || ^4.0 || ^5.0", + "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", + "symfony/polyfill-php70": "^1.0", + "symfony/polyfill-php72": "^1.4", + "symfony/process": "^3.0 || ^4.0 || ^5.0", + "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", + "justinrainbow/json-schema": "^5.0", + "keradus/cli-executor": "^1.2", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.1", + "php-cs-fixer/accessible-object": "^1.0", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.1", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.1", + "phpunitgoodpractices/traits": "^1.8", + "symfony/phpunit-bridge": "^4.3 || ^5.0", + "symfony/yaml": "^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters in cache signature.", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", + "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + }, + "config": { + "sort-packages": true + }, + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "classmap": [ + "tests/Test/AbstractFixerTestCase.php", + "tests/Test/AbstractIntegrationCaseFactory.php", + "tests/Test/AbstractIntegrationTestCase.php", + "tests/Test/Assert/AssertTokensTrait.php", + "tests/Test/IntegrationCase.php", + "tests/Test/IntegrationCaseFactory.php", + "tests/Test/IntegrationCaseFactoryInterface.php", + "tests/Test/InternalIntegrationCaseFactory.php", + "tests/Test/IsIdenticalConstraint.php", + "tests/TestCase.php" + ] + }, + "autoload-dev": { + "psr-4": { + "PhpCsFixer\\Tests\\": "tests/" + } + }, + "bin": [ + "php-cs-fixer" + ] +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/doc/COOKBOOK-FIXERS.md b/lib/composer/friendsofphp/php-cs-fixer/doc/COOKBOOK-FIXERS.md new file mode 100644 index 0000000000000000000000000000000000000000..afc627aa90e4ca56ccd73d436a4fad3bb4aef780 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/doc/COOKBOOK-FIXERS.md @@ -0,0 +1,530 @@ +Cookbook - Making a new Fixer for PHP CS Fixer +============================================== + +You want to make a new fixer to PHP CS Fixer and do not know how to +start. Follow this document and you will be able to do it. + +## Background + +In order to be able to create a new fixer, you need some background. +PHP CS Fixer is a transcompiler which takes valid PHP code and pretty +print valid PHP code. It does all transformations in multiple passes, +a.k.a., multi-pass compiler. + +Therefore, a new fixer is meant to be ideally +[idempotent](https://en.wikipedia.org/wiki/Idempotence#Computer_science_meaning), +or at least atomic in its actions. More on this later. + +All contributions go through a code review process. Do not feel +discouraged - it is meant only to give more people more chance to +contribute, and to detect bugs ([Linus' +Law](https://en.wikipedia.org/wiki/Linus%27s_Law)). + +If possible, try to get acquainted with the public interface for the +[Tokens class](/src/Tokenizer/Tokens.php) +and [Token class](/src/Tokenizer/Token.php) +classes. + +## Assumptions + +* You are familiar with Test Driven Development. +* Forked FriendsOfPHP/PHP-CS-Fixer into your own GitHub Account. +* Cloned your forked repository locally. +* Installed the dependencies of PHP CS Fixer using [Composer](https://getcomposer.org/). +* You have read [`CONTRIBUTING.md`](/CONTRIBUTING.md). + +## Step by step + +For this step-by-step, we are going to create a simple fixer that +removes all comments of the code that are preceded by ';' (semicolon). + +We are calling it `remove_comments` (code name), or, +`RemoveCommentsFixer` (class name). + +### Step 1 - Creating files + +Create a new file in `src/Fixer/Comment/RemoveCommentsFixer.php`. +Put this content inside: +```php + + * Dariusz Rumiń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\Tokenizer\Tokens; + +/** + * @author Your name + */ +final class RemoveCommentsFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + // Return a definition of the fixer, it will be used in the README.rst. + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + // Check whether the collection is a candidate for fixing. + // Has to be ultra cheap to execute. + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + // Add the fixing logic of the fixer here. + } +} +``` + +Note how the class and file name match. Also keep in mind that all +fixers must implement `Fixer\FixerInterface`. In this case, the fixer is +inheriting from `AbstractFixer`, which fulfills the interface with some +default behavior. + +Now let us create the test file at +`tests/Fixer/Comment/RemoveCommentsFixerTest.php`. Put this content inside: + +```php + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Fixer\Comment; + +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @author Your name + * + * @internal + * + * @covers \PhpCsFixer\Fixer\Comment\RemoveCommentsFixer + */ +final class RemoveCommentsFixerTest extends AbstractFixerTestCase +{ + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFixCases + */ + public function testFix($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public function provideFixCases() + { + return []; + } +} +``` +### Step 2 - Using tests to define fixers behavior + +Now that the files are created, you can start writing test to define the +behavior of the fixer. You have to do it in two ways: first, ensuring +the fixer changes what it should be changing; second, ensuring that +fixer does not change what is not supposed to change. Thus: + +#### Keeping things as they are: +`tests/Fixer/Comment/RemoveCommentsFixerTest.php`@provideFixCases: +```php + ... + public function provideFixCases() + { + 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\Tests\Fixer\Comment; + +use PhpCsFixer\Tests\Fixer\AbstractFixerTestBase; + +/** + * @author Your name + * + * @internal + */ +final class RemoveCommentsFixerTest extends AbstractFixerTestBase +{ + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFixCases + */ + public function testFix($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public function provideFixCases() + { + return [ + [ + ' README.rst` + +Next, we must filter what type of tokens we want to fix. Here, we are interested in code that contains `T_COMMENT` tokens: +`src/Fixer/Comment/RemoveCommentsFixer.php`: +```php +final class RemoveCommentsFixer extends AbstractFixer +{ + ... + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_COMMENT); + } +} +``` + +For now, let us just make a fixer that applies no modification: +`src/Fixer/Comment/RemoveCommentsFixer.php`: +```php +class RemoveCommentsFixer extends AbstractFixer +{ + ... + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + // no action + } +} +``` + +Run `$ phpunit tests/Fixer/Comment/RemoveCommentsFixerTest.php`. +You are going to see that the tests fails. + +### Break +Now we have pretty much a cradle to work with. A file with a failing +test, and the fixer, that for now does not do anything. + +How do fixers work? In the PHP CS Fixer, they work by iterating through +pieces of codes (each being a Token), and inspecting what exists before +and after that bit and making a decision, usually: + + * Adding code. + * Modifying code. + * Deleting code. + * Ignoring code. + +In our case, we want to find all comments, and foreach (pun intended) +one of them check if they are preceded by a semicolon symbol. + +Now you need to do some reading, because all these symbols obey a list +defined by the PHP compiler. It is the ["List of Parser +Tokens"](https://php.net/manual/en/tokens.php). + +Internally, PHP CS Fixer transforms some of PHP native tokens into custom +tokens through the use of [Transfomers](/src/Tokenizer/Transformer), +they aim to help you reason about the changes you may want to do in the +fixers. + +So we can get to move forward, humor me in believing that comments have +one symbol name: `T_COMMENT`. + +### Step 3 - Implement your solution - continuation. + +We do not want all symbols to be analysed. Only `T_COMMENT`. So let us +iterate the token(s) we are interested in. +`src/Fixer/Comment/RemoveCommentsFixer.php`: +```php +final class RemoveCommentsFixer extends AbstractFixer +{ + ... + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_COMMENT)) { + continue; + } + + // need to figure out what to do here! + } + } +} +``` + +OK, now for each `T_COMMENT`, all we need to do is check if the previous +token is a semicolon. +`src/Fixer/Comment/RemoveCommentsFixer.php`: +```php +final class RemoveCommentsFixer extends AbstractFixer +{ + ... + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_COMMENT)) { + continue; + } + + $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevTokenIndex]; + + if ($prevToken->equals(';')) { + $tokens->clearAt($index); + } + } + } +} +``` + +So the fixer in the end looks like this: +```php + + * Dariusz Rumiń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\Tokenizer\Tokens; + +/** + * @author Your name + */ +final class RemoveCommentsFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Removes all comments of the code that are preceded by ";" (semicolon).', // Trailing dot is important. We thrive to use English grammar properly. + [ + new CodeSample( + 'isTokenKindFound(T_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) { + foreach($tokens as $index => $token){ + if (!$token->isGivenKind(T_COMMENT)) { + continue; + } + + $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevTokenIndex]; + + if ($prevToken->equals(';')) { + $tokens->clearAt($index); + } + } + } +} +``` + +### Step 4 - Format, Commit, PR. + +Note that so far, we have not coded adhering to PSR-1/2. This is done on +purpose. For every commit you make, you must use PHP CS Fixer to fix +itself. Thus, on the command line call: + +`$ php php-cs-fixer fix` + +This will fix all the coding style mistakes. + +After the final CS fix, you are ready to commit. Do it. + +Now, go to GitHub and open a Pull Request. + + +### Step 5 - Peer review: it is all about code and community building. + +Congratulations, you have made your first fixer. Be proud. Your work +will be reviewed carefully by PHP CS Fixer community. + +The review usually flows like this: + +1. People will check your code for common mistakes and logical +caveats. Usually, the person building a fixer is blind about some +behavior mistakes of fixers. Expect to write few more tests to cater for +the reviews. +2. People will discuss the relevance of your fixer. If it is +something that goes along with Symfony style standards, or PSR-1/PSR-2 +standards, they will ask you to add it to existing ruleset. +3. People will also discuss whether your fixer is idempotent or not. +If they understand that your fixer must always run before or after a +certain fixer, they will ask you to override a method named +`getPriority()`. Do not be afraid of asking the reviewer for help on how +to do it. +4. People may ask you to rebase your code to unify commits or to get +rid of merge commits. +5. Go to 1 until no actions are needed anymore. + +Your fixer will be incorporated in the next release. + +# Congratulations! You have done it. + + + +## Q&A + +#### Why is not my PR merged yet? + +PHP CS Fixer is used by many people, that expect it to be stable. So +sometimes, few PR are delayed a bit so to avoid cluttering at @dev +channel on composer. + +Other possibility is that reviewers are giving time to other members of +PHP CS Fixer community to partake on the review debates of your fixer. + +In any case, we care a lot about what you do and we want to see it being +part of the application as soon as possible. + +#### Why am I asked to use `getPrevMeaningfulToken()` instead of `getPrevNonWhitespace()`? + +The main difference is that `getPrevNonWhitespace()` ignores only +whitespaces (`T_WHITESPACE`), while `getPrevMeaningfulToken()` ignores +whitespaces and comments. And usually that is what you want. For +example: + +```php +$a->/*comment*/func(); +``` + +If you are inspecting `func()`, and you want to check whether this is +part of an object, if you use `getPrevNonWhitespace()` you are going to +get `/*comment*/`, which might belie your test. On the other hand, if +you use `getPrevMeaningfulToken()`, no matter if you have got a comment +or a whitespace, the returned token will always be `->`. diff --git a/lib/composer/friendsofphp/php-cs-fixer/doc/checkstyle.xsd b/lib/composer/friendsofphp/php-cs-fixer/doc/checkstyle.xsd new file mode 100644 index 0000000000000000000000000000000000000000..02249fa5de7d2f7ed63032642c9823cd0e2795fb --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/doc/checkstyle.xsd @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/composer/friendsofphp/php-cs-fixer/doc/junit-10.xsd b/lib/composer/friendsofphp/php-cs-fixer/doc/junit-10.xsd new file mode 100644 index 0000000000000000000000000000000000000000..1f41ea36a62eeb96a7b8b5b32c2afe7ab98d7b67 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/doc/junit-10.xsd @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/composer/friendsofphp/php-cs-fixer/doc/schema.json b/lib/composer/friendsofphp/php-cs-fixer/doc/schema.json new file mode 100644 index 0000000000000000000000000000000000000000..15db1e455f2cd16e18cfa86f4e0df95c10773f57 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/doc/schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "files": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "appliedFixers": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + } + } + }, + "required": [ + "name" + ] + } + }, + "time": { + "type": "object", + "properties": { + "total": { + "type": "number" + } + }, + "required": [ + "total" + ] + }, + "memory": { + "type": "number" + } + }, + "required": [ + "files", + "time", + "memory" + ] +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/doc/xml.xsd b/lib/composer/friendsofphp/php-cs-fixer/doc/xml.xsd new file mode 100644 index 0000000000000000000000000000000000000000..c7159a36f051aea978ef359321c9ec8d910ebbe9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/doc/xml.xsd @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/composer/friendsofphp/php-cs-fixer/php-cs-fixer b/lib/composer/friendsofphp/php-cs-fixer/php-cs-fixer new file mode 100755 index 0000000000000000000000000000000000000000..0a61d13333cf8d23b74715923df2727127a82eba --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/php-cs-fixer @@ -0,0 +1,99 @@ +#!/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. + */ + +if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + error_reporting(-1); +} + +if (defined('HHVM_VERSION_ID')) { + fwrite(STDERR, "HHVM is not supported.\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 { + exit(1); + } +} elseif (!defined('PHP_VERSION_ID') || \PHP_VERSION_ID < 50600 || \PHP_VERSION_ID >= 70500) { + fwrite(STDERR, "PHP needs to be a minimum version of PHP 5.6.0 and maximum version of PHP 7.4.*.\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 { + 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); + } + } +} +unset($extension); + +set_error_handler(static function ($severity, $message, $file, $line) { + if ($severity & error_reporting()) { + throw new ErrorException($message, 0, $severity, $file, $line); + } +}); + +$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; + + unset($possibleFiles, $possibleFile, $file); +} +unset($require); + +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', '--ansi'); +$xdebug->check(); +unset($xdebug); + +$application = new Application(); +$application->run(); + +__HALT_COMPILER(); diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/AbstractAlignFixerHelper.php b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractAlignFixerHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..a24881ee3cfc9a036af4c416db672b38beaf859e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractAlignFixerHelper.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\Tokenizer\Tokens; + +/** + * @author Carlos Cirello + * + * @internal + * + * @deprecated + */ +abstract class AbstractAlignFixerHelper +{ + /** + * @const Placeholder used as anchor for right alignment. + */ + const ALIGNABLE_PLACEHOLDER = "\x2 ALIGNABLE%d \x3"; + + /** + * Keep track of the deepest level ever achieved while + * parsing the code. Used later to replace alignment + * placeholders with spaces. + * + * @var int + */ + protected $deepestLevel = 0; + + public function fix(Tokens $tokens) + { + // 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 ALIGNABLE_PLACEHOLDER to its content. + // The placeholder will be resolved by `replacePlaceholder` 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; + + $this->injectAlignmentPlaceholders($tokensClone, 0, \count($tokens)); + $content = $this->replacePlaceholder($tokensClone); + + $tokens->setCode($content); + } + + /** + * Inject into the text placeholders of candidates of vertical alignment. + * + * @param int $startAt + * @param int $endAt + */ + abstract protected function injectAlignmentPlaceholders(Tokens $tokens, $startAt, $endAt); + + /** + * Look for group of placeholders, and provide vertical alignment. + * + * @return string + */ + protected function replacePlaceholder(Tokens $tokens) + { + $tmpCode = $tokens->generateCode(); + + for ($j = 0; $j <= $this->deepestLevel; ++$j) { + $placeholder = sprintf(self::ALIGNABLE_PLACEHOLDER, $j); + + if (false === strpos($tmpCode, $placeholder)) { + continue; + } + + $lines = explode("\n", $tmpCode); + $linesWithPlaceholder = []; + $blockSize = 0; + + $linesWithPlaceholder[$blockSize] = []; + + foreach ($lines as $index => $line) { + if (substr_count($line, $placeholder) > 0) { + $linesWithPlaceholder[$blockSize][] = $index; + } else { + ++$blockSize; + $linesWithPlaceholder[$blockSize] = []; + } + } + + foreach ($linesWithPlaceholder as $group) { + if (\count($group) < 1) { + continue; + } + + $rightmostSymbol = 0; + foreach ($group as $index) { + $rightmostSymbol = max($rightmostSymbol, strpos(utf8_decode($lines[$index]), $placeholder)); + } + + foreach ($group as $index) { + $line = $lines[$index]; + $currentSymbol = strpos(utf8_decode($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/lib/composer/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..93b11687e75bda7437b9ad0da1bb9c32d3a8c71a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.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; + +use PhpCsFixer\Doctrine\Annotation\Tokens as DoctrineAnnotationTokens; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\Tokenizer\Token as PhpToken; +use PhpCsFixer\Tokenizer\Tokens as PhpTokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @internal + */ +abstract class AbstractDoctrineAnnotationFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @var array + */ + private $classyElements; + + /** + * {@inheritdoc} + */ + public function isCandidate(PhpTokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, PhpTokens $phpTokens) + { + // fetch indexes one time, this is safe as we never add or remove a token during fixing + $analyzer = new TokensAnalyzer($phpTokens); + $this->classyElements = $analyzer->getClassyElements(); + + /** @var PhpToken $docCommentToken */ + foreach ($phpTokens->findGivenKind(T_DOC_COMMENT) as $index => $docCommentToken) { + if (!$this->nextElementAcceptsDoctrineAnnotations($phpTokens, $index)) { + continue; + } + + $tokens = DoctrineAnnotationTokens::createFromDocComment( + $docCommentToken, + $this->configuration['ignored_tags'] + ); + $this->fixAnnotations($tokens); + $phpTokens[$index] = new PhpToken([T_DOC_COMMENT, $tokens->getCode()]); + } + } + + /** + * Fixes Doctrine annotations from the given PHPDoc style comment. + */ + abstract protected function fixAnnotations(DoctrineAnnotationTokens $doctrineAnnotationTokens); + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('ignored_tags', 'List of tags that must not be treated as Doctrine Annotations.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function ($values) { + 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', + + // other + 'fix', + 'FIXME', + 'fixme', + 'override', + ]) + ->getOption(), + ]); + } + + /** + * @param int $index + * + * @return bool + */ + private function nextElementAcceptsDoctrineAnnotations(PhpTokens $tokens, $index) + { + do { + $index = $tokens->getNextMeaningfulToken($index); + + if (null === $index) { + return false; + } + } while ($tokens[$index]->isGivenKind([T_ABSTRACT, T_FINAL])); + + if ($tokens[$index]->isClassy()) { + return true; + } + + while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT])) { + $index = $tokens->getNextMeaningfulToken($index); + } + + return isset($this->classyElements[$index]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/AbstractFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b8ae6ddb411372cb9ad35c47efa86ad32689e10b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractFixer.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; + +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\ConfigurationException\InvalidForEnvFixerConfigurationException; +use PhpCsFixer\ConfigurationException\RequiredFixerConfigurationException; +use PhpCsFixer\Console\Application; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\DefinedFixerInterface; +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, DefinedFixerInterface +{ + /** + * @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) + { + 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); + } + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + $nameParts = explode('\\', static::class); + $name = substr(end($nameParts), 0, -\strlen('Fixer')); + + return Utils::camelCaseToUnderscore($name); + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function supports(\SplFileInfo $file) + { + return true; + } + + public function configure(array $configuration = null) + { + if (!$this instanceof ConfigurationDefinitionFixerInterface) { + throw new \LogicException('Cannot configure using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface".'); + } + + if (null === $configuration) { + $message = 'Passing NULL to set default configuration is deprecated and will not be supported in 3.0, use an empty array instead.'; + + if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + throw new \InvalidArgumentException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); + } + + @trigger_error($message, E_USER_DEPRECATED); + + $configuration = []; + } + + foreach ($this->getConfigurationDefinition()->getOptions() as $option) { + if (!$option instanceof DeprecatedFixerOption) { + continue; + } + + $name = $option->getName(); + if (\array_key_exists($name, $configuration)) { + $message = 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()) + ); + + if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + throw new \InvalidArgumentException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); + } + + @trigger_error($message, E_USER_DEPRECATED); + } + } + + 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 + ); + } + } + + /** + * {@inheritdoc} + */ + public function getConfigurationDefinition() + { + if (!$this instanceof ConfigurationDefinitionFixerInterface) { + throw new \LogicException('Cannot get configuration definition using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface".'); + } + + if (null === $this->configurationDefinition) { + $this->configurationDefinition = $this->createConfigurationDefinition(); + } + + return $this->configurationDefinition; + } + + public function setWhitespacesConfig(WhitespacesFixerConfig $config) + { + 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); + + /** + * @return FixerConfigurationResolverInterface + */ + protected function createConfigurationDefinition() + { + if (!$this instanceof ConfigurationDefinitionFixerInterface) { + throw new \LogicException('Cannot create configuration definition using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface".'); + } + + throw new \LogicException('Not implemented.'); + } + + private function getDefaultWhitespacesFixerConfig() + { + static $defaultWhitespacesFixerConfig = null; + + if (null === $defaultWhitespacesFixerConfig) { + $defaultWhitespacesFixerConfig = new WhitespacesFixerConfig(' ', "\n"); + } + + return $defaultWhitespacesFixerConfig; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..363a76f07a355dbf290e755a1ee7ba7efe0128de --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.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; + +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + * + * @author SpacePossum + */ +abstract class AbstractFopenFlagFixer extends AbstractFunctionReferenceFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_STRING, T_CONSTANT_ENCAPSED_STRING]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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, $argumentStartIndex, $argumentEndIndex); + + /** + * @param string $mode + * + * @return bool + */ + protected function isValidModeString($mode) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..430e81033cb9db68864ef283659823477971e9d1 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.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; + +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + * + * @author Vladimir Reznichenko + */ +abstract class AbstractFunctionReferenceFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * Looks up Tokens sequence for suitable candidates and delivers boundaries information, + * which can be supplied by other methods in this abstract class. + * + * @param string $functionNameToSearch + * @param int $start + * @param null|int $end + * + * @return null|int[] returns $functionName, $openParenthesis, $closeParenthesis packed into array + */ + protected function find($functionNameToSearch, Tokens $tokens, $start = 0, $end = null) + { + // make interface consistent with findSequence + $end = null === $end ? $tokens->count() : $end; + + // find raw sequence which we can analyse for context + $candidateSequence = [[T_STRING, $functionNameToSearch], '(']; + $matches = $tokens->findSequence($candidateSequence, $start, $end, false); + if (null === $matches) { + // not found, simply return without further attempts + return null; + } + + // translate results for humans + list($functionName, $openParenthesis) = array_keys($matches); + + $functionsAnalyzer = new FunctionsAnalyzer(); + + if (!$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/lib/composer/friendsofphp/php-cs-fixer/src/AbstractLinesBeforeNamespaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractLinesBeforeNamespaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..2e0faabe290e005b12977719e070e744e6a1b3b4 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractLinesBeforeNamespaceFixer.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; + +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * This abstract fixer is responsible for ensuring that a certain number of + * lines prefix a namespace declaration. + * + * @author Graham Campbell + * + * @internal + */ +abstract class AbstractLinesBeforeNamespaceFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * Make sure # of line breaks prefixing namespace is within given range. + * + * @param int $index + * @param int $expectedMin min. # of line breaks + * @param int $expectedMax max. # of line breaks + */ + protected function fixLinesBeforeNamespace(Tokens $tokens, $index, $expectedMin, $expectedMax) + { + // 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 = false !== strpos($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(); + $newlinesForWhitespaceToken = $expectedMax; + 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/lib/composer/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..947362b5d67a374979b6765945f2eb398cd01710 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.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; + +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +abstract class AbstractNoUselessElseFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getPriority() + { + // should be run before NoWhitespaceInBlankLineFixer, NoExtraBlankLinesFixer, BracesFixer and after NoEmptyStatementFixer. + return 25; + } + + /** + * @param int $index + * + * @return bool + */ + protected function isSuperfluousElse(Tokens $tokens, $index) + { + $previousBlockStart = $index; + do { + // Check if all 'if', 'else if ' and 'elseif' blocks above this 'else' always end, + // if so this 'else' is overcomplete. + list($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]]) + || $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, $index) + { + $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 + * + * @return bool + */ + private function isInConditional(Tokens $tokens, $index, $lowerLimitIndex) + { + $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 a 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 + * @param int $lowerLimitIndex + * + * @return bool + */ + private function isInConditionWithoutBraces(Tokens $tokens, $index, $lowerLimitIndex) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..5042105c01ea9a2be4cbde8deac3a2a0bb51a5c0 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.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; + +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +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 string[] + */ + protected $tags; + + /** + * {@inheritdoc} + */ + public function __construct() + { + parent::__construct(); + + $this->tags = Annotation::getTagsWithTypes(); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $annotations = $doc->getAnnotationsOfType($this->tags); + + if (empty($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + $this->fixTypes($annotation); + } + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + + /** + * Actually normalize the given type. + * + * @param string $type + * + * @return string + */ + abstract protected function normalize($type); + + /** + * 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) + { + $types = $annotation->getTypes(); + + $new = $this->normalizeTypes($types); + + if ($types !== $new) { + $annotation->setTypes($new); + } + } + + /** + * @param string[] $types + * + * @return string[] + */ + private function normalizeTypes(array $types) + { + foreach ($types as $index => $type) { + $types[$index] = $this->normalizeType($type); + } + + return $types; + } + + /** + * Prepare the type and normalize it. + * + * @param string $type + * + * @return string + */ + private function normalizeType($type) + { + if ('[]' === substr($type, -2)) { + return $this->normalize(substr($type, 0, -2)).'[]'; + } + + return $this->normalize($type); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b17336120bcb8083054da11e0b40c1e92fb11a76 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.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\Fixer\FixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +abstract class AbstractProxyFixer extends AbstractFixer +{ + /** + * @var array + */ + protected $proxyFixers; + + public function __construct() + { + foreach (Utils::sortFixers($this->createProxyFixers()) as $proxyFixer) { + $this->proxyFixers[$proxyFixer->getName()] = $proxyFixer; + } + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + foreach ($this->proxyFixers as $fixer) { + if ($fixer->isCandidate($tokens)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + foreach ($this->proxyFixers as $fixer) { + if ($fixer->isRisky()) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + 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(); + } + + /** + * {@inheritdoc} + */ + public function supports(\SplFileInfo $file) + { + foreach ($this->proxyFixers as $fixer) { + if ($fixer->supports($file)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function setWhitespacesConfig(WhitespacesFixerConfig $config) + { + parent::setWhitespacesConfig($config); + + foreach ($this->proxyFixers as $fixer) { + if ($fixer instanceof WhitespacesAwareFixerInterface) { + $fixer->setWhitespacesConfig($config); + } + } + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($this->proxyFixers as $fixer) { + $fixer->fix($file, $tokens); + } + } + + /** + * @return FixerInterface[] + */ + abstract protected function createProxyFixers(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/AbstractPsrAutoloadingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractPsrAutoloadingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..65f90ea001e9d3fd3b8d9e0568d5518c63c56cd6 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/AbstractPsrAutoloadingFixer.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; + +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jordi Boggiano + * @author Dariusz Rumiński + * @author Bram Gotink + * @author Graham Campbell + * + * @internal + */ +abstract class AbstractPsrAutoloadingFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return -10; + } + + /** + * {@inheritdoc} + */ + public function supports(\SplFileInfo $file) + { + if ($file instanceof StdinFileInfo) { + return false; + } + + $filenameParts = explode('.', $file->getBasename(), 2); + + if ( + // ignore file with extension other than php + (!isset($filenameParts[1]) || 'php' !== $filenameParts[1]) + // ignore file with name that cannot be a class name + || 0 === Preg::match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $filenameParts[0]) + ) { + return false; + } + + try { + $tokens = Tokens::fromCode(sprintf('isKeyword() || $tokens[3]->isMagicConstant()) { + // name can not be a class name - detected by PHP 5.x + return false; + } + } catch (\ParseError $e) { + // name can not be a class name - detected by PHP 7.x + return false; + } + + // ignore stubs/fixtures, since they are typically containing invalid files for various reasons + return !Preg::match('{[/\\\\](stub|fixture)s?[/\\\\]}i', $file->getRealPath()); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Cache/Cache.php b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/Cache.php new file mode 100644 index 0000000000000000000000000000000000000000..a2e7aa7e959fadbcb1246c0ff6ae5b67434c47c0 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/Cache.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\Cache; + +/** + * @author Andreas Möller + * + * @internal + */ +final class Cache implements CacheInterface +{ + /** + * @var SignatureInterface + */ + private $signature; + + /** + * @var array + */ + private $hashes = []; + + public function __construct(SignatureInterface $signature) + { + $this->signature = $signature; + } + + public function getSignature() + { + return $this->signature; + } + + public function has($file) + { + return \array_key_exists($file, $this->hashes); + } + + public function get($file) + { + if (!$this->has($file)) { + return null; + } + + return $this->hashes[$file]; + } + + public function set($file, $hash) + { + $this->hashes[$file] = $hash; + } + + public function clear($file) + { + unset($this->hashes[$file]); + } + + public function toJson() + { + $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()) { + throw new \UnexpectedValueException(sprintf( + 'Can not 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; + } + + /** + * @param string $json + * + * @throws \InvalidArgumentException + * + * @return Cache + */ + public static function fromJson($json) + { + $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)) { + throw new \InvalidArgumentException(sprintf( + 'JSON data is missing keys "%s"', + implode('", "', $missingKeys) + )); + } + + $signature = new Signature( + $data['php'], + $data['version'], + $data['indent'], + $data['lineEnding'], + $data['rules'] + ); + + $cache = new self($signature); + + $cache->hashes = $data['hashes']; + + return $cache; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..693b0ee42a14e5c8067e8fc6a61f724cd3be06a0 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.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\Cache; + +/** + * @author Andreas Möller + * + * @internal + */ +interface CacheInterface +{ + /** + * @return SignatureInterface + */ + public function getSignature(); + + /** + * @param string $file + * + * @return bool + */ + public function has($file); + + /** + * @param string $file + * + * @return null|int + */ + public function get($file); + + /** + * @param string $file + * @param int $hash + */ + public function set($file, $hash); + + /** + * @param string $file + */ + public function clear($file); + + /** + * @return string + */ + public function toJson(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..daa8bbd138afb41a22483cdbb461e857d193992f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.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 Dariusz Rumiński + * + * @internal + */ +interface CacheManagerInterface +{ + /** + * @param string $file + * @param string $fileContent + * + * @return bool + */ + public function needFixing($file, $fileContent); + + /** + * @param string $file + * @param string $fileContent + */ + public function setFile($file, $fileContent); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Cache/Directory.php b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/Directory.php new file mode 100644 index 0000000000000000000000000000000000000000..8c4e7719fbe15a72c3a119059978f4af8df4f5b4 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/Directory.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\Cache; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class Directory implements DirectoryInterface +{ + /** + * @var string + */ + private $directoryName; + + /** + * @param string $directoryName + */ + public function __construct($directoryName) + { + $this->directoryName = $directoryName; + } + + public function getRelativePathTo($file) + { + $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($path) + { + return str_replace(['\\', '/'], \DIRECTORY_SEPARATOR, $path); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Cache/DirectoryInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/DirectoryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a22582e57f2bfaf9d8e3d6db3b333098be0d06ae --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/DirectoryInterface.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\Cache; + +/** + * @author Dariusz Rumiński + */ +interface DirectoryInterface +{ + /** + * @param string $file + * + * @return string + */ + public function getRelativePathTo($file); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.php b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.php new file mode 100644 index 0000000000000000000000000000000000000000..6d76a888c386ebe6e66a66f19549f3ad2bbc7bc9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.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\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 +{ + /** + * @var FileHandlerInterface + */ + private $handler; + + /** + * @var SignatureInterface + */ + private $signature; + + /** + * @var CacheInterface + */ + private $cache; + + /** + * @var bool + */ + private $isDryRun; + + /** + * @var DirectoryInterface + */ + private $cacheDirectory; + + /** + * @param bool $isDryRun + */ + public function __construct( + FileHandlerInterface $handler, + SignatureInterface $signature, + $isDryRun = false, + DirectoryInterface $cacheDirectory = null + ) { + $this->handler = $handler; + $this->signature = $signature; + $this->isDryRun = $isDryRun; + $this->cacheDirectory = $cacheDirectory ?: new Directory(''); + + $this->readCache(); + } + + public function __destruct() + { + $this->writeCache(); + } + + public function needFixing($file, $fileContent) + { + $file = $this->cacheDirectory->getRelativePathTo($file); + + return !$this->cache->has($file) || $this->cache->get($file) !== $this->calcHash($fileContent); + } + + public function setFile($file, $fileContent) + { + $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); + + return; + } + + $this->cache->set($file, $hash); + } + + private function readCache() + { + $cache = $this->handler->read(); + + if (!$cache || !$this->signature->equals($cache->getSignature())) { + $cache = new Cache($this->signature); + } + + $this->cache = $cache; + } + + private function writeCache() + { + $this->handler->write($this->cache); + } + + private function calcHash($content) + { + return crc32($content); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Cache/FileHandler.php b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/FileHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..e138a96e418c25034721ea848864f929169258cc --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/FileHandler.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\Cache; + +use Symfony\Component\Filesystem\Exception\IOException; + +/** + * @author Andreas Möller + * + * @internal + */ +final class FileHandler implements FileHandlerInterface +{ + /** + * @var string + */ + private $file; + + /** + * @param string $file + */ + public function __construct($file) + { + $this->file = $file; + } + + public function getFile() + { + return $this->file; + } + + public function read() + { + if (!file_exists($this->file)) { + return null; + } + + $content = file_get_contents($this->file); + + try { + $cache = Cache::fromJson($content); + } catch (\InvalidArgumentException $exception) { + return null; + } + + return $cache; + } + + public function write(CacheInterface $cache) + { + $content = $cache->toJson(); + + if (file_exists($this->file)) { + if (is_dir($this->file)) { + throw new IOException( + sprintf('Cannot write cache file "%s" as the location exists as directory.', realpath($this->file)), + 0, + null, + $this->file + ); + } + + if (!is_writable($this->file)) { + throw new IOException( + sprintf('Cannot write to file "%s" as it is not writable.', realpath($this->file)), + 0, + null, + $this->file + ); + } + } else { + @touch($this->file); + @chmod($this->file, 0666); + } + + $bytesWritten = @file_put_contents($this->file, $content); + + if (false === $bytesWritten) { + $error = error_get_last(); + + throw new IOException( + sprintf('Failed to write file "%s", "%s".', $this->file, isset($error['message']) ? $error['message'] : 'no reason available'), + 0, + null, + $this->file + ); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Cache/FileHandlerInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/FileHandlerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..29ff5c9adda94cf99c7c1d5b98de0257d44401be --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/FileHandlerInterface.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\Cache; + +/** + * @author Andreas Möller + * + * @internal + */ +interface FileHandlerInterface +{ + /** + * @return string + */ + public function getFile(); + + /** + * @return null|CacheInterface + */ + public function read(); + + public function write(CacheInterface $cache); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Cache/NullCacheManager.php b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/NullCacheManager.php new file mode 100644 index 0000000000000000000000000000000000000000..74f3b625bb16887c3b32815dc8c6533cfd5fb222 --- /dev/null +++ b/lib/composer/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($file, $fileContent) + { + return true; + } + + public function setFile($file, $fileContent) + { + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Cache/Signature.php b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/Signature.php new file mode 100644 index 0000000000000000000000000000000000000000..1af7a3f85d58f0bfd5ba60f84ce7aea3bba86e2e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/Signature.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\Cache; + +/** + * @author Andreas Möller + * + * @internal + */ +final class Signature implements SignatureInterface +{ + /** + * @var string + */ + private $phpVersion; + + /** + * @var string + */ + private $fixerVersion; + + /** + * @var string + */ + private $indent; + + /** + * @var string + */ + private $lineEnding; + + /** + * @var array + */ + private $rules; + + /** + * @param string $phpVersion + * @param string $fixerVersion + * @param string $indent + * @param string $lineEnding + */ + public function __construct($phpVersion, $fixerVersion, $indent, $lineEnding, array $rules) + { + $this->phpVersion = $phpVersion; + $this->fixerVersion = $fixerVersion; + $this->indent = $indent; + $this->lineEnding = $lineEnding; + $this->rules = self::utf8Encode($rules); + } + + public function getPhpVersion() + { + return $this->phpVersion; + } + + public function getFixerVersion() + { + return $this->fixerVersion; + } + + public function getIndent() + { + return $this->indent; + } + + public function getLineEnding() + { + return $this->lineEnding; + } + + public function getRules() + { + return $this->rules; + } + + public function equals(SignatureInterface $signature) + { + return $this->phpVersion === $signature->getPhpVersion() + && $this->fixerVersion === $signature->getFixerVersion() + && $this->indent === $signature->getIndent() + && $this->lineEnding === $signature->getLineEnding() + && $this->rules === $signature->getRules(); + } + + private static function utf8Encode(array $data) + { + if (!\function_exists('mb_detect_encoding')) { + return $data; + } + + array_walk_recursive($data, static function (&$item) { + if (\is_string($item) && !mb_detect_encoding($item, 'utf-8', true)) { + $item = utf8_encode($item); + } + }); + + return $data; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..5a7131eb7d1fdc414558959a325cd0092bd21c7f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.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\Cache; + +/** + * @author Andreas Möller + * + * @internal + */ +interface SignatureInterface +{ + /** + * @return string + */ + public function getPhpVersion(); + + /** + * @return string + */ + public function getFixerVersion(); + + /** + * @return string + */ + public function getIndent(); + + /** + * @return string + */ + public function getLineEnding(); + + /** + * @return array + */ + public function getRules(); + + /** + * @param SignatureInterface $signature + * + * @return bool + */ + public function equals(self $signature); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Config.php b/lib/composer/friendsofphp/php-cs-fixer/src/Config.php new file mode 100644 index 0000000000000000000000000000000000000000..83d9a3ed25ae920aceb404a18eef7c866da3f1cb --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Config.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; + +use PhpCsFixer\Fixer\FixerInterface; + +/** + * @author Fabien Potencier + * @author Katsuhiro Ogawa + * @author Dariusz Rumiński + */ +class Config implements ConfigInterface +{ + private $cacheFile = '.php_cs.cache'; + private $customFixers = []; + private $finder; + private $format = 'txt'; + private $hideProgress = false; + private $indent = ' '; + private $isRiskyAllowed = false; + private $lineEnding = "\n"; + private $name; + private $phpExecutable; + private $rules = ['@PSR2' => true]; + private $usingCache = true; + + public function __construct($name = 'default') + { + $this->name = $name; + } + + /** + * @return static + */ + public static function create() + { + return new static(); + } + + /** + * {@inheritdoc} + */ + public function getCacheFile() + { + return $this->cacheFile; + } + + /** + * {@inheritdoc} + */ + public function getCustomFixers() + { + return $this->customFixers; + } + + /** + * @return Finder + */ + public function getFinder() + { + if (null === $this->finder) { + $this->finder = new Finder(); + } + + return $this->finder; + } + + /** + * {@inheritdoc} + */ + public function getFormat() + { + return $this->format; + } + + /** + * {@inheritdoc} + */ + public function getHideProgress() + { + return $this->hideProgress; + } + + /** + * {@inheritdoc} + */ + public function getIndent() + { + return $this->indent; + } + + /** + * {@inheritdoc} + */ + public function getLineEnding() + { + return $this->lineEnding; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getPhpExecutable() + { + return $this->phpExecutable; + } + + /** + * {@inheritdoc} + */ + public function getRiskyAllowed() + { + return $this->isRiskyAllowed; + } + + /** + * {@inheritdoc} + */ + public function getRules() + { + return $this->rules; + } + + /** + * {@inheritdoc} + */ + public function getUsingCache() + { + return $this->usingCache; + } + + /** + * {@inheritdoc} + */ + public function registerCustomFixers($fixers) + { + if (false === \is_array($fixers) && false === $fixers instanceof \Traversable) { + throw new \InvalidArgumentException(sprintf( + 'Argument must be an array or a Traversable, got "%s".', + \is_object($fixers) ? \get_class($fixers) : \gettype($fixers) + )); + } + + foreach ($fixers as $fixer) { + $this->addCustomFixer($fixer); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setCacheFile($cacheFile) + { + $this->cacheFile = $cacheFile; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setFinder($finder) + { + if (false === \is_array($finder) && false === $finder instanceof \Traversable) { + throw new \InvalidArgumentException(sprintf( + 'Argument must be an array or a Traversable, got "%s".', + \is_object($finder) ? \get_class($finder) : \gettype($finder) + )); + } + + $this->finder = $finder; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setFormat($format) + { + $this->format = $format; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setHideProgress($hideProgress) + { + $this->hideProgress = $hideProgress; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setIndent($indent) + { + $this->indent = $indent; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setLineEnding($lineEnding) + { + $this->lineEnding = $lineEnding; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setPhpExecutable($phpExecutable) + { + $this->phpExecutable = $phpExecutable; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setRiskyAllowed($isRiskyAllowed) + { + $this->isRiskyAllowed = $isRiskyAllowed; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setRules(array $rules) + { + $this->rules = $rules; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setUsingCache($usingCache) + { + $this->usingCache = $usingCache; + + return $this; + } + + private function addCustomFixer(FixerInterface $fixer) + { + $this->customFixers[] = $fixer; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/ConfigInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/ConfigInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..52f8354b1d3062441c05de5bb11e25208ffb1169 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/ConfigInterface.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\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(); + + /** + * Returns the custom fixers to use. + * + * @return FixerInterface[] + */ + public function getCustomFixers(); + + /** + * Returns files to scan. + * + * @return iterable|\Traversable + */ + public function getFinder(); + + /** + * @return string + */ + public function getFormat(); + + /** + * Returns true if progress should be hidden. + * + * @return bool + */ + public function getHideProgress(); + + /** + * @return string + */ + public function getIndent(); + + /** + * @return string + */ + public function getLineEnding(); + + /** + * 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(); + + /** + * Get configured PHP executable, if any. + * + * @return null|string + */ + public function getPhpExecutable(); + + /** + * Check if it is allowed to run risky fixers. + * + * @return bool + */ + public function getRiskyAllowed(); + + /** + * Get rules. + * + * Keys of array are names of fixers/sets, values are true/false. + * + * @return array + */ + public function getRules(); + + /** + * Returns true if caching should be enabled. + * + * @return bool + */ + public function getUsingCache(); + + /** + * Adds a suite of custom fixers. + * + * Name of custom fixer should follow `VendorName/rule_name` convention. + * + * @param FixerInterface[]|iterable|\Traversable $fixers + */ + public function registerCustomFixers($fixers); + + /** + * Sets the path to the cache file. + * + * @param string $cacheFile + * + * @return self + */ + public function setCacheFile($cacheFile); + + /** + * @param iterable|string[]|\Traversable $finder + * + * @return self + */ + public function setFinder($finder); + + /** + * @param string $format + * + * @return self + */ + public function setFormat($format); + + /** + * @param bool $hideProgress + * + * @return self + */ + public function setHideProgress($hideProgress); + + /** + * @param string $indent + * + * @return self + */ + public function setIndent($indent); + + /** + * @param string $lineEnding + * + * @return self + */ + public function setLineEnding($lineEnding); + + /** + * Set PHP executable. + * + * @param null|string $phpExecutable + * + * @return self + */ + public function setPhpExecutable($phpExecutable); + + /** + * Set if it is allowed to run risky fixers. + * + * @param bool $isRiskyAllowed + * + * @return self + */ + public function setRiskyAllowed($isRiskyAllowed); + + /** + * 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). + * + * @return self + */ + public function setRules(array $rules); + + /** + * @param bool $usingCache + * + * @return self + */ + public function setUsingCache($usingCache); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.php b/lib/composer/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.php new file mode 100644 index 0000000000000000000000000000000000000000..3c4e43978a932308b7f2227f52b9d8fb08bc89d9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.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\ConfigurationException; + +use PhpCsFixer\Console\Command\FixCommandExitStatusCalculator; + +/** + * Exceptions of this type are thrown on misconfiguration of the Fixer. + * + * @author SpacePossum + * + * @internal + * @final Only internal extending this class is supported + */ +class InvalidConfigurationException extends \InvalidArgumentException +{ + /** + * @param string $message + * @param null|int $code + * @param null|\Throwable $previous + */ + public function __construct($message, $code = null, $previous = null) + { + parent::__construct( + $message, + null === $code ? FixCommandExitStatusCalculator::EXIT_STATUS_FLAG_HAS_INVALID_CONFIG : $code, + $previous + ); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.php b/lib/composer/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.php new file mode 100644 index 0000000000000000000000000000000000000000..b5c3322e7d8cd2a1dfb06a4941529b0aed4fe911 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.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\ConfigurationException; + +use PhpCsFixer\Console\Command\FixCommandExitStatusCalculator; + +/** + * Exception thrown by Fixers on misconfiguration. + * + * @author SpacePossum + * + * @internal + * @final Only internal extending this class is supported + */ +class InvalidFixerConfigurationException extends InvalidConfigurationException +{ + /** + * @var string + */ + private $fixerName; + + /** + * @param string $fixerName + * @param string $message + * @param null|\Throwable $previous + */ + public function __construct($fixerName, $message, $previous = null) + { + parent::__construct( + sprintf('[%s] %s', $fixerName, $message), + FixCommandExitStatusCalculator::EXIT_STATUS_FLAG_HAS_INVALID_FIXER_CONFIG, + $previous + ); + $this->fixerName = $fixerName; + } + + /** + * @return string + */ + public function getFixerName() + { + return $this->fixerName; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidForEnvFixerConfigurationException.php b/lib/composer/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidForEnvFixerConfigurationException.php new file mode 100644 index 0000000000000000000000000000000000000000..5cb0efbda1ec33946248a4f2bb6e6924329d321e --- /dev/null +++ b/lib/composer/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/lib/composer/friendsofphp/php-cs-fixer/src/ConfigurationException/RequiredFixerConfigurationException.php b/lib/composer/friendsofphp/php-cs-fixer/src/ConfigurationException/RequiredFixerConfigurationException.php new file mode 100644 index 0000000000000000000000000000000000000000..d7645dbd9ec42543ae325098599c8596f2065d31 --- /dev/null +++ b/lib/composer/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/lib/composer/friendsofphp/php-cs-fixer/src/Console/Application.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Application.php new file mode 100644 index 0000000000000000000000000000000000000000..dd7582ec65f6ae3e811b51272556c9df917acbe9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Application.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\Console; + +use PhpCsFixer\Console\Command\DescribeCommand; +use PhpCsFixer\Console\Command\FixCommand; +use PhpCsFixer\Console\Command\HelpCommand; +use PhpCsFixer\Console\Command\ReadmeCommand; +use PhpCsFixer\Console\Command\SelfUpdateCommand; +use PhpCsFixer\Console\SelfUpdate\GithubClient; +use PhpCsFixer\Console\SelfUpdate\NewVersionChecker; +use PhpCsFixer\PharChecker; +use PhpCsFixer\ToolInfo; +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 +{ + const VERSION = '2.16.3'; + const VERSION_CODENAME = 'Yellow Bird'; + + /** + * @var ToolInfo + */ + private $toolInfo; + + public function __construct() + { + if (!getenv('PHP_CS_FIXER_FUTURE_MODE')) { + error_reporting(-1); + } + + parent::__construct('PHP CS Fixer', self::VERSION); + + $this->toolInfo = new ToolInfo(); + + $this->add(new DescribeCommand()); + $this->add(new FixCommand($this->toolInfo)); + $this->add(new ReadmeCommand()); + $this->add(new SelfUpdateCommand( + new NewVersionChecker(new GithubClient()), + $this->toolInfo, + new PharChecker() + )); + } + + /** + * @return int + */ + public static function getMajorVersion() + { + return (int) explode('.', self::VERSION)[0]; + } + + /** + * {@inheritdoc} + */ + public function doRun(InputInterface $input, OutputInterface $output) + { + $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(); + foreach ($warningsDetector->getWarnings() as $warning) { + $stdErr->writeln(sprintf($stdErr->isDecorated() ? '%s' : '%s', $warning)); + } + } + + return parent::doRun($input, $output); + } + + /** + * {@inheritdoc} + */ + public function getLongVersion() + { + $version = sprintf( + '%s %s by Fabien Potencier and Dariusz Ruminski', + parent::getLongVersion(), + self::VERSION_CODENAME + ); + + $commit = '@git-commit@'; + + if ('@'.'git-commit@' !== $commit) { + $version .= ' ('.substr($commit, 0, 7).')'; + } + + return $version; + } + + /** + * {@inheritdoc} + */ + protected function getDefaultCommands() + { + return [new HelpCommand(), new ListCommand()]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..cedba65c09fe57f3d17a01c1e475af2a0b158474 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php @@ -0,0 +1,415 @@ + + * Dariusz 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\Differ\DiffConsoleFormatter; +use PhpCsFixer\Differ\FullDiffer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\DefinedFixerInterface; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +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\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSampleInterface; +use PhpCsFixer\FixerFactory; +use PhpCsFixer\Preg; +use PhpCsFixer\RuleSet; +use PhpCsFixer\StdinFileInfo; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; +use PhpCsFixer\WordMatcher; +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\Output\OutputInterface; + +/** + * @author Dariusz Rumiński + * @author SpacePossum + * + * @internal + */ +final class DescribeCommand extends Command +{ + protected static $defaultName = 'describe'; + + /** + * @var string[] + */ + private $setNames; + + /** + * @var FixerFactory + */ + private $fixerFactory; + + /** + * @var array + */ + private $fixers; + + public function __construct(FixerFactory $fixerFactory = null) + { + parent::__construct(); + + if (null === $fixerFactory) { + $fixerFactory = new FixerFactory(); + $fixerFactory->registerBuiltInFixers(); + } + + $this->fixerFactory = $fixerFactory; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDefinition( + [ + new InputArgument('name', InputArgument::REQUIRED, 'Name of rule / set.'), + ] + ) + ->setDescription('Describe rule / ruleset.') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $name = $input->getArgument('name'); + + try { + if ('@' === $name[0]) { + $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; + } + + /** + * @param string $name + */ + private function describeRule(OutputInterface $output, $name) + { + $fixers = $this->getFixers(); + + if (!isset($fixers[$name])) { + throw new DescribeNameNotFoundException($name, 'rule'); + } + + /** @var FixerInterface $fixer */ + $fixer = $fixers[$name]; + if ($fixer instanceof DefinedFixerInterface) { + $definition = $fixer->getDefinition(); + } else { + $definition = new FixerDefinition('Description is not available.', []); + } + + $description = $definition->getSummary(); + if ($fixer instanceof DeprecatedFixerInterface) { + $successors = $fixer->getSuccessorsNames(); + $message = [] === $successors + ? 'will be removed on next major version' + : sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors)); + $message = Preg::replace('/(`.+?`)/', '$1', $message); + $description .= sprintf(' DEPRECATED: %s.', $message); + } + + $output->writeln(sprintf('Description of %s rule.', $name)); + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { + $output->writeln(sprintf('Fixer class: %s.', \get_class($fixer))); + } + + $output->writeln($description); + if ($definition->getDescription()) { + $output->writeln($definition->getDescription()); + } + $output->writeln(''); + + if ($fixer->isRisky()) { + $output->writeln('Fixer applying this rule is risky.'); + + if ($definition->getRiskyDescription()) { + $output->writeln($definition->getRiskyDescription()); + } + + $output->writeln(''); + } + + if ($fixer instanceof ConfigurationDefinitionFixerInterface) { + $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) { + foreach ($allowed as &$value) { + if ($value instanceof AllowedValueSubset) { + $value = 'a subset of '.HelpCommand::toString($value->getAllowedValues()).''; + } else { + $value = ''.HelpCommand::toString($value).''; + } + } + } else { + $allowed = array_map( + static function ($type) { + return ''.$type.''; + }, + $option->getAllowedTypes() + ); + } + + if (null !== $allowed) { + $line .= ' ('.implode(', ', $allowed).')'; + } + + $description = Preg::replace('/(`.+?`)/', '$1', OutputFormatter::escape($option->getDescription())); + $line .= ': '.lcfirst(Preg::replace('/\.$/', '', $description)).'; '; + if ($option->hasDefault()) { + $line .= sprintf( + 'defaults to %s', + HelpCommand::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(''); + } elseif ($fixer instanceof ConfigurableFixerInterface) { + $output->writeln('Fixer is configurable.'); + + if ($definition->getConfigurationDescription()) { + $output->writeln($definition->getConfigurationDescription()); + } + + if ($definition->getDefaultConfiguration()) { + $output->writeln(sprintf('Default configuration: %s.', HelpCommand::toString($definition->getDefaultConfiguration()))); + } + + $output->writeln(''); + } + + /** @var CodeSampleInterface[] $codeSamples */ + $codeSamples = array_filter($definition->getCodeSamples(), static function (CodeSampleInterface $codeSample) { + if ($codeSample instanceof VersionSpecificCodeSampleInterface) { + return $codeSample->isSuitableFor(\PHP_VERSION_ID); + } + + return true; + }); + + if (!\count($codeSamples)) { + $output->writeln([ + 'Fixing examples can not 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(null === $configuration ? [] : $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, HelpCommand::toString($codeSample->getConfiguration()))); + } + } else { + $output->writeln(sprintf(' * Example #%d.', $index + 1)); + } + + $output->writeln($diffFormatter->format($diff, ' %s')); + $output->writeln(''); + } + } + } + + /** + * @param string $name + */ + private function describeSet(OutputInterface $output, $name) + { + if (!\in_array($name, $this->getSetNames(), true)) { + throw new DescribeNameNotFoundException($name, 'set'); + } + + $ruleSet = new RuleSet([$name => true]); + $rules = $ruleSet->getRules(); + ksort($rules); + + $fixers = $this->getFixers(); + + $output->writeln(sprintf('Description of %s set.', $name)); + $output->writeln(''); + + $help = ''; + + foreach ($rules as $rule => $config) { + $fixer = $fixers[$rule]; + + if (!$fixer instanceof DefinedFixerInterface) { + throw new \RuntimeException(sprintf( + 'Cannot describe rule %s, the fixer does not implement %s', + $rule, + DefinedFixerInterface::class + )); + } + + $definition = $fixer->getDefinition(); + $help .= sprintf( + " * %s%s\n | %s\n%s\n", + $rule, + $fixer->isRisky() ? ' risky' : '', + $definition->getSummary(), + true !== $config ? sprintf(" | Configuration: %s\n", HelpCommand::toString($config)) : '' + ); + } + + $output->write($help); + } + + /** + * @return array + */ + private function getFixers() + { + 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 string[] + */ + private function getSetNames() + { + if (null !== $this->setNames) { + return $this->setNames; + } + + $set = new RuleSet(); + $this->setNames = $set->getSetDefinitionNames(); + sort($this->setNames); + + return $this->setNames; + } + + /** + * @param string $type 'rule'|'set' + */ + private function describeList(OutputInterface $output, $type) + { + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) { + $describe = [ + 'set' => $this->getSetNames(), + 'rules' => $this->getFixers(), + ]; + } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { + $describe = 'set' === $type ? ['set' => $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)); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/DescribeNameNotFoundException.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/DescribeNameNotFoundException.php new file mode 100644 index 0000000000000000000000000000000000000000..bfe5c0b79c384920e6c1561eeb8253ebf0ab6c68 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/DescribeNameNotFoundException.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\Console\Command; + +/** + * @author SpacePossum + * + * @internal + */ +final class DescribeNameNotFoundException extends \InvalidArgumentException +{ + /** + * @var string + */ + private $name; + + /** + * @var string 'rule'|'set' + */ + private $type; + + /** + * @param string $name + * @param string $type + */ + public function __construct($name, $type) + { + $this->name = $name; + $this->type = $type; + + parent::__construct(); + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..5e56680cbd359b7d356356d5b8c1c6becb39163c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php @@ -0,0 +1,270 @@ + + * Dariusz 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\Console\Output\ErrorOutput; +use PhpCsFixer\Console\Output\NullOutput; +use PhpCsFixer\Console\Output\ProcessOutput; +use PhpCsFixer\Error\ErrorsManager; +use PhpCsFixer\Report\ReportSummary; +use PhpCsFixer\Runner\Runner; +use PhpCsFixer\ToolInfoInterface; +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 + * + * @internal + */ +final class FixCommand extends Command +{ + protected static $defaultName = 'fix'; + + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + + /** + * @var ErrorsManager + */ + private $errorsManager; + + /** + * @var Stopwatch + */ + private $stopwatch; + + /** + * @var ConfigInterface + */ + private $defaultConfig; + + /** + * @var ToolInfoInterface + */ + private $toolInfo; + + public function __construct(ToolInfoInterface $toolInfo) + { + parent::__construct(); + + $this->defaultConfig = new Config(); + $this->errorsManager = new ErrorsManager(); + $this->eventDispatcher = new EventDispatcher(); + $this->stopwatch = new Stopwatch(); + $this->toolInfo = $toolInfo; + } + + /** + * {@inheritdoc} + * + * Override here to only generate the help copy when used. + */ + public function getHelp() + { + return HelpCommand::getHelpCopy(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDefinition( + [ + new InputArgument('path', InputArgument::IS_ARRAY, 'The path.'), + new InputOption('path-mode', '', InputOption::VALUE_REQUIRED, 'Specify path mode (can be override or intersection).', '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 .php_cs file.'), + new InputOption('dry-run', '', InputOption::VALUE_NONE, 'Only shows which files would have been modified.'), + new InputOption('rules', '', InputOption::VALUE_REQUIRED, 'The rules.'), + 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, 'Also produce diff for each file.'), + new InputOption('diff-format', '', InputOption::VALUE_REQUIRED, 'Specify diff format.'), + 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, run-in, estimating, estimating-max or dots).'), + ] + ) + ->setDescription('Fixes a directory or a file.') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $verbosity = $output->getVerbosity(); + + $passedConfig = $input->getOption('config'); + $passedRules = $input->getOption('rules'); + + $resolver = new ConfigurationResolver( + $this->defaultConfig, + [ + 'allow-risky' => $input->getOption('allow-risky'), + 'config' => $passedConfig, + 'dry-run' => $input->getOption('dry-run'), + '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'), + 'diff-format' => $input->getOption('diff-format'), + '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) { + if (null !== $passedConfig && null !== $passedRules) { + if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + throw new \RuntimeException('Passing both `config` and `rules` options is not possible. This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set.'); + } + + $stdErr->writeln([ + sprintf($stdErr->isDecorated() ? '%s' : '%s', 'When passing both "--config" and "--rules" the rules within the configuration file are not used.'), + sprintf($stdErr->isDecorated() ? '%s' : '%s', 'Passing both options is deprecated; version v3.0 PHP-CS-Fixer will exit with a configuration error code.'), + ]); + } + + $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)); + } + } + } + + $progressType = $resolver->getProgress(); + $finder = $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.') + ); + } + + // @TODO 3.0 remove `run-in` and `estimating` + if ('none' === $progressType || null === $stdErr) { + $progressOutput = new NullOutput(); + } elseif ('run-in' === $progressType) { + $progressOutput = new ProcessOutput($stdErr, $this->eventDispatcher, null, null); + } else { + $finder = new \ArrayIterator(iterator_to_array($finder)); + $progressOutput = new ProcessOutput( + $stdErr, + $this->eventDispatcher, + 'estimating' !== $progressType ? (new Terminal())->getWidth() : null, + \count($finder) + ); + } + + $runner = new Runner( + $finder, + $resolver->getFixers(), + $resolver->getDiffer(), + 'none' !== $progressType ? $this->eventDispatcher : null, + $this->errorsManager, + $resolver->getLinter(), + $resolver->isDryRun(), + $resolver->getCacheManager(), + $resolver->getDirectory(), + $resolver->shouldStopOnViolation() + ); + + $this->stopwatch->start('fixFiles'); + $changed = $runner->fix(); + $this->stopwatch->stop('fixFiles'); + + $progressOutput->printLegend(); + + $fixEvent = $this->stopwatch->getEvent('fixFiles'); + + $reportSummary = new ReportSummary( + $changed, + $fixEvent->getDuration(), + $fixEvent->getMemory(), + OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity(), + $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 + ); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.php new file mode 100644 index 0000000000000000000000000000000000000000..732ddd9bb268422393f28f8d77c4b752a0b7f7da --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.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\Console\Command; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class FixCommandExitStatusCalculator +{ + // Exit status 1 is reserved for environment constraints not matched. + const EXIT_STATUS_FLAG_HAS_INVALID_FILES = 4; + const EXIT_STATUS_FLAG_HAS_CHANGED_FILES = 8; + const EXIT_STATUS_FLAG_HAS_INVALID_CONFIG = 16; + const EXIT_STATUS_FLAG_HAS_INVALID_FIXER_CONFIG = 32; + const EXIT_STATUS_FLAG_EXCEPTION_IN_APP = 64; + + /** + * @param bool $isDryRun + * @param bool $hasChangedFiles + * @param bool $hasInvalidErrors + * @param bool $hasExceptionErrors + * @param bool $hasLintErrorsAfterFixing + * + * @return int + */ + public function calculate($isDryRun, $hasChangedFiles, $hasInvalidErrors, $hasExceptionErrors, $hasLintErrorsAfterFixing) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..32ebf56ec9510fb55de0ed1557a5ca10210574ac --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php @@ -0,0 +1,626 @@ + + * Dariusz 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\AbstractFixer; +use PhpCsFixer\Console\Application; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\FixerConfiguration\AliasedFixerOption; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\DeprecatedFixerOption; +use PhpCsFixer\FixerConfiguration\FixerOptionInterface; +use PhpCsFixer\FixerFactory; +use PhpCsFixer\Preg; +use PhpCsFixer\RuleSet; +use PhpCsFixer\Utils; +use Symfony\Component\Console\Command\HelpCommand as BaseHelpCommand; +use Symfony\Component\Console\Formatter\OutputFormatter; +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 + * @author SpacePossum + * + * @internal + */ +final class HelpCommand extends BaseHelpCommand +{ + protected static $defaultName = 'help'; + + /** + * Returns help-copy suitable for console output. + * + * @return string + */ + public static function getHelpCopy() + { + $template = + <<<'EOF' +The %command.name% command tries to fix 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 XML schemas + +* ``junit`` follows the `JUnit xml schema from Jenkins `_ +* ``checkstyle`` follows the common `"checkstyle" 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 + +* ``--verbose=0`` or no option: normal +* ``--verbose``, ``--verbose=1``, ``-v``: verbose +* ``--verbose=2``, ``-vv``: very verbose +* ``--verbose=3``, ``-vvv``: debug + +The --rules option limits the rules to apply to the +project: + + $ php %command.full_name% /path/to/project --rules=@PSR2 + +By default the PSR1 and PSR2 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 blacklist 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 blacklist rules, applying exact rules along with above blacklisted 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 --diff-format option allows to specify in which format the fixer should output the changes it makes: + +* udiff: unified diff format; +* sbd: Sebastianbergmann/diff format (default when using `--diff` without specifying `diff-format`). + +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; +* run-in: [deprecated] simple single-line progress output; +* estimating: [deprecated] multiline progress output with number of files and percentage on each line. Note that with this option, the files list is evaluated before processing to get the total number of files and then kept in memory to avoid using the file iterator twice. This has an impact on memory usage so using this option is not recommended on very large projects; +* estimating-max: [deprecated] same as dots; +* dots: same as estimating but using all terminal columns instead of default 80. + +If the option is not provided, it defaults to run-in 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=estimating + +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 (unified differ, estimating, full-width progress indicator): + + $ PHP_CS_FIXER_FUTURE_MODE=1 php %command.full_name% -v --diff + +Choose from the list of available rules: + +%%%FIXERS_DETAILS%%% + +The --dry-run option displays the files that need to be +fixed but without actually modifying them: + + $ php %command.full_name% /path/to/code --dry-run + +Config file +----------- + +Instead of using command line options to customize the rule, you can save the +project configuration in a .php_cs.dist file in the root directory of your project. +The file must return an instance of `PhpCsFixer\ConfigInterface` (%%%CONFIG_INTERFACE_URL%%%) +which lets you configure the rules, the files and directories that +need to be analyzed. You may also create .php_cs file, which is +the local configuration that will be used instead of the project configuration. It +is a good practice to add that file into your .gitignore file. +With the --config option you can specify the path to the +.php_cs file. + +The example below will add two rules to the default list of PSR2 set rules: + + exclude('somedir') + ->notPath('src/Symfony/Component/Translation/Tests/fixtures/resources.php') + ->in(__DIR__) + ; + + return PhpCsFixer\Config::create() + ->setRules([ + '@PSR2' => true, + 'strict_param' => true, + 'array_syntax' => ['syntax' => 'short'], + ]) + ->setFinder($finder) + ; + + ?> + +**NOTE**: ``exclude`` will work only for directories, so if you need to exclude file, try ``notPath``. +Both ``exclude`` and ``notPath`` methods accept only relative paths to the ones defined with the ``in`` method. + +See `Symfony\Finder` (https://symfony.com/doc/current/components/finder.html) +online documentation for other `Finder` methods. + +You may also use a blacklist for the rules instead of the above shown whitelist approach. +The following example shows how to use all ``Symfony`` rules but the ``full_opening_tag`` rule. + + exclude('somedir') + ->in(__DIR__) + ; + + return PhpCsFixer\Config::create() + ->setRules([ + '@Symfony' => true, + 'full_opening_tag' => false, + ]) + ->setFinder($finder) + ; + + ?> + +You may want to use non-linux whitespaces in your project. Then you need to +configure them in your config file. + + setIndent("\t") + ->setLineEnding("\r\n") + ; + + ?> + +By using ``--using-cache`` option with ``yes`` or ``no`` you can set if the caching +mechanism should be used. + +Caching +------- + +The caching mechanism is enabled by default. This will speed up further runs by +fixing only files that were modified since the last run. The tool will fix all +files if the tool version has changed or the list of rules has changed. +Cache is supported only for tool downloaded as phar file or installed via +composer. + +Cache can be disabled via ``--using-cache`` option or config file: + + setUsingCache(false) + ; + + ?> + +Cache file can be specified via ``--cache-file`` option or config file: + + setCacheFile(__DIR__.'/.php_cs.cache') + ; + + ?> + +Using PHP CS Fixer on CI +------------------------ + +Require ``friendsofphp/php-cs-fixer`` as a ``dev`` dependency: + + $ ./composer.phar require --dev friendsofphp/php-cs-fixer + +Then, add the following command to your CI: + +%%%CI_INTEGRATION%%% + +Where ``$COMMIT_RANGE`` is your range of commits, e.g. ``$TRAVIS_COMMIT_RANGE`` or ``HEAD~..HEAD``. + +Exit code +--------- + +Exit code 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. + +(Applies to exit code of the ``fix`` command only) +EOF + ; + + return strtr($template, [ + '%%%CONFIG_INTERFACE_URL%%%' => sprintf( + 'https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/v%s/src/ConfigInterface.php', + self::getLatestReleaseVersionFromChangeLog() + ), + '%%%CI_INTEGRATION%%%' => implode("\n", array_map( + static function ($line) { return ' $ '.$line; }, + \array_slice(file(__DIR__.'/../../../ci-integration.sh', FILE_IGNORE_NEW_LINES), 3) + )), + '%%%FIXERS_DETAILS%%%' => self::getFixersHelp(), + ]); + } + + /** + * @param mixed $value + * + * @return string + */ + public static function toString($value) + { + if (\is_array($value)) { + // Output modifications: + // - remove new-lines + // - combine multiple whitespaces + // - switch array-syntax to short array-syntax + // - remove whitespace at array opening + // - remove trailing array comma and whitespace at array closing + // - remove numeric array indexes + static $replaces = [ + ['#\r|\n#', '#\s{1,}#', '#array\s*\((.*)\)#s', '#\[\s+#', '#,\s*\]#', '#\d+\s*=>\s*#'], + ['', ' ', '[$1]', '[', ']', ''], + ]; + + $str = var_export($value, true); + do { + $strNew = Preg::replace( + $replaces[0], + $replaces[1], + $str + ); + + if ($strNew === $str) { + break; + } + + $str = $strNew; + } while (true); + } else { + $str = var_export($value, true); + } + + return Preg::replace('/\bNULL\b/', 'null', $str); + } + + /** + * Returns the allowed values of the given option that can be converted to a string. + * + * @return null|array + */ + public static function getDisplayableAllowedValues(FixerOptionInterface $option) + { + $allowed = $option->getAllowedValues(); + + if (null !== $allowed) { + $allowed = array_filter($allowed, static function ($value) { + return !($value instanceof \Closure); + }); + + usort($allowed, static function ($valueA, $valueB) { + if ($valueA instanceof AllowedValueSubset) { + return -1; + } + + if ($valueB instanceof AllowedValueSubset) { + return 1; + } + + return strcasecmp( + self::toString($valueA), + self::toString($valueB) + ); + }); + + if (0 === \count($allowed)) { + $allowed = null; + } + } + + return $allowed; + } + + /** + * @throws \RuntimeException when failing to parse the change log file + * + * @return string + */ + public static function getLatestReleaseVersionFromChangeLog() + { + static $version = null; + + if (null !== $version) { + return $version; + } + + $changelogFile = self::getChangeLogFile(); + if (null === $changelogFile) { + $version = Application::VERSION; + + return $version; + } + + $changelog = @file_get_contents($changelogFile); + if (false === $changelog) { + $error = error_get_last(); + + throw new \RuntimeException(sprintf( + 'Failed to read content of the changelog file "%s".%s', + $changelogFile, + $error ? ' '.$error['message'] : '' + )); + } + + for ($i = Application::getMajorVersion(); $i > 0; --$i) { + if (1 === Preg::match('/Changelog for v('.$i.'.\d+.\d+)/', $changelog, $matches)) { + $version = $matches[1]; + + break; + } + } + + if (null === $version) { + throw new \RuntimeException(sprintf('Failed to parse changelog data of "%s".', $changelogFile)); + } + + return $version; + } + + /** + * {@inheritdoc} + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + $output->getFormatter()->setStyle('url', new OutputFormatterStyle('blue')); + } + + /** + * @return null|string + */ + private static function getChangeLogFile() + { + $changelogFile = __DIR__.'/../../../CHANGELOG.md'; + + return is_file($changelogFile) ? $changelogFile : null; + } + + /** + * @return string + */ + private static function getFixersHelp() + { + $help = ''; + $fixerFactory = new FixerFactory(); + /** @var AbstractFixer[] $fixers */ + $fixers = $fixerFactory->registerBuiltInFixers()->getFixers(); + + // sort fixers by name + usort( + $fixers, + static function (FixerInterface $a, FixerInterface $b) { + return strcmp($a->getName(), $b->getName()); + } + ); + + $ruleSets = []; + foreach (RuleSet::create()->getSetDefinitionNames() as $setName) { + $ruleSets[$setName] = new RuleSet([$setName => true]); + } + + $getSetsWithRule = static function ($rule) use ($ruleSets) { + $sets = []; + + foreach ($ruleSets as $setName => $ruleSet) { + if ($ruleSet->hasRule($rule)) { + $sets[] = $setName; + } + } + + return $sets; + }; + + $count = \count($fixers) - 1; + foreach ($fixers as $i => $fixer) { + $sets = $getSetsWithRule($fixer->getName()); + + $description = $fixer->getDefinition()->getSummary(); + + if ($fixer instanceof DeprecatedFixerInterface) { + $successors = $fixer->getSuccessorsNames(); + $message = [] === $successors + ? 'will be removed on next major version' + : sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors)); + $description .= sprintf(' DEPRECATED: %s.', $message); + } + + $description = implode("\n | ", self::wordwrap( + Preg::replace('/(`.+?`)/', '$1', $description), + 72 + )); + + if (!empty($sets)) { + $help .= sprintf(" * %s [%s]\n | %s\n", $fixer->getName(), implode(', ', $sets), $description); + } else { + $help .= sprintf(" * %s\n | %s\n", $fixer->getName(), $description); + } + + if ($fixer->isRisky()) { + $help .= sprintf( + " | *Risky rule: %s.*\n", + Preg::replace( + '/(`.+?`)/', + '$1', + lcfirst(Preg::replace('/\.$/', '', $fixer->getDefinition()->getRiskyDescription())) + ) + ); + } + + if ($fixer instanceof ConfigurationDefinitionFixerInterface) { + $configurationDefinition = $fixer->getConfigurationDefinition(); + $configurationDefinitionOptions = $configurationDefinition->getOptions(); + if (\count($configurationDefinitionOptions)) { + $help .= " |\n | Configuration options:\n"; + + usort( + $configurationDefinitionOptions, + static function (FixerOptionInterface $optionA, FixerOptionInterface $optionB) { + return strcmp($optionA->getName(), $optionB->getName()); + } + ); + + foreach ($configurationDefinitionOptions as $option) { + $line = ''.OutputFormatter::escape($option->getName()).''; + + $allowed = self::getDisplayableAllowedValues($option); + if (null !== $allowed) { + foreach ($allowed as &$value) { + if ($value instanceof AllowedValueSubset) { + $value = 'a subset of '.self::toString($value->getAllowedValues()).''; + } else { + $value = ''.self::toString($value).''; + } + } + } else { + $allowed = array_map( + static function ($type) { + return ''.$type.''; + }, + $option->getAllowedTypes() + ); + } + + if (null !== $allowed) { + $line .= ' ('.implode(', ', $allowed).')'; + } + + $line .= ': '.Preg::replace( + '/(`.+?`)/', + '$1', + lcfirst(Preg::replace('/\.$/', '', OutputFormatter::escape($option->getDescription()))) + ).'; '; + if ($option->hasDefault()) { + $line .= 'defaults to '.self::toString($option->getDefault()).''; + } else { + $line .= 'required'; + } + + if ($option instanceof DeprecatedFixerOption) { + $line .= '. DEPRECATED: '.Preg::replace( + '/(`.+?`)/', + '$1', + lcfirst(Preg::replace('/\.$/', '', OutputFormatter::escape($option->getDeprecationMessage()))) + ); + } + + if ($option instanceof AliasedFixerOption) { + $line .= '; DEPRECATED alias: '.$option->getAlias().''; + } + + foreach (self::wordwrap($line, 72) as $index => $line) { + $help .= (0 === $index ? ' | - ' : ' | ').$line."\n"; + } + } + } + } elseif ($fixer instanceof ConfigurableFixerInterface) { + $help .= " | *Configurable rule.*\n"; + } + + if ($count !== $i) { + $help .= "\n"; + } + } + + // prevent "\" from being rendered as an escaped literal style tag + return Preg::replace('#\\\\()#', '<<$1', $help); + } + + /** + * Wraps a string to the given number of characters, ignoring style tags. + * + * @param string $string + * @param int $width + * + * @return string[] + */ + private static function wordwrap($string, $width) + { + $result = []; + $currentLine = 0; + $lineLength = 0; + foreach (explode(' ', $string) as $word) { + $wordLength = \strlen(Preg::replace('~~', '', $word)); + if (0 !== $lineLength) { + ++$wordLength; // space before word + } + + if ($lineLength + $wordLength > $width) { + ++$currentLine; + $lineLength = 0; + } + + $result[$currentLine][] = $word; + $lineLength += $wordLength; + } + + return array_map(static function ($line) { + return implode(' ', $line); + }, $result); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/ReadmeCommand.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/ReadmeCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..33e8c2f5baea9be7bd428d85908284b4fe421ff6 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/ReadmeCommand.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\Console\Command; + +use PhpCsFixer\Preg; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Fabien Potencier + * @author Dariusz Rumiński + * + * @internal + */ +final class ReadmeCommand extends Command +{ + protected static $defaultName = 'readme'; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setDescription('Generates the README content, based on the fix command help.'); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $header = <<header('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. + +The PHP CS Fixer is maintained on GitHub at https://github.com/FriendsOfPHP/PHP-CS-Fixer +bug reports and ideas about new features are welcome there. + +You can talk to us at https://gitter.im/PHP-CS-Fixer/Lobby about the project, +configuration, possible improvements, ideas and questions, please visit us! + +{$this->header('Requirements', '-')} + +PHP needs to be a minimum version of PHP 5.6.0. + +{$this->header('Installation', '-')} + +{$this->header('Locally', '~')} + +Download the `php-cs-fixer.phar`_ file and store it somewhere on your computer. + +{$this->header('Globally (manual)', '~')} + +You can run these commands to easily access latest ``php-cs-fixer`` from anywhere on +your system: + +.. code-block:: bash + + $ wget %download.url% -O php-cs-fixer + +or with specified version: + +.. code-block:: bash + + $ wget %download.version_url% -O php-cs-fixer + +or with curl: + +.. code-block:: bash + + $ curl -L %download.url% -o php-cs-fixer + +then: + +.. code-block:: bash + + $ sudo chmod a+x php-cs-fixer + $ sudo mv php-cs-fixer /usr/local/bin/php-cs-fixer + +Then, just run ``php-cs-fixer``. + +{$this->header('Globally (Composer)', '~')} + +To install PHP CS Fixer, `install Composer `_ and issue the following command: + +.. code-block:: bash + + $ composer global require friendsofphp/php-cs-fixer + +Then make sure you have the global Composer binaries directory in your ``PATH``. This directory is platform-dependent, see `Composer documentation `_ for details. Example for some Unix systems: + +.. code-block:: bash + + $ export PATH="\$PATH:\$HOME/.composer/vendor/bin" + +{$this->header('Globally (homebrew)', '~')} + +.. code-block:: bash + + $ brew install php-cs-fixer + +{$this->header('Locally (PHIVE)', '~')} + +Install `PHIVE `_ and issue the following command: + +.. code-block:: bash + + $ phive install php-cs-fixer # use `--global` for global install + +{$this->header('Update', '-')} + +{$this->header('Locally', '~')} + +The ``self-update`` command tries to update ``php-cs-fixer`` itself: + +.. code-block:: bash + + $ php php-cs-fixer.phar self-update + +{$this->header('Globally (manual)', '~')} + +You can update ``php-cs-fixer`` through this command: + +.. code-block:: bash + + $ sudo php-cs-fixer self-update + +{$this->header('Globally (Composer)', '~')} + +You can update ``php-cs-fixer`` through this command: + +.. code-block:: bash + + $ ./composer.phar global update friendsofphp/php-cs-fixer + +{$this->header('Globally (homebrew)', '~')} + +You can update ``php-cs-fixer`` through this command: + +.. code-block:: bash + + $ brew upgrade php-cs-fixer + +{$this->header('Locally (PHIVE)', '~')} + +.. code-block:: bash + + $ phive update php-cs-fixer + +{$this->header('Usage', '-')} + +EOF; + + $footer = <<header('Helpers', '-')} + +Dedicated plugins exist for: + +* `Atom`_ +* `NetBeans`_ +* `PhpStorm`_ +* `Sublime Text`_ +* `Vim`_ +* `VS Code`_ + +{$this->header('Contribute', '-')} + +The tool comes with quite a few built-in fixers, but everyone is more than +welcome to `contribute`_ more of them. + +{$this->header('Fixers', '~')} + +A *fixer* is a class that tries to fix one CS issue (a ``Fixer`` class must +implement ``FixerInterface``). + +{$this->header('Configs', '~')} + +A *config* knows about the CS rules and the files and directories that must be +scanned by the tool when run in the directory of your project. It is useful for +projects that follow a well-known directory structures (like for Symfony +projects for instance). + +.. _php-cs-fixer.phar: %download.url% +.. _Atom: https://github.com/Glavin001/atom-beautify +.. _NetBeans: http://plugins.netbeans.org/plugin/49042/php-cs-fixer +.. _PhpStorm: https://medium.com/@valeryan/how-to-configure-phpstorm-to-use-php-cs-fixer-1844991e521f +.. _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 +.. _contribute: https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/CONTRIBUTING.md + +EOF; + + $command = $this->getApplication()->get('fix'); + $help = $command->getHelp(); + $help = str_replace('%command.full_name%', 'php-cs-fixer.phar '.$command->getName(), $help); + $help = str_replace('%command.name%', $command->getName(), $help); + $help = Preg::replace('##', '``', $help); + $help = Preg::replace('#`(``.+?``)`#', '$1', $help); + $help = Preg::replace('#^(\s+)``(.+)``$#m', '$1$2', $help); + $help = Preg::replace('#^ \* ``(.+)``(.*?\n)#m', "* **$1**$2\n", $help); + $help = Preg::replace('#^ \\| #m', ' ', $help); + $help = Preg::replace('#^ \\|#m', '', $help); + $help = Preg::replace('#^(?= \\*Risky rule: )#m', "\n", $help); + $help = Preg::replace("#^( Configuration options:\n)( - )#m", "$1\n$2", $help); + $help = Preg::replace("#^\n( +\\$ )#m", "\n.. code-block:: bash\n\n$1", $help); + $help = Preg::replace("#^\n( +<\\?php)#m", "\n.. code-block:: php\n\n$1", $help); + $help = Preg::replaceCallback( + '#^\s*<\?(\w+).*?\?>#ms', + static function ($matches) { + $result = Preg::replace("#^\\.\\. code-block:: bash\n\n#m", '', $matches[0]); + + if ('php' !== $matches[1]) { + $result = Preg::replace("#<\\?{$matches[1]}\\s*#", '', $result); + } + + return Preg::replace("#\n\n +\\?>#", '', $result); + }, + $help + ); + + // Transform links + // In the console output these have the form + // `description` (http://...) + // Make to RST http://www.sphinx-doc.org/en/stable/rest.html#hyperlinks + // `description `_ + + $help = Preg::replaceCallback( + '#`(.+)`\s?\((.+)<\/url>\)#', + static function (array $matches) { + return sprintf('`%s <%s>`_', str_replace('\\', '\\\\', $matches[1]), $matches[2]); + }, + $help + ); + + $help = Preg::replace('#^ #m', ' ', $help); + $help = Preg::replace('#\*\* +\[#', '** [', $help); + + $downloadLatestUrl = sprintf('https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v%s/php-cs-fixer.phar', HelpCommand::getLatestReleaseVersionFromChangeLog()); + $downloadUrl = 'https://cs.symfony.com/download/php-cs-fixer-v2.phar'; + + $header = str_replace('%download.version_url%', $downloadLatestUrl, $header); + $header = str_replace('%download.url%', $downloadUrl, $header); + $footer = str_replace('%download.version_url%', $downloadLatestUrl, $footer); + $footer = str_replace('%download.url%', $downloadUrl, $footer); + + $output->write($header."\n".$help."\n".$footer); + + return 0; + } + + private function header($name, $underline) + { + return $name."\n".str_repeat($underline, \strlen($name)); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/SelfUpdateCommand.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Command/SelfUpdateCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..de4111b711a98aaaa2126c30b03cb3e4ff9e35ec --- /dev/null +++ b/lib/composer/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\SelfUpdate\NewVersionCheckerInterface; +use PhpCsFixer\PharCheckerInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\ToolInfoInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Igor Wiedler + * @author Stephane PY + * @author Grégoire Pineau + * @author Dariusz Rumiński + * @author SpacePossum + * + * @internal + */ +final class SelfUpdateCommand extends Command +{ + protected static $defaultName = 'self-update'; + + /** + * @var NewVersionCheckerInterface + */ + private $versionChecker; + + /** + * @var ToolInfoInterface + */ + private $toolInfo; + + /** + * @var PharCheckerInterface + */ + private $pharChecker; + + public function __construct( + NewVersionCheckerInterface $versionChecker, + ToolInfoInterface $toolInfo, + PharCheckerInterface $pharChecker + ) { + parent::__construct(); + + $this->versionChecker = $versionChecker; + $this->toolInfo = $toolInfo; + $this->pharChecker = $pharChecker; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $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/FriendsOfPHP/PHP-CS-Fixer/releases + +$ php php-cs-fixer.phar %command.name% + +EOT + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + 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/FriendsOfPHP/PHP-CS-Fixer/blob/%s/UPGRADE.md', $latestVersion)); + $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 = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; + + 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)', $remoteTag)); + + return 0; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..64114bd6a52f37da15e12e670f5e8f66b71dd6a7 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php @@ -0,0 +1,935 @@ + + * Dariusz 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\Differ\DifferInterface; +use PhpCsFixer\Differ\NullDiffer; +use PhpCsFixer\Differ\SebastianBergmannDiffer; +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\Report\ReporterFactory; +use PhpCsFixer\Report\ReporterInterface; +use PhpCsFixer\RuleSet; +use PhpCsFixer\StdinFileInfo; +use PhpCsFixer\ToolInfoInterface; +use PhpCsFixer\Utils; +use PhpCsFixer\WhitespacesFixerConfig; +use PhpCsFixer\WordMatcher; +use Symfony\Component\Console\Output\OutputInterface; +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 +{ + const PATH_MODE_OVERRIDE = 'override'; + const PATH_MODE_INTERSECTION = 'intersection'; + + /** + * @var null|bool + */ + private $allowRisky; + + /** + * @var null|ConfigInterface + */ + private $config; + + /** + * @var null|string + */ + private $configFile; + + /** + * @var string + */ + private $cwd; + + /** + * @var ConfigInterface + */ + private $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; + + /** + * @var ToolInfoInterface + */ + private $toolInfo; + + /** + * @var array + */ + private $options = [ + 'allow-risky' => null, + 'cache-file' => null, + 'config' => null, + 'diff' => null, + 'diff-format' => 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, + ]; + + private $cacheFile; + private $cacheManager; + private $differ; + private $directory; + private $finder; + private $format; + private $linter; + private $path; + private $progress; + private $ruleSet; + private $usingCache; + + /** + * @var FixerFactory + */ + private $fixerFactory; + + /** + * @param string $cwd + */ + public function __construct( + ConfigInterface $config, + array $options, + $cwd, + ToolInfoInterface $toolInfo + ) { + $this->cwd = $cwd; + $this->defaultConfig = $config; + $this->toolInfo = $toolInfo; + + foreach ($options as $name => $value) { + $this->setOption($name, $value); + } + } + + /** + * @return null|string + */ + public function getCacheFile() + { + 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; + } + + /** + * @return CacheManagerInterface + */ + public function getCacheManager() + { + if (null === $this->cacheManager) { + if ($this->getUsingCache() && ($this->toolInfo->isInstalledAsPhar() || $this->toolInfo->isInstalledByComposer())) { + $this->cacheManager = new FileCacheManager( + new FileHandler($this->getCacheFile()), + new Signature( + PHP_VERSION, + $this->toolInfo->getVersion(), + $this->getConfig()->getIndent(), + $this->getConfig()->getLineEnding(), + $this->getRules() + ), + $this->isDryRun(), + $this->getDirectory() + ); + } else { + $this->cacheManager = new NullCacheManager(); + } + } + + return $this->cacheManager; + } + + /** + * @return ConfigInterface + */ + public function getConfig() + { + if (null === $this->config) { + foreach ($this->computeConfigFiles() as $configFile) { + if (!file_exists($configFile)) { + continue; + } + + $config = self::separatedContextLessInclude($configFile); + + // 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".', $configFile, \is_object($config) ? \get_class($config) : \gettype($config))); + } + + $this->config = $config; + $this->configFile = $configFile; + + break; + } + + if (null === $this->config) { + $this->config = $this->defaultConfig; + } + } + + return $this->config; + } + + /** + * @return null|string + */ + public function getConfigFile() + { + if (null === $this->configFile) { + $this->getConfig(); + } + + return $this->configFile; + } + + /** + * @return DifferInterface + */ + public function getDiffer() + { + if (null === $this->differ) { + $mapper = [ + 'null' => static function () { return new NullDiffer(); }, + 'sbd' => static function () { return new SebastianBergmannDiffer(); }, + 'udiff' => static function () { return new UnifiedDiffer(); }, + ]; + + if ($this->options['diff-format']) { + $option = $this->options['diff-format']; + if (!isset($mapper[$option])) { + throw new InvalidConfigurationException(sprintf( + '"diff-format" must be any of "%s", got "%s".', + implode('", "', array_keys($mapper)), + $option + )); + } + } else { + $default = 'sbd'; // @TODO: 3.0 change to udiff as default + + if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + $default = 'udiff'; + } + + $option = $this->options['diff'] ? $default : 'null'; + } + + $this->differ = $mapper[$option](); + } + + return $this->differ; + } + + /** + * @return DirectoryInterface + */ + public function getDirectory() + { + 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; + } + + $this->directory = new Directory(\dirname($absolutePath)); + } + + return $this->directory; + } + + /** + * @return FixerInterface[] An array of FixerInterface + */ + public function getFixers() + { + 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 function (FixerInterface $fixer) { + return $fixer->getName(); + }, + array_filter( + $this->fixers, + static function (FixerInterface $fixer) { + return $fixer->isRisky(); + } + ) + ); + + if (\count($riskyFixers)) { + 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?', implode(', ', $riskyFixers))); + } + } + } + + return $this->fixers; + } + + /** + * @return LinterInterface + */ + public function getLinter() + { + if (null === $this->linter) { + $this->linter = new Linter($this->getConfig()->getPhpExecutable()); + } + + return $this->linter; + } + + /** + * Returns path. + * + * @return string[] + */ + public function getPath() + { + 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 ($path) use ($cwd, $filesystem) { + $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 + * + * @return string + */ + public function getProgress() + { + if (null === $this->progress) { + if (OutputInterface::VERBOSITY_VERBOSE <= $this->options['verbosity'] && 'txt' === $this->getFormat()) { + $progressType = $this->options['show-progress']; + $progressTypes = ['none', 'run-in', 'estimating', 'estimating-max', 'dots']; + + if (null === $progressType) { + $default = 'run-in'; + + if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + $default = 'dots'; + } + + $progressType = $this->getConfig()->getHideProgress() ? 'none' : $default; + } elseif (!\in_array($progressType, $progressTypes, true)) { + throw new InvalidConfigurationException(sprintf( + 'The progress type "%s" is not defined, supported are "%s".', + $progressType, + implode('", "', $progressTypes) + )); + } elseif (\in_array($progressType, ['estimating', 'estimating-max', 'run-in'], true)) { + $message = 'Passing `estimating`, `estimating-max` or `run-in` is deprecated and will not be supported in 3.0, use `none` or `dots` instead.'; + + if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + throw new \InvalidArgumentException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); + } + + @trigger_error($message, E_USER_DEPRECATED); + } + + $this->progress = $progressType; + } else { + $this->progress = 'none'; + } + } + + return $this->progress; + } + + /** + * @return ReporterInterface + */ + public function getReporter() + { + if (null === $this->reporter) { + $reporterFactory = ReporterFactory::create(); + $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, implode('", "', $formats))); + } + } + + return $this->reporter; + } + + /** + * @return bool + */ + public function getRiskyAllowed() + { + 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 + */ + public function getRules() + { + return $this->getRuleSet()->getRules(); + } + + /** + * @return bool + */ + public function getUsingCache() + { + if (null === $this->usingCache) { + if (null === $this->options['using-cache']) { + $this->usingCache = $this->getConfig()->getUsingCache(); + } else { + $this->usingCache = $this->resolveOptionBooleanValue('using-cache'); + } + } + + return $this->usingCache; + } + + public function getFinder() + { + if (null === $this->finder) { + $this->finder = $this->resolveFinder(); + } + + return $this->finder; + } + + /** + * Returns dry-run flag. + * + * @return bool + */ + public function isDryRun() + { + 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() + { + return $this->options['stop-on-violation']; + } + + /** + * @return bool + */ + public function configFinderIsOverridden() + { + if (null === $this->configFinderIsOverridden) { + $this->resolveFinder(); + } + + return $this->configFinderIsOverridden; + } + + /** + * Compute file candidates for config file. + * + * @return string[] + */ + private function computeConfigFiles() + { + $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]) && $dirName = pathinfo($path[0], PATHINFO_DIRNAME)) { + $configDir = $dirName; + } else { + $configDir = $path[0]; + } + + $candidates = [ + $configDir.\DIRECTORY_SEPARATOR.'.php_cs', + $configDir.\DIRECTORY_SEPARATOR.'.php_cs.dist', + ]; + + if ($configDir !== $this->cwd) { + $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs'; + $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs.dist'; + } + + return $candidates; + } + + /** + * @return FixerFactory + */ + private function createFixerFactory() + { + if (null === $this->fixerFactory) { + $fixerFactory = new FixerFactory(); + $fixerFactory->registerBuiltInFixers(); + $fixerFactory->registerCustomFixers($this->getConfig()->getCustomFixers()); + + $this->fixerFactory = $fixerFactory; + } + + return $this->fixerFactory; + } + + /** + * @return string + */ + private function getFormat() + { + if (null === $this->format) { + $this->format = null === $this->options['format'] + ? $this->getConfig()->getFormat() + : $this->options['format']; + } + + return $this->format; + } + + private function getRuleSet() + { + if (null === $this->ruleSet) { + $rules = $this->parseRules(); + $this->validateRules($rules); + + $this->ruleSet = new RuleSet($rules); + } + + return $this->ruleSet; + } + + /** + * @return bool + */ + private function isStdIn() + { + if (null === $this->isStdIn) { + $this->isStdIn = 1 === \count($this->options['path']) && '-' === $this->options['path'][0]; + } + + return $this->isStdIn; + } + + /** + * @param iterable $iterable + * + * @return \Traversable + */ + private function iterableToTraversable($iterable) + { + return \is_array($iterable) ? new \ArrayIterator($iterable) : $iterable; + } + + /** + * Compute rules. + * + * @return array + */ + private function parseRules() + { + 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 ('{' === $rules[0]) { + $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 ('-' === $rule[0]) { + $rules[substr($rule, 1)] = false; + } else { + $rules[$rule] = true; + } + } + + return $rules; + } + + /** + * @throws InvalidConfigurationException + */ + private function validateRules(array $rules) + { + /** + * 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); + + /** @var string[] $configuredFixers */ + $configuredFixers = array_keys($ruleSet->getRules()); + + $fixers = $this->createFixerFactory()->getFixers(); + + /** @var string[] $availableFixers */ + $availableFixers = array_map(static function (FixerInterface $fixer) { + return $fixer->getName(); + }, $fixers); + + $unknownFixers = array_diff( + $configuredFixers, + $availableFixers + ); + + if (\count($unknownFixers)) { + $matcher = new WordMatcher($availableFixers); + + $message = 'The rules contain unknown fixers: '; + foreach ($unknownFixers as $unknownFixer) { + $alternative = $matcher->match($unknownFixer); + $message .= sprintf( + '"%s"%s, ', + $unknownFixer, + null === $alternative ? '' : ' (did you mean "'.$alternative.'"?)' + ); + } + + throw new InvalidConfigurationException(substr($message, 0, -2).'.'); + } + + 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()) + : sprintf('. Use %s instead.', str_replace('`', '"', Utils::naturalLanguageJoinWithBackticks($successors))); + + $message = "Rule \"{$fixerName}\" is deprecated{$messageEnd}"; + + if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + throw new \RuntimeException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); + } + + @trigger_error($message, E_USER_DEPRECATED); + } + } + } + + /** + * Apply path on config instance. + */ + private function resolveFinder() + { + $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'], + implode('", "', $modes) + )); + } + + $isIntersectionPathMode = self::PATH_MODE_INTERSECTION === $this->options['path-mode']; + + $paths = array_filter(array_map( + static function ($path) { + return realpath($path); + }, + $this->getPath() + )); + + if (!\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) { + $currentRealPath = $current->getRealPath(); + + if (\in_array($currentRealPath, $pathsByType['file'], true)) { + return true; + } + + foreach ($pathsByType['dir'] as $path) { + if (0 === strpos($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 string $name + * @param mixed $value + */ + private function setOption($name, $value) + { + if (!\array_key_exists($name, $this->options)) { + throw new InvalidConfigurationException(sprintf('Unknown option name: "%s".', $name)); + } + + $this->options[$name] = $value; + } + + /** + * @param string $optionName + * + * @return bool + */ + private function resolveOptionBooleanValue($optionName) + { + $value = $this->options[$optionName]; + if (\is_bool($value)) { + return $value; + } + + 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; + } + + $message = sprintf('Expected "yes" or "no" for option "%s", other values are deprecated and support will be removed in 3.0. Got "%s", this implicitly set the option to "false".', $optionName, $value); + + if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + throw new InvalidConfigurationException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); + } + + @trigger_error($message, E_USER_DEPRECATED); + + return false; + } + + private static function separatedContextLessInclude($path) + { + return include $path; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php new file mode 100644 index 0000000000000000000000000000000000000000..aafd6fed2dc53785f7e04534d7254fdb14cb6654 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php @@ -0,0 +1,154 @@ + + * Dariusz 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\Formatter\OutputFormatter; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author SpacePossum + * + * @internal + */ +final class ErrorOutput +{ + /** + * @var OutputInterface + */ + private $output; + + /** + * @var bool + */ + private $isDecorated; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + $this->isDecorated = $output->isDecorated(); + } + + /** + * @param string $process + * @param Error[] $errors + */ + public function listErrors($process, array $errors) + { + $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'], $trace['function']) && \Symfony\Component\Console\Command\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 (!empty($diff)) { + $diffFormatter = new DiffConsoleFormatter( + $this->isDecorated, + sprintf( + ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', + PHP_EOL, + PHP_EOL + ) + ); + + $this->output->writeln($diffFormatter->format($diff)); + } + } + } + } + + private function outputTrace(array $trace) + { + 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'])); + } + } + + /** + * @param string $string + * + * @return string + */ + private function prepareOutput($string) + { + return $this->isDecorated + ? OutputFormatter::escape($string) + : $string + ; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/NullOutput.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/NullOutput.php new file mode 100644 index 0000000000000000000000000000000000000000..281388c1764bb0d26bc126e8d5b883f1901c20cc --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/NullOutput.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\Console\Output; + +/** + * @internal + */ +final class NullOutput implements ProcessOutputInterface +{ + public function printLegend() + { + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutput.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutput.php new file mode 100644 index 0000000000000000000000000000000000000000..61b69b9a6afc26fe8bb568c03306dfa6d9483b4e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutput.php @@ -0,0 +1,145 @@ + + * Dariusz 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\FixerFileProcessedEvent; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Output writer to show the process of a FixCommand. + * + * @internal + */ +final class ProcessOutput implements ProcessOutputInterface +{ + /** + * File statuses map. + * + * @var array + */ + private static $eventStatusMap = [ + FixerFileProcessedEvent::STATUS_UNKNOWN => ['symbol' => '?', 'format' => '%s', 'description' => 'unknown'], + FixerFileProcessedEvent::STATUS_INVALID => ['symbol' => 'I', 'format' => '%s', 'description' => 'invalid file syntax (file ignored)'], + FixerFileProcessedEvent::STATUS_SKIPPED => ['symbol' => 'S', 'format' => '%s', 'description' => 'skipped (cached or empty file)'], + FixerFileProcessedEvent::STATUS_NO_CHANGES => ['symbol' => '.', 'format' => '%s', 'description' => 'no changes'], + FixerFileProcessedEvent::STATUS_FIXED => ['symbol' => 'F', 'format' => '%s', 'description' => 'fixed'], + FixerFileProcessedEvent::STATUS_EXCEPTION => ['symbol' => 'E', 'format' => '%s', 'description' => 'error'], + FixerFileProcessedEvent::STATUS_LINT => ['symbol' => 'E', 'format' => '%s', 'description' => 'error'], + ]; + + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var null|int + */ + private $files; + + /** + * @var int + */ + private $processedFiles = 0; + + /** + * @var null|int + */ + private $symbolsPerLine; + + /** + * @TODO 3.0 make all parameters mandatory (`null` not allowed) + * + * @param null|int $width + * @param null|int $nbFiles + */ + public function __construct(OutputInterface $output, EventDispatcherInterface $dispatcher, $width, $nbFiles) + { + $this->output = $output; + $this->eventDispatcher = $dispatcher; + $this->eventDispatcher->addListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']); + $this->symbolsPerLine = $width; + + if (null !== $nbFiles) { + $this->files = $nbFiles; + + // 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, ($width ?: 80) - \strlen((string) $this->files) * 2 - 11); + } + } + + public function __destruct() + { + $this->eventDispatcher->removeListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']); + } + + public function onFixerFileProcessed(FixerFileProcessedEvent $event) + { + if ( + null === $this->files + && null !== $this->symbolsPerLine + && 0 === $this->processedFiles % $this->symbolsPerLine + && 0 !== $this->processedFiles + ) { + $this->output->writeln(''); + } + + $status = self::$eventStatusMap[$event->getStatus()]; + $this->output->write($this->output->isDecorated() ? sprintf($status['format'], $status['symbol']) : $status['symbol']); + + ++$this->processedFiles; + + if (null !== $this->files) { + $symbolsOnCurrentLine = $this->processedFiles % $this->symbolsPerLine; + $isLast = $this->processedFiles === $this->files; + + if (0 === $symbolsOnCurrentLine || $isLast) { + $this->output->write(sprintf( + '%s %'.\strlen((string) $this->files).'d / %d (%3d%%)', + $isLast && 0 !== $symbolsOnCurrentLine ? str_repeat(' ', $this->symbolsPerLine - $symbolsOnCurrentLine) : '', + $this->processedFiles, + $this->files, + round($this->processedFiles / $this->files * 100) + )); + + if (!$isLast) { + $this->output->writeln(''); + } + } + } + } + + public function printLegend() + { + $symbols = []; + + foreach (self::$eventStatusMap as $status) { + $symbol = $status['symbol']; + if ('' === $symbol || isset($symbols[$symbol])) { + continue; + } + + $symbols[$symbol] = sprintf('%s-%s', $this->output->isDecorated() ? sprintf($status['format'], $symbol) : $symbol, $status['description']); + } + + $this->output->write(sprintf("\nLegend: %s\n", implode(', ', $symbols))); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutputInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutputInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..2d7838236b4b6e64cb0520503f83c1519e9877f7 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutputInterface.php @@ -0,0 +1,21 @@ + + * Dariusz 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; + +/** + * @internal + */ +interface ProcessOutputInterface +{ + public function printLegend(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.php new file mode 100644 index 0000000000000000000000000000000000000000..7cae3ac63b3e03424aaa926cba78a0c730d0c39c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.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\SelfUpdate; + +/** + * @internal + */ +final class GithubClient implements GithubClientInterface +{ + /** + * {@inheritdoc} + */ + public function getTags() + { + $url = 'https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/tags'; + + $result = @file_get_contents( + $url, + false, + stream_context_create([ + 'http' => [ + 'header' => 'User-Agent: FriendsOfPHP/PHP-CS-Fixer', + ], + ]) + ); + + if (false === $result) { + throw new \RuntimeException(sprintf('Failed to load tags at "%s".', $url)); + } + + $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.', + $url, + json_last_error_msg() + )); + } + + return $result; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a9671ac0c6dddcb2ddfeabf41520a62f8d459f79 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.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\Console\SelfUpdate; + +/** + * @internal + */ +interface GithubClientInterface +{ + /** + * @return array + */ + public function getTags(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php new file mode 100644 index 0000000000000000000000000000000000000000..023d6e77efd27542b9f389e7c661428633ef3e60 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php @@ -0,0 +1,114 @@ + + * Dariusz 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 +{ + /** + * @var GithubClientInterface + */ + private $githubClient; + + /** + * @var VersionParser + */ + private $versionParser; + + /** + * @var null|string[] + */ + private $availableVersions; + + public function __construct(GithubClientInterface $githubClient) + { + $this->githubClient = $githubClient; + $this->versionParser = new VersionParser(); + } + + /** + * {@inheritdoc} + */ + public function getLatestVersion() + { + $this->retrieveAvailableVersions(); + + return $this->availableVersions[0]; + } + + /** + * {@inheritdoc} + */ + public function getLatestVersionOfMajor($majorVersion) + { + $this->retrieveAvailableVersions(); + + $semverConstraint = '^'.$majorVersion; + + foreach ($this->availableVersions as $availableVersion) { + if (Semver::satisfies($availableVersion, $semverConstraint)) { + return $availableVersion; + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function compareVersions($versionA, $versionB) + { + $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() + { + if (null !== $this->availableVersions) { + return; + } + + foreach ($this->githubClient->getTags() as $tag) { + $version = $tag['name']; + + 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/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionCheckerInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionCheckerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b596d5824edb8475340c560f17b0f006803dc6fc --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionCheckerInterface.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\SelfUpdate; + +/** + * @internal + */ +interface NewVersionCheckerInterface +{ + /** + * Returns the tag of the latest version. + * + * @return string + */ + public function getLatestVersion(); + + /** + * Returns the tag of the latest minor/patch version of the given major version. + * + * @param int $majorVersion + * + * @return null|string + */ + public function getLatestVersionOfMajor($majorVersion); + + /** + * Returns -1, 0, or 1 if the first version is respectively less than, + * equal to, or greater than the second. + * + * @param string $versionA + * @param string $versionB + * + * @return int + */ + public function compareVersions($versionA, $versionB); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.php b/lib/composer/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.php new file mode 100644 index 0000000000000000000000000000000000000000..3f5d130d91d24b1d91a1498f6901037298d12d1a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.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\Console; + +use PhpCsFixer\ToolInfo; +use PhpCsFixer\ToolInfoInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class WarningsDetector +{ + /** + * @var ToolInfoInterface + */ + private $toolInfo; + + /** + * @var string[] + */ + private $warnings = []; + + public function __construct(ToolInfoInterface $toolInfo) + { + $this->toolInfo = $toolInfo; + } + + public function detectOldMajor() + { + // @TODO 3.0 to be activated with new MAJOR release + // $this->warnings[] = 'You are running PHP CS Fixer v2, which is not maintained anymore. Please update to v3.'; + } + + public function detectOldVendor() + { + 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 string[] + */ + public function getWarnings() + { + if (!\count($this->warnings)) { + return []; + } + + return array_unique(array_merge( + $this->warnings, + ['If you need help while solving warnings, ask at https://gitter.im/PHP-CS-Fixer, we will help you!'] + )); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.php b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..831404d92769232a1ed37dc5a4f1a679f6d31697 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.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\Differ; + +use PhpCsFixer\Preg; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class DiffConsoleFormatter +{ + /** + * @var bool + */ + private $isDecoratedOutput; + + /** + * @var string + */ + private $template; + + /** + * @param bool $isDecoratedOutput + * @param string $template + */ + public function __construct($isDecoratedOutput, $template = '%s') + { + $this->isDecoratedOutput = $isDecoratedOutput; + $this->template = $template; + } + + /** + * @param string $diff + * @param string $lineTemplate + * + * @return string + */ + public function format($diff, $lineTemplate = '%s') + { + $isDecorated = $this->isDecoratedOutput; + + $template = $isDecorated + ? $this->template + : Preg::replace('/<[^<>]+>/', '', $this->template) + ; + + return sprintf( + $template, + implode( + PHP_EOL, + array_map( + static function ($line) use ($isDecorated, $lineTemplate) { + if ($isDecorated) { + $count = 0; + $line = Preg::replaceCallback( + [ + '/^(\+.*)/', + '/^(\-.*)/', + '/^(@.*)/', + ], + static function ($matches) { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Differ/DifferInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/DifferInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..e530d2636aec53d57b5812292918da1757e07605 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/DifferInterface.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\Differ; + +/** + * @author Dariusz Rumiński + */ +interface DifferInterface +{ + /** + * Create diff. + * + * @param string $old + * @param string $new + * + * @return string + * + * TODO: on 3.0 pass the file name (if applicable) for which this diff is + */ + public function diff($old, $new); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php new file mode 100644 index 0000000000000000000000000000000000000000..9dcb7cc6b5d34f17c7d429721c5fcacc5781d6e3 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php @@ -0,0 +1,48 @@ + + * Dariusz 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\Diff\v3_0\Differ; +use PhpCsFixer\Diff\v3_0\Output\StrictUnifiedDiffOutputBuilder; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class FullDiffer implements DifferInterface +{ + /** + * @var Differ + */ + private $differ; + + public function __construct() + { + $this->differ = new Differ(new StrictUnifiedDiffOutputBuilder([ + 'collapseRanges' => false, + 'commonLineThreshold' => 100, + 'contextLines' => 100, + 'fromFile' => 'Original', + 'toFile' => 'New', + ])); + } + + /** + * {@inheritdoc} + */ + public function diff($old, $new) + { + return $this->differ->diff($old, $new); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.php new file mode 100644 index 0000000000000000000000000000000000000000..12399c48233a9420104d7bbd6ff826898781e027 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.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\Differ; + +/** + * @author Dariusz Rumiński + */ +final class NullDiffer implements DifferInterface +{ + /** + * {@inheritdoc} + */ + public function diff($old, $new) + { + return ''; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Differ/SebastianBergmannDiffer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/SebastianBergmannDiffer.php new file mode 100644 index 0000000000000000000000000000000000000000..d829dbc36cc2f0c20932ee6d722a24131d373e1f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/SebastianBergmannDiffer.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\Differ; + +use PhpCsFixer\Diff\v1_4\Differ; + +/** + * @author Dariusz Rumiński + */ +final class SebastianBergmannDiffer implements DifferInterface +{ + /** + * @var Differ + */ + private $differ; + + public function __construct() + { + $this->differ = new Differ(); + } + + /** + * {@inheritdoc} + */ + public function diff($old, $new) + { + return $this->differ->diff($old, $new); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Differ/SebastianBergmannShortDiffer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/SebastianBergmannShortDiffer.php new file mode 100644 index 0000000000000000000000000000000000000000..4c7f2e2ceca8c9a40fde97082b3547b75a81baf7 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/SebastianBergmannShortDiffer.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\Differ; + +use PhpCsFixer\Diff\v1_4\Differ; + +/** + * @author SpacePossum + */ +final class SebastianBergmannShortDiffer implements DifferInterface +{ + /** + * @var Differ + */ + private $differ; + + public function __construct() + { + $this->differ = new Differ("--- Original\n+++ New\n", false); + } + + /** + * {@inheritdoc} + */ + public function diff($old, $new) + { + return $this->differ->diff($old, $new); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.php new file mode 100644 index 0000000000000000000000000000000000000000..aa88a6454da2b1463888ae0847fb60223d9a6e7a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.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\Differ; + +use PhpCsFixer\Diff\v3_0\Differ; +use PhpCsFixer\Diff\v3_0\Output\StrictUnifiedDiffOutputBuilder; + +/** + * @author SpacePossum + */ +final class UnifiedDiffer implements DifferInterface +{ + /** + * @var Differ + */ + private $differ; + + public function __construct() + { + $this->differ = new Differ(new StrictUnifiedDiffOutputBuilder([ + 'fromFile' => 'Original', + 'toFile' => 'New', + ])); + } + + /** + * {@inheritdoc} + */ + public function diff($old, $new) + { + return $this->differ->diff($old, $new); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php new file mode 100644 index 0000000000000000000000000000000000000000..70dcc35aac74adfbec5b85657434247049dc5a03 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php @@ -0,0 +1,307 @@ + + * Dariusz 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 an entire annotation from a docblock. + * + * @author Graham Campbell + * @author Dariusz Rumiński + */ +class Annotation +{ + /** + * Regex to match any types, shall be used with `x` modifier. + * + * @internal + */ + const REGEX_TYPES = ' + # is any non-array, non-generic, non-alternated type, eg `int` or `\Foo` + # is array of , eg `int[]` or `\Foo[]` + # is generic collection type, like `array`, `Collection` and more complex like `Collection>` + # is , or type, like `int`, `bool[]` or `Collection` + # is one or more types alternated via `|`, like `int|bool[]|Collection` + (? + (? + (? + (?&simple)(\[\])* + ) + | + (? + [@$?]?[\\\\\w]+ + ) + | + (? + (?&simple) + < + (?:(?&types),\s*)?(?:(?&types)|(?&generic)) + > + ) + ) + (?: + \| + (?:(?&simple)|(?&array)|(?&generic)) + )* + ) + '; + + /** + * All the annotation tag names with types. + * + * @var string[] + */ + private static $tags = [ + 'method', + 'param', + 'property', + 'property-read', + 'property-write', + 'return', + 'throws', + 'type', + 'var', + ]; + + /** + * The lines that make up the annotation. + * + * @var Line[] + */ + private $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|string[] + */ + private $types; + + /** + * Create a new line instance. + * + * @param Line[] $lines + */ + public function __construct(array $lines) + { + $this->lines = array_values($lines); + + $keys = array_keys($lines); + + $this->start = $keys[0]; + $this->end = end($keys); + } + + /** + * Get the string representation of object. + * + * @return string + */ + public function __toString() + { + return $this->getContent(); + } + + /** + * Get all the annotation tag names with types. + * + * @return string[] + */ + public static function getTagsWithTypes() + { + return self::$tags; + } + + /** + * Get the start position of this annotation. + * + * @return int + */ + public function getStart() + { + return $this->start; + } + + /** + * Get the end position of this annotation. + * + * @return int + */ + public function getEnd() + { + return $this->end; + } + + /** + * Get the associated tag. + * + * @return Tag + */ + public function getTag() + { + if (null === $this->tag) { + $this->tag = new Tag($this->lines[0]); + } + + return $this->tag; + } + + /** + * Get the types associated with this annotation. + * + * @return string[] + */ + public function getTypes() + { + if (null === $this->types) { + $this->types = []; + + $content = $this->getTypesContent(); + + while ('' !== $content && false !== $content) { + Preg::match( + '{^'.self::REGEX_TYPES.'$}x', + $content, + $matches + ); + + $this->types[] = $matches['type']; + $content = substr($content, \strlen($matches['type']) + 1); + } + } + + return $this->types; + } + + /** + * Set the types associated with this annotation. + * + * @param string[] $types + */ + public function setTypes(array $types) + { + $pattern = '/'.preg_quote($this->getTypesContent(), '/').'/'; + + $this->lines[0]->setContent(Preg::replace($pattern, implode('|', $types), $this->lines[0]->getContent(), 1)); + + $this->clearCache(); + } + + /** + * Get the normalized types associated with this annotation, so they can easily be compared. + * + * @return string[] + */ + public function getNormalizedTypes() + { + $normalized = array_map(static function ($type) { + return strtolower($type); + }, $this->getTypes()); + + sort($normalized); + + return $normalized; + } + + /** + * Remove this annotation by removing all its lines. + */ + public function remove() + { + foreach ($this->lines as $line) { + $line->remove(); + } + + $this->clearCache(); + } + + /** + * Get the annotation content. + * + * @return string + */ + public function getContent() + { + return implode('', $this->lines); + } + + public function supportTypes() + { + 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. + * + * @return string + */ + private function getTypesContent() + { + if (null === $this->typesContent) { + $name = $this->getTag()->getName(); + + if (!$this->supportTypes()) { + throw new \RuntimeException('This tag does not support types.'); + } + + $matchingResult = Preg::match( + '{^(?:\s*\*|/\*\*)\s*@'.$name.'\s+'.self::REGEX_TYPES.'(?:\h.*)?$}sx', + $this->lines[0]->getContent(), + $matches + ); + + $this->typesContent = 1 === $matchingResult + ? $matches['types'] + : ''; + } + + return $this->typesContent; + } + + private function clearCache() + { + $this->types = null; + $this->typesContent = null; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php new file mode 100644 index 0000000000000000000000000000000000000000..ec3e1677e98cc825e8c70cfc038dbdb624c63cb7 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php @@ -0,0 +1,269 @@ + + * Dariusz 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 class represents a docblock. + * + * It internally splits it up into "lines" that we can manipulate. + * + * @author Graham Campbell + */ +class DocBlock +{ + /** + * The array of lines. + * + * @var Line[] + */ + private $lines = []; + + /** + * The array of annotations. + * + * @var null|Annotation[] + */ + private $annotations; + + /** + * Create a new docblock instance. + * + * @param string $content + */ + public function __construct($content) + { + foreach (Preg::split('/([^\n\r]+\R*)/', $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $line) { + $this->lines[] = new Line($line); + } + } + + /** + * Get the string representation of object. + * + * @return string + */ + public function __toString() + { + return $this->getContent(); + } + + /** + * Get this docblock's lines. + * + * @return Line[] + */ + public function getLines() + { + return $this->lines; + } + + /** + * Get a single line. + * + * @param int $pos + * + * @return null|Line + */ + public function getLine($pos) + { + return isset($this->lines[$pos]) ? $this->lines[$pos] : null; + } + + /** + * Get this docblock's annotations. + * + * @return Annotation[] + */ + public function getAnnotations() + { + 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); + // 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() + { + return 1 !== \count($this->lines); + } + + /** + * Take a one line doc block, and turn it into a multi line doc block. + * + * @param string $indent + * @param string $lineEnd + */ + public function makeMultiLine($indent, $lineEnd) + { + 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() + { + if (!$this->isMultiLine()) { + return; + } + + $usefulLines = array_filter( + $this->lines, + static function (Line $line) { + return $line->containsUsefulContent(); + } + ); + + if (1 < \count($usefulLines)) { + return; + } + + $lineContent = ''; + if (\count($usefulLines)) { + $lineContent = $this->getSingleLineDocBlockEntry(array_shift($usefulLines)); + } + + $this->lines = [new Line('/** '.$lineContent.' */')]; + } + + /** + * @param int $pos + * + * @return null|Annotation + */ + public function getAnnotation($pos) + { + $annotations = $this->getAnnotations(); + + return isset($annotations[$pos]) ? $annotations[$pos] : null; + } + + /** + * Get specific types of annotations only. + * + * If none exist, we're returning an empty array. + * + * @param string|string[] $types + * + * @return Annotation[] + */ + public function getAnnotationsOfType($types) + { + $annotations = []; + $types = (array) $types; + + foreach ($this->getAnnotations() as $annotation) { + $tag = $annotation->getTag()->getName(); + foreach ($types as $type) { + if ($type === $tag) { + $annotations[] = $annotation; + } + } + } + + return $annotations; + } + + /** + * Get the actual content of this docblock. + * + * @return string + */ + public function getContent() + { + return implode('', $this->lines); + } + + private function findAnnotationLength($start) + { + $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; + } + + /** + * @return string + */ + private function getSingleLineDocBlockEntry(Line $line) + { + $lineString = $line->getContent(); + + if (0 === \strlen($lineString)) { + return $lineString; + } + + $lineString = str_replace('*/', '', $lineString); + $lineString = trim($lineString); + + if ('/**' === substr($lineString, 0, 3)) { + $lineString = substr($lineString, 3); + } elseif ('*' === substr($lineString, 0, 1)) { + $lineString = substr($lineString, 1); + } + + return trim($lineString); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/Line.php b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/Line.php new file mode 100644 index 0000000000000000000000000000000000000000..3e198611e98c21814b1999e8c714822cbf4dab6f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/Line.php @@ -0,0 +1,144 @@ + + * Dariusz 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 + */ +class Line +{ + /** + * The content of this line. + * + * @var string + */ + private $content; + + /** + * Create a new line instance. + * + * @param string $content + */ + public function __construct($content) + { + $this->content = $content; + } + + /** + * Get the string representation of object. + * + * @return string + */ + public function __toString() + { + return $this->content; + } + + /** + * Get the content of this line. + * + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * Does this line contain useful content? + * + * If the line contains text or tags, then this is true. + * + * @return bool + */ + public function containsUsefulContent() + { + return 0 !== 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. + * + * @return bool + */ + public function containsATag() + { + return 0 !== Preg::match('/\\*\s*@/', $this->content); + } + + /** + * Is the line the start of a docblock? + * + * @return bool + */ + public function isTheStart() + { + return false !== strpos($this->content, '/**'); + } + + /** + * Is the line the end of a docblock? + * + * @return bool + */ + public function isTheEnd() + { + return false !== strpos($this->content, '*/'); + } + + /** + * Set the content of this line. + * + * @param string $content + */ + public function setContent($content) + { + $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 indexes of lines + * during the execution of an algorithm. + */ + public function remove() + { + $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 indexes of lines + * during the execution of an algorithm. + */ + public function addBlank() + { + $matched = Preg::match('/^(\h*\*)[^\r\n]*(\r?\n)$/', $this->content, $matches); + + if (1 !== $matched) { + return; + } + + $this->content .= $matches[1].$matches[2]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/ShortDescription.php b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/ShortDescription.php new file mode 100644 index 0000000000000000000000000000000000000000..e4228bacd7da188ec422e4ff173c6c379a4af017 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/ShortDescription.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\DocBlock; + +/** + * This class represents a short description (aka summary) of a docblock. + * + * @internal + */ +final class ShortDescription +{ + /** + * The docblock containing the short description. + * + * @var DocBlock + */ + private $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. + * + * @return null|int + */ + public function getEnd() + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/Tag.php b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/Tag.php new file mode 100644 index 0000000000000000000000000000000000000000..9efa00b92496a867e92ee792fa583228ee6fc893 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/Tag.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\DocBlock; + +use PhpCsFixer\Preg; + +/** + * This represents a tag, as defined by the proposed PSR PHPDoc standard. + * + * @author Graham Campbell + */ +class Tag +{ + /** + * All the tags defined by the proposed PSR PHPDoc standard. + * + * @var string[] + */ + private static $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. + * + * @var Line + */ + private $line; + + /** + * The cached tag name. + * + * @var null|string + */ + private $name; + + /** + * Create a new tag instance. + */ + public function __construct(Line $line) + { + $this->line = $line; + } + + /** + * Get the tag name. + * + * This may be "param", or "return", etc. + * + * @return string + */ + public function getName() + { + 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. + * + * @param string $name + */ + public function setName($name) + { + $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. + * + * @return bool + */ + public function valid() + { + return \in_array($this->getName(), self::$tags, true); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.php b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..9504e728e972db4487bfbbc5a5f4a86e603dec7e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.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\DocBlock; + +/** + * This class is responsible for comparing tags to see if they should be kept + * together, or kept apart. + * + * @author Graham Campbell + */ +class TagComparator +{ + /** + * Groups of tags that should be allowed to immediately follow each other. + * + * @var array + */ + private static $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? + * + * @return bool + */ + public static function shouldBeTogether(Tag $first, Tag $second) + { + $firstName = $first->getName(); + $secondName = $second->getName(); + + if ($firstName === $secondName) { + return true; + } + + foreach (self::$groups as $group) { + if (\in_array($firstName, $group, true) && \in_array($secondName, $group, true)) { + return true; + } + } + + return false; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.php b/lib/composer/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.php new file mode 100644 index 0000000000000000000000000000000000000000..890b5e48db66a07233f3f07adc18d4d8c0186d4b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.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\Doctrine\Annotation; + +use Doctrine\Common\Annotations\DocLexer; + +/** + * A Doctrine annotation token. + * + * @internal + */ +final class Token +{ + /** + * @var int + */ + private $type; + + /** + * @var string + */ + private $content; + + /** + * @param int $type The type + * @param string $content The content + */ + public function __construct($type = DocLexer::T_NONE, $content = '') + { + $this->type = $type; + $this->content = $content; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * @param int $type + */ + public function setType($type) + { + $this->type = $type; + } + + /** + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * @param string $content + */ + public function setContent($content) + { + $this->content = $content; + } + + /** + * Returns whether the token type is one of the given types. + * + * @param int|int[] $types + * + * @return bool + */ + public function isType($types) + { + if (!\is_array($types)) { + $types = [$types]; + } + + return \in_array($this->getType(), $types, true); + } + + /** + * Overrides the content with an empty string. + */ + public function clear() + { + $this->setContent(''); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php b/lib/composer/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php new file mode 100644 index 0000000000000000000000000000000000000000..15dfd0c9e81e7798e7970a34857c8cbe27ca07fb --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php @@ -0,0 +1,377 @@ + + * Dariusz 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 Doctrine\Common\Annotations\DocLexer; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token as PhpToken; + +/** + * A list of Doctrine annotation tokens. + * + * @internal + */ +final class Tokens extends \SplFixedArray +{ + /** + * @param string[] $ignoredTags + * + * @throws \InvalidArgumentException + * + * @return self + */ + public static function createFromDocComment(PhpToken $input, array $ignoredTags = []) + { + if (!$input->isGivenKind(T_DOC_COMMENT)) { + throw new \InvalidArgumentException('Input must be a T_DOC_COMMENT token.'); + } + + $tokens = new self(); + + $content = $input->getContent(); + $ignoredTextPosition = 0; + $currentPosition = 0; + 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 && DocLexer::T_AT !== $token['type']) { + break; + } + + if (1 === $index) { + if (DocLexer::T_IDENTIFIER !== $token['type'] || \in_array($token['value'], $ignoredTags, true)) { + break; + } + + $nbScannedTokensToUse = 2; + } + + if ($index >= 2 && 0 === $nbScopes && !\in_array($token['type'], [DocLexer::T_NONE, DocLexer::T_OPEN_PARENTHESIS], true)) { + break; + } + + $scannedTokens[] = $token; + + if (DocLexer::T_OPEN_PARENTHESIS === $token['type']) { + ++$nbScopes; + } elseif (DocLexer::T_CLOSE_PARENTHESIS === $token['type']) { + 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 (DocLexer::T_STRING === $token['type']) { + $token['value'] = '"'.str_replace('"', '""', $token['value']).'"'; + } + + $missingTextLength = $token['position'] - $lastTokenEndIndex; + if ($missingTextLength > 0) { + $tokens[] = new Token(DocLexer::T_NONE, substr( + $content, + $nextAtPosition + $lastTokenEndIndex, + $missingTextLength + )); + } + + $tokens[] = new Token($token['type'], $token['value']); + $lastTokenEndIndex = $token['position'] + \strlen($token['value']); + } + + $currentPosition = $ignoredTextPosition = $nextAtPosition + $token['position'] + \strlen($token['value']); + } else { + $currentPosition = $nextAtPosition + 1; + } + } + + if ($ignoredTextPosition < \strlen($content)) { + $tokens[] = new Token(DocLexer::T_NONE, substr($content, $ignoredTextPosition)); + } + + return $tokens; + } + + /** + * Returns the index of the closest next token that is neither a comment nor a whitespace token. + * + * @param int $index + * + * @return null|int + */ + public function getNextMeaningfulToken($index) + { + return $this->getMeaningfulTokenSibling($index, 1); + } + + /** + * Returns the index of the closest previous token that is neither a comment nor a whitespace token. + * + * @param int $index + * + * @return null|int + */ + public function getPreviousMeaningfulToken($index) + { + return $this->getMeaningfulTokenSibling($index, -1); + } + + /** + * Returns the index of the closest next token of the given type. + * + * @param string|string[] $type + * @param int $index + * + * @return null|int + */ + public function getNextTokenOfType($type, $index) + { + return $this->getTokenOfTypeSibling($index, $type, 1); + } + + /** + * Returns the index of the closest previous token of the given type. + * + * @param string|string[] $type + * @param int $index + * + * @return null|int + */ + public function getPreviousTokenOfType($type, $index) + { + return $this->getTokenOfTypeSibling($index, $type, -1); + } + + /** + * Returns the index of the last token that is part of the annotation at the given index. + * + * @param int $index + * + * @return null|int + */ + public function getAnnotationEnd($index) + { + $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 index of the close brace that matches the open brace at the given index. + * + * @param int $index + * + * @return null|int + */ + public function getArrayEnd($index) + { + $level = 1; + for (++$index, $max = \count($this); $index < $max; ++$index) { + if ($this[$index]->isType(DocLexer::T_OPEN_CURLY_BRACES)) { + ++$level; + } elseif ($this[$index]->isType($index, DocLexer::T_CLOSE_CURLY_BRACES)) { + --$level; + } + + if (0 === $level) { + return $index; + } + } + + return null; + } + + /** + * Returns the code from the tokens. + * + * @return string + */ + public function getCode() + { + $code = ''; + foreach ($this as $token) { + $code .= $token->getContent(); + } + + return $code; + } + + /** + * Inserts a token at the given index. + * + * @param int $index + */ + public function insertAt($index, Token $token) + { + $this->setSize($this->getSize() + 1); + + for ($i = $this->getSize() - 1; $i > $index; --$i) { + $this[$i] = isset($this[$i - 1]) ? $this[$i - 1] : new Token(); + } + + $this[$index] = $token; + } + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException + */ + public function offsetSet($index, $token) + { + 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 + )); + } + + if (null === $index) { + $index = \count($this); + $this->setSize($this->getSize() + 1); + } + + parent::offsetSet($index, $token); + } + + /** + * {@inheritdoc} + * + * @throws \OutOfBoundsException + */ + public function offsetUnset($index) + { + 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); + } + + /** + * @param int $index + * @param int $direction + * + * @return null|int + */ + private function getMeaningfulTokenSibling($index, $direction) + { + while (true) { + $index += $direction; + + if (!$this->offsetExists($index)) { + break; + } + + if (!$this[$index]->isType(DocLexer::T_NONE)) { + return $index; + } + } + + return null; + } + + /** + * @param int $index + * @param string|string[] $type + * @param int $direction + * + * @return null|int + */ + private function getTokenOfTypeSibling($index, $type, $direction) + { + while (true) { + $index += $direction; + + if (!$this->offsetExists($index)) { + break; + } + + if ($this[$index]->isType($type)) { + return $index; + } + } + + return null; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Error/Error.php b/lib/composer/friendsofphp/php-cs-fixer/src/Error/Error.php new file mode 100644 index 0000000000000000000000000000000000000000..d7aae68a17b9f1d29c7a65b3ab45f80de095ebe5 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Error/Error.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\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. + */ + const TYPE_INVALID = 1; + + /** + * Error which has occurred during fixing phase. + */ + const TYPE_EXCEPTION = 2; + + /** + * Error which has occurred in linting phase, after applying any fixers. + */ + const TYPE_LINT = 3; + + /** + * @var int + */ + private $type; + + /** + * @var string + */ + private $filePath; + + /** + * @var null|\Throwable + */ + private $source; + + /** + * @var array + */ + private $appliedFixers; + + /** + * @var null|string + */ + private $diff; + + /** + * @param int $type + * @param string $filePath + * @param null|\Throwable $source + * @param null|string $diff + */ + public function __construct($type, $filePath, $source = null, array $appliedFixers = [], $diff = null) + { + $this->type = $type; + $this->filePath = $filePath; + $this->source = $source; + $this->appliedFixers = $appliedFixers; + $this->diff = $diff; + } + + /** + * @return string + */ + public function getFilePath() + { + return $this->filePath; + } + + /** + * @return null|\Throwable + */ + public function getSource() + { + return $this->source; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * @return array + */ + public function getAppliedFixers() + { + return $this->appliedFixers; + } + + /** + * @return null|string + */ + public function getDiff() + { + return $this->diff; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.php b/lib/composer/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.php new file mode 100644 index 0000000000000000000000000000000000000000..a5d09596b23ca52bc7801cd67902f0316ce38d4b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.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\Error; + +/** + * Manager of errors that occur during fixing. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class ErrorsManager +{ + /** + * @var Error[] + */ + private $errors = []; + + /** + * Returns errors reported during linting before fixing. + * + * @return Error[] + */ + public function getInvalidErrors() + { + return array_filter($this->errors, static function (Error $error) { + return Error::TYPE_INVALID === $error->getType(); + }); + } + + /** + * Returns errors reported during fixing. + * + * @return Error[] + */ + public function getExceptionErrors() + { + return array_filter($this->errors, static function (Error $error) { + return Error::TYPE_EXCEPTION === $error->getType(); + }); + } + + /** + * Returns errors reported during linting after fixing. + * + * @return Error[] + */ + public function getLintErrors() + { + return array_filter($this->errors, static function (Error $error) { + return Error::TYPE_LINT === $error->getType(); + }); + } + + /** + * Returns true if no errors were reported. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->errors); + } + + public function report(Error $error) + { + $this->errors[] = $error; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Event/Event.php b/lib/composer/friendsofphp/php-cs-fixer/src/Event/Event.php new file mode 100644 index 0000000000000000000000000000000000000000..81313365bc021a162ec0b2b6a1299edca82530c2 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Event/Event.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\Event; + +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +// Since PHP-CS-FIXER is PHP 5.6 compliant we can't always use Symfony Contracts (currently needs PHP ^7.1.3) +// This conditional inheritance will be useless when PHP-CS-FIXER no longer supports PHP versions +// inferior to Symfony/Contracts PHP minimal version +if (is_subclass_of(EventDispatcher::class, EventDispatcherInterface::class)) { + class Event extends \Symfony\Contracts\EventDispatcher\Event + { + } +} else { + class Event extends \Symfony\Component\EventDispatcher\Event + { + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FileReader.php b/lib/composer/friendsofphp/php-cs-fixer/src/FileReader.php new file mode 100644 index 0000000000000000000000000000000000000000..2e4736e8deb9b9a9ea6a384ec67d514487856a2e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FileReader.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; + +/** + * 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 can not. + * Consecutive try will provide empty content for stdin-alike file. + * This reader unifies access to them. + * + * @internal + */ +final class FileReader +{ + /** + * @var null|self + */ + private static $instance; + + /** + * @var null|string + */ + private $stdinContent; + + /** + * @return self + */ + public static function createSingleton() + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * @param string $filePath + * + * @return string + */ + public function read($filePath) + { + if ('php://stdin' === $filePath) { + if (null === $this->stdinContent) { + $this->stdinContent = $this->readRaw($filePath); + } + + return $this->stdinContent; + } + + return $this->readRaw($filePath); + } + + /** + * @param string $realPath + * + * @return string + */ + private function readRaw($realPath) + { + $content = @file_get_contents($realPath); + + if (false === $content) { + $error = error_get_last(); + + throw new \RuntimeException(sprintf( + 'Failed to read content from "%s".%s', + $realPath, + $error ? ' '.$error['message'] : '' + )); + } + + return $content; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FileRemoval.php b/lib/composer/friendsofphp/php-cs-fixer/src/FileRemoval.php new file mode 100644 index 0000000000000000000000000000000000000000..2ba59884116612fadae2cb7c2569082d30641aa3 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FileRemoval.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; + +/** + * 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 $files = []; + + public function __construct() + { + register_shutdown_function([$this, 'clean']); + } + + public function __destruct() + { + $this->clean(); + } + + /** + * Adds a file to be removed. + * + * @param string $path + */ + public function observe($path) + { + $this->files[$path] = true; + } + + /** + * Removes a file from shutdown removal. + * + * @param string $path + */ + public function delete($path) + { + if (isset($this->files[$path])) { + unset($this->files[$path]); + } + $this->unlink($path); + } + + /** + * Removes attached files. + */ + public function clean() + { + foreach ($this->files as $file => $value) { + $this->unlink($file); + } + $this->files = []; + } + + private function unlink($path) + { + @unlink($path); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Finder.php b/lib/composer/friendsofphp/php-cs-fixer/src/Finder.php new file mode 100644 index 0000000000000000000000000000000000000000..ba4fad2a26a3bebdbe34de6e6196668c0075c41b --- /dev/null +++ b/lib/composer/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') + ->ignoreDotFiles(true) + ->ignoreVCS(true) + ->exclude('vendor') + ; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..09d63df50e834bad0e8bfa418bdb6e92e6079eca --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php @@ -0,0 +1,145 @@ + + * Dariusz Rumiń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\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class BacktickToShellExecFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound('`'); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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. + */ + public function getPriority() + { + return 2; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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. + */ + private function fixBackticks(Tokens $tokens, array $backtickTokens) + { + // Track indexes for final override + ksort($backtickTokens); + $openingBacktickIndex = key($backtickTokens); + end($backtickTokens); + $closingBacktickIndex = key($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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..6186c1ffb24e4a530407c2a1acfd31806332eb86 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.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\Fixer\Alias; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\PregException; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; + +/** + * @author Matteo Beccati + */ +final class EregToPregFixer extends AbstractFixer +{ + /** + * @var array 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 $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 array the list of preg delimiters, in order of preference + */ + private static $delimiters = ['/', '#', '!']; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replace deprecated `ereg` regular expression functions with `preg`.', + [new CodeSample("isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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 (null !== $currIndex) { + $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 indexes, i.e.: + // 0 => function name, + // 1 => bracket "(" + // 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(); + $string = substr($regexTokenContent, 1, -1); + $quote = $regexTokenContent[0]; + $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, $quote.$preg.$quote]); + } + } + } + + /** + * Check the validity of a PCRE. + * + * @param string $pattern the regular expression + * + * @return bool + */ + private function checkPreg($pattern) + { + 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($pattern) + { + // try do find something that's not used + $delimiters = []; + foreach (self::$delimiters as $k => $d) { + if (false === strpos($pattern, $d)) { + return $d; + } + + $delimiters[$d] = [substr_count($pattern, $d), $k]; + } + + // return the least used delimiter, using the position in the list as a tie breaker + uasort($delimiters, static function ($a, $b) { + if ($a[0] === $b[0]) { + return Utils::cmpInt($a, $b); + } + + return $a[0] < $b[0] ? -1 : 1; + }); + + return key($delimiters); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..8c61cb748e6038cfb0143853b8439beca2b11a1b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.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\Alias; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class MbStrFunctionsFixer extends AbstractFunctionReferenceFixer +{ + /** + * @var array the list of the string-related function names and their mb_ equivalent + */ + private static $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 + */ + private $functions; + + public function __construct() + { + parent::__construct(); + + $this->functions = array_filter( + self::$functionsMap, + static function (array $mapping) { + return \function_exists($mapping['alternativeName']) && (new \ReflectionFunction($mapping['alternativeName']))->isInternal(); + } + ); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replace non multibyte-safe functions with corresponding mb function.', + [ + new CodeSample( + 'isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + foreach ($this->functions as $functionIdentity => $functionReplacement) { + $currIndex = 0; + while (null !== $currIndex) { + // 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; + } + + list($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']]); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..8fe7ae65ba1533be88d79a587cd137d8d8ae6908 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.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\Alias; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 ConfigurationDefinitionFixerInterface +{ + /** @var array stores alias (key) - master (value) functions mapping */ + private $aliases = []; + + /** @var array stores alias (key) - master (value) functions mapping */ + private static $internalSet = [ + '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', + ]; + + /** @var array stores alias (key) - master (value) functions mapping */ + private static $imapSet = [ + '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', + ]; + + /** @var array stores alias (key) - master (value) functions mapping */ + private static $mbregSet = [ + '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', + ]; + + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->aliases = []; + foreach ($this->configuration['sets'] as $set) { + if ('@all' === $set) { + $this->aliases = self::$internalSet; + $this->aliases = array_merge($this->aliases, self::$imapSet); + $this->aliases = array_merge($this->aliases, self::$mbregSet); + + break; + } + if ('@internal' === $set) { + $this->aliases = array_merge($this->aliases, self::$internalSet); + } elseif ('@IMAP' === $set) { + $this->aliases = array_merge($this->aliases, self::$imapSet); + } elseif ('@mbreg' === $set) { + $this->aliases = array_merge($this->aliases, self::$mbregSet); + } + } + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + /** @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 + $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; + if (!$nextToken->equals('(')) { + continue; + } + + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + $tokens[$index] = new Token([T_STRING, $this->aliases[$tokenContent]]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $sets = ['@internal', '@IMAP', '@mbreg', '@all']; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('sets', 'List of sets to fix. Defined sets are `@internal` (native functions), `@IMAP` (IMAP functions), `@mbreg` (from `ext-mbstring`) `@all` (all listed sets).')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($sets)]) + ->setDefault(['@internal', '@IMAP']) + ->getOption(), + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..bf664fdefe2e4e14705dda82b184160a66bf48ac --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.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\Alias; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Sullivan Senechal + * @author SpacePossum + */ +final class NoMixedEchoPrintFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @deprecated will be removed in 3.0 + */ + public static $defaultConfig = ['use' => 'echo']; + + /** + * @var string + */ + private $callBack; + + /** + * @var int T_ECHO or T_PRINT + */ + private $candidateTokenType; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + if ('echo' === $this->configuration['use']) { + $this->candidateTokenType = T_PRINT; + $this->callBack = 'fixPrintToEcho'; + } else { + $this->candidateTokenType = T_ECHO; + $this->callBack = 'fixEchoToPrint'; + } + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Either language construct `print` or `echo` should be used.', + [ + new CodeSample(" 'print']), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after NoShortEchoTagFixer. + */ + public function getPriority() + { + return -10; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound($this->candidateTokenType); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $callBack = $this->callBack; + foreach ($tokens as $index => $token) { + if ($token->isGivenKind($this->candidateTokenType)) { + $this->{$callBack}($tokens, $index); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('use', 'The desired language construct.')) + ->setAllowedValues(['print', 'echo']) + ->setDefault('echo') + ->getOption(), + ]); + } + + /** + * @param int $index + */ + private function fixEchoToPrint(Tokens $tokens, $index) + { + $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']); + } + + /** + * @param int $index + */ + private function fixPrintToEcho(Tokens $tokens, $index) + { + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + + if (!$prevToken->equalsAny([';', '{', '}', [T_OPEN_TAG]])) { + return; + } + + $tokens[$index] = new Token([T_ECHO, 'echo']); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..a83ba8714229529f63860a515efe116beed78bd3 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.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\Alias; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class PowToExponentiationFixer extends AbstractFunctionReferenceFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + // minimal candidate to fix is seven tokens: pow(x,x); + return $tokens->count() > 7 && $tokens->isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 + // indexes 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; + } + + $numberOfTokensAdded += $this->fixPowToExponentiation( + $tokens, + $candidate[0], // functionNameIndex, + $candidate[1], // openParenthesisIndex, + $candidate[2], // closeParenthesisIndex, + $arguments + ); + } + } + + /** + * @return array[] + */ + private function findPowCalls(Tokens $tokens) + { + $candidates = []; + + // Minimal candidate to fix is seven tokens: pow(x,x); + $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 int $functionNameIndex + * @param int $openParenthesisIndex + * @param int $closeParenthesisIndex + * @param array $arguments + * + * @return int number of tokens added to the collection + */ + private function fixPowToExponentiation(Tokens $tokens, $functionNameIndex, $openParenthesisIndex, $closeParenthesisIndex, array $arguments) + { + // 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 parenthesis + 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); + + $prev = $tokens->getPrevMeaningfulToken($functionNameIndex); + if ($tokens[$prev]->isGivenKind(T_NS_SEPARATOR)) { + $tokens->clearAt($prev); + } + + return $added; + } + + /** + * @param int $argumentStartIndex + * @param int $argumentEndIndex + * + * @return bool + */ + private function isParenthesisNeeded(Tokens $tokens, $argumentStartIndex, $argumentEndIndex) + { + static $allowedKinds = [ + T_DNUMBER, T_LNUMBER, T_VARIABLE, T_STRING, T_OBJECT_OPERATOR, 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, + ]; + + for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) { + if ($tokens[$i]->isGivenKind($allowedKinds) || $tokens->isEmptyAt($i)) { + continue; + } + + if (null !== $blockType = Tokens::detectBlockType($tokens[$i])) { + $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; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..665948e6503ce4556f6bd799df82faaff16162e3 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.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\Alias; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 ConfigurationDefinitionFixerInterface +{ + /** + * @var array + */ + private static $argumentCounts = [ + 'getrandmax' => [0], + 'mt_rand' => [1, 2], + 'rand' => [0, 2], + 'srand' => [0, 1], + ]; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + foreach ($this->configuration['replacements'] as $functionName => $replacement) { + $this->configuration['replacements'][$functionName] = [ + 'alternativeName' => $replacement, + 'argumentCount' => self::$argumentCounts[$functionName], + ]; + } + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replaces `rand`, `srand`, `getrandmax` functions calls with their `mt_*` analogs.', + [ + new CodeSample(" ['getrandmax' => 'mt_getrandmax']] + ), + ], + null, + 'Risky when the configured functions are overridden.' + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + foreach ($this->configuration['replacements'] as $functionIdentity => $functionReplacement) { + if ($functionIdentity === $functionReplacement['alternativeName']) { + continue; + } + + $currIndex = 0; + while (null !== $currIndex) { + // 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; + } + + list($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; + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolverRootless('replacements', [ + (new FixerOptionBuilder('replacements', 'Mapping between replaced functions with the new ones.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function ($value) { + 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, + \is_object($replacement) ? \get_class($replacement) : \gettype($replacement) + )); + } + } + + return true; + }]) + ->setDefault([ + 'getrandmax' => 'mt_getrandmax', + 'rand' => 'mt_rand', + 'srand' => 'mt_srand', + ]) + ->getOption(), + ], $this->getName()); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..71c2b8f03fecca778a3a38a420a24e89508a8929 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php @@ -0,0 +1,249 @@ + + * Dariusz Rumiń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\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class SetTypeToCastFixer extends AbstractFunctionReferenceFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Cast shall be used, not `settype`.', + [ + new CodeSample( + 'isAllTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_STRING, T_VARIABLE]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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]->isGivenKind(T_OPEN_TAG) && !$tokens[$prev]->equalsAny([';', '{'])) { + 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->findSettypeNullCall($tokens, $functionNameIndex, $argumentToken); + } else { + $this->fixSettypeCall($tokens, $functionNameIndex, $argumentToken, new Token($map[$type])); + } + } + } + + private function findSettypeCalls(Tokens $tokens) + { + $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; + } + + /** + * @param int $functionNameIndex + * @param int $openParenthesisIndex + * @param int $firstArgumentStart + * @param int $commaIndex + * @param int $secondArgumentStart + * @param int $closeParenthesisIndex + */ + private function removeSettypeCall( + Tokens $tokens, + $functionNameIndex, + $openParenthesisIndex, + $firstArgumentStart, + $commaIndex, + $secondArgumentStart, + $closeParenthesisIndex + ) { + $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(); + } + + /** + * @param int $functionNameIndex + */ + private function fixSettypeCall( + Tokens $tokens, + $functionNameIndex, + Token $argumentToken, + Token $castToken + ) { + $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 + } + + /** + * @param int $functionNameIndex + */ + private function findSettypeNullCall( + Tokens $tokens, + $functionNameIndex, + Token $argumentToken + ) { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..eaaebf322bf4d1bcbf9eed0d750e3410b1141196 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.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\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + * @author Sebastiaan Stok + * @author Dariusz Rumiński + * @author SpacePossum + */ +final class ArraySyntaxFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + private $candidateTokenKind; + private $fixCallback; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->resolveCandidateTokenKind(); + $this->resolveFixCallback(); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHP arrays should be declared using the configured syntax.', + [ + new CodeSample( + " 'short'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BinaryOperatorSpacesFixer, TernaryOperatorSpacesFixer. + */ + public function getPriority() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound($this->candidateTokenKind); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $callback = $this->fixCallback; + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + if ($tokens[$index]->isGivenKind($this->candidateTokenKind)) { + $this->{$callback}($tokens, $index); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('syntax', 'Whether to use the `long` or `short` array syntax.')) + ->setAllowedValues(['long', 'short']) + ->setDefault('long') + ->getOption(), + ]); + } + + /** + * @param int $index + */ + private function fixToLongArraySyntax(Tokens $tokens, $index) + { + $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'])); + } + + /** + * @param int $index + */ + private function fixToShortArraySyntax(Tokens $tokens, $index) + { + $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() + { + $this->fixCallback = sprintf('fixTo%sArraySyntax', ucfirst($this->configuration['syntax'])); + } + + private function resolveCandidateTokenKind() + { + $this->candidateTokenKind = 'long' === $this->configuration['syntax'] ? CT::T_ARRAY_SQUARE_BRACE_OPEN : T_ARRAY; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..cd24bf8ee4a64cf53edc0adf456a5f64bd623b67 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.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\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Carlos Cirello + * @author Dariusz Rumiński + * @author Graham Campbell + */ +final class NoMultilineWhitespaceAroundDoubleArrowFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Operator `=>` should not be surrounded by multi-line whitespaces.', + [new CodeSample(" 2);\n")] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BinaryOperatorSpacesFixer, TrailingCommaInMultilineArrayFixer. + */ + public function getPriority() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOUBLE_ARROW); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOUBLE_ARROW)) { + continue; + } + + $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); + } + } + } + + /** + * @param int $index + */ + private function fixWhitespace(Tokens $tokens, $index) + { + $token = $tokens[$index]; + + if ($token->isWhitespace() && !$token->isWhitespace(" \t")) { + $tokens[$index] = new Token([T_WHITESPACE, rtrim($token->getContent()).' ']); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..f2a10172e7c1f86ffe28943574184923ec2f0acf --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.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\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Dariusz Rumiński + * @author Sebastiaan Stok + */ +final class NoTrailingCommaInSinglelineArrayFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHP single-line arrays should not have trailing comma.', + [new CodeSample("isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($index = 0, $c = $tokens->count(); $index < $c; ++$index) { + if ($tokensAnalyzer->isArray($index)) { + $this->fixArray($tokens, $index); + } + } + } + + /** + * @param int $index + */ + private function fixArray(Tokens $tokens, $index) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + if ($tokensAnalyzer->isArrayMultiLine($index)) { + return; + } + + $startIndex = $index; + + if ($tokens[$startIndex]->isGivenKind(T_ARRAY)) { + $startIndex = $tokens->getNextTokenOfKind($startIndex, ['(']); + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); + } else { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); + } + + $beforeEndIndex = $tokens->getPrevMeaningfulToken($endIndex); + $beforeEndToken = $tokens[$beforeEndIndex]; + + if ($beforeEndToken->equals(',')) { + $tokens->removeTrailingWhitespace($beforeEndIndex); + $tokens->clearAt($beforeEndIndex); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..266be3d70da0130d6908de3eb9fb38a895198358 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.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\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author Adam Marczuk + */ +final class NoWhitespaceBeforeCommaInArrayFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'In array declaration, there MUST NOT be a whitespace before each comma.', + [ + new CodeSample(" true] + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + $this->fixSpacing($index, $tokens); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('after_heredoc', 'Whether the whitespace between heredoc end and comma should be removed.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->setNormalizer(static function (Options $options, $value) { + if (\PHP_VERSION_ID < 70300 && $value) { + throw new InvalidOptionsForEnvException('"after_heredoc" option can only be enabled with PHP 7.3+.'); + } + + return $value; + }) + ->getOption(), + ]); + } + + /** + * Method to fix spacing in array declaration. + * + * @param int $index + */ + private function fixSpacing($index, Tokens $tokens) + { + 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() && + ($this->configuration['after_heredoc'] || !$tokens[$prevIndex]->equals([T_END_HEREDOC])) + ) { + $tokens->removeLeadingWhitespace($i); + } + } + } + + /** + * Method to move index over the non-array elements like function calls or function declarations. + * + * @param int $index + * + * @return int New index + */ + private function skipNonArrayElements($index, Tokens $tokens) + { + 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; + } + } + + return $index; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..733dc2309d9929d9bb8507e7e939ba5d32af2146 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.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\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class NormalizeIndexBraceFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Array index should always be written by using square braces.', + [new CodeSample("isTokenKindFound(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrailingCommaInMultilineArrayFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrailingCommaInMultilineArrayFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..7ab031d696a89d2708d9fa983da80e4f7c10f1b5 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrailingCommaInMultilineArrayFixer.php @@ -0,0 +1,147 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 + */ +final class TrailingCommaInMultilineArrayFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHP multi-line arrays should have a trailing comma.', + [ + new CodeSample(" true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after NoMultilineWhitespaceAroundDoubleArrowFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if ($tokensAnalyzer->isArray($index) && $tokensAnalyzer->isArrayMultiLine($index)) { + $this->fixArray($tokens, $index); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('after_heredoc', 'Whether a trailing comma should also be placed after heredoc end.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->setNormalizer(static function (Options $options, $value) { + if (\PHP_VERSION_ID < 70300 && $value) { + throw new InvalidOptionsForEnvException('"after_heredoc" option can only be enabled with PHP 7.3+.'); + } + + return $value; + }) + ->getOption(), + ]); + } + + /** + * @param int $index + */ + private function fixArray(Tokens $tokens, $index) + { + $startIndex = $index; + + if ($tokens[$startIndex]->isGivenKind(T_ARRAY)) { + $startIndex = $tokens->getNextTokenOfKind($startIndex, ['(']); + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); + } else { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); + } + + $beforeEndIndex = $tokens->getPrevMeaningfulToken($endIndex); + $beforeEndToken = $tokens[$beforeEndIndex]; + + // if there is some item between braces then add `,` after it + if ( + $startIndex !== $beforeEndIndex && !$beforeEndToken->equals(',') && + ($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, ' '); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..a5bbc67cdfe41822946e5b4d82be90cca7e5e45e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.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\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jared Henderson + */ +final class TrimArraySpacesFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = 0, $c = $tokens->count(); $index < $c; ++$index) { + if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + self::fixArray($tokens, $index); + } + } + } + + /** + * Method to trim leading/trailing whitespace within single line arrays. + * + * @param int $index + */ + private static function fixArray(Tokens $tokens, $index) + { + $startIndex = $index; + + if ($tokens[$startIndex]->isGivenKind(T_ARRAY)) { + $startIndex = $tokens->getNextMeaningfulToken($startIndex); + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_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") + || '/*' === substr($nextNonWhitespaceToken->getContent(), 0, 2) + ) + ) { + $tokens->clearAt($nextIndex); + } + + if ( + $prevToken->isWhitespace(" \t") + && !$prevNonWhitespaceToken->equals(',') + ) { + $tokens->clearAt($prevIndex); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..bda2e59f156fc8cdcb28345196f852655e9ee50a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.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\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Adam Marczuk + */ +final class WhitespaceAfterCommaInArrayFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'In array declaration, there MUST be a whitespace after each comma.', + [new CodeSample("isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + $this->fixSpacing($index, $tokens); + } + } + } + + /** + * Method to fix spacing in array declaration. + * + * @param int $index + */ + private function fixSpacing($index, Tokens $tokens) + { + 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(',') && !$tokens[$i + 1]->isWhitespace()) { + $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' '])); + } + } + } + + /** + * Method to move index over the non-array elements like function calls or function declarations. + * + * @param int $index + * + * @return int New index + */ + private function skipNonArrayElements($index, Tokens $tokens) + { + 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; + } + } + + return $index; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..3af76754caa1f89ed8ac7df8259784e3808e8570 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php @@ -0,0 +1,1070 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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.1, ¶4.4, ¶5. + * + * @author Dariusz Rumiński + */ +final class BracesFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @internal + */ + const LINE_NEXT = 'next'; + + /** + * @internal + */ + const LINE_SAME = 'same'; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 ArrayIndentationFixer, MethodArgumentSpaceFixer, MethodChainingIndentationFixer. + * Must run after ClassAttributesSeparationFixer, ElseifFixer, LineEndingFixer, MethodSeparationFixer, NoAlternativeSyntaxFixer, NoEmptyStatementFixer, NoUselessElseFixer, SingleTraitInsertPerStatementFixer. + */ + public function getPriority() + { + return -25; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $this->fixCommentBeforeBrace($tokens); + $this->fixMissingControlBraces($tokens); + $this->fixIndents($tokens); + $this->fixControlContinuationBraces($tokens); + $this->fixSpaceAroundToken($tokens); + $this->fixDoWhile($tokens); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (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(), + ]); + } + + private function fixCommentBeforeBrace(Tokens $tokens) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $controlTokens = $this->getControlTokens(); + + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind($controlTokens)) { + $prevIndex = $this->findParenthesisEnd($tokens, $index); + } elseif ( + ($token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index)) || + ($token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) + ) { + $prevIndex = $tokens->getNextTokenOfKind($index, ['{']); + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + } else { + continue; + } + + $commentIndex = $tokens->getNextNonWhitespace($prevIndex); + $commentToken = $tokens[$commentIndex]; + + if (!$commentToken->isGivenKind(T_COMMENT) || 0 === strpos($commentToken->getContent(), '/*')) { + continue; + } + + $braceIndex = $tokens->getNextMeaningfulToken($commentIndex); + $braceToken = $tokens[$braceIndex]; + + if (!$braceToken->equals('{')) { + continue; + } + + /** @var Token $tokenTmp */ + $tokenTmp = $tokens[$braceIndex]; + + $newBraceIndex = $prevIndex + 1; + for ($i = $braceIndex; $i > $newBraceIndex; --$i) { + // we might be moving one white space next to another, these have to be merged + /** @var Token $previousToken */ + $previousToken = $tokens[$i - 1]; + $tokens[$i] = $previousToken; + if ($tokens[$i]->isWhitespace() && $tokens[$i + 1]->isWhitespace()) { + $tokens[$i] = new Token([T_WHITESPACE, $tokens[$i]->getContent().$tokens[$i + 1]->getContent()]); + $tokens->clearAt($i + 1); + } + } + + $tokens[$newBraceIndex] = $tokenTmp; + $c = $tokens[$braceIndex]->getContent(); + if (substr_count($c, "\n") > 1) { + // left trim till last line break + $tokens[$braceIndex] = new Token([T_WHITESPACE, substr($c, strrpos($c, "\n"))]); + } + } + } + + private function fixControlContinuationBraces(Tokens $tokens) + { + $controlContinuationTokens = $this->getControlContinuationTokens(); + + for ($index = \count($tokens) - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind($controlContinuationTokens)) { + continue; + } + + $prevIndex = $tokens->getPrevNonWhitespace($index); + $prevToken = $tokens[$prevIndex]; + + if (!$prevToken->equals('}')) { + continue; + } + + $tokens->ensureWhitespaceAtIndex( + $index - 1, + 1, + self::LINE_NEXT === $this->configuration['position_after_control_structures'] ? + $this->whitespacesConfig->getLineEnding().$this->detectIndent($tokens, $index) + : ' ' + ); + } + } + + private function fixDoWhile(Tokens $tokens) + { + for ($index = \count($tokens) - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_DO)) { + continue; + } + + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); + $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex); + $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex); + $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($endBraceIndex); + $nextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex]; + + if (!$nextNonWhitespaceToken->isGivenKind(T_WHILE)) { + continue; + } + + $tokens->ensureWhitespaceAtIndex($nextNonWhitespaceIndex - 1, 1, ' '); + } + } + + private function fixIndents(Tokens $tokens) + { + $classyTokens = Token::getClassyTokenKinds(); + $classyAndFunctionTokens = array_merge([T_FUNCTION], $classyTokens); + $controlTokens = $this->getControlTokens(); + $indentTokens = array_filter( + array_merge($classyAndFunctionTokens, $controlTokens), + static function ($item) { + return T_SWITCH !== $item; + } + ); + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($index = 0, $limit = \count($tokens); $index < $limit; ++$index) { + $token = $tokens[$index]; + + // if token is not a structure element - continue + if (!$token->isGivenKind($indentTokens)) { + continue; + } + + // do not change indent for `while` in `do ... while ...` + if ( + $token->isGivenKind(T_WHILE) + && $tokensAnalyzer->isWhilePartOfDoWhile($index) + ) { + continue; + } + + if ( + $this->configuration['allow_single_line_closure'] + && $token->isGivenKind(T_FUNCTION) + && $tokensAnalyzer->isLambda($index) + ) { + $braceEndIndex = $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_CURLY_BRACE, + $tokens->getNextTokenOfKind($index, ['{']) + ); + + if (!$this->isMultilined($tokens, $index, $braceEndIndex)) { + $index = $braceEndIndex; + + continue; + } + } + + if ($token->isGivenKind($classyAndFunctionTokens)) { + $startBraceIndex = $tokens->getNextTokenOfKind($index, [';', '{']); + $startBraceToken = $tokens[$startBraceIndex]; + } else { + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); + $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex); + $startBraceToken = $tokens[$startBraceIndex]; + } + + // structure without braces block - nothing to do, e.g. do { } while (true); + if (!$startBraceToken->equals('{')) { + continue; + } + + $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($startBraceIndex, " \t"); + $nextNonWhitespace = $tokens[$nextNonWhitespaceIndex]; + + /* if CLOSE_TAG is after { on the same line, do not indent. e.g. */ + if ($nextNonWhitespace->isGivenKind(T_CLOSE_TAG)) { + continue; + } + + /* if CLOSE_TAG is after { on the next line and a comment on this line, do not indent. e.g. */ + if ($nextNonWhitespace->isComment() && $tokens[$tokens->getNextMeaningfulToken($nextNonWhitespaceIndex)]->isGivenKind(T_CLOSE_TAG)) { + continue; + } + + $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex); + + $indent = $this->detectIndent($tokens, $index); + + // fix indent near closing brace + $tokens->ensureWhitespaceAtIndex($endBraceIndex - 1, 1, $this->whitespacesConfig->getLineEnding().$indent); + + // fix indent between braces + $lastCommaIndex = $tokens->getPrevTokenOfKind($endBraceIndex - 1, [';', '}']); + + $nestLevel = 1; + for ($nestIndex = $lastCommaIndex; $nestIndex >= $startBraceIndex; --$nestIndex) { + $nestToken = $tokens[$nestIndex]; + + if ($nestToken->equalsAny([')', [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE]])) { + $nestIndex = $tokens->findBlockStart( + $nestToken->equals(')') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION, + $nestIndex + ); + + continue; + } + + if (1 === $nestLevel) { + // Next token is the beginning of a line that can be indented when + // the current token is a `;`, a `}` or the opening `{` of current + // scope. Current token may also be a comment that follows `;` or + // `}`, in which case indentation will only be fixed if this + // comment is followed by a newline. + $nextLineCanBeIndented = false; + if ($nestToken->equalsAny([';', '}'])) { + $nextLineCanBeIndented = true; + } elseif ($this->isCommentWithFixableIndentation($tokens, $nestIndex)) { + for ($i = $nestIndex; $i > $startBraceIndex; --$i) { + if ($tokens[$i]->equalsAny([';', '}'])) { + $nextLineCanBeIndented = true; + + break; + } + + if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) { + break; + } + } + + if ($nextLineCanBeIndented || $i === $startBraceIndex) { + $nextToken = $tokens[$nestIndex + 1]; + $nextLineCanBeIndented = $nextToken->isWhitespace() && 1 === Preg::match('/\R/', $nextToken->getContent()); + } + } + + if (!$nextLineCanBeIndented) { + continue; + } + + $nextNonWhitespaceNestIndex = $tokens->getNextNonWhitespace($nestIndex); + $nextNonWhitespaceNestToken = $tokens[$nextNonWhitespaceNestIndex]; + + if ( + // next Token is not a comment on its own line + !($nextNonWhitespaceNestToken->isComment() && ( + !$tokens[$nextNonWhitespaceNestIndex - 1]->isWhitespace() + || !Preg::match('/\R/', $tokens[$nextNonWhitespaceNestIndex - 1]->getContent()) + )) && + // and it is not a `$foo = function () {};` situation + !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equalsAny([';', ',', ']', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]])) && + // and it is not a `Foo::{bar}()` situation + !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equals('(')) && + // and it is not a `${"a"}->...` and `${"b{$foo}"}->...` situation + !($nestToken->equals('}') && $tokens[$nestIndex - 1]->equalsAny(['"', "'", [T_CONSTANT_ENCAPSED_STRING], [T_VARIABLE]])) && + // and next token is not a closing tag that would break heredoc/nowdoc syntax + !($tokens[$nestIndex - 1]->isGivenKind(T_END_HEREDOC) && $nextNonWhitespaceNestToken->isGivenKind(T_CLOSE_TAG)) + ) { + if ( + ( + self::LINE_NEXT !== $this->configuration['position_after_control_structures'] + && $nextNonWhitespaceNestToken->isGivenKind($this->getControlContinuationTokens()) + && !$tokens[$tokens->getPrevNonWhitespace($nextNonWhitespaceNestIndex)]->isComment() + ) + || $nextNonWhitespaceNestToken->isGivenKind(T_CLOSE_TAG) + || ( + self::LINE_NEXT !== $this->configuration['position_after_control_structures'] + && $nextNonWhitespaceNestToken->isGivenKind(T_WHILE) + && $tokensAnalyzer->isWhilePartOfDoWhile($nextNonWhitespaceNestIndex) + ) + ) { + $whitespace = ' '; + } else { + $nextToken = $tokens[$nestIndex + 1]; + $nextWhitespace = ''; + + if ($nextToken->isWhitespace()) { + $nextWhitespace = rtrim($nextToken->getContent(), " \t"); + + if ('' !== $nextWhitespace) { + $nextWhitespace = Preg::replace( + sprintf('/%s$/', $this->whitespacesConfig->getLineEnding()), + '', + $nextWhitespace, + 1 + ); + } + } + + $whitespace = $nextWhitespace.$this->whitespacesConfig->getLineEnding().$indent; + + if (!$nextNonWhitespaceNestToken->equals('}')) { + $determineIsIndentableBlockContent = static function ($contentIndex) use ($tokens) { + if (!$tokens[$contentIndex]->isComment()) { + return true; + } + + if (!$tokens[$tokens->getPrevMeaningfulToken($contentIndex)]->equals(';')) { + return true; + } + + $nextIndex = $tokens->getNextMeaningfulToken($contentIndex); + + if (!$tokens[$nextIndex]->equals('}')) { + return true; + } + + $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); + + if (null === $nextNextIndex) { + return true; + } + + if ($tokens[$nextNextIndex]->equalsAny([ + [T_ELSE], + [T_ELSEIF], + ',', + ])) { + return false; + } + + return true; + }; + + // add extra indent only if current content is not a comment for content outside of current block + if ($determineIsIndentableBlockContent($nestIndex + 2)) { + $whitespace .= $this->whitespacesConfig->getIndent(); + } + } + } + + $this->ensureWhitespaceAtIndexAndIndentMultilineComment($tokens, $nestIndex + 1, $whitespace); + } + } + + if ($nestToken->equals('}')) { + ++$nestLevel; + + continue; + } + + if ($nestToken->equals('{')) { + --$nestLevel; + + continue; + } + } + + // fix indent near opening brace + if (isset($tokens[$startBraceIndex + 2]) && $tokens[$startBraceIndex + 2]->equals('}')) { + $tokens->ensureWhitespaceAtIndex($startBraceIndex + 1, 0, $this->whitespacesConfig->getLineEnding().$indent); + } else { + $nextToken = $tokens[$startBraceIndex + 1]; + $nextNonWhitespaceToken = $tokens[$tokens->getNextNonWhitespace($startBraceIndex)]; + + // set indent only if it is not a case, when comment is following { on same line + if ( + !$nextNonWhitespaceToken->isComment() + || ($nextToken->isWhitespace() && 1 === substr_count($nextToken->getContent(), "\n")) // preserve blank lines + ) { + $this->ensureWhitespaceAtIndexAndIndentMultilineComment( + $tokens, + $startBraceIndex + 1, + $this->whitespacesConfig->getLineEnding().$indent.$this->whitespacesConfig->getIndent() + ); + } + } + + if ($token->isGivenKind($classyTokens) && !$tokensAnalyzer->isAnonymousClass($index)) { + if (self::LINE_SAME === $this->configuration['position_after_functions_and_oop_constructs'] && !$tokens[$tokens->getPrevNonWhitespace($startBraceIndex)]->isComment()) { + $ensuredWhitespace = ' '; + } else { + $ensuredWhitespace = $this->whitespacesConfig->getLineEnding().$indent; + } + + $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, $ensuredWhitespace); + } elseif ( + $token->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index) + || ( + self::LINE_NEXT === $this->configuration['position_after_control_structures'] && $token->isGivenKind($controlTokens) + || ( + self::LINE_NEXT === $this->configuration['position_after_anonymous_constructs'] + && ( + $token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index) + || $token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index) + ) + ) + ) + ) { + $isAnonymousClass = $token->isGivenKind($classyTokens) && $tokensAnalyzer->isAnonymousClass($index); + + $closingParenthesisIndex = $tokens->getPrevTokenOfKind($startBraceIndex, [')']); + if (null === $closingParenthesisIndex && !$isAnonymousClass) { + continue; + } + + if ( + !$isAnonymousClass + && $tokens[$closingParenthesisIndex - 1]->isWhitespace() + && false !== strpos($tokens[$closingParenthesisIndex - 1]->getContent(), "\n") + ) { + if (!$tokens[$startBraceIndex - 2]->isComment()) { + $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); + } + } else { + if ( + self::LINE_SAME === $this->configuration['position_after_functions_and_oop_constructs'] + && ( + $token->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index) + || $token->isGivenKind($classyTokens) && !$tokensAnalyzer->isAnonymousClass($index) + ) + && !$tokens[$tokens->getPrevNonWhitespace($startBraceIndex)]->isComment() + ) { + $ensuredWhitespace = ' '; + } else { + $ensuredWhitespace = $this->whitespacesConfig->getLineEnding().$indent; + } + + $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, $ensuredWhitespace); + } + } else { + $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); + } + + // reset loop limit due to collection change + $limit = \count($tokens); + } + } + + private function fixMissingControlBraces(Tokens $tokens) + { + $controlTokens = $this->getControlTokens(); + + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind($controlTokens)) { + continue; + } + + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); + $nextAfterParenthesisEndIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); + $tokenAfterParenthesis = $tokens[$nextAfterParenthesisEndIndex]; + + // if Token after parenthesis is { then we do not need to insert brace, but to fix whitespace before it + if ($tokenAfterParenthesis->equals('{') && self::LINE_SAME === $this->configuration['position_after_control_structures']) { + $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' '); + + continue; + } + + // do not add braces for cases: + // - structure without block, e.g. while ($iter->next()); + // - structure with block, e.g. while ($i) {...}, while ($i) : {...} endwhile; + if ($tokenAfterParenthesis->equalsAny([';', '{', ':'])) { + continue; + } + + // do not add for short 'if' followed by alternative loop, + // for example: if ($a) while ($b): ? > X < ?php endwhile; ? > + if ($tokenAfterParenthesis->isGivenKind([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(':')) { + continue; + } + } + + $statementEndIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); + + // insert closing brace + $tokens->insertAt($statementEndIndex + 1, [new Token([T_WHITESPACE, ' ']), new Token('}')]); + + // insert missing `;` if needed + if (!$tokens[$statementEndIndex]->equalsAny([';', '}'])) { + $tokens->insertAt($statementEndIndex + 1, new Token(';')); + } + + // insert opening brace + $tokens->insertAt($parenthesisEndIndex + 1, new Token('{')); + $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' '); + } + } + + private function fixSpaceAroundToken(Tokens $tokens) + { + $controlTokens = $this->getControlTokens(); + + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + // Declare tokens don't follow the same rules are other control statements + if ($token->isGivenKind(T_DECLARE)) { + $this->fixDeclareStatement($tokens, $index); + } elseif ($token->isGivenKind($controlTokens) || $token->isGivenKind(CT::T_USE_LAMBDA)) { + $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($index); + + if (!$tokens[$nextNonWhitespaceIndex]->equals(':')) { + $tokens->ensureWhitespaceAtIndex( + $index + 1, + 0, + self::LINE_NEXT === $this->configuration['position_after_control_structures'] && !$tokens[$nextNonWhitespaceIndex]->equals('(') ? + $this->whitespacesConfig->getLineEnding().$this->detectIndent($tokens, $index) + : ' ' + ); + } + + $prevToken = $tokens[$index - 1]; + + if (!$prevToken->isWhitespace() && !$prevToken->isComment() && !$prevToken->isGivenKind(T_OPEN_TAG)) { + $tokens->ensureWhitespaceAtIndex($index - 1, 1, ' '); + } + } + } + } + + /** + * @param int $index + * + * @return string + */ + private function detectIndent(Tokens $tokens, $index) + { + while (true) { + $whitespaceIndex = $tokens->getPrevTokenOfKind($index, [[T_WHITESPACE]]); + + if (null === $whitespaceIndex) { + return ''; + } + + $whitespaceToken = $tokens[$whitespaceIndex]; + + if (false !== strpos($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); + } + + /** + * @param int $structureTokenIndex + * + * @return int + */ + private function findParenthesisEnd(Tokens $tokens, $structureTokenIndex) + { + $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); + } + + /** + * @param int $parenthesisEndIndex + * + * @return int + */ + private function findStatementEnd(Tokens $tokens, $parenthesisEndIndex) + { + $nextIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); + $nextToken = $tokens[$nextIndex]; + + if (!$nextToken) { + return $parenthesisEndIndex; + } + + 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); + $nextToken = isset($nextIndex) ? $tokens[$nextIndex] : null; + if ($nextToken && $nextToken->isGivenKind($this->getControlContinuationTokensForOpeningToken($openingTokenKind))) { + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex); + + $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); + + if ($nextToken->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); + } + } + } + + private function getControlTokens() + { + 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; + } + + private function getControlContinuationTokens() + { + static $tokens = [ + T_CATCH, + T_ELSE, + T_ELSEIF, + T_FINALLY, + ]; + + return $tokens; + } + + private function getControlContinuationTokensForOpeningToken($openingTokenKind) + { + 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 []; + } + + private function getFinalControlContinuationTokensForOpeningToken($openingTokenKind) + { + if (T_IF === $openingTokenKind) { + return [T_ELSE]; + } + + if (T_TRY === $openingTokenKind) { + return [T_FINALLY]; + } + + return []; + } + + /** + * @param int $index + */ + private function fixDeclareStatement(Tokens $tokens, $index) + { + $tokens->removeTrailingWhitespace($index); + + $startParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(']); + $tokens->removeTrailingWhitespace($startParenthesisIndex); + + $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex); + $tokens->removeLeadingWhitespace($endParenthesisIndex); + + $startBraceIndex = $tokens->getNextTokenOfKind($endParenthesisIndex, [';', '{']); + $startBraceToken = $tokens[$startBraceIndex]; + + if ($startBraceToken->equals('{')) { + $this->fixSingleLineWhitespaceForDeclare($tokens, $startBraceIndex); + } + } + + /** + * @param int $startBraceIndex + */ + private function fixSingleLineWhitespaceForDeclare(Tokens $tokens, $startBraceIndex) + { + // fix single-line whitespace before { + // eg: `declare(ticks=1){` => `declare(ticks=1) {` + // eg: `declare(ticks=1) {` => `declare(ticks=1) {` + if ( + !$tokens[$startBraceIndex - 1]->isWhitespace() || + $tokens[$startBraceIndex - 1]->isWhitespace(" \t") + ) { + $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); + } + } + + /** + * @param int $index + * @param string $whitespace + */ + private function ensureWhitespaceAtIndexAndIndentMultilineComment(Tokens $tokens, $index, $whitespace) + { + if ($tokens[$index]->isWhitespace()) { + $nextTokenIndex = $tokens->getNextNonWhitespace($index); + } else { + $nextTokenIndex = $index; + } + + $nextToken = $tokens[$nextTokenIndex]; + if ($nextToken->isComment()) { + $previousToken = $tokens[$nextTokenIndex - 1]; + $nextTokenContent = $nextToken->getContent(); + + // do not indent inline comments used to comment out unused code + if ( + $previousToken->isWhitespace() + && 1 === Preg::match('/\R$/', $previousToken->getContent()) + && ( + (0 === strpos($nextTokenContent, '//'.$this->whitespacesConfig->getIndent()) || '//' === $nextTokenContent) + || (0 === strpos($nextTokenContent, '#'.$this->whitespacesConfig->getIndent()) || '#' === $nextTokenContent) + ) + ) { + return; + } + + $tokens[$nextTokenIndex] = new Token([ + $nextToken->getId(), + Preg::replace( + '/(\R)'.$this->detectIndent($tokens, $nextTokenIndex).'(\h*\S+.*)/', + '$1'.Preg::replace('/^.*\R(\h*)$/s', '$1', $whitespace).'$2', + $nextToken->getContent() + ), + ]); + } + + $tokens->ensureWhitespaceAtIndex($index, 0, $whitespace); + } + + /** + * @param int $startParenthesisIndex + * @param int $endParenthesisIndex + * + * @return bool + */ + private function isMultilined(Tokens $tokens, $startParenthesisIndex, $endParenthesisIndex) + { + for ($i = $startParenthesisIndex; $i < $endParenthesisIndex; ++$i) { + if (false !== strpos($tokens[$i]->getContent(), "\n")) { + return true; + } + } + + return false; + } + + /** + * 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. + * + * @param int $index + * + * @return bool + */ + private function isCommentWithFixableIndentation(Tokens $tokens, $index) + { + if (!$tokens[$index]->isComment()) { + return false; + } + + if (0 === strpos($tokens[$index]->getContent(), '/*')) { + return true; + } + + $firstCommentIndex = $index; + while (true) { + $i = $this->getSiblingContinuousSingleLineComment($tokens, $firstCommentIndex, false); + if (null === $i) { + break; + } + + $firstCommentIndex = $i; + } + + $lastCommentIndex = $index; + while (true) { + $i = $this->getSiblingContinuousSingleLineComment($tokens, $lastCommentIndex, true); + if (null === $i) { + break; + } + + $lastCommentIndex = $i; + } + + if ($firstCommentIndex === $lastCommentIndex) { + return true; + } + + for ($i = $firstCommentIndex + 1; $i < $lastCommentIndex; ++$i) { + if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) { + return false; + } + } + + return true; + } + + /** + * @param int $index + * @param bool $after + * + * @return null|int + */ + private function getSiblingContinuousSingleLineComment(Tokens $tokens, $index, $after) + { + $siblingIndex = $index; + do { + if ($after) { + $siblingIndex = $tokens->getNextTokenOfKind($siblingIndex, [[T_COMMENT]]); + } else { + $siblingIndex = $tokens->getPrevTokenOfKind($siblingIndex, [[T_COMMENT]]); + } + + if (null === $siblingIndex) { + return null; + } + } while (0 === strpos($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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..9bb271223b5520b70982e53bcdece62b2ce4cefe --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.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\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 $BOM; + + public function __construct() + { + parent::__construct(); + + $this->BOM = pack('CCC', 0xef, 0xbb, 0xbf); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHP code MUST use only UTF-8 without BOM (remove BOM).', + [ + new CodeSample( + $this->BOM.'getContent(); + + if (0 === strncmp($content, $this->BOM, 3)) { + /** @var false|string $newContent until support for PHP 5.6 is dropped */ + $newContent = substr($content, 3); + + if (false === $newContent) { + $newContent = ''; // substr returns false rather than an empty string when starting at the end + } + + if ('' === $newContent) { + $tokens->clearAt(0); + } else { + $tokens[0] = new Token([$tokens[0]->getId(), $newContent]); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d006925dd1ee458e7476c9fe1d99def9fefe2544 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.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\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Options; + +/** + * Removes Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols. + * + * @author Ivan Boprzenkov + */ +final class NonPrintableCharacterFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + private $symbolsReplace; + + private static $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 + ]; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols.', + [ + new CodeSample( + ' true] + ), + ], + null, + 'Risky when strings contain intended invisible characters.' + ); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(self::$tokens); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('use_escape_sequences_in_strings', 'Whether characters should be replaced with escape sequences in strings.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) // @TODO 3.0 change to true + ->setNormalizer(static function (Options $options, $value) { + if (\PHP_VERSION_ID < 70000 && $value) { + throw new InvalidOptionsForEnvException('Escape sequences require PHP 7.0+.'); + } + + return $value; + }) + ->getOption(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $replacements = []; + $escapeSequences = []; + foreach ($this->symbolsReplace as $character => list($replacement, $codepoint)) { + $replacements[$character] = $replacement; + $escapeSequences[$character] = '\u{'.$codepoint.'}'; + } + + foreach ($tokens as $index => $token) { + $content = $token->getContent(); + + if ( + $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 (false !== strpos($previousTokenContent, '\'')) { + $tokens[$index - 1] = new Token([T_START_HEREDOC, str_replace('\'', '', $previousTokenContent)]); + $stringTypeChanged = true; + } + } elseif ("'" === $content[0]) { + $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('/^\'(.*)\'$/', '"$1"', $content); + } + + $tokens[$index] = new Token([$token->getId(), strtr($content, $escapeSequences)]); + + continue; + } + + if ($token->isGivenKind(self::$tokens)) { + $tokens[$index] = new Token([$token->getId(), strtr($content, $replacements)]); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/Psr0Fixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/Psr0Fixer.php new file mode 100644 index 0000000000000000000000000000000000000000..f0ce65a1205d89a778a1bb18a2093e44a34b4cc8 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/Psr0Fixer.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\Fixer\Basic; + +use PhpCsFixer\AbstractPsrAutoloadingFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FileSpecificCodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jordi Boggiano + * @author Dariusz Rumiński + * @author Bram Gotink + * @author Graham Campbell + */ +final class Psr0Fixer extends AbstractPsrAutoloadingFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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( + ' realpath(__DIR__.'/../..')] + ), + ], + null, + 'This fixer may change your class name, which will break the code that depends on the old name.' + ); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $namespace = false; + $namespaceIndex = 0; + $namespaceEndIndex = 0; + + $classyName = null; + $classyIndex = 0; + + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(T_NAMESPACE)) { + if (false !== $namespace) { + return; + } + + $namespaceIndex = $tokens->getNextMeaningfulToken($index); + $namespaceEndIndex = $tokens->getNextTokenOfKind($index, [';']); + + $namespace = trim($tokens->generatePartialCode($namespaceIndex, $namespaceEndIndex - 1)); + } elseif ($token->isClassy()) { + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + if ($prevToken->isGivenKind(T_NEW)) { + continue; + } + + if (null !== $classyName) { + return; + } + + $classyIndex = $tokens->getNextMeaningfulToken($index); + $classyName = $tokens[$classyIndex]->getContent(); + } + } + + if (null === $classyName) { + return; + } + + if (false !== $namespace) { + $normNamespace = str_replace('\\', '/', $namespace); + $path = str_replace('\\', '/', $file->getRealPath()); + $dir = \dirname($path); + + if ('' !== $this->configuration['dir']) { + /** @var false|string $dir until support for PHP 5.6 is dropped */ + $dir = substr($dir, \strlen(realpath($this->configuration['dir'])) + 1); + + if (false === $dir) { + $dir = ''; + } + + if (\strlen($normNamespace) > \strlen($dir)) { + if ('' !== $dir) { + $normNamespace = substr($normNamespace, -\strlen($dir)); + } else { + $normNamespace = ''; + } + } + } + + /** @var false|string $dir until support for PHP 5.6 is dropped */ + $dir = substr($dir, -\strlen($normNamespace)); + if (false === $dir) { + $dir = ''; + } + + $filename = basename($path, '.php'); + + if ($classyName !== $filename) { + $tokens[$classyIndex] = new Token([T_STRING, $filename]); + } + + if ($normNamespace !== $dir && strtolower($normNamespace) === strtolower($dir)) { + for ($i = $namespaceIndex; $i <= $namespaceEndIndex; ++$i) { + $tokens->clearAt($i); + } + $namespace = substr($namespace, 0, -\strlen($dir)).str_replace('/', '\\', $dir); + + $newNamespace = Tokens::fromCode('clearRange(0, 2); + $newNamespace->clearEmptyTokens(); + + $tokens->insertAt($namespaceIndex, $newNamespace); + } + } else { + $normClass = str_replace('_', '/', $classyName); + $path = str_replace('\\', '/', $file->getRealPath()); + $filename = substr($path, -\strlen($normClass) - 4, -4); + + if ($normClass !== $filename && strtolower($normClass) === strtolower($filename)) { + $tokens[$classyIndex] = new Token([T_STRING, str_replace('/', '_', $filename)]); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('dir', 'The directory where the project code is placed.')) + ->setAllowedTypes(['string']) + ->setDefault('') + ->getOption(), + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/Psr4Fixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/Psr4Fixer.php new file mode 100644 index 0000000000000000000000000000000000000000..ee687a8f8d1f9db158719c9b7e1360c3d3c46a94 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Basic/Psr4Fixer.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\AbstractPsrAutoloadingFixer; +use PhpCsFixer\FixerDefinition\FileSpecificCodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jordi Boggiano + * @author Dariusz Rumiński + * @author Bram Gotink + * @author Graham Campbell + */ +final class Psr4Fixer extends AbstractPsrAutoloadingFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Class names should match the file name.', + [ + new FileSpecificCodeSample( + ' $token) { + if ($token->isGivenKind(T_NAMESPACE)) { + if ($isNamespaceFound) { + return; + } + + $isNamespaceFound = true; + } elseif ($token->isClassy()) { + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + if ($prevToken->isGivenKind(T_NEW)) { + continue; + } + + if (null !== $classyName) { + return; + } + + $classyIndex = $tokens->getNextMeaningfulToken($index); + $classyName = $tokens[$classyIndex]->getContent(); + } + } + + if (null === $classyName) { + return; + } + + if ($isNamespaceFound) { + $filename = basename(str_replace('\\', '/', $file->getRealPath()), '.php'); + + if ($classyName !== $filename) { + $tokens[$classyIndex] = new Token([T_STRING, $filename]); + } + } else { + $normClass = str_replace('_', '/', $classyName); + $filename = substr(str_replace('\\', '/', $file->getRealPath()), -\strlen($normClass) - 4, -4); + + if ($normClass !== $filename && strtolower($normClass) === strtolower($filename)) { + $tokens[$classyIndex] = new Token([T_STRING, str_replace('/', '_', $filename)]); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b0e1042a1208132a8d01589b99292329aa471829 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.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\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 ConfigurationDefinitionFixerInterface +{ + /** + * Hold the function that will be used to convert the constants. + * + * @var callable + */ + private $fixFunction; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + if ('lower' === $this->configuration['case']) { + $this->fixFunction = static function ($token) { + return strtolower($token); + }; + } + + if ('upper' === $this->configuration['case']) { + $this->fixFunction = static function ($token) { + return strtoupper($token); + }; + } + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'The PHP constants `true`, `false`, and `null` MUST be written using the correct casing.', + [new CodeSample("isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('case', 'Whether to use the `upper` or `lower` case syntax.')) + ->setAllowedValues(['upper', 'lower']) + ->setDefault('lower') + ->getOption(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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)) + ) { + $tokens[$index] = new Token([$token->getId(), $fixFunction($token->getContent())]); + } + } + } + + /** + * @param int $index + * + * @return bool + */ + private function isNeighbourAccepted(Tokens $tokens, $index) + { + static $forbiddenTokens = [ + T_AS, + T_CLASS, + T_CONST, + T_EXTENDS, + T_IMPLEMENTS, + T_INSTANCEOF, + T_INSTEADOF, + T_INTERFACE, + T_NEW, + T_NS_SEPARATOR, + T_OBJECT_OPERATOR, + T_PAAMAYIM_NEKUDOTAYIM, + T_TRAIT, + T_USE, + CT::T_USE_TRAIT, + CT::T_USE_LAMBDA, + ]; + + $token = $tokens[$index]; + + if ($token->equalsAny(['{', '}'])) { + return false; + } + + return !$token->isGivenKind($forbiddenTokens); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseConstantsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseConstantsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..fc393b3b97753ab6e7ebb917a33fc164b649005e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseConstantsFixer.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\Casing; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * Fixer for rules defined in PSR2 ¶2.5. + * + * @author Dariusz Rumiński + * + * @deprecated proxy to ConstantCaseFixer + */ +final class LowercaseConstantsFixer extends AbstractProxyFixer implements DeprecatedFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'The PHP constants `true`, `false`, and `null` MUST be in lower case.', + [new CodeSample("proxyFixers); + } + + /** + * {@inheritdoc} + */ + protected function createProxyFixers() + { + $fixer = new ConstantCaseFixer(); + $fixer->configure(['case' => 'lower']); + + return [$fixer]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..a6e8e3ed13b66c30c2e0a05eb881e328bffac9b2 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.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\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 +{ + private static $excludedTokens = [T_HALT_COMPILER]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHP keywords MUST be in lower case.', + [ + new CodeSample( + 'isAnyTokenKindsFound(Token::getKeywords()); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if ($token->isKeyword() && !$token->isGivenKind(self::$excludedTokens)) { + $tokens[$index] = new Token([$token->getId(), strtolower($token->getContent())]); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..fcd7aff4048c9dbd5ee8cbf4ff3bdb197e4f8f45 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.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\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class LowercaseStaticReferenceFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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) + { + 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_OBJECT_OPERATOR, T_PRIVATE, T_PROTECTED, T_PUBLIC])) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + if ($tokens[$nextIndex]->isGivenKind([T_FUNCTION, T_NS_SEPARATOR, T_PRIVATE, T_PROTECTED, T_PUBLIC])) { + continue; + } + + if ('static' === $newContent && $tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { + continue; + } + + $tokens[$index] = new Token([$token->getId(), $newContent]); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..be9484f6d0f703193604ac2166a14a34dd5cfe7f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.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\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author ntzm + */ +final class MagicConstantCasingFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Magic constants should be referred to using the correct casing.', + [new CodeSample("isAnyTokenKindsFound($this->getMagicConstantTokens()); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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() + { + 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() + { + static $magicConstantTokens = null; + + if (null === $magicConstantTokens) { + $magicConstantTokens = array_keys($this->getMagicConstants()); + } + + return $magicConstantTokens; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..123d251943c853f1f5db90253ff1f1d233c93353 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.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\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class MagicMethodCasingFixer extends AbstractFixer +{ + private static $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', + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Magic method definitions and calls must be using the correct casing.', + [ + new CodeSample( + '__INVOKE(1); +' + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_STRING) && $tokens->isAnyTokenKindsFound([T_FUNCTION, T_OBJECT_OPERATOR, T_DOUBLE_COLON]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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 ('__' !== substr($content, 0, 2)) { + 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); + } + } + } + + /** + * @param int $index + * + * @return bool + */ + private function isFunctionSignature(Tokens $tokens, $index) + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevIndex]->isGivenKind(T_FUNCTION)) { + return false; // not a method signature + } + + return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); + } + + /** + * @param int $index + * + * @return bool + */ + private function isMethodCall(Tokens $tokens, $index) + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevIndex]->equals([T_OBJECT_OPERATOR, '->'])) { + return false; // not a "simple" method call + } + + return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); + } + + /** + * @param int $index + * + * @return bool + */ + private function isStaticMethodCall(Tokens $tokens, $index) + { + $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('('); + } + + /** + * @param string $name + * + * @return bool + */ + private function isMagicMethodName($name) + { + return isset(self::$magicNames[$name]); + } + + /** + * @param string $name name of a magic method + * + * @return string + */ + private function getMagicMethodNameInCorrectCasing($name) + { + return self::$magicNames[$name]; + } + + /** + * @param int $index + * @param string $nameInCorrectCasing + */ + private function setTokenToCorrectCasing(Tokens $tokens, $index, $nameInCorrectCasing) + { + $tokens[$index] = new Token([T_STRING, $nameInCorrectCasing]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d4cd3694e5bb652caadf5999ca4dee2922435fae --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.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\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class NativeFunctionCasingFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Function defined by PHP should be called using the correct casing.', + [new CodeSample("isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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 (!$tokens[$index]->isGivenKind(T_STRING)) { + continue; + } + + $next = $tokens->getNextMeaningfulToken($index); + if (!$tokens[$next]->equals('(')) { + $index = $next; + + continue; + } + + $functionNamePrefix = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$functionNamePrefix]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION, CT::T_RETURN_REF])) { + continue; + } + + if ($tokens[$functionNamePrefix]->isGivenKind(T_NS_SEPARATOR)) { + // skip if the call is to a constructor or to a function in a namespace other than the default + $prev = $tokens->getPrevMeaningfulToken($functionNamePrefix); + if ($tokens[$prev]->isGivenKind([T_STRING, T_NEW])) { + 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]]); + $index = $next; + } + } + + /** + * @return array + */ + private function getNativeFunctionNames() + { + $allFunctions = get_defined_functions(); + $functions = []; + foreach ($allFunctions['internal'] as $function) { + $functions[strtolower($function)] = $function; + } + + return $functions; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..2d12e720478fbdf2dbeeb3d3a325ae2cac65951b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.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\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class NativeFunctionTypeDeclarationCasingFixer extends AbstractFixer +{ + /** + * https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration. + * + * self PHP 5.0.0 + * array PHP 5.1.0 + * callable PHP 5.4.0 + * bool PHP 7.0.0 + * float PHP 7.0.0 + * int PHP 7.0.0 + * string PHP 7.0.0 + * iterable PHP 7.1.0 + * void PHP 7.1.0 + * object PHP 7.2.0 + * + * @var array + */ + private $hints; + + /** + * @var FunctionsAnalyzer + */ + private $functionsAnalyzer; + + public function __construct() + { + parent::__construct(); + + $this->hints = [ + 'array' => true, + 'callable' => true, + 'self' => true, + ]; + + if (\PHP_VERSION_ID >= 70000) { + $this->hints = array_merge( + $this->hints, + [ + 'bool' => true, + 'float' => true, + 'int' => true, + 'string' => true, + ] + ); + } + + if (\PHP_VERSION_ID >= 70100) { + $this->hints = array_merge( + $this->hints, + [ + 'iterable' => true, + 'void' => true, + ] + ); + } + + if (\PHP_VERSION_ID >= 70200) { + $this->hints = array_merge($this->hints, ['object' => true]); + } + + $this->functionsAnalyzer = new FunctionsAnalyzer(); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Native type hints for functions should use the correct case.', + [ + new CodeSample("isAllTokenKindsFound([T_FUNCTION, T_STRING]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if ($tokens[$index]->isGivenKind(T_FUNCTION)) { + if (\PHP_VERSION_ID >= 70000) { + $this->fixFunctionReturnType($tokens, $index); + } + + $this->fixFunctionArgumentTypes($tokens, $index); + } + } + } + + /** + * @param int $index + */ + private function fixFunctionArgumentTypes(Tokens $tokens, $index) + { + foreach ($this->functionsAnalyzer->getFunctionArguments($tokens, $index) as $argument) { + $this->fixArgumentType($tokens, $argument->getTypeAnalysis()); + } + } + + /** + * @param int $index + */ + private function fixFunctionReturnType(Tokens $tokens, $index) + { + $this->fixArgumentType($tokens, $this->functionsAnalyzer->getFunctionReturnType($tokens, $index)); + } + + private function fixArgumentType(Tokens $tokens, TypeAnalysis $type = null) + { + if (null === $type) { + return; + } + + $argumentIndex = $type->getStartIndex(); + if ($argumentIndex !== $type->getEndIndex()) { + return; // the type to fix are always unqualified and so are always composed as one token + } + + $lowerCasedName = strtolower($type->getName()); + if (!isset($this->hints[$lowerCasedName])) { + return; // check of type is of interest based on name (slower check than previous index based) + } + + $tokens[$argumentIndex] = new Token([$tokens[$argumentIndex]->getId(), $lowerCasedName]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..744901480b34a507c8d73ced43ca2d6baf35d9c2 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.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\CastNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class CastSpacesFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @internal + */ + const INSIDE_CAST_SPACE_REPLACE_MAP = [ + ' ' => '', + "\t" => '', + "\n" => '', + "\r" => '', + "\0" => '', + "\x0B" => '', + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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() + { + return -10; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(Token::getCastTokenKinds()); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('space', 'spacing to apply between cast and variable.')) + ->setAllowedValues(['none', 'single']) + ->setDefault('single') + ->getOption(), + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..f774f52c778d78d52ed538d188e22f44235a3778 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.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\CastNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class LowercaseCastFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Cast should be written in lower case.', + [ + new VersionSpecificCodeSample( + 'isAnyTokenKindsFound(Token::getCastTokenKinds()); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..695452142cfb51b0c8d8cfba7ccda6fa6845be6f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.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\CastNotation; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vladimir Reznichenko + */ +final class ModernizeTypesCastingFixer extends AbstractFunctionReferenceFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replaces `intval`, `floatval`, `doubleval`, `strval` and `boolval` function calls with according type casting operator.', + [ + new CodeSample( + 'isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + // replacement patterns + static $replacement = [ + 'intval' => [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; + while (null !== $currIndex) { + // 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; + } + + list($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 parenthesises then + $countParamTokens = 0; + for ($paramContentIndex = $openParenthesis + 1; $paramContentIndex < $paramContentEnd; ++$paramContentIndex) { + //not a space, means some sensible token + if (!$tokens[$paramContentIndex]->isGivenKind(T_WHITESPACE)) { + ++$countParamTokens; + } + } + + $preserveParenthesises = $countParamTokens > 1; + + $afterCloseParenthesisIndex = $tokens->getNextMeaningfulToken($closeParenthesis); + $afterCloseParenthesisToken = $tokens[$afterCloseParenthesisIndex]; + $wrapInParenthesises = $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 ($wrapInParenthesises) { + array_unshift($replacementSequence, new Token('(')); + } + + if (!$preserveParenthesises) { + // 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 ($wrapInParenthesises) { + $tokens->insertAt($closeParenthesis, new Token(')')); + } + + $tokens->overrideRange($functionName, $functionName, $replacementSequence); + + // nested transformations support + $currIndex = $functionName; + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..c1cdfde3c8ddc8fc5104665158ab621e3dcab390 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.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\CastNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class NoShortBoolCastFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + * + * Must run before CastSpacesFixer. + */ + public function getPriority() + { + return -9; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Short cast `bool` using double exclamation mark should not be used.', + [new CodeSample("isTokenKindFound('!'); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = \count($tokens) - 1; $index > 1; --$index) { + if ($tokens[$index]->equals('!')) { + $index = $this->fixShortCast($tokens, $index); + } + } + } + + /** + * @param int $index + * + * @return int + */ + private function fixShortCast(Tokens $tokens, $index) + { + 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; + } + + /** + * @param int $start + * @param int $end + */ + private function fixShortCastToBoolCast(Tokens $tokens, $start, $end) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d8accc579e506e367ce97fb77140e70c2aae8a88 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.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\CastNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class NoUnsetCastFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Variables must be set `null` instead of using `(unset)` casting.', + [new CodeSample("isTokenKindFound(T_UNSET_CAST); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = \count($tokens) - 1; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind(T_UNSET_CAST)) { + $this->fixUnsetCast($tokens, $index); + } + } + } + + /** + * @param int $index + */ + private function fixUnsetCast(Tokens $tokens, $index) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..8a1665bedb747bf53d6b4846a667d1adc9d6b6ef --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.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\CastNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class ShortScalarCastFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Cast `(boolean)` and `(integer)` should be written as `(bool)` and `(int)`, `(double)` and `(real)` as `(float)`, `(binary)` as `(string)`.', + [ + new VersionSpecificCodeSample( + "isAnyTokenKindsFound(Token::getCastTokenKinds()); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..2ab99bb85722228cb69697df7377ca762caac303 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php @@ -0,0 +1,392 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use SplFileInfo; + +/** + * 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'. + * + * @author SpacePossum + */ +final class ClassAttributesSeparationFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var array + */ + private $classElementTypes = []; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->classElementTypes = []; // reset previous configuration + foreach ($this->configuration['elements'] as $element) { + $this->classElementTypes[$element] = true; + } + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Class, trait and interface elements must be separated with one blank line.', + [ + new CodeSample( + ' ['property']] + ), + new CodeSample( + ' ['const']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BracesFixer, IndentationTypeFixer. + * Must run after OrderedClassElementsFixer. + */ + public function getPriority() + { + return 55; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(SplFileInfo $file, Tokens $tokens) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $class = $classStart = $classEnd = false; + + foreach (array_reverse($tokensAnalyzer->getClassyElements(), true) as $index => $element) { + if (!isset($this->classElementTypes[$element['type']])) { + continue; // not configured to be fixed + } + + if ($element['classIndex'] !== $class) { + $class = $element['classIndex']; + $classStart = $tokens->getNextTokenOfKind($class, ['{']); + $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart); + } + + if ('method' === $element['type'] && !$tokens[$class]->isGivenKind(T_INTERFACE)) { + // method of class or trait + $attributes = $tokensAnalyzer->getMethodAttributes($index); + + $methodEnd = true === $attributes['abstract'] + ? $tokens->getNextTokenOfKind($index, [';']) + : $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($index, ['{'])) + ; + + $this->fixSpaceBelowClassMethod($tokens, $classEnd, $methodEnd); + $this->fixSpaceAboveClassElement($tokens, $classStart, $index); + + continue; + } + + // `const`, `property` or `method` of an `interface` + $this->fixSpaceBelowClassElement($tokens, $classEnd, $tokens->getNextTokenOfKind($index, [';'])); + $this->fixSpaceAboveClassElement($tokens, $classStart, $index); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $types = ['const', 'method', 'property']; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('elements', sprintf('List of classy elements; \'%s\'.', implode("', '", $types)))) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($types)]) + ->setDefault(['const', 'method', 'property']) + ->getOption(), + ]); + } + + /** + * Fix spacing below 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 int $classEndIndex + * @param int $elementEndIndex + */ + private function fixSpaceBelowClassElement(Tokens $tokens, $classEndIndex, $elementEndIndex) + { + for ($nextNotWhite = $elementEndIndex + 1;; ++$nextNotWhite) { + if (($tokens[$nextNotWhite]->isComment() || $tokens[$nextNotWhite]->isWhitespace()) && false === strpos($tokens[$nextNotWhite]->getContent(), "\n")) { + continue; + } + + break; + } + + if ($tokens[$nextNotWhite]->isWhitespace()) { + $nextNotWhite = $tokens->getNextNonWhitespace($nextNotWhite); + } + + $this->correctLineBreaks($tokens, $elementEndIndex, $nextNotWhite, $nextNotWhite === $classEndIndex ? 1 : 2); + } + + /** + * Fix spacing below a method of a class or trait. + * + * Deals with comments, PHPDocs and spaces above the method with respect to the position of the + * method within the class or trait. + * + * @param int $classEndIndex + * @param int $elementEndIndex + */ + private function fixSpaceBelowClassMethod(Tokens $tokens, $classEndIndex, $elementEndIndex) + { + $nextNotWhite = $tokens->getNextNonWhitespace($elementEndIndex); + + $this->correctLineBreaks($tokens, $elementEndIndex, $nextNotWhite, $nextNotWhite === $classEndIndex ? 1 : 2); + } + + /** + * 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 int $classStartIndex index of the class Token the element is in + * @param int $elementIndex index of the element to fix + */ + private function fixSpaceAboveClassElement(Tokens $tokens, $classStartIndex, $elementIndex) + { + static $methodAttr = [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]; + + $nonWhiteAbove = null; + + // find out where the element definition starts + $firstElementAttributeIndex = $elementIndex; + for ($i = $elementIndex; $i > $classStartIndex; --$i) { + $nonWhiteAbove = $tokens->getNonWhitespaceSibling($i, -1); + if (null !== $nonWhiteAbove && $tokens[$nonWhiteAbove]->isGivenKind($methodAttr)) { + $firstElementAttributeIndex = $nonWhiteAbove; + } else { + break; + } + } + + // deal with comments above a element + if ($tokens[$nonWhiteAbove]->isGivenKind(T_COMMENT)) { + if (1 === $firstElementAttributeIndex - $nonWhiteAbove) { + // no white space found between comment and element start + $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, 1); + + return; + } + + // $tokens[$nonWhiteAbove+1] is always a white space token here + if (substr_count($tokens[$nonWhiteAbove + 1]->getContent(), "\n") > 1) { + // more than one line break, always bring it back to 2 line breaks between the element start and what is above it + $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, 2); + + return; + } + + // there are 2 cases: + if ($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, $firstElementAttributeIndex, 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); + $nonWhiteAboveComment = $tokens->getNonWhitespaceSibling($nonWhiteAbove, -1); + + $this->correctLineBreaks($tokens, $nonWhiteAboveComment, $nonWhiteAbove, $nonWhiteAboveComment === $classStartIndex ? 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, $firstElementAttributeIndex, 2); + } + + return; + } + + // deal with element without a PHPDoc above it + if (false === $tokens[$nonWhiteAbove]->isGivenKind(T_DOC_COMMENT)) { + $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, $nonWhiteAbove === $classStartIndex ? 1 : 2); + + return; + } + + // there should be one linebreak between the element and the PHPDoc above it + $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, 1); + + // there should be one blank line between the PHPDoc and whatever is above (with the exception when it is directly after a class opening) + $nonWhiteAbovePHPDoc = $tokens->getNonWhitespaceSibling($nonWhiteAbove, -1); + $this->correctLineBreaks($tokens, $nonWhiteAbovePHPDoc, $nonWhiteAbove, $nonWhiteAbovePHPDoc === $classStartIndex ? 1 : 2); + } + + /** + * @param int $startIndex + * @param int $endIndex + * @param int $reqLineCount + */ + private function correctLineBreaks(Tokens $tokens, $startIndex, $endIndex, $reqLineCount = 2) + { + $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; + } + } + } + + /** + * @param int $whiteSpaceStartIndex + * @param int $whiteSpaceEndIndex + * + * @return int + */ + private function getLineBreakCount(Tokens $tokens, $whiteSpaceStartIndex, $whiteSpaceEndIndex) + { + $lineCount = 0; + for ($i = $whiteSpaceStartIndex; $i < $whiteSpaceEndIndex; ++$i) { + $lineCount += substr_count($tokens[$i]->getContent(), "\n"); + } + + return $lineCount; + } + + /** + * @param int $commentIndex + * + * @return int + */ + private function findCommentBlockStart(Tokens $tokens, $commentIndex) + { + $start = $commentIndex; + for ($i = $commentIndex - 1; $i > 0; --$i) { + if ($tokens[$i]->isComment()) { + $start = $i; + + continue; + } + + if (!$tokens[$i]->isWhitespace() || $this->getLineBreakCount($tokens, $i, $i + 1) > 1) { + break; + } + } + + return $start; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..3cc538a2c1ef33ad3ae76481f4a98c12c797e0b5 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AliasedFixerOptionBuilder; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +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. + * + * @author SpacePossum + */ +final class ClassDefinitionFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Whitespace around the keywords of a class, trait or interfaces definition should be one space.', + [ + new CodeSample( + ' true] + ), + new CodeSample( + ' true] + ), + new CodeSample( + ' true] + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + // -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); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new AliasedFixerOptionBuilder( + new FixerOptionBuilder('multi_line_extends_each_single_line', 'Whether definitions should be multiline.'), + 'multiLineExtendsEachSingleLine' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new AliasedFixerOptionBuilder( + new FixerOptionBuilder('single_item_single_line', 'Whether definitions should be single line when including a single item.'), + 'singleItemSingleLine' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new AliasedFixerOptionBuilder( + new FixerOptionBuilder('single_line', 'Whether definitions should be single line.'), + 'singleLine' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * @param int $classyIndex Class definition token start index + */ + private function fixClassyDefinition(Tokens $tokens, $classyIndex) + { + $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']); + } + + // 4.1 The extends and implements keywords MUST be declared on the same line as the class name. + $this->makeClassyDefinitionSingleLine( + $tokens, + $classDefInfo['anonymousClass'] ? $tokens->getPrevMeaningfulToken($classyIndex) : $classDefInfo['start'], + $end + ); + } + + /** + * @param int $classOpenIndex + * + * @return array + */ + private function fixClassyDefinitionExtends(Tokens $tokens, $classOpenIndex, array $classExtendsInfo) + { + $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex); + + if ($this->configuration['single_line'] || false === $classExtendsInfo['multiLine']) { + $this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex); + $classExtendsInfo['multiLine'] = false; + } elseif ($this->configuration['single_item_single_line'] && 1 === $classExtendsInfo['numberOfExtends']) { + $this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex); + $classExtendsInfo['multiLine'] = false; + } elseif ($this->configuration['multi_line_extends_each_single_line'] && $classExtendsInfo['multiLine']) { + $this->makeClassyInheritancePartMultiLine($tokens, $classExtendsInfo['start'], $endIndex); + $classExtendsInfo['multiLine'] = true; + } + + return $classExtendsInfo; + } + + /** + * @param int $classOpenIndex + * + * @return array + */ + private function fixClassyDefinitionImplements(Tokens $tokens, $classOpenIndex, array $classImplementsInfo) + { + $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex); + + if ($this->configuration['single_line'] || false === $classImplementsInfo['multiLine']) { + $this->makeClassyDefinitionSingleLine($tokens, $classImplementsInfo['start'], $endIndex); + $classImplementsInfo['multiLine'] = false; + } elseif ($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; + } + + /** + * @return int + */ + private function fixClassyDefinitionOpenSpacing(Tokens $tokens, array $classDefInfo) + { + 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 && false !== strpos($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; + } + + /** + * @param int $classyIndex + * + * @return array + */ + private function getClassyDefinitionInfo(Tokens $tokens, $classyIndex) + { + $openIndex = $tokens->getNextTokenOfKind($classyIndex, ['{']); + $prev = $tokens->getPrevMeaningfulToken($classyIndex); + $startIndex = $tokens[$prev]->isGivenKind([T_FINAL, T_ABSTRACT]) ? $prev : $classyIndex; + + $extends = false; + $implements = false; + $anonymousClass = false; + + if (!$tokens[$classyIndex]->isGivenKind(T_TRAIT)) { + $extends = $tokens->findGivenKind(T_EXTENDS, $classyIndex, $openIndex); + $extends = \count($extends) ? $this->getClassyInheritanceInfo($tokens, key($extends), 'numberOfExtends') : false; + + if (!$tokens[$classyIndex]->isGivenKind(T_INTERFACE)) { + $implements = $tokens->findGivenKind(T_IMPLEMENTS, $classyIndex, $openIndex); + $implements = \count($implements) ? $this->getClassyInheritanceInfo($tokens, key($implements), 'numberOfImplements') : false; + $tokensAnalyzer = new TokensAnalyzer($tokens); + $anonymousClass = $tokensAnalyzer->isAnonymousClass($classyIndex); + } + } + + return [ + 'start' => $startIndex, + 'classy' => $classyIndex, + 'open' => $openIndex, + 'extends' => $extends, + 'implements' => $implements, + 'anonymousClass' => $anonymousClass, + ]; + } + + /** + * @param int $startIndex + * @param string $label + * + * @return array + */ + private function getClassyInheritanceInfo(Tokens $tokens, $startIndex, $label) + { + $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'] && false !== strpos($tokens[$i]->getContent(), "\n")) { + $implementsInfo['multiLine'] = true; + } + } + + return $implementsInfo; + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function makeClassyDefinitionSingleLine(Tokens $tokens, $startIndex, $endIndex) + { + for ($i = $endIndex; $i >= $startIndex; --$i) { + if ($tokens[$i]->isWhitespace()) { + $prevNonWhite = $tokens->getPrevNonWhitespace($i); + $nextNonWhite = $tokens->getNextNonWhitespace($i); + + if ($tokens[$prevNonWhite]->isComment() || $tokens[$nextNonWhite]->isComment()) { + $content = $tokens[$prevNonWhite]->getContent(); + if (!('#' === $content || '//' === substr($content, 0, 2))) { + $content = $tokens[$nextNonWhite]->getContent(); + if (!('#' === $content || '//' === substr($content, 0, 2))) { + $tokens[$i] = new Token([T_WHITESPACE, ' ']); + } + } + + continue; + } + + if ($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 (!$tokens[$i]->isComment()) { + continue; + } + + if (!$tokens[$i + 1]->isWhitespace() && !$tokens[$i + 1]->isComment() && false === strpos($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, ' '])); + } + } + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function makeClassyInheritancePartMultiLine(Tokens $tokens, $startIndex, $endIndex) + { + 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 (false !== strpos($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; + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..43a3161159dd8c02d414b32ed5f19c44dfe1b9dc --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.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\ClassNotation; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * @author Filippo Tessarotto + */ +final class FinalClassFixer extends AbstractProxyFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'All classes must be final, except abstract ones and Doctrine entities.', + [ + new CodeSample( + 'configure([ + 'annotation-white-list' => [], + 'consider-absent-docblock-as-internal-class' => true, + ]); + + return [$fixer]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b8503d43ac38c5fa4d7ca172beff9872a33e6f22 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.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\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author Dariusz Rumiński + * @author SpacePossum + */ +final class FinalInternalClassFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $intersect = array_intersect_assoc( + $this->configuration['annotation-white-list'], + $this->configuration['annotation-black-list'] + ); + + if (\count($intersect)) { + throw new InvalidFixerConfigurationException($this->getName(), sprintf('Annotation cannot be used in both the white- and black list, got duplicates: "%s".', implode('", "', array_keys($intersect)))); + } + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Internal classes should be `final`.', + [ + new CodeSample(" ['@Custom'], + ] + ), + ], + null, + 'Changing classes to `final` might cause code execution to break.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before FinalStaticAccessFixer, SelfStaticAccessorFixer. + * Must run after PhpUnitInternalClassFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_CLASS); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + if (!$tokens[$index]->isGivenKind(T_CLASS) || !$this->isClassCandidate($tokens, $index)) { + continue; + } + + // make class final + $tokens->insertAt( + $index, + [ + new Token([T_FINAL, 'final']), + new Token([T_WHITESPACE, ' ']), + ] + ); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $annotationsAsserts = [static function (array $values) { + foreach ($values as $value) { + if (!\is_string($value) || '' === $value) { + return false; + } + } + + return true; + }]; + + $annotationsNormalizer = static function (Options $options, array $value) { + $newValue = []; + foreach ($value as $key) { + if ('@' === $key[0]) { + $key = substr($key, 1); + } + + $newValue[strtolower($key)] = true; + } + + return $newValue; + }; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('annotation-white-list', 'Class level annotations tags that must be set in order to fix the class. (case insensitive)')) + ->setAllowedTypes(['array']) + ->setAllowedValues($annotationsAsserts) + ->setDefault(['@internal']) + ->setNormalizer($annotationsNormalizer) + ->getOption(), + (new FixerOptionBuilder('annotation-black-list', 'Class level annotations 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([ + '@final', + '@Entity', + '@ORM\Entity', + '@ORM\Mapping\Entity', + '@Mapping\Entity', + ]) + ->setNormalizer($annotationsNormalizer) + ->getOption(), + (new FixerOptionBuilder('consider-absent-docblock-as-internal-class', 'Should classes without any DocBlock be fixed to final?')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * @param int $index T_CLASS index + * + * @return bool + */ + private function isClassCandidate(Tokens $tokens, $index) + { + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind([T_ABSTRACT, T_FINAL, T_NEW])) { + return false; // ignore class; it is abstract or already final + } + + $docToken = $tokens[$tokens->getPrevNonWhitespace($index)]; + + if (!$docToken->isGivenKind(T_DOC_COMMENT)) { + return $this->configuration['consider-absent-docblock-as-internal-class']; + } + + $doc = new DocBlock($docToken->getContent()); + $tags = []; + + foreach ($doc->getAnnotations() as $annotation) { + Preg::match('/@\S+(?=\s|$)/', $annotation->getContent(), $matches); + $tag = strtolower(substr(array_shift($matches), 1)); + foreach ($this->configuration['annotation-black-list'] as $tagStart => $true) { + if (0 === strpos($tag, $tagStart)) { + return false; // ignore class: class-level PHPDoc contains tag that has been black listed through configuration + } + } + + $tags[$tag] = true; + } + + foreach ($this->configuration['annotation-white-list'] as $tag => $true) { + if (!isset($tags[$tag])) { + return false; // ignore class: class-level PHPDoc does not contain all tags that has been white listed through configuration + } + } + + return true; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..fe6828eb9e6b8801a221e08d1f6bd3f18e1d7384 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.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\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class FinalPublicMethodForAbstractClassFixer extends AbstractFixer +{ + /** + * @var array + */ + private $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, + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'All `public` methods of `abstract` classes should be `final`.', + [ + new CodeSample( + 'isAllTokenKindsFound([T_CLASS, T_ABSTRACT, T_PUBLIC, T_FUNCTION]); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $classes = array_keys($tokens->findGivenKind(T_CLASS)); + + while ($classIndex = array_pop($classes)) { + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)]; + if (!$prevToken->isGivenKind([T_ABSTRACT])) { + continue; + } + + $classOpen = $tokens->getNextTokenOfKind($classIndex, ['{']); + $classClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen); + + $this->fixClass($tokens, $classOpen, $classClose); + } + } + + /** + * @param int $classOpenIndex + * @param int $classCloseIndex + */ + private function fixClass(Tokens $tokens, $classOpenIndex, $classCloseIndex) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalStaticAccessFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalStaticAccessFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..673e55b1eda132f4dcca8a6973f756fdcbdc52bb --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalStaticAccessFixer.php @@ -0,0 +1,154 @@ + + * Dariusz Rumiń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\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author ntzm + */ +final class FinalStaticAccessFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Converts `static` access to `self` access in `final` classes.', + [ + new CodeSample( + 'isAllTokenKindsFound([T_FINAL, T_CLASS, T_STATIC]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + if (!$tokens[$index]->isGivenKind(T_FINAL)) { + continue; + } + + $classTokenIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$classTokenIndex]->isGivenKind(T_CLASS)) { + continue; + } + + $startClassIndex = $tokens->getNextTokenOfKind( + $classTokenIndex, + ['{'] + ); + + $endClassIndex = $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_CURLY_BRACE, + $startClassIndex + ); + + $this->replaceStaticAccessWithSelfAccessBetween( + $tokens, + $startClassIndex, + $endClassIndex + ); + } + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function replaceStaticAccessWithSelfAccessBetween( + Tokens $tokens, + $startIndex, + $endIndex + ) { + for ($index = $startIndex; $index <= $endIndex; ++$index) { + if ($tokens[$index]->isGivenKind(T_CLASS)) { + $index = $this->getEndOfAnonymousClass($tokens, $index); + + continue; + } + + if (!$tokens[$index]->isGivenKind(T_STATIC)) { + continue; + } + + $doubleColonIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$doubleColonIndex]->isGivenKind(T_DOUBLE_COLON)) { + continue; + } + + $tokens[$index] = new Token([T_STRING, 'self']); + } + } + + /** + * @param int $index + * + * @return int + */ + private function getEndOfAnonymousClass(Tokens $tokens, $index) + { + $instantiationBraceStart = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$instantiationBraceStart]->equals('(')) { + $index = $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $instantiationBraceStart + ); + } + + $bodyBraceStart = $tokens->getNextTokenOfKind($index, ['{']); + + return $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_CURLY_BRACE, + $bodyBraceStart + ); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/MethodSeparationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/MethodSeparationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..dbb3812b16f7077d25be55843f74c8ef70323a1e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/MethodSeparationFixer.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\ClassNotation; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * @author SpacePossum + * + * @deprecated in 2.8, proxy to ClassAttributesSeparationFixer + */ +final class MethodSeparationFixer extends AbstractProxyFixer implements DeprecatedFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Methods must be separated with one blank line.', + [ + new CodeSample( + 'proxyFixers); + } + + /** + * {@inheritdoc} + */ + protected function createProxyFixers() + { + $fixer = new ClassAttributesSeparationFixer(); + $fixer->configure(['elements' => ['method']]); + + return [$fixer]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..faea8c6d2e05d4106160529e6a1a74941a0bfd50 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.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\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Ceeram + */ +final class NoBlankLinesAfterClassOpeningFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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. + * + * @param int $index + */ + private function fixWhitespace(Tokens $tokens, $index) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..2d665c489c96a463e0bc641a8d830394c8b683df --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.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\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author ntzm + */ +final class NoNullPropertyInitializationFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { + if (!$tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_VAR])) { + continue; + } + + while (true) { + $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() && false !== strpos($tokens[$i]->getContent(), "\n")) + && !$tokens[$i]->isComment() + ) { + $tokens->clearAt($i); + } + } + } + + ++$index; + } + + if (!$tokens[$index]->equals(',')) { + break; + } + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..92f24fe4223ddc6231af4a4bf622af344452306a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php @@ -0,0 +1,390 @@ + + * Dariusz Rumiń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\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Matteo Beccati + */ +final class NoPhp4ConstructorFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Convert PHP4-style constructors to `__construct`.', + [ + new CodeSample('isTokenKindFound(T_CLASS); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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 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, $className, $classStart, $classEnd) + { + $php4 = $this->findFunction($tokens, $className, $classStart, $classEnd); + + if (null === $php4) { + // no PHP4-constructor! + return; + } + + if (!empty($php4['modifiers'][T_ABSTRACT]) || !empty($php4['modifiers'][T_STATIC])) { + // PHP4 constructor can't be abstract or static + return; + } + + $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, ...)? + list($seq, $case) = $this->getWrapperMethodSequence($tokens, '__construct', $php4['startIndex'], $php4['bodyIndex']); + 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)? + list($seq, $case) = $this->getWrapperMethodSequence($tokens, $className, $php4['startIndex'], $php4['bodyIndex']); + 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']); + } + } + + /** + * 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, $classStart, $classEnd) + { + // 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 indexes + $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']); + } + } + + // using $this->ParentClassName() + $parentSeq = $tokens->findSequence([ + [T_VARIABLE, '$this'], + [T_OBJECT_OPERATOR], + [T_STRING, $parentClass], + '(', + ], $classStart, $classEnd, [2 => false]); + + if (null !== $parentSeq) { + // we only need indexes + $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, $start, $end) + { + $seq = [ + [T_VARIABLE, '$this'], + [T_OBJECT_OPERATOR], + [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 an array containing the sequence and case sensitiveness [ 0 => $seq, 1 => $case ] + */ + private function getWrapperMethodSequence(Tokens $tokens, $method, $startIndex, $bodyIndex) + { + // initialise sequence as { $this->{$method}( + $seq = [ + '{', + [T_VARIABLE, '$this'], + [T_OBJECT_OPERATOR], + [T_STRING, $method], + '(', + ]; + $case = [3 => false]; + + // 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[] = '}'; + + return [$seq, $case]; + } + + /** + * Find a function or method matching a given name within certain bounds. + * + * @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 An associative array, if a match is found: + * + * - 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) + */ + private function findFunction(Tokens $tokens, $name, $startIndex, $endIndex) + { + $function = $tokens->findSequence([ + [T_FUNCTION], + [T_STRING, $name], + '(', + ], $startIndex, $endIndex, false); + + if (null === $function) { + return null; + } + + // keep only the indexes + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..e8259da98cd7057d894ed132ce14124cb0a76ff4 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.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\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class NoUnneededFinalMethodFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'A `final` class must not have `final` methods and `private` methods must not be `final`.', + [ + new CodeSample( + 'isAllTokenKindsFound([T_CLASS, T_FINAL]); + } + + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $tokensCount = \count($tokens); + for ($index = 0; $index < $tokensCount; ++$index) { + if (!$tokens[$index]->isGivenKind(T_CLASS)) { + continue; + } + + $classOpen = $tokens->getNextTokenOfKind($index, ['{']); + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + $classIsFinal = $prevToken->isGivenKind(T_FINAL); + + $this->fixClass($tokens, $classOpen, $classIsFinal); + } + } + + /** + * @param int $classOpenIndex + * @param bool $classIsFinal + */ + private function fixClass(Tokens $tokens, $classOpenIndex, $classIsFinal) + { + $tokensCount = \count($tokens); + for ($index = $classOpenIndex + 1; $index < $tokensCount; ++$index) { + // Class end + if ($tokens[$index]->equals('}')) { + return; + } + + // Skip method content + if ($tokens[$index]->equals('{')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + + if (!$tokens[$index]->isGivenKind(T_FINAL)) { + continue; + } + + if (!$classIsFinal && !$this->isPrivateMethod($tokens, $index, $classOpenIndex)) { + continue; + } + + $tokens->clearAt($index); + + $nextTokenIndex = $index + 1; + if ($tokens[$nextTokenIndex]->isWhitespace()) { + $tokens->clearAt($nextTokenIndex); + } + } + } + + /** + * @param int $index + * @param int $classOpenIndex + * + * @return bool + */ + private function isPrivateMethod(Tokens $tokens, $index, $classOpenIndex) + { + $index = max($classOpenIndex + 1, $tokens->getPrevTokenOfKind($index, [';', '{', '}'])); + + while (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + if ($tokens[$index]->isGivenKind(T_PRIVATE)) { + return true; + } + + ++$index; + } + + return false; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..02292d08d30646aaf23f922b2a804514ea7d6643 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php @@ -0,0 +1,500 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class OrderedClassElementsFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** @internal */ + const SORT_ALPHA = 'alpha'; + + /** @internal */ + const SORT_NONE = 'none'; + + /** + * @var array Array containing all class element base types (keys) and their parent types (values) + */ + private static $typeHierarchy = [ + 'use_trait' => null, + 'public' => null, + 'protected' => null, + 'private' => null, + '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_static' => ['property_static', 'property_public'], + 'property_protected_static' => ['property_static', 'property_protected'], + 'property_private_static' => ['property_static', 'property_private'], + 'method' => null, + 'method_static' => ['method'], + 'method_public' => ['method', 'public'], + 'method_protected' => ['method', 'protected'], + 'method_private' => ['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 $specialTypes = [ + 'construct' => null, + 'destruct' => null, + 'magic' => null, + 'phpunit' => null, + ]; + + /** + * Array of supported sort algorithms in configuration. + * + * @var string[] + */ + private $supportedSortAlgorithms = [ + self::SORT_NONE, + self::SORT_ALPHA, + ]; + + /** + * @var array Resolved configuration array (type => position) + */ + private $typePosition; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->typePosition = []; + $pos = 0; + foreach ($this->configuration['order'] as $type) { + $this->typePosition[$type] = $pos++; + } + + foreach (self::$typeHierarchy as $type => $parents) { + if (isset($this->typePosition[$type])) { + continue; + } + + if (!$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; + } + // last digit is used by phpunit method ordering + $pos *= 10; + } + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Orders the elements of classes/interfaces/traits.', + [ + new CodeSample( + ' ['method_private', 'method_public']] + ), + new CodeSample( + ' ['method_public'], 'sortAlgorithm' => 'alpha'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before ClassAttributesSeparationFixer, MethodSeparationFixer, NoBlankLinesAfterClassOpeningFixer, SpaceAfterSemicolonFixer. + * Must run after NoPhp4ConstructorFixer, ProtectedToPrivateFixer. + */ + public function getPriority() + { + return 65; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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; + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolverRootless('order', [ + (new FixerOptionBuilder('order', 'List of strings defining order of elements.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(array_keys(array_merge(self::$typeHierarchy, self::$specialTypes)))]) + ->setDefault([ + 'use_trait', + '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('sortAlgorithm', 'How multiple occurrences of same type statements should be sorted')) + ->setAllowedValues($this->supportedSortAlgorithms) + ->setDefault(self::SORT_NONE) + ->getOption(), + ], $this->getName()); + } + + /** + * @param int $startIndex + * + * @return array[] + */ + private function getElements(Tokens $tokens, $startIndex) + { + static $elementTokenKinds = [CT::T_USE_TRAIT, T_CONST, T_VARIABLE, T_FUNCTION]; + + ++$startIndex; + $elements = []; + + while (true) { + $element = [ + 'start' => $startIndex, + 'visibility' => 'public', + 'static' => false, + ]; + + for ($i = $startIndex;; ++$i) { + $token = $tokens[$i]; + + // class end + if ($token->equals('}')) { + return $elements; + } + + if ($token->isGivenKind(T_STATIC)) { + $element['static'] = true; + + continue; + } + + 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', '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; + } + } + + /** + * @param int $index + * + * @return array|string type or array of type and name + */ + private function detectElementType(Tokens $tokens, $index) + { + $token = $tokens[$index]; + + if ($token->isGivenKind(CT::T_USE_TRAIT)) { + return 'use_trait'; + } + + 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, 'tearDownAfterClass'], + [T_STRING, 'setUp'], + [T_STRING, 'tearDown'], + ], false) + ) { + return ['phpunit', strtolower($nameToken->getContent())]; + } + + if ('__' === substr($nameToken->getContent(), 0, 2)) { + return 'magic'; + } + + return 'method'; + } + + /** + * @param int $index + * + * @return int + */ + private function findElementEnd(Tokens $tokens, $index) + { + $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 array[] $elements + * + * @return array[] + */ + private function sortElements(array $elements) + { + static $phpunitPositions = [ + 'setupbeforeclass' => 1, + 'teardownafterclass' => 2, + 'setup' => 3, + 'teardown' => 4, + ]; + + foreach ($elements as &$element) { + $type = $element['type']; + + if (\array_key_exists($type, self::$specialTypes)) { + if (isset($this->typePosition[$type])) { + $element['position'] = $this->typePosition[$type]; + if ('phpunit' === $type) { + $element['position'] += $phpunitPositions[$element['name']]; + } + + continue; + } + + $type = 'method'; + } + + if (\in_array($type, ['constant', 'property', 'method'], true)) { + $type .= '_'.$element['visibility']; + if ($element['static']) { + $type .= '_static'; + } + } + + $element['position'] = $this->typePosition[$type]; + } + unset($element); + + usort($elements, function (array $a, array $b) { + if ($a['position'] === $b['position']) { + return $this->sortGroupElements($a, $b); + } + + return $a['position'] > $b['position'] ? 1 : -1; + }); + + return $elements; + } + + private function sortGroupElements(array $a, array $b) + { + $selectedSortAlgorithm = $this->configuration['sortAlgorithm']; + + if (self::SORT_ALPHA === $selectedSortAlgorithm) { + return strcasecmp($a['name'], $b['name']); + } + + return $a['start'] > $b['start'] ? 1 : -1; + } + + /** + * @param int $startIndex + * @param int $endIndex + * @param array[] $elements + */ + private function sortTokens(Tokens $tokens, $startIndex, $endIndex, array $elements) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..e8ad880136ce3177946e687e4d00dbf3dfc8bb16 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.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\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dave van der Brugge + */ +final class OrderedInterfacesFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** @internal */ + const OPTION_DIRECTION = 'direction'; + + /** @internal */ + const OPTION_ORDER = 'order'; + + /** @internal */ + const DIRECTION_ASCEND = 'ascend'; + + /** @internal */ + const DIRECTION_DESCEND = 'descend'; + + /** @internal */ + const ORDER_ALPHA = 'alpha'; + + /** @internal */ + const ORDER_LENGTH = 'length'; + + /** + * Array of supported directions in configuration. + * + * @var string[] + */ + private $supportedDirectionOptions = [ + self::DIRECTION_ASCEND, + self::DIRECTION_DESCEND, + ]; + + /** + * Array of supported orders in configuration. + * + * @var string[] + */ + private $supportedOrderOptions = [ + self::ORDER_ALPHA, + self::ORDER_LENGTH, + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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, + ] + ), + ], + null, + "Risky for `implements` when specifying both an interface and its parent interface, because PHP doesn't break on `parent, child` but does on `child, parent`." + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_IMPLEMENTS) + || $tokens->isAllTokenKindsFound([T_INTERFACE, T_EXTENDS]); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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; + } + } + + $interfaceIndex = 0; + $interfaces = [['tokens' => []]]; + + $implementsStart = $index + 1; + $classStart = $tokens->getNextTokenOfKind($implementsStart, ['{']); + $implementsEnd = $tokens->getPrevNonWhitespace($classStart); + + for ($i = $implementsStart; $i <= $implementsEnd; ++$i) { + if ($tokens[$i]->equals(',')) { + ++$interfaceIndex; + $interfaces[$interfaceIndex] = ['tokens' => []]; + + continue; + } + + $interfaces[$interfaceIndex]['tokens'][] = $tokens[$i]; + } + + if (1 === \count($interfaces)) { + continue; + } + + foreach ($interfaces as $interfaceIndex => $interface) { + $interfaceTokens = Tokens::fromArray($interface['tokens'], false); + + $normalized = ''; + $actualInterfaceIndex = $interfaceTokens->getNextMeaningfulToken(-1); + + while ($interfaceTokens->offsetExists($actualInterfaceIndex)) { + $token = $interfaceTokens[$actualInterfaceIndex]; + + if (null === $token || $token->isComment() || $token->isWhitespace()) { + break; + } + + $normalized .= str_replace('\\', ' ', $token->getContent()); + ++$actualInterfaceIndex; + } + + $interfaces[$interfaceIndex]['normalized'] = $normalized; + $interfaces[$interfaceIndex]['originalIndex'] = $interfaceIndex; + } + + usort($interfaces, function (array $first, array $second) { + $score = self::ORDER_LENGTH === $this->configuration[self::OPTION_ORDER] + ? \strlen($first['normalized']) - \strlen($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); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder(self::OPTION_ORDER, 'How the interfaces should be ordered')) + ->setAllowedValues($this->supportedOrderOptions) + ->setDefault(self::ORDER_ALPHA) + ->getOption(), + (new FixerOptionBuilder(self::OPTION_DIRECTION, 'Which direction the interfaces should be ordered')) + ->setAllowedValues($this->supportedDirectionOptions) + ->setDefault(self::DIRECTION_ASCEND) + ->getOption(), + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..2b6760dd131572ac102d1716813eee1c51d1f9ff --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.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\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + * @author SpacePossum + */ +final class ProtectedToPrivateFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Converts `protected` variables and methods to `private` where possible.', + [ + new CodeSample( + 'isAllTokenKindsFound([T_CLASS, T_FINAL, T_PROTECTED]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $end = \count($tokens) - 3; // min. number of tokens to form a class candidate to fix + for ($index = 0; $index < $end; ++$index) { + if (!$tokens[$index]->isGivenKind(T_CLASS)) { + continue; + } + + $classOpen = $tokens->getNextTokenOfKind($index, ['{']); + $classClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen); + + if (!$this->skipClass($tokens, $index, $classOpen, $classClose)) { + $this->fixClass($tokens, $classOpen, $classClose); + } + + $index = $classClose; + } + } + + /** + * @param int $classOpenIndex + * @param int $classCloseIndex + */ + private function fixClass(Tokens $tokens, $classOpenIndex, $classCloseIndex) + { + for ($index = $classOpenIndex + 1; $index < $classCloseIndex; ++$index) { + if ($tokens[$index]->equals('{')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + + if (!$tokens[$index]->isGivenKind(T_PROTECTED)) { + continue; + } + + $tokens[$index] = new Token([T_PRIVATE, 'private']); + } + } + + /** + * Decide whether or not skip the fix for given class. + * + * @param int $classIndex + * @param int $classOpenIndex + * @param int $classCloseIndex + * + * @return bool + */ + private function skipClass(Tokens $tokens, $classIndex, $classOpenIndex, $classCloseIndex) + { + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)]; + if (!$prevToken->isGivenKind(T_FINAL)) { + return true; + } + + for ($index = $classIndex; $index < $classOpenIndex; ++$index) { + if ($tokens[$index]->isGivenKind(T_EXTENDS)) { + return true; + } + } + + $useIndex = $tokens->getNextTokenOfKind($classIndex, [[CT::T_USE_TRAIT]]); + + return $useIndex && $useIndex < $classCloseIndex; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..ed6966a4108fbdf23115eab2c2484fc247e1175f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.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\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gregor Harlan + */ +final class SelfAccessorFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + foreach ((new NamespacesAnalyzer())->getDeclarations($tokens) 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). + * + * @param string $namespace + * @param string $name + * @param int $startIndex + * @param int $endIndex + */ + private function replaceNameOccurrences(Tokens $tokens, $namespace, $name, $startIndex, $endIndex) + { + $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_FUNCTION)) { + $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_OBJECT_OPERATOR, T_STRING])) { + continue; + } + + if ( + $prevToken->isGivenKind([T_INSTANCEOF, T_NEW]) + || $nextToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM) + || ( + null !== $insideMethodSignatureUntil + && $i < $insideMethodSignatureUntil + && $prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON], [CT::T_NULLABLE_TYPE]]) + ) + ) { + for ($j = $classStartIndex; $j < $i; ++$j) { + $tokens->clearTokenAndMergeSurroundingWhitespace($j); + } + $tokens[$i] = new Token([T_STRING, 'self']); + } + } + } + + private function getClassStart(Tokens $tokens, $index, $namespace) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..98ea55218668a3acace517a75a75dcfd3d6024f8 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php @@ -0,0 +1,206 @@ + + * Dariusz Rumiń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\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +final class SelfStaticAccessorFixer extends AbstractFixer +{ + /** + * @var TokensAnalyzer + */ + private $tokensAnalyzer; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Inside a `final` class or anonymous class `self` should be preferred to `static`.', + [ + new CodeSample( + 'isAllTokenKindsFound([T_CLASS, T_STATIC]) && $tokens->isAnyTokenKindsFound([T_DOUBLE_COLON, T_NEW, T_INSTANCEOF]); + } + + /** + * {@inheritdoc} + * + * Must run after FinalInternalClassFixer, FunctionToConstantFixer, PhpUnitTestCaseStaticMethodCallsFixer. + */ + public function getPriority() + { + return -10; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $this->tokensAnalyzer = $tokensAnalyzer = new TokensAnalyzer($tokens); + + $classIndex = $tokens->getNextTokenOfKind(0, [[T_CLASS]]); + + while (null !== $classIndex) { + if ( + $tokens[$tokens->getPrevMeaningfulToken($classIndex)]->isGivenKind(T_FINAL) + || $tokensAnalyzer->isAnonymousClass($classIndex) + ) { + $classIndex = $this->fixClass($tokens, $classIndex); + } + + $classIndex = $tokens->getNextTokenOfKind($classIndex, [[T_CLASS]]); + } + } + + /** + * @param int $index + * + * @return int + */ + private function fixClass(Tokens $tokens, $index) + { + $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->fixClass($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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d009bb4eedc0adcb14ab757b222282c7b886a9ab --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php @@ -0,0 +1,232 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 SpacePossum + * @author Dariusz Rumiński + */ +final class SingleClassElementPerStatementFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'There MUST NOT be more than one property or constant declared per statement.', + [ + new CodeSample( + ' ['property']] + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $values = ['const', 'property']; + + return new FixerConfigurationResolverRootless('elements', [ + (new FixerOptionBuilder('elements', 'List of strings which element should be modified.')) + ->setDefault($values) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($values)]) + ->getOption(), + ], $this->getName()); + } + + /** + * @param string $type + * @param int $index + */ + private function fixElement(Tokens $tokens, $type, $index) + { + $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, [';']) + ); + } + + /** + * @param string $type + * @param int $startIndex + * @param int $endIndex + */ + private function expandElement(Tokens $tokens, $type, $startIndex, $endIndex) + { + $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); + } + } + + /** + * @param string $type + * @param int $startIndex + * @param int $endIndex + * + * @return Token[] + */ + private function getModifiersSequences(Tokens $tokens, $type, $startIndex, $endIndex) + { + 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]; + } 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..a2419b5828957a0cacc79223c7e70519c4338b21 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.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\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class SingleTraitInsertPerStatementFixer extends AbstractFixer +{ + public function getDefinition() + { + 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) + { + 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 $useTraitIndex + * @param int[] $candidates ',' indexes to fix + */ + private function fixTraitUse(Tokens $tokens, $useTraitIndex, array $candidates) + { + 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 (1 === 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); + } + } + + /** + * @param int $index + * + * @return int[] + */ + private function getCandidates(Tokens $tokens, $index) + { + $indexes = []; + $index = $tokens->getNextTokenOfKind($index, [',', ';', '{']); + + while (!$tokens[$index]->equals(';')) { + if ($tokens[$index]->equals('{')) { + return []; // do not fix use cases with grouping + } + + $indexes[] = $index; + $index = $tokens->getNextTokenOfKind($index, [',', ';', '{']); + } + + return array_reverse($indexes); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..5dbf3f7c9026145c9b3ab34ca8d51ba42c88131f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.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\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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; + +/** + * Fixer for rules defined in PSR2 ¶4.3, ¶4.5. + * + * @author Dariusz Rumiński + * @author SpacePossum + */ +final class VisibilityRequiredFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolverRootless('elements', [ + (new FixerOptionBuilder('elements', 'The structural elements to fix (PHP >= 7.1 required for `const`).')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(['property', 'method', 'const'])]) + ->setNormalizer(static function (Options $options, $value) { + if (\PHP_VERSION_ID < 70100 && \in_array('const', $value, true)) { + throw new InvalidOptionsForEnvException('"const" option can only be enabled with PHP 7.1+.'); + } + + return $value; + }) + ->setDefault(['property', 'method']) + ->getOption(), + ], $this->getName()); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $elements = $tokensAnalyzer->getClassyElements(); + + $propertyTypeDeclarationKinds = [T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT]; + + foreach (array_reverse($elements, true) as $index => $element) { + if (!\in_array($element['type'], $this->configuration['elements'], true)) { + continue; + } + + $abstractFinalIndex = null; + $visibilityIndex = null; + $staticIndex = null; + $typeIndex = null; + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + $expectedKinds = [T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR]; + if ('property' === $element['type']) { + $expectedKinds = array_merge($expectedKinds, $propertyTypeDeclarationKinds); + } + + 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($propertyTypeDeclarationKinds)) { + $typeIndex = $prevIndex; + } else { + $visibilityIndex = $prevIndex; + } + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + } + + if (null !== $typeIndex) { + $index = $typeIndex; + } + + if ($tokens[$prevIndex]->equals(',')) { + continue; + } + + if (null !== $staticIndex) { + if ($this->isKeywordPlacedProperly($tokens, $staticIndex, $index)) { + $index = $staticIndex; + } else { + $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $staticIndex, $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); + } + } + + /** + * @param int $keywordIndex + * @param int $comparedIndex + * + * @return bool + */ + private function isKeywordPlacedProperly(Tokens $tokens, $keywordIndex, $comparedIndex) + { + return $keywordIndex + 2 === $comparedIndex && ' ' === $tokens[$keywordIndex + 1]->getContent(); + } + + /** + * @param int $fromIndex + * @param int $toIndex + */ + private function moveTokenAndEnsureSingleSpaceFollows(Tokens $tokens, $fromIndex, $toIndex) + { + $tokens->insertAt($toIndex, [$tokens[$fromIndex], new Token([T_WHITESPACE, ' '])]); + + $tokens->clearAt($fromIndex); + if ($tokens[$fromIndex + 1]->isWhitespace()) { + $tokens->clearAt($fromIndex + 1); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..8acf40da8564ae7f7a8d123e3caa0fca1723650c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.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\ClassUsage; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class DateTimeImmutableFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Class `DateTimeImmutable` should be used instead of `DateTime`.', + [new CodeSample("isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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 ($token->isGivenKind(T_USE) && $isInNamespace) { + $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; + } + + $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 + } elseif ('date_create' === $lowercaseContent) { + $this->fixFunctionUsage($tokens, $index, 'date_create_immutable'); + } elseif ('date_create_from_format' === $lowercaseContent) { + $this->fixFunctionUsage($tokens, $index, 'date_create_immutable_from_format'); + } + } + } + + /** + * @param int $index + * @param bool $isInNamespace + * @param bool $isImported + */ + private function fixClassUsage(Tokens $tokens, $index, $isInNamespace, $isImported) + { + $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, T_OBJECT_OPERATOR])) { + $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, '\\'])); + } + } + } + + /** + * @param int $index + * @param string $replacement + */ + private function fixFunctionUsage(Tokens $tokens, $index, $replacement) + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevIndex]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR])) { + return; + } + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + if ($tokens[$prevPrevIndex]->isGivenKind([T_NEW, T_STRING])) { + return; + } + } + + $tokens[$index] = new Token([T_STRING, $replacement]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..e3e4e24579f2146ddf355f89a86b0072212aded7 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php @@ -0,0 +1,232 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var string[] + */ + private $ignoredTags = []; + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_COMMENT); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + * + * Must run before GeneralPhpdocAnnotationRemoveFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocInlineTagFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + */ + public function getPriority() + { + // Should be run before all other PHPDoc fixers + return 26; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Comments with annotation should be docblock when used on structural elements.', + [new CodeSample("ignoredTags = array_map( + static function ($tag) { + return strtolower($tag); + }, + $this->configuration['ignored_tags'] + ); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('ignored_tags', sprintf('List of ignored tags'))) + ->setAllowedTypes(['array']) + ->setDefault([]) + ->getOption(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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 + * + * @return bool + */ + private function isCommentCandidate(Tokens $tokens, array $indices) + { + return array_reduce( + $indices, + function ($carry, $index) use ($tokens) { + if ($carry) { + return true; + } + if (1 !== 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, $indices) + { + if (1 === \count($indices)) { + $this->fixCommentSingleLine($tokens, reset($indices)); + } else { + $this->fixCommentMultiLine($tokens, $indices); + } + } + + /** + * @param int $index + */ + private function fixCommentSingleLine(Tokens $tokens, $index) + { + $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) + { + $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 (false !== strpos($tokens[$index]->getContent(), '*/')) { + return; + } + $newContent .= $indent.' *'.$this->getMessage($tokens[$index]->getContent()).$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($content) + { + if (0 === strpos($content, '#')) { + return substr($content, 1); + } + if (0 === strpos($content, '//')) { + return substr($content, 2); + } + + return rtrim(ltrim($content, '/*'), '*/'); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/HashToSlashCommentFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/HashToSlashCommentFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..62a3161149c85d15ea04515022b6bc9add0cf124 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/HashToSlashCommentFixer.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\Comment; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * Changes single comments prefixes '#' with '//'. + * + * @author SpacePossum + * + * @deprecated in 2.4, proxy to SingleLineCommentStyleFixer + */ +final class HashToSlashCommentFixer extends AbstractProxyFixer implements DeprecatedFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Single line comments should use double slashes `//` and not hash `#`.', + [new CodeSample("proxyFixers); + } + + /** + * {@inheritdoc} + */ + protected function createProxyFixers() + { + $fixer = new SingleLineCommentStyleFixer(); + $fixer->configure(['comment_types' => ['hash']]); + + return [$fixer]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b47fb4d30f79fec1220a054afae8f981e82e8f24 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php @@ -0,0 +1,452 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AliasedFixerOptionBuilder; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author Antonio J. García Lagar + * @author SpacePossum + */ +final class HeaderCommentFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + const HEADER_PHPDOC = 'PHPDoc'; + const HEADER_COMMENT = 'comment'; + + /** @deprecated will be removed in 3.0 */ + const HEADER_LOCATION_AFTER_OPEN = 1; + /** @deprecated will be removed in 3.0 */ + const HEADER_LOCATION_AFTER_DECLARE_STRICT = 2; + + /** @deprecated will be removed in 3.0 */ + const HEADER_LINE_SEPARATION_BOTH = 1; + /** @deprecated will be removed in 3.0 */ + const HEADER_LINE_SEPARATION_TOP = 2; + /** @deprecated will be removed in 3.0 */ + const HEADER_LINE_SEPARATION_BOTTOM = 3; + /** @deprecated will be removed in 3.0 */ + const HEADER_LINE_SEPARATION_NONE = 4; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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', + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return isset($tokens[0]) && $tokens[0]->isGivenKind(T_OPEN_TAG) && $tokens->isMonolithicPhp(); + } + + /** + * {@inheritdoc} + * + * Must run after DeclareStrictTypesFixer, NoBlankLinesAfterPhpdocFixer. + */ + public function getPriority() + { + // 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; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $location = $this->configuration['location']; + + $locationIndexes = []; + foreach (['after_open', 'after_declare_strict'] as $possibleLocation) { + $locationIndex = $this->findHeaderCommentInsertionIndex($tokens, $possibleLocation); + + if (!isset($locationIndexes[$locationIndex]) || $possibleLocation === $location) { + $locationIndexes[$locationIndex] = $possibleLocation; + + continue; + } + } + + foreach (array_values($locationIndexes) 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); + } elseif ($this->getHeaderAsComment() !== $tokens[$headerCurrentIndex]->getContent() || $possibleLocation !== $location) { + $this->removeHeader($tokens, $headerCurrentIndex); + + if ('' === $this->configuration['header']) { + continue; + } + + if ($possibleLocation === $location) { + $this->insertHeader($tokens, $headerNewIndex); + } + } else { + $this->fixWhiteSpaceAroundHeader($tokens, $headerCurrentIndex); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $fixerName = $this->getName(); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('header', 'Proper header content.')) + ->setAllowedTypes(['string']) + ->setNormalizer(static function (Options $options, $value) use ($fixerName) { + if ('' === trim($value)) { + return ''; + } + + if (false !== strpos($value, '*/')) { + throw new InvalidFixerConfigurationException($fixerName, 'Cannot use \'*/\' in header.'); + } + + return $value; + }) + ->getOption(), + (new AliasedFixerOptionBuilder( + new FixerOptionBuilder('comment_type', 'Comment syntax type.'), + 'commentType' + )) + ->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. + * + * @return string + */ + private function getHeaderAsComment() + { + $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.' */'; + } + + /** + * @param int $headerNewIndex + * + * @return null|int + */ + private function findHeaderCommentCurrentIndex(Tokens $tokens, $headerNewIndex) + { + $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. + * + * @param string $location + * + * @return int + */ + private function findHeaderCommentInsertionIndex(Tokens $tokens, $location) + { + if ('after_open' === $location) { + return 1; + } + + $index = $tokens->getNextMeaningfulToken(0); + if (null === $index) { + // file without meaningful tokens but an open tag, comment should always be placed directly after the open tag + return 1; + } + + if (!$tokens[$index]->isGivenKind(T_DECLARE)) { + return 1; + } + + $next = $tokens->getNextMeaningfulToken($index); + if (null === $next || !$tokens[$next]->equals('(')) { + return 1; + } + + $next = $tokens->getNextMeaningfulToken($next); + if (null === $next || !$tokens[$next]->equals([T_STRING, 'strict_types'], false)) { + return 1; + } + + $next = $tokens->getNextMeaningfulToken($next); + if (null === $next || !$tokens[$next]->equals('=')) { + return 1; + } + + $next = $tokens->getNextMeaningfulToken($next); + if (null === $next || !$tokens[$next]->isGivenKind(T_LNUMBER)) { + return 1; + } + + $next = $tokens->getNextMeaningfulToken($next); + if (null === $next || !$tokens[$next]->equals(')')) { + return 1; + } + + $next = $tokens->getNextMeaningfulToken($next); + if (null === $next || !$tokens[$next]->equals(';')) { // don't insert after close tag + return 1; + } + + return $next + 1; + } + + /** + * @param int $headerIndex + */ + private function fixWhiteSpaceAroundHeader(Tokens $tokens, $headerIndex) + { + $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)])); + } + } + + /** + * @param int $index + * @param int $direction + * + * @return int + */ + private function getLineBreakCount(Tokens $tokens, $index, $direction) + { + $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, $index) + { + $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); + + if ('' !== $content) { + $tokens[$prevIndex] = new Token([T_WHITESPACE, $content]); + } else { + $tokens->clearAt($prevIndex); + } + } + + $nextIndex = $index + 1; + $nextToken = isset($tokens[$nextIndex]) ? $tokens[$nextIndex] : null; + + if (!$newlineRemoved && null !== $nextToken && $nextToken->isWhitespace()) { + $content = Preg::replace('/^\R/', '', $nextToken->getContent()); + + if ('' !== $content) { + $tokens[$nextIndex] = new Token([T_WHITESPACE, $content]); + } else { + $tokens->clearAt($nextIndex); + } + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + + /** + * @param int $index + */ + private function insertHeader(Tokens $tokens, $index) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..0d79ec63286d8f8f2bba0062178a6d12f723daa6 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.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\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class MultilineCommentOpeningClosingFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + $originalContent = $token->getContent(); + + if ( + !$token->isGivenKind(T_DOC_COMMENT) + && !($token->isGivenKind(T_COMMENT) && 0 === strpos($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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..68c2950e7311fbb63191424ad5d68f9c641f00b6 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.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\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class NoEmptyCommentFixer extends AbstractFixer +{ + const TYPE_HASH = 1; + const TYPE_DOUBLE_SLASH = 2; + const TYPE_SLASH_ASTERISK = 3; + + /** + * {@inheritdoc} + * + * Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer. + * Must run after PhpdocToCommentFixer. + */ + public function getPriority() + { + return 2; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'There should not be any empty comments.', + [new CodeSample("isTokenKindFound(T_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { + if (!$tokens[$index]->isGivenKind(T_COMMENT)) { + continue; + } + + list($blockStart, $index, $isEmpty) = $this->getCommentBlock($tokens, $index); + 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 + */ + private function getCommentBlock(Tokens $tokens, $index) + { + $commentType = $this->getCommentType($tokens[$index]->getContent()); + $empty = $this->isEmptyComment($tokens[$index]->getContent()); + $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 [$start, $index - 1, $empty]; + } + + /** + * @param string $content + * + * @return int + */ + private function getCommentType($content) + { + if ('#' === $content[0]) { + return self::TYPE_HASH; + } + + if ('*' === $content[1]) { + return self::TYPE_SLASH_ASTERISK; + } + + return self::TYPE_DOUBLE_SLASH; + } + + /** + * @param int $whiteStart + * @param int $whiteEnd + * + * @return int + */ + private function getLineBreakCount(Tokens $tokens, $whiteStart, $whiteEnd) + { + $lineCount = 0; + for ($i = $whiteStart; $i < $whiteEnd; ++$i) { + $lineCount += Preg::matchAll('/\R/u', $tokens[$i]->getContent(), $matches); + } + + return $lineCount; + } + + /** + * @param string $content + * + * @return bool + */ + private function isEmptyComment($content) + { + 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 1 === Preg::match($mapper[$type], $content); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..90da2d176d2fcd69c02b74ac781318e719213306 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.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\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class NoTrailingWhitespaceInCommentFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'There MUST be no trailing spaces inside comment or PHPDoc.', + [new CodeSample('isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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 ('/*' === substr($token->getContent(), 0, 2)) { + $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"); + if ('' !== $trimmedContent) { + $tokens[$index + 1] = new Token([T_WHITESPACE, $trimmedContent]); + } else { + $tokens->clearAt($index + 1); + } + } + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..58fdc1319a11aeba3533b098219505b0a21af118 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.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\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class SingleLineCommentStyleFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @var bool + */ + private $asteriskEnabled; + + /** + * @var bool + */ + private $hashEnabled; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->asteriskEnabled = \in_array('asterisk', $this->configuration['comment_types'], true); + $this->hashEnabled = \in_array('hash', $this->configuration['comment_types'], true); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_COMMENT)) { + continue; + } + + $content = $token->getContent(); + $commentContent = substr($content, 2, -2) ?: ''; + + if ($this->hashEnabled && '#' === $content[0]) { + $tokens[$index] = new Token([$token->getId(), '//'.substr($content, 1)]); + + continue; + } + + if ( + !$this->asteriskEnabled + || false !== strpos($commentContent, '?>') + || '/*' !== substr($content, 0, 2) + || 1 === Preg::match('/[^\s\*].*\R.*[^\s\*]/s', $commentContent) + ) { + continue; + } + + $nextTokenIndex = $index + 1; + if (isset($tokens[$nextTokenIndex])) { + $nextToken = $tokens[$nextTokenIndex]; + if (!$nextToken->isWhitespace() || 1 !== Preg::match('/\R/', $nextToken->getContent())) { + continue; + } + + $tokens[$nextTokenIndex] = new Token([$nextToken->getId(), ltrim($nextToken->getContent(), " \t")]); + } + + $content = '//'; + if (1 === Preg::match('/[^\s\*]/', $commentContent)) { + $content = '// '.Preg::replace('/[\s\*]*([^\s\*](?:.+[^\s\*])?)[\s\*]*/', '\1', $commentContent); + } + $tokens[$index] = new Token([$token->getId(), $content]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..31c4b44ad21938ab8d2baf241d6111febc7bcce1 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.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; + +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; + +/** + * @author Dariusz Rumiński + * @author SpacePossum + * + * @todo Will incorporate `ConfigurationDefinitionFixerInterface` in 3.0 + */ +interface ConfigurableFixerInterface extends FixerInterface +{ + /** + * Set configuration. + * + * New configuration must override current one, not patch it. + * Using `null` makes fixer to use default configuration (or reset configuration from previously configured back + * to default one). + * + * Some fixers may have no configuration, then - simply pass null. + * 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 null|array $configuration configuration depends on Fixer + * + * @throws InvalidFixerConfigurationException + */ + public function configure(array $configuration = null); + + /* + * Defines the available configuration options of the fixer. + * + * @return FixerConfigurationResolverInterface + * + * @todo uncomment at 3.0 + */ + // public function getConfigurationDefinition(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ConfigurationDefinitionFixerInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ConfigurationDefinitionFixerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1512940a71306585341651c75e14aa3f2be55c2b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ConfigurationDefinitionFixerInterface.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; + +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; + +/** + * @deprecated Will be incorporated into `ConfigurableFixerInterface` in 3.0 + */ +interface ConfigurationDefinitionFixerInterface extends ConfigurableFixerInterface +{ + /** + * Defines the available configuration options of the fixer. + * + * @return FixerConfigurationResolverInterface + */ + public function getConfigurationDefinition(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..718fe2656551eda7c057381fbf3f8a1bd4b45b09 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php @@ -0,0 +1,284 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; +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 ConfigurationDefinitionFixerInterface +{ + /** + * @var array + */ + private $constantsToEscape = []; + + /** + * @var array + */ + private $caseInsensitiveConstantsToEscape = []; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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. + */ + public function getPriority() + { + return 10; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + 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 = array_merge($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 function ($function) { return 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); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + if ('all' === $this->configuration['scope']) { + $this->fixConstantInvocations($tokens, 0, \count($tokens) - 1); + + return; + } + + $namespaces = (new NamespacesAnalyzer())->getDeclarations($tokens); + + // 'scope' is 'namespaced' here + /** @var NamespaceAnalysis $namespace */ + foreach (array_reverse($namespaces) as $namespace) { + if ('' === $namespace->getFullName()) { + continue; + } + + $this->fixConstantInvocations($tokens, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex()); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $constantChecker = static function ($value) { + 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.', + \is_object($constantName) ? \get_class($constantName) : \gettype($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(), + ]); + } + + /** + * @param int $start + * @param int $end + */ + private function fixConstantInvocations(Tokens $tokens, $start, $end) + { + $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); + $useConstantDeclarations = []; + foreach ($useDeclarations as $use) { + if ($use->isConstant()) { + $useConstantDeclarations[$use->getShortName()] = true; + } + } + + $tokenAnalyzer = new TokensAnalyzer($tokens); + + $indexes = []; + for ($index = $start; $index < $end; ++$index) { + $token = $tokens[$index]; + + // test if we are at a constant call + if (!$token->isGivenKind(T_STRING)) { + continue; + } + + $tokenContent = $token->getContent(); + + if (!isset($this->constantsToEscape[$tokenContent]) && !isset($this->caseInsensitiveConstantsToEscape[strtolower($tokenContent)])) { + continue; + } + + if (isset($useConstantDeclarations[$tokenContent])) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + if (!$tokenAnalyzer->isConstantInvocation($index)) { + continue; + } + + $indexes[] = $index; + } + + $indexes = array_reverse($indexes); + foreach ($indexes as $index) { + $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..bc1598df09e136fa8a78117a0687ff8334b97968 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.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\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/IncludeFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/IncludeFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..17de89d0e7cd84de127b2b32e9841740345d6ae3 --- /dev/null +++ b/lib/composer/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\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 +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Include/Require and file path should be divided with a single space. File path should not be placed under brackets.', + [ + new CodeSample( + 'isAnyTokenKindsFound([T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $this->clearIncludies($tokens, $this->findIncludies($tokens)); + } + + private function clearIncludies(Tokens $tokens, array $includies) + { + $blocksAnalyzer = new BlocksAnalyzer(); + + foreach ($includies as $includy) { + if ($includy['end'] && !$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, ' '])); + } + } + } + + private function findIncludies(Tokens $tokens) + { + 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; + } + + /** + * @param int $index + */ + private function removeWhitespaceAroundIfPossible(Tokens $tokens, $index) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..135bfc9117497446fffc5c9974ad5e6ccc66d780 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.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\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Eddilbert Macharia + */ +final class NoAlternativeSyntaxFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replace control structure alternative syntax to use braces.', + [ + new CodeSample( + "isAnyTokenKindsFound( + [ + T_ENDIF, + T_ENDWHILE, + T_ENDFOREACH, + T_ENDFOR, + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BracesFixer, ElseifFixer, NoSuperfluousElseifFixer, NoUselessElseFixer. + */ + public function getPriority() + { + return 26; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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, $structureTokenIndex) + { + $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); + } + + /** + * 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($index, Token $token, Tokens $tokens) + { + if ($token->isGivenKind([T_IF, T_FOREACH, T_WHILE, T_FOR])) { + $openIndex = $tokens->getNextTokenOfKind($index, ['(']); + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + $afterParenthesisIndex = $tokens->getNextNonWhitespace($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])) { + return; + } + + $nextTokenIndex = $tokens->getNextMeaningfulToken($index); + $nextToken = $tokens[$nextTokenIndex]; + $tokens[$index] = new Token('}'); + if ($nextToken->equals(';')) { + $tokens->clearAt($nextTokenIndex); + } + } + + /** + * Handle the else:. + * + * @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($index, Token $token, Tokens $tokens) + { + 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($index, Token $token, Tokens $tokens) + { + 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: . + * + * @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, $index, $colonIndex) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..04c7e6b946d93c39ab4fcea8eb51c3ab316f7498 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php @@ -0,0 +1,369 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +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 ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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.' + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound([T_CASE, T_DEFAULT]); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('comment_text', 'The text to use in the added comment and to detect it.')) + ->setAllowedTypes(['string']) + ->setAllowedValues([ + static function ($value) { + if (\is_string($value) && Preg::match('/\R/', $value)) { + throw new InvalidOptionsException('The comment text must not contain new lines.'); + } + + return true; + }, + ]) + ->setNormalizer(static function (Options $options, $value) { + return rtrim($value); + }) + ->setDefault('no break') + ->getOption(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($position = \count($tokens) - 1; $position >= 0; --$position) { + if ($tokens[$position]->isGivenKind([T_CASE, T_DEFAULT])) { + $this->fixCase($tokens, $position); + } + } + } + + /** + * @param int $casePosition + */ + private function fixCase(Tokens $tokens, $casePosition) + { + $empty = true; + $fallThrough = true; + $commentPosition = null; + for ($i = $tokens->getNextTokenOfKind($casePosition, [':', ';']) + 1, $max = \count($tokens); $i < $max; ++$i) { + if ($tokens[$i]->isGivenKind([T_SWITCH, T_IF, T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_WHILE, T_DO, T_FUNCTION, T_CLASS])) { + $empty = false; + $i = $this->getStructureEnd($tokens, $i); + + continue; + } + + if ($tokens[$i]->isGivenKind([T_BREAK, T_CONTINUE, T_RETURN, T_EXIT, T_THROW, T_GOTO])) { + $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; + } + } + } + + /** + * @return bool + */ + private function isNoBreakComment(Token $token) + { + if (!$token->isComment()) { + return false; + } + + $text = preg_quote($this->configuration['comment_text'], '~'); + + return 1 === Preg::match("~^((//|#)\\s*{$text}\\s*)|(/\\*\\*?\\s*{$text}\\s*\\*/)$~i", $token->getContent()); + } + + /** + * @param int $casePosition + */ + private function insertCommentAt(Tokens $tokens, $casePosition) + { + $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 = $this->getIndentAt($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); + } + + /** + * @param int $position + * + * @return int The newline token position + */ + private function ensureNewLineAt(Tokens $tokens, $position) + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + $content = $lineEnding.$this->getIndentAt($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; + } + + /** + * @param int $commentPosition + */ + private function removeComment(Tokens $tokens, $commentPosition) + { + 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()); + if ('' !== $content) { + $tokens[$whitespacePosition] = new Token([T_WHITESPACE, $content]); + } else { + $tokens->clearAt($whitespacePosition); + } + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($commentPosition); + } + + /** + * @param int $position + * + * @return string + */ + private function getIndentAt(Tokens $tokens, $position) + { + while (true) { + $position = $tokens->getPrevTokenOfKind($position, [[T_WHITESPACE]]); + + if (null === $position) { + break; + } + + $content = $tokens[$position]->getContent(); + + $prevToken = $tokens[$position - 1]; + if ($prevToken->isGivenKind(T_OPEN_TAG) && Preg::match('/\R$/', $prevToken->getContent())) { + $content = $this->whitespacesConfig->getLineEnding().$content; + } + + if (Preg::match('/\R(\h*)$/', $content, $matches)) { + return $matches[1]; + } + } + + return ''; + } + + /** + * @param int $position + * + * @return int + */ + private function getStructureEnd(Tokens $tokens, $position) + { + $initialToken = $tokens[$position]; + + if ($initialToken->isGivenKind([T_FOR, T_FOREACH, T_WHILE, T_IF, T_ELSEIF, T_SWITCH, T_FUNCTION])) { + $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; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..37cf0bfc1fb54d990218c3e1259501738f77d447 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php @@ -0,0 +1,113 @@ + + * Dariusz Rumiń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\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoSuperfluousElseifFixer extends AbstractNoUselessElseFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound([T_ELSE, T_ELSEIF]); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replaces superfluous `elseif` with `if`.', + [ + new CodeSample(" $token) { + if ($this->isElseif($tokens, $index) && $this->isSuperfluousElse($tokens, $index)) { + $this->convertElseifToIf($tokens, $index); + } + } + } + + /** + * @param int $index + * + * @return bool + */ + private function isElseif(Tokens $tokens, $index) + { + if ($tokens[$index]->isGivenKind(T_ELSEIF)) { + return true; + } + + return $tokens[$index]->isGivenKind(T_ELSE) && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_IF); + } + + /** + * @param int $index + */ + private function convertElseifToIf(Tokens $tokens, $index) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b08e236d777ba6b92ceb704dd49bcbbc638b8dae --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.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\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class NoTrailingCommaInListCallFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Remove trailing commas in list function calls.', + [new CodeSample("isTokenKindFound(T_LIST); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_LIST)) { + continue; + } + + $openIndex = $tokens->getNextMeaningfulToken($index); + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + $markIndex = null; + $prevIndex = $tokens->getPrevNonWhitespace($closeIndex); + + while ($tokens[$prevIndex]->equals(',')) { + $markIndex = $prevIndex; + $prevIndex = $tokens->getPrevNonWhitespace($prevIndex); + } + + if (null !== $markIndex) { + $tokens->clearRange( + $tokens->getPrevNonWhitespace($markIndex) + 1, + $closeIndex - 1 + ); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..40ffd93a6abd2ab437c0e5f19dd2ef7385cc801d --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.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\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Sullivan Senechal + * @author Dariusz Rumiński + * @author Gregor Harlan + */ +final class NoUnneededControlParenthesesFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + private static $loops = [ + 'break' => ['lookupTokens' => T_BREAK, 'neededSuccessors' => [';']], + 'clone' => ['lookupTokens' => T_CLONE, 'neededSuccessors' => [';', ':', ',', ')'], 'forbiddenContents' => ['?', ':']], + 'continue' => ['lookupTokens' => T_CONTINUE, 'neededSuccessors' => [';']], + 'echo_print' => ['lookupTokens' => [T_ECHO, T_PRINT], 'neededSuccessors' => [';', [T_CLOSE_TAG]]], + 'return' => ['lookupTokens' => T_RETURN, 'neededSuccessors' => [';', [T_CLOSE_TAG]]], + 'switch_case' => ['lookupTokens' => T_CASE, 'neededSuccessors' => [';', ':']], + 'yield' => ['lookupTokens' => T_YIELD, 'neededSuccessors' => [';', ')']], + ]; + + /** + * Dynamic `null` coalesce option set on constructor. + */ + public function __construct() + { + parent::__construct(); + + // To be moved back to compile time property declaration when PHP support of PHP CS Fixer will be 7.0+ + if (\defined('T_COALESCE')) { + self::$loops['clone']['forbiddenContents'][] = [T_COALESCE, '??']; + } + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + $types = []; + + foreach (self::$loops as $loop) { + $types[] = (array) $loop['lookupTokens']; + } + $types = array_merge(...$types); + + return $tokens->isAnyTokenKindsFound($types); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Removes unneeded parentheses around control statements.', + [ + new CodeSample( + ' ['break', 'continue']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoTrailingWhitespaceFixer. + */ + public function getPriority() + { + return 30; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + // Checks if specific statements are set and uses them in this case. + $loops = array_intersect_key(self::$loops, array_flip($this->configuration['statements'])); + + foreach ($tokens as $index => $token) { + if (!$token->equalsAny(['(', [CT::T_BRACE_CLASS_INSTANTIATION_OPEN]])) { + continue; + } + + $blockStartIndex = $index; + $index = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$index]; + + foreach ($loops as $loop) { + if (!$prevToken->isGivenKind($loop['lookupTokens'])) { + continue; + } + + $blockEndIndex = $tokens->findBlockEnd( + $token->equals('(') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION, + $blockStartIndex + ); + $blockEndNextIndex = $tokens->getNextMeaningfulToken($blockEndIndex); + + if (!$tokens[$blockEndNextIndex]->equalsAny($loop['neededSuccessors'])) { + continue; + } + + if (\array_key_exists('forbiddenContents', $loop)) { + $forbiddenTokenIndex = $tokens->getNextTokenOfKind($blockStartIndex, $loop['forbiddenContents']); + // A forbidden token is found and is inside the parenthesis. + if (null !== $forbiddenTokenIndex && $forbiddenTokenIndex < $blockEndIndex) { + continue; + } + } + + if ($tokens[$blockStartIndex - 1]->isWhitespace() || $tokens[$blockStartIndex - 1]->isComment()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($blockStartIndex); + } else { + // Adds a space to prevent broken code like `return2`. + $tokens[$blockStartIndex] = new Token([T_WHITESPACE, ' ']); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($blockEndIndex); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolverRootless('statements', [ + (new FixerOptionBuilder('statements', 'List of control statements to fix.')) + ->setAllowedTypes(['array']) + ->setDefault([ + 'break', + 'clone', + 'continue', + 'echo_print', + 'return', + 'switch_case', + 'yield', + ]) + ->getOption(), + ], $this->getName()); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..3f3ccaaa5f062747ae25e9086f65a2231ea1061b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.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\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class NoUnneededCurlyBracesFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Removes unneeded curly braces that are superfluous and aren\'t part of a control structure\'s body.', + [ + new CodeSample( + ' true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoUselessElseFixer, NoUselessReturnFixer, ReturnAssignmentFixer. + */ + public function getPriority() + { + return 26; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound('}'); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($this->findCurlyBraceOpen($tokens) as $index) { + if ($this->isOverComplete($tokens, $index)) { + $this->clearOverCompleteBraces($tokens, $index, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index)); + } + } + + if ($this->configuration['namespaces']) { + $this->clearIfIsOverCompleteNamespaceBlock($tokens); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('namespaces', 'Remove unneeded curly 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, $openIndex, $closeIndex) + { + $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($openIndex); + } + + private function findCurlyBraceOpen(Tokens $tokens) + { + for ($i = \count($tokens) - 1; $i > 0; --$i) { + if ($tokens[$i]->equals('{')) { + yield $i; + } + } + } + + /** + * @param int $index index of `{` token + * + * @return bool + */ + private function isOverComplete(Tokens $tokens, $index) + { + static $whiteList = ['{', '}', [T_OPEN_TAG], ':', ';']; + + return $tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny($whiteList); + } + + private function clearIfIsOverCompleteNamespaceBlock(Tokens $tokens) + { + if (Tokens::isLegacyMode()) { + $index = $tokens->getNextTokenOfKind(0, [[T_NAMESPACE]]); + $secondNamespaceIndex = $tokens->getNextTokenOfKind($index, [[T_NAMESPACE]]); + + if (null !== $secondNamespaceIndex) { + return; + } + } elseif (1 !== $tokens->countTokenKind(T_NAMESPACE)) { + return; // fast check, we never fix if multiple namespaces are defined + } + + $index = $tokens->getNextTokenOfKind(0, [[T_NAMESPACE]]); + + do { + $index = $tokens->getNextMeaningfulToken($index); + } while ($tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR])); + + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..7326befcccfae6b20090e60220585bebbff5ed30 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.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\ControlStructure; + +use PhpCsFixer\AbstractNoUselessElseFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class NoUselessElseFixer extends AbstractNoUselessElseFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_ELSE); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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, $index) + { + $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, $index) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..a7e9682fe0daff609754d9e6e25dfb40bb31a9f4 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php @@ -0,0 +1,113 @@ + + * Dariusz Rumiń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\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶5.2. + * + * @author SpacePossum + */ +final class SwitchCaseSemicolonToColonFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'A case should be followed by a colon and not a semicolon.', + [ + new CodeSample( + 'isAnyTokenKindsFound([T_CASE, T_DEFAULT]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if ($token->isGivenKind([T_CASE, T_DEFAULT])) { + $this->fixSwitchCase($tokens, $index); + } + } + } + + /** + * @param int $index + */ + protected function fixSwitchCase(Tokens $tokens, $index) + { + $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); + + if ($tokens[$index]->equals(';')) { + $tokens[$index] = new Token(':'); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d47492c23e0042fc004a6613eab53ad7a9fd1da9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.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\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶5.2. + * + * @author Sullivan Senechal + */ +final class SwitchCaseSpaceFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Removes extra spaces between colon and case value.', + [ + new CodeSample( + 'isAnyTokenKindsFound([T_CASE, T_DEFAULT]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind([T_CASE, T_DEFAULT])) { + continue; + } + + $ternariesCount = 0; + for ($colonIndex = $index + 1;; ++$colonIndex) { + // We have to skip ternary case for colons. + if ($tokens[$colonIndex]->equals('?')) { + ++$ternariesCount; + } + + if ($tokens[$colonIndex]->equalsAny([':', ';'])) { + if (0 === $ternariesCount) { + break; + } + + --$ternariesCount; + } + } + + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..3b8f6b704e4022061d73925501743b964fd64efd --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php @@ -0,0 +1,735 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Bram Gotink + * @author Dariusz Rumiński + * @author SpacePossum + */ +final class YodaStyleFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @var array + */ + private $candidatesMap; + + /** + * @var array + */ + private $candidateTypesConfiguration; + + /** + * @var array + */ + private $candidateTypes; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->resolveConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Write conditions in Yoda style (`true`), non-Yoda style (`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, + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after IsNullFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound($this->candidateTypes); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $this->fixTokens($tokens); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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, $index) + { + ++$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, $index) + { + --$index; + $nonBlockFound = false; + + while (0 <= $index) { + $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; + $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); + } + + /** + * @return Tokens + */ + private function fixTokens(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. + * + * @param int $startLeft + * @param int $endLeft + * @param int $compareOperatorIndex + * @param int $startRight + * @param int $endRight + * + * @return int a upper bound for all non-fixed comparisons + */ + private function fixTokensCompare( + Tokens $tokens, + $startLeft, + $endLeft, + $compareOperatorIndex, + $startRight, + $endRight + ) { + $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; + } + + /** + * @param int $start + * @param int $end + * + * @return Tokens + */ + private function fixTokensComparePart(Tokens $tokens, $start, $end) + { + $newTokens = $tokens->generatePartialCode($start, $end); + $newTokens = $this->fixTokens(Tokens::fromCode(sprintf('clearAt(\count($newTokens) - 1); + $newTokens->clearAt(0); + $newTokens->clearEmptyTokens(); + + return $newTokens; + } + + /** + * @param int $index + * @param bool $yoda + * + * @return null|array + */ + private function getCompareFixableInfo(Tokens $tokens, $index, $yoda) + { + $left = $this->getLeftSideCompareFixableInfo($tokens, $index); + $right = $this->getRightSideCompareFixableInfo($tokens, $index); + + if ($yoda) { + $expectedAssignableSide = $right; + $expectedValueSide = $left; + } else { + if ($tokens[$tokens->getNextMeaningfulToken($right['end'])]->equals('=')) { + return null; + } + + $expectedAssignableSide = $left; + $expectedValueSide = $right; + } + + if ( + // variable cannot be moved to expected side + !( + !$this->isVariable($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end'], false) + && !$this->isListStatement($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end']) + && $this->isVariable($tokens, $expectedValueSide['start'], $expectedValueSide['end'], false) + ) + // variable cannot be moved to expected side (strict mode) + && !( + $this->configuration['always_move_variable'] + && !$this->isVariable($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end'], true) + && !$this->isListStatement($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end']) + && $this->isVariable($tokens, $expectedValueSide['start'], $expectedValueSide['end'], true) + ) + ) { + return null; + } + + return [ + 'left' => $left, + 'right' => $right, + ]; + } + + /** + * @param int $index + * + * @return array + */ + private function getLeftSideCompareFixableInfo(Tokens $tokens, $index) + { + return [ + 'start' => $this->findComparisonStart($tokens, $index), + 'end' => $tokens->getPrevMeaningfulToken($index), + ]; + } + + /** + * @param int $index + * + * @return array + */ + private function getRightSideCompareFixableInfo(Tokens $tokens, $index) + { + return [ + 'start' => $tokens->getNextMeaningfulToken($index), + 'end' => $this->findComparisonEnd($tokens, $index), + ]; + } + + /** + * @param int $index + * @param int $end + * + * @return bool + */ + private function isListStatement(Tokens $tokens, $index, $end) + { + 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) + { + static $tokens; + + if (null === $tokens) { + $tokens = [ + T_AND_EQUAL, // &= + T_BOOLEAN_AND, // && + T_BOOLEAN_OR, // || + T_CASE, // case + T_CONCAT_EQUAL, // .= + T_DIV_EQUAL, // /= + T_DOUBLE_ARROW, // => + T_ECHO, // echo + T_GOTO, // goto + T_LOGICAL_AND, // and + T_LOGICAL_OR, // or + T_LOGICAL_XOR, // xor + T_MINUS_EQUAL, // -= + T_MOD_EQUAL, // %= + T_MUL_EQUAL, // *= + T_OPEN_TAG, // >= + T_THROW, // throw + T_XOR_EQUAL, // ^= + ]; + + if (\defined('T_COALESCE')) { + $tokens[] = T_COALESCE; // ?? + } + + if (\defined('T_COALESCE_EQUAL')) { + $tokens[] = T_COALESCE_EQUAL; // ??= + } + } + + static $otherTokens = [ + // bitwise and, or, xor + '&', '|', '^', + // ternary operators + '?', ':', + // assignment + '=', + // end of PHP statement + ',', ';', + ]; + + return $token->isGivenKind($tokens) || $token->equalsAny($otherTokens); + } + + /** + * 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, $start, $end, $strict) + { + $tokenAnalyzer = new TokensAnalyzer($tokens); + + if ($start === $end) { + return $tokens[$start]->isGivenKind(T_VARIABLE); + } + + if ($strict) { + if ($tokens[$start]->equals('(')) { + return false; + } + + 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->isGivenKind(T_OBJECT_OPERATOR)) { + $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([[T_OBJECT_OPERATOR, '->'], '[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']])) { + 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]->isGivenKind(T_OBJECT_OPERATOR)) { + return false; + } + + $index = $tokens->getNextMeaningfulToken($index); + $expectString = true; + + continue; + } + + break; + } + + return !$this->isConstant($tokens, $start, $end); + } + + private function isConstant(Tokens $tokens, $index, $end) + { + $expectNumberOnly = false; + $expectNothing = false; + + for (; $index <= $end; ++$index) { + $token = $tokens[$index]; + + if ($token->isComment() || $token->isWhitespace()) { + if ($expectNothing) { + return false; + } + + 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() + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DefinedFixerInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DefinedFixerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..939e718577dab80023cfab72788eb47434b83504 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DefinedFixerInterface.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\Fixer; + +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @author Dariusz Rumiński + * @author SpacePossum + */ +interface DefinedFixerInterface extends FixerInterface +{ + /** + * Returns the definition of the fixer. + * + * @return FixerDefinitionInterface + */ + public function getDefinition(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a33e632e7c3c4bfceaacc0c5e3f5fad19274ac5f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.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\Fixer; + +/** + * @author Kuba Werłos + */ +interface DeprecatedFixerInterface extends FixerInterface +{ + /** + * Returns names of fixers to use instead, if any. + * + * @return string[] + */ + public function getSuccessorsNames(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..f8e3155d1807e5a999ce8134cfa7b1a7e8921206 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.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\DoctrineAnnotation; + +use Doctrine\Common\Annotations\DocLexer; +use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * Forces the configured operator for assignment in arrays in Doctrine Annotations. + */ +final class DoctrineAnnotationArrayAssignmentFixer extends AbstractDoctrineAnnotationFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Doctrine annotations must use configured operator for assignment in arrays.', + [ + new CodeSample( + " ':'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before DoctrineAnnotationSpacesFixer. + */ + public function getPriority() + { + return 1; + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $options = parent::createConfigurationDefinition()->getOptions(); + + $operator = new FixerOptionBuilder('operator', 'The operator to use.'); + $options[] = $operator + ->setAllowedValues(['=', ':']) + ->setDefault('=') + ->getOption() + ; + + return new FixerConfigurationResolver($options); + } + + /** + * {@inheritdoc} + */ + protected function fixAnnotations(Tokens $tokens) + { + $scopes = []; + foreach ($tokens 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..31a693a000e1392c9a6cdcba8a45881102439a41 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.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\DoctrineAnnotation; + +use Doctrine\Common\Annotations\DocLexer; +use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\Token; +use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * Adds braces to Doctrine annotations when missing. + */ +final class DoctrineAnnotationBracesFixer extends AbstractDoctrineAnnotationFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Doctrine annotations without arguments must use the configured syntax.', + [ + new CodeSample( + " 'with_braces'] + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver(array_merge( + parent::createConfigurationDefinition()->getOptions(), + [ + (new FixerOptionBuilder('syntax', 'Whether to add or remove braces.')) + ->setAllowedValues(['with_braces', 'without_braces']) + ->setDefault('without_braces') + ->getOption(), + ] + )); + } + + /** + * {@inheritdoc} + */ + protected function fixAnnotations(Tokens $tokens) + { + if ('without_braces' === $this->configuration['syntax']) { + $this->removesBracesFromAnnotations($tokens); + } else { + $this->addBracesToAnnotations($tokens); + } + } + + private function addBracesToAnnotations(Tokens $tokens) + { + 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) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..7d97c162344ff4807450e421e3fc34265a79f9b6 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.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\DoctrineAnnotation; + +use Doctrine\Common\Annotations\DocLexer; +use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; + +final class DoctrineAnnotationIndentationFixer extends AbstractDoctrineAnnotationFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Doctrine annotations must be indented with four spaces.', + [ + new CodeSample(" true] + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver(array_merge( + parent::createConfigurationDefinition()->getOptions(), + [ + (new FixerOptionBuilder('indent_mixed_lines', 'Whether to indent lines that have content before closing parenthesis.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ] + )); + } + + /** + * {@inheritdoc} + */ + protected function fixAnnotations(Tokens $tokens) + { + $annotationPositions = []; + for ($index = 0, $max = \count($tokens); $index < $max; ++$index) { + if (!$tokens[$index]->isType(DocLexer::T_AT)) { + continue; + } + + $annotationEndIndex = $tokens->getAnnotationEnd($index); + if (null === $annotationEndIndex) { + return; + } + + $annotationPositions[] = [$index, $annotationEndIndex]; + $index = $annotationEndIndex; + } + + $indentLevel = 0; + foreach ($tokens as $index => $token) { + if (!$token->isType(DocLexer::T_NONE) || false === strpos($token->getContent(), "\n")) { + continue; + } + + if (!$this->indentationCanBeFixed($tokens, $index, $annotationPositions)) { + continue; + } + + $braces = $this->getLineBracesCount($tokens, $index); + $delta = $braces[0] - $braces[1]; + $mixedBraces = 0 === $delta && $braces[0] > 0; + $extraIndentLevel = 0; + + if ($indentLevel > 0 && ($delta < 0 || $mixedBraces)) { + --$indentLevel; + + if ($this->configuration['indent_mixed_lines'] && $this->isClosingLineWithMeaningfulContent($tokens, $index)) { + $extraIndentLevel = 1; + } + } + + $token->setContent(Preg::replace( + '/(\n( +\*)?) *$/', + '$1'.str_repeat(' ', 4 * ($indentLevel + $extraIndentLevel) + 1), + $token->getContent() + )); + + if ($delta > 0 || $mixedBraces) { + ++$indentLevel; + } + } + } + + /** + * @param int $index + * + * @return int[] + */ + private function getLineBracesCount(Tokens $tokens, $index) + { + $opening = 0; + $closing = 0; + + while (isset($tokens[++$index])) { + $token = $tokens[$index]; + if ($token->isType(DocLexer::T_NONE) && false !== strpos($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]; + } + + /** + * @param int $index + * + * @return bool + */ + private function isClosingLineWithMeaningfulContent(Tokens $tokens, $index) + { + while (isset($tokens[++$index])) { + $token = $tokens[$index]; + if ($token->isType(DocLexer::T_NONE)) { + if (false !== strpos($token->getContent(), "\n")) { + return false; + } + + continue; + } + + return !$token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES]); + } + + return false; + } + + /** + * @param int $newLineTokenIndex + * @param array> $annotationPositions Pairs of begin and end indexes of main annotations + * + * @return bool + */ + private function indentationCanBeFixed(Tokens $tokens, $newLineTokenIndex, array $annotationPositions) + { + 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 (false !== strpos($token->getContent(), "\n")) { + return false; + } + + return $tokens[$index]->isType(DocLexer::T_AT); + } + + return false; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..86c6dce7e4204ad196e4396625ded22cce79876f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php @@ -0,0 +1,345 @@ + + * Dariusz Rumiń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 Doctrine\Common\Annotations\DocLexer; +use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\Token; +use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; + +/** + * Fixes spaces around commas and assignment operators in Doctrine annotations. + */ +final class DoctrineAnnotationSpacesFixer extends AbstractDoctrineAnnotationFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Fixes spaces in Doctrine annotations.', + [ + new CodeSample( + "configuration['around_argument_assignments']) { + foreach ([ + 'before_argument_assignments', + 'after_argument_assignments', + ] as $newOption) { + if (!\array_key_exists($newOption, $configuration)) { + $this->configuration[$newOption] = null; + } + } + } + + if (!$this->configuration['around_array_assignments']) { + foreach ([ + 'before_array_assignments_equals', + 'after_array_assignments_equals', + 'before_array_assignments_colon', + 'after_array_assignments_colon', + ] as $newOption) { + if (!\array_key_exists($newOption, $configuration)) { + $this->configuration[$newOption] = null; + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver(array_merge( + 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('around_argument_assignments', 'Whether to fix spaces around argument assignment operator.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->setDeprecationMessage('Use options `before_argument_assignments` and `after_argument_assignments` instead.') + ->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('around_array_assignments', 'Whether to fix spaces around array assignment operators.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->setDeprecationMessage('Use options `before_array_assignments_equals`, `after_array_assignments_equals`, `before_array_assignments_colon` and `after_array_assignments_colon` instead.') + ->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(), + ] + )); + } + + /** + * {@inheritdoc} + */ + protected function fixAnnotations(Tokens $tokens) + { + if ($this->configuration['around_parentheses']) { + $this->fixSpacesAroundParentheses($tokens); + } + + if ($this->configuration['around_commas']) { + $this->fixSpacesAroundCommas($tokens); + } + + 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($tokens); + } + } + + private function fixSpacesAroundParentheses(Tokens $tokens) + { + $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 (false !== strpos($token->getContent(), "\n")) { + continue; + } + + $token->clear(); + } + } + } + + private function fixSpacesAroundCommas(Tokens $tokens) + { + $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) + { + $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); + } + } + } + } + + /** + * @param int $index + * @param null|bool $insert + */ + private function updateSpacesAfter(Tokens $tokens, $index, $insert) + { + $this->updateSpacesAt($tokens, $index + 1, $index + 1, $insert); + } + + /** + * @param int $index + * @param null|bool $insert + */ + private function updateSpacesBefore(Tokens $tokens, $index, $insert) + { + $this->updateSpacesAt($tokens, $index - 1, $index, $insert); + } + + /** + * @param int $index + * @param int $insertIndex + * @param null|bool $insert + */ + private function updateSpacesAt(Tokens $tokens, $index, $insertIndex, $insert) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FixerInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FixerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..e4f5172703bf2c436b7743cbd60fc8b03e0d2782 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FixerInterface.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\Fixer; + +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. + * + * @return bool + */ + public function isCandidate(Tokens $tokens); + + /** + * Check if fixer is risky or not. + * + * Risky fixer could change code behavior! + * + * @return bool + */ + public function isRisky(); + + /** + * Fixes a file. + * + * @param \SplFileInfo $file A \SplFileInfo instance + * @param Tokens $tokens Tokens collection + */ + public function fix(\SplFileInfo $file, Tokens $tokens); + + /** + * 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(); + + /** + * Returns the priority of the fixer. + * + * The default priority is 0 and higher priorities are executed first. + * + * @return int + */ + public function getPriority(); + + /** + * 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); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d06235f0d63e9ebda9ef79217f39b7a8dc982c90 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.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\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class CombineNestedDirnameFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replace multiple nested calls of `dirname` by only one call with second `$level` parameter. Requires PHP >= 7.0.', + [ + new VersionSpecificCodeSample( + "= 70000 && $tokens->isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + * + * Must run before MethodArgumentSpaceFixer, NoSpacesInsideParenthesisFixer. + * Must run after DirConstantFixer. + */ + public function getPriority() + { + return 3; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $dirnameInfo = $this->getDirnameInfo($tokens, $index); + + if (!$dirnameInfo) { + continue; + } + + $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][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['indexes'][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|bool `false` when it is not a (supported) `dirname` call, an array with info about the dirname call otherwise + */ + private function getDirnameInfo(Tokens $tokens, $index, $firstArgumentEndIndex = null) + { + if (!$tokens[$index]->equals([T_STRING, 'dirname'], false)) { + return false; + } + + if (!(new FunctionsAnalyzer())->isGlobalFunctionCall($tokens, $index)) { + return false; + } + + $info['indexes'] = []; + + $prev = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prev]->isGivenKind(T_NS_SEPARATOR)) { + $info['indexes'][] = $prev; + } + + $info['indexes'][] = $index; + + // opening parenthesis "(" + $next = $tokens->getNextMeaningfulToken($index); + $info['indexes'][] = $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 ($blockType) { + $next = $tokens->findBlockEnd($blockType['type'], $next); + } + + $next = $tokens->getNextMeaningfulToken($next); + } + } + + $info['indexes'][] = $next; + + if ($tokens[$next]->equals(',')) { + $next = $tokens->getNextMeaningfulToken($next); + $info['indexes'][] = $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['indexes'][] = $next; + $next = $tokens->getNextMeaningfulToken($next); + } + + if (!$tokens[$next]->equals(')')) { + return false; + } + + $info['indexes'][] = $next; + $info['end'] = $next; + + return $info; + } + + private function combineDirnames(Tokens $tokens, array $dirnameInfoArray) + { + $outerDirnameInfo = array_pop($dirnameInfoArray); + $levels = $outerDirnameInfo['levels']; + + foreach ($dirnameInfoArray as $dirnameInfo) { + $levels += $dirnameInfo['levels']; + + foreach ($dirnameInfo['indexes'] 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..c0d7667ffde51734e9292bb3d89ec4ce531fdd96 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.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\AbstractFopenFlagFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class FopenFlagOrderFixer extends AbstractFopenFlagFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 string[] $flags + * + * @return string[] + */ + private function sortFlags(array $flags) + { + usort( + $flags, + static function ($flag1, $flag2) { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..ea9ba961110b87d156fa46e6e376da656e45a1fd --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php @@ -0,0 +1,114 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class FopenFlagsFixer extends AbstractFopenFlagFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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.' + ); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('b_mode', 'The `b` flag must be used (`true`) or omitted (`false`).')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + /** + * @param int $argumentStartIndex + * @param int $argumentEndIndex + */ + protected function fixFopenFlagToken(Tokens $tokens, $argumentStartIndex, $argumentEndIndex) + { + $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 ($this->configuration['b_mode']) { + if (false === strpos($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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..dee90ba098cd0d49b77e47477f902646fb56558e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.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\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +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 ConfigurationDefinitionFixerInterface +{ + /** + * @internal + */ + const SPACING_NONE = 'none'; + + /** + * @internal + */ + const SPACING_ONE = 'one'; + + private $supportedSpacings = [self::SPACING_NONE, self::SPACING_ONE]; + + private $singleLineWhitespaceOptions = " \t"; + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + if (\PHP_VERSION_ID >= 70400 && $tokens->isTokenKindFound(T_FN)) { + return true; + } + + return $tokens->isTokenKindFound(T_FUNCTION); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Spaces should be properly placed in a function declaration.', + [ + new CodeSample( + ' self::SPACING_NONE] + ), + new VersionSpecificCodeSample( + ' null; +', + new VersionSpecification(70400), + ['closure_function_spacing' => self::SPACING_NONE] + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if ( + !$token->isGivenKind(T_FUNCTION) + && (\PHP_VERSION_ID < 70400 || !$token->isGivenKind(T_FN)) + ) { + continue; + } + + $startParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(', ';', [T_CLOSE_TAG]]); + if (!$tokens[$startParenthesisIndex]->equals('(')) { + continue; + } + + $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex); + $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); + + // 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); + } + + if ($isLambda && self::SPACING_NONE === $this->configuration['closure_function_spacing']) { + // 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, ' '); + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('closure_function_spacing', 'Spacing to use before open parenthesis for closures.')) + ->setDefault(self::SPACING_ONE) + ->setAllowedValues($this->supportedSpacings) + ->getOption(), + ]); + } + + private function fixParenthesisInnerEdge(Tokens $tokens, $start, $end) + { + // remove single-line whitespace before ) + if ($tokens[$end - 1]->isWhitespace($this->singleLineWhitespaceOptions)) { + $tokens->clearAt($end - 1); + } + + // remove single-line whitespace after ( + if ($tokens[$start + 1]->isWhitespace($this->singleLineWhitespaceOptions)) { + $tokens->clearAt($start + 1); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..531494c30308b30094bfe01f35093a37ed8c26ca --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.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\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class FunctionTypehintSpaceFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Ensure single space between function\'s argument and its typehint.', + [ + new CodeSample("= 70400 && $tokens->isTokenKindFound(T_FN)) { + return true; + } + + return $tokens->isTokenKindFound(T_FUNCTION); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if ( + !$token->isGivenKind(T_FUNCTION) + && (\PHP_VERSION_ID < 70400 || !$token->isGivenKind(T_FN)) + ) { + continue; + } + + $arguments = $functionsAnalyzer->getFunctionArguments($tokens, $index); + + foreach (array_reverse($arguments) as $argument) { + $type = $argument->getTypeAnalysis(); + + if (!$type instanceof TypeAnalysis) { + continue; + } + + $whitespaceTokenIndex = $type->getEndIndex() + 1; + + if ($tokens[$whitespaceTokenIndex]->equals([T_WHITESPACE])) { + if (' ' === $tokens[$whitespaceTokenIndex]->getContent()) { + continue; + } + + $tokens->clearAt($whitespaceTokenIndex); + } + + $tokens->insertAt($whitespaceTokenIndex, new Token([T_WHITESPACE, ' '])); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..e7bf072c6c2ffba409c5dac8c09f7a3c17876870 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php @@ -0,0 +1,150 @@ + + * Dariusz Rumiń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\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 +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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() + { + return -1; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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 = key($argumentsIndices); + $tokens->insertAt($firstArgumentIndex, [ + new Token([T_CONSTANT_ENCAPSED_STRING, "''"]), + new Token(','), + new Token([T_WHITESPACE, ' ']), + ]); + + continue; + } + + if (2 === \count($argumentsIndices)) { + list($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 = key($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); + } + } + } + + /** + * @param int $functionNameIndex + * + * @return array In the format: startIndex => endIndex + */ + private function getArgumentIndices(Tokens $tokens, $functionNameIndex) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..34d9623ace88994396f196f061341d6be2e4e758 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php @@ -0,0 +1,510 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Options; + +/** + * Fixer for rules defined in PSR2 ¶4.4, ¶4.6. + * + * @author Kuanhung Chen + */ +final class MethodArgumentSpaceFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * Method to insert space after comma and remove space before comma. + * + * @param int $index + */ + public function fixSpace(Tokens $tokens, $index) + { + @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); + $this->fixSpace2($tokens, $index); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 VersionSpecificCodeSample( + <<<'SAMPLE' + true] + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound('('); + } + + public function configure(array $configuration = null) + { + parent::configure($configuration); + + if ($this->configuration['ensure_fully_multiline'] && 'ignore' === $this->configuration['on_multiline']) { + $this->configuration['on_multiline'] = 'ensure_fully_multiline'; + } + } + + /** + * {@inheritdoc} + * + * Must run after BracesFixer, CombineNestedDirnameFixer, ImplodeCallFixer, PowToExponentiationFixer. + */ + public function getPriority() + { + return -26; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $expectedTokens = [T_LIST, T_FUNCTION]; + if (\PHP_VERSION_ID >= 70400) { + $expectedTokens[] = T_FN; + } + + 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); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('keep_multiple_spaces_after_comma', 'Whether keep multiple spaces after comma.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder( + 'ensure_fully_multiline', + 'ensure every argument of a multiline argument list is on its own line' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) // @TODO 3.0 remove + ->setDeprecationMessage('Use option `on_multiline` instead.') + ->getOption(), + (new FixerOptionBuilder( + 'on_multiline', + 'Defines how to handle function arguments lists that contain newlines.' + )) + ->setAllowedValues(['ignore', 'ensure_single_line', 'ensure_fully_multiline']) + ->setDefault('ignore') // @TODO 3.0 should be 'ensure_fully_multiline' + ->getOption(), + (new FixerOptionBuilder('after_heredoc', 'Whether the whitespace between heredoc end and comma should be removed.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->setNormalizer(static function (Options $options, $value) { + if (\PHP_VERSION_ID < 70300 && $value) { + throw new InvalidOptionsForEnvException('"after_heredoc" option can only be enabled with PHP 7.3+.'); + } + + return $value; + }) + ->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, $startFunctionIndex) + { + $endFunctionIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startFunctionIndex); + + $isMultiline = false; + + $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->fixSpace2($tokens, $index); + if (!$isMultiline && $this->isNewline($tokens[$index + 1])) { + $isMultiline = true; + + break; + } + } + } + + return $isMultiline; + } + + /** + * @param int $startParenthesisIndex + * @param int $endParenthesisIndex + * + * @return null|int + */ + private function findWhitespaceIndexAfterParenthesis(Tokens $tokens, $startParenthesisIndex, $endParenthesisIndex) + { + $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; + } + + /** + * @param int $index + * + * @return bool Whether newlines were removed from the whitespace token + */ + private function ensureSingleLine(Tokens $tokens, $index) + { + $previousToken = $tokens[$index - 1]; + if ($previousToken->isComment() && 0 !== strpos($previousToken->getContent(), '/*')) { + return false; + } + + $content = Preg::replace('/\R\h*/', '', $tokens[$index]->getContent()); + if ('' !== $content) { + $tokens[$index] = new Token([T_WHITESPACE, $content]); + } else { + $tokens->clearAt($index); + } + + return true; + } + + /** + * @param int $startFunctionIndex + */ + private function ensureFunctionFullyMultiline(Tokens $tokens, $startFunctionIndex) + { + // find out what the indentation is + $searchIndex = $startFunctionIndex; + do { + $prevWhitespaceTokenIndex = $tokens->getPrevTokenOfKind( + $searchIndex, + [[T_WHITESPACE]] + ); + $searchIndex = $prevWhitespaceTokenIndex; + } while (null !== $prevWhitespaceTokenIndex + && false === strpos($tokens[$prevWhitespaceTokenIndex]->getContent(), "\n") + ); + + if (null === $prevWhitespaceTokenIndex) { + $existingIndentation = ''; + } 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 ($token->equals(',') && !$tokens[$tokens->getNextMeaningfulToken($index)]->equals(')')) { + $this->fixNewline($tokens, $index, $indentation); + } + } + + $this->fixNewline($tokens, $startFunctionIndex, $indentation, false); + } + + /** + * Method to insert newline after comma 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, $index, $indentation, $override = true) + { + if ($tokens[$index + 1]->isComment()) { + return; + } + + if ($tokens[$index + 2]->isComment()) { + $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index + 2); + if (!$this->isNewline($tokens[$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. + * + * @param int $index + */ + private function fixSpace2(Tokens $tokens, $index) + { + // remove space before comma if exist + if ($tokens[$index - 1]->isWhitespace()) { + $prevIndex = $tokens->getPrevNonWhitespace($index - 1); + + if ( + !$tokens[$prevIndex]->equals(',') && !$tokens[$prevIndex]->isComment() && + ($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 ( + (!$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 + * + * @return bool + */ + private function isCommentLastLineToken(Tokens $tokens, $index) + { + 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. + * + * @return bool + */ + private function isNewline(Token $token) + { + return $token->isWhitespace() && false !== strpos($token->getContent(), "\n"); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..5d8f7981d37889f5e9b012e6cd48cea383cd995a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php @@ -0,0 +1,423 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Andreas Möller + * @author SpacePossum + */ +final class NativeFunctionInvocationFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @internal + */ + 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 + */ + const SET_COMPILER_OPTIMIZED = '@compiler_optimized'; + + /** + * @internal + */ + const SET_INTERNAL = '@internal'; + + /** + * @var callable + */ + private $functionFilter; + + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->functionFilter = $this->getFunctionFilter(); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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. + */ + public function getPriority() + { + return 10; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + if ('all' === $this->configuration['scope']) { + $this->fixFunctionCalls($tokens, $this->functionFilter, 0, \count($tokens) - 1, false); + + return; + } + + $namespaces = (new NamespacesAnalyzer())->getDeclarations($tokens); + + // 'scope' is 'namespaced' here + /** @var NamespaceAnalysis $namespace */ + foreach (array_reverse($namespaces) as $namespace) { + $this->fixFunctionCalls($tokens, $this->functionFilter, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex(), '' === $namespace->getFullName()); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('exclude', 'List of functions to ignore.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function (array $value) { + 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.', + \is_object($functionName) ? \get_class($functionName) : \gettype($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) { + 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.', + \is_object($functionName) ? \get_class($functionName) : \gettype($functionName) + )); + } + + $sets = [ + self::SET_ALL, + self::SET_INTERNAL, + self::SET_COMPILER_OPTIMIZED, + ]; + + if ('@' === $functionName[0] && !\in_array($functionName, $sets, true)) { + throw new InvalidOptionsException(sprintf('Unknown set "%s", known sets are "%s".', $functionName, implode('", "', $sets))); + } + } + + return true; + }]) + ->setDefault([self::SET_INTERNAL]) + ->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(false) // @TODO: 3.0 change to true as default + ->getOption(), + ]); + } + + /** + * @param int $start + * @param int $end + * @param bool $tryToRemove + */ + private function fixFunctionCalls(Tokens $tokens, callable $functionFilter, $start, $end, $tryToRemove) + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + $insertAtIndexes = []; + for ($index = $start; $index < $end; ++$index) { + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$functionFilter($tokens[$index]->getContent()) || $tryToRemove) { + if (!$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 + } + + $insertAtIndexes[] = $index; + } + + foreach (array_reverse($insertAtIndexes) as $index) { + $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); + } + } + + /** + * @return callable + */ + private function getFunctionFilter() + { + $exclude = $this->normalizeFunctionNames($this->configuration['exclude']); + + if (\in_array(self::SET_ALL, $this->configuration['include'], true)) { + if (\count($exclude) > 0) { + return static function ($functionName) use ($exclude) { + return !isset($exclude[strtolower($functionName)]); + }; + } + + return static function () { + return 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 ('@' !== $additional[0]) { + $include[strtolower($additional)] = true; + } + } + + if (\count($exclude) > 0) { + return static function ($functionName) use ($include, $exclude) { + return isset($include[strtolower($functionName)]) && !isset($exclude[strtolower($functionName)]); + }; + } + + return static function ($functionName) use ($include) { + return isset($include[strtolower($functionName)]); + }; + } + + /** + * @return array normalized function names of which the PHP compiler optimizes + */ + private function getAllCompilerOptimizedFunctionsNormalized() + { + 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_string', + 'ord', + 'strlen', + 'strval', + // @see https://github.com/php/php-src/blob/php-7.2.6/ext/opcache/Optimizer/pass1_5.c + 'constant', + 'define', + 'dirname', + 'extension_loaded', + 'function_exists', + 'is_callable', + ]); + } + + /** + * @return array normalized function names of all internal defined functions + */ + private function getAllInternalFunctionsNormalized() + { + return $this->normalizeFunctionNames(get_defined_functions()['internal']); + } + + /** + * @param string[] $functionNames + * + * @return array all function names lower cased + */ + private function normalizeFunctionNames(array $functionNames) + { + foreach ($functionNames as $index => $functionName) { + $functionNames[strtolower($functionName)] = true; + unset($functionNames[$index]); + } + + return $functionNames; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..5890cf2ce6e0ba70f76c9312d7a3940d66a37f4a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.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\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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(array_merge($this->getFunctionyTokenKinds(), [T_STRING])); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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, $index) + { + // remove space before opening brace + if ($tokens[$index - 1]->isWhitespace()) { + $tokens->clearAt($index - 1); + } + } + + /** + * @return array + */ + private function getBraceAfterVariableKinds() + { + 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 int[] Token names + */ + private function getFunctionyTokenKinds() + { + 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() + { + static $languageConstructionTokens = [ + T_ECHO, + T_PRINT, + T_INCLUDE, + T_INCLUDE_ONCE, + T_REQUIRE, + T_REQUIRE_ONCE, + ]; + + return $languageConstructionTokens; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..0ee4f2ed571b6a4c0e3977b46b531b47d53d2ca7 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php @@ -0,0 +1,218 @@ + + * Dariusz Rumiń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\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Mark Scherer + * @author Lucas Manzke + * @author Gregor Harlan + */ +final class NoUnreachableDefaultArgumentValueFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'In function arguments there must not be arguments with default values before non-default ones.', + [ + new CodeSample( + '= 70400 && $tokens->isTokenKindFound(T_FN)) { + return true; + } + + return $tokens->isTokenKindFound(T_FUNCTION); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($i = 0, $l = $tokens->count(); $i < $l; ++$i) { + if ( + !$tokens[$i]->isGivenKind(T_FUNCTION) + && (\PHP_VERSION_ID < 70400 || !$tokens[$i]->isGivenKind(T_FN)) + ) { + continue; + } + + $startIndex = $tokens->getNextTokenOfKind($i, ['(']); + $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); + + $this->fixFunctionDefinition($tokens, $startIndex, $i); + } + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function fixFunctionDefinition(Tokens $tokens, $startIndex, $endIndex) + { + $lastArgumentIndex = $this->getLastNonDefaultArgumentIndex($tokens, $startIndex, $endIndex); + + if (!$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; + } + + $endIndex = $tokens->getPrevTokenOfKind($lastArgumentIndex, [',']); + $endIndex = $tokens->getPrevMeaningfulToken($endIndex); + $this->removeDefaultArgument($tokens, $i, $endIndex); + } + } + + /** + * @param int $startIndex + * @param int $endIndex + * + * @return null|int + */ + private function getLastNonDefaultArgumentIndex(Tokens $tokens, $startIndex, $endIndex) + { + 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; + } + + /** + * @param int $variableIndex + * + * @return bool + */ + private function isEllipsis(Tokens $tokens, $variableIndex) + { + return $tokens[$tokens->getPrevMeaningfulToken($variableIndex)]->isGivenKind(T_ELLIPSIS); + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function removeDefaultArgument(Tokens $tokens, $startIndex, $endIndex) + { + for ($i = $startIndex; $i <= $endIndex;) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + $this->clearWhitespacesBeforeIndex($tokens, $i); + $i = $tokens->getNextMeaningfulToken($i); + } + } + + /** + * @param int $index Index of "=" + * + * @return bool + */ + private function isNonNullableTypehintedNullableVariable(Tokens $tokens, $index) + { + $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); + } + + /** + * @param int $index + */ + private function clearWhitespacesBeforeIndex(Tokens $tokens, $index) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..4599c8a86f8af628e0f4a1bfa528082a5746d113 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.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\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis; +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 ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Adds or removes `?` before type declarations for parameters with a default `null` value.', + [ + new VersionSpecificCodeSample( + " false] + ), + ], + 'Rule is applied only in a PHP 7.1+ environment.' + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + if (\PHP_VERSION_ID < 70100) { + return false; + } + + if (!$tokens->isTokenKindFound(T_VARIABLE)) { + return false; + } + + if (\PHP_VERSION_ID >= 70400 && $tokens->isTokenKindFound(T_FN)) { + return true; + } + + return $tokens->isTokenKindFound(T_FUNCTION); + } + + /** + * {@inheritdoc} + * + * Must run before NoUnreachableDefaultArgumentValueFixer. + */ + public function getPriority() + { + return 1; + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('use_nullable_type_declaration', 'Whether to add or remove `?` before type declarations for parameters with a default `null` value.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + $tokenKinds = [T_FUNCTION]; + if (\PHP_VERSION_ID >= 70400) { + $tokenKinds[] = 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 ArgumentAnalysis[] $arguments + */ + private function fixFunctionParameters(Tokens $tokens, array $arguments) + { + foreach (array_reverse($arguments) as $argumentInfo) { + // If the parameter doesn't have a type declaration or a default value null we can continue + if ( + !$argumentInfo->hasTypeAnalysis() + || !$argumentInfo->hasDefault() + || 'null' !== strtolower($argumentInfo->getDefault()) + ) { + continue; + } + + $argumentTypeInfo = $argumentInfo->getTypeAnalysis(); + if (true === $this->configuration['use_nullable_type_declaration']) { + if (!$argumentTypeInfo->isNullable()) { + $tokens->insertAt($argumentTypeInfo->getStartIndex(), new Token([CT::T_NULLABLE_TYPE, '?'])); + } + } else { + if ($argumentTypeInfo->isNullable()) { + $tokens->removeTrailingWhitespace($argumentTypeInfo->getStartIndex()); + $tokens->clearTokenAndMergeSurroundingWhitespace($argumentTypeInfo->getStartIndex()); + } + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..0e0e90c9b87cb847cdf404ea69e6f47ffb3df942 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php @@ -0,0 +1,430 @@ + + * Dariusz Rumiń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\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jan Gantzert + */ +final class PhpdocToParamTypeFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** @internal */ + const CLASS_REGEX = '/^\\\\?[a-zA-Z_\\x7f-\\xff](?:\\\\?[a-zA-Z0-9_\\x7f-\\xff]+)*(?\[\])*$/'; + + /** @internal */ + const MINIMUM_PHP_VERSION = 70000; + + /** + * @var array{int, string}[] + */ + private $blacklistFuncNames = [ + [T_STRING, '__clone'], + [T_STRING, '__destruct'], + ]; + + /** + * @var array + */ + private $skippedTypes = [ + 'mixed' => true, + 'resource' => true, + 'static' => true, + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'EXPERIMENTAL: Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0.', + [ + new VersionSpecificCodeSample( + '= self::MINIMUM_PHP_VERSION && $tokens->isTokenKindFound(T_FUNCTION); + } + + /** + * {@inheritdoc} + * + * Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer. + * Must run after CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority() + { + return 8; + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; 0 < $index; --$index) { + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + continue; + } + + $funcName = $tokens->getNextMeaningfulToken($index); + if ($tokens[$funcName]->equalsAny($this->blacklistFuncNames, false)) { + continue; + } + + $paramTypeAnnotations = $this->findParamAnnotations($tokens, $index); + + foreach ($paramTypeAnnotations as $paramTypeAnnotation) { + if (\PHP_VERSION_ID < self::MINIMUM_PHP_VERSION) { + continue; + } + + $types = array_values($paramTypeAnnotation->getTypes()); + $paramType = current($types); + + if (isset($this->skippedTypes[$paramType])) { + continue; + } + + $hasIterable = false; + $hasNull = false; + $hasVoid = false; + $hasArray = false; + $hasString = false; + $hasInt = false; + $hasFloat = false; + $hasBool = false; + $hasCallable = false; + $hasObject = false; + $minimumTokenPhpVersion = self::MINIMUM_PHP_VERSION; + + foreach ($types as $key => $type) { + if (1 !== Preg::match(self::CLASS_REGEX, $type, $matches)) { + continue; + } + + if (isset($matches['array'])) { + $hasArray = true; + unset($types[$key]); + } + + if ('iterable' === $type) { + $hasIterable = true; + unset($types[$key]); + $minimumTokenPhpVersion = 70100; + } + + if ('null' === $type) { + $hasNull = true; + unset($types[$key]); + $minimumTokenPhpVersion = 70100; + } + + if ('void' === $type) { + $hasVoid = true; + unset($types[$key]); + } + + if ('string' === $type) { + $hasString = true; + unset($types[$key]); + } + + if ('int' === $type) { + $hasInt = true; + unset($types[$key]); + } + + if ('float' === $type) { + $hasFloat = true; + unset($types[$key]); + } + + if ('bool' === $type) { + $hasBool = true; + unset($types[$key]); + } + + if ('callable' === $type) { + $hasCallable = true; + unset($types[$key]); + } + + if ('array' === $type) { + $hasArray = true; + unset($types[$key]); + } + + if ('object' === $type) { + $hasObject = true; + unset($types[$key]); + $minimumTokenPhpVersion = 70200; + } + } + + if (\PHP_VERSION_ID < $minimumTokenPhpVersion) { + continue; + } + + $typesCount = \count($types); + + if (1 < $typesCount) { + continue; + } + + if (0 === $typesCount) { + $paramType = ''; + } elseif (1 === $typesCount) { + $paramType = array_shift($types); + } + + $startIndex = $tokens->getNextTokenOfKind($index, ['(']) + 1; + $variableIndex = $this->findCorrectVariable($tokens, $startIndex - 1, $paramTypeAnnotation); + + if (null === $variableIndex) { + continue; + } + + $byRefIndex = $tokens->getPrevMeaningfulToken($variableIndex); + if ($tokens[$byRefIndex]->equals('&')) { + $variableIndex = $byRefIndex; + } + + if (!('(' === $tokens[$variableIndex - 1]->getContent()) && $this->hasParamTypeHint($tokens, $variableIndex - 2)) { + continue; + } + + $this->fixFunctionDefinition( + $paramType, + $tokens, + $variableIndex, + $hasNull, + $hasArray, + $hasIterable, + $hasVoid, + $hasString, + $hasInt, + $hasFloat, + $hasBool, + $hasCallable, + $hasObject + ); + } + } + } + + /** + * Find all the param annotations in the function's PHPDoc comment. + * + * @param int $index The index of the function token + * + * @return Annotation[] + */ + private function findParamAnnotations(Tokens $tokens, $index) + { + 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 []; + } + + $doc = new DocBlock($tokens[$index]->getContent()); + + return $doc->getAnnotationsOfType('param'); + } + + /** + * @param int $index + * @param Annotation $paramTypeAnnotation + * + * @return null|int + */ + private function findCorrectVariable(Tokens $tokens, $index, $paramTypeAnnotation) + { + $nextFunction = $tokens->getNextTokenOfKind($index, [[T_FUNCTION]]); + $variableIndex = $tokens->getNextTokenOfKind($index, [[T_VARIABLE]]); + + if (\is_int($nextFunction) && $variableIndex > $nextFunction) { + return null; + } + + if (!isset($tokens[$variableIndex])) { + return null; + } + + $variableToken = $tokens[$variableIndex]->getContent(); + Preg::match('/@param\s*[^\s!<]+\s*([^\s]+)/', $paramTypeAnnotation->getContent(), $paramVariable); + if (isset($paramVariable[1]) && $paramVariable[1] === $variableToken) { + return $variableIndex; + } + + return $this->findCorrectVariable($tokens, $index + 1, $paramTypeAnnotation); + } + + /** + * 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 ; + * + * @return bool + */ + private function hasParamTypeHint(Tokens $tokens, $index) + { + return $tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR, CT::T_ARRAY_TYPEHINT, T_CALLABLE, CT::T_NULLABLE_TYPE]); + } + + /** + * @param string $paramType + * @param int $index The index of the end of the function definition line, EG at { or ; + * @param bool $hasNull + * @param bool $hasArray + * @param bool $hasIterable + * @param bool $hasVoid + * @param bool $hasString + * @param bool $hasInt + * @param bool $hasFloat + * @param bool $hasBool + * @param bool $hasCallable + * @param bool $hasObject + */ + private function fixFunctionDefinition( + $paramType, + Tokens $tokens, + $index, + $hasNull, + $hasArray, + $hasIterable, + $hasVoid, + $hasString, + $hasInt, + $hasFloat, + $hasBool, + $hasCallable, + $hasObject + ) { + $newTokens = []; + + if (true === $hasVoid) { + $newTokens[] = new Token('void'); + } elseif (true === $hasIterable && true === $hasArray) { + $newTokens[] = new Token([CT::T_ARRAY_TYPEHINT, 'array']); + } elseif (true === $hasIterable) { + $newTokens[] = new Token([T_STRING, 'iterable']); + } elseif (true === $hasArray) { + $newTokens[] = new Token([CT::T_ARRAY_TYPEHINT, 'array']); + } elseif (true === $hasString) { + $newTokens[] = new Token([T_STRING, 'string']); + } elseif (true === $hasInt) { + $newTokens[] = new Token([T_STRING, 'int']); + } elseif (true === $hasFloat) { + $newTokens[] = new Token([T_STRING, 'float']); + } elseif (true === $hasBool) { + $newTokens[] = new Token([T_STRING, 'bool']); + } elseif (true === $hasCallable) { + $newTokens[] = new Token([T_CALLABLE, 'callable']); + } elseif (true === $hasObject) { + $newTokens[] = new Token([T_STRING, 'object']); + } + + if ('' !== $paramType && [] !== $newTokens) { + return; + } + + foreach (explode('\\', $paramType) as $nsIndex => $value) { + if (0 === $nsIndex && '' === $value) { + continue; + } + + if (0 < $nsIndex) { + $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); + } + $newTokens[] = new Token([T_STRING, $value]); + } + + if (true === $hasNull) { + array_unshift($newTokens, new Token([CT::T_NULLABLE_TYPE, '?'])); + } + + $newTokens[] = new Token([T_WHITESPACE, ' ']); + $tokens->insertAt($index, $newTokens); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..ad54528d9e037f909bff6b2341593f2d93bd310f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php @@ -0,0 +1,340 @@ + + * Dariusz Rumiń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\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class PhpdocToReturnTypeFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @var array> + */ + private $blacklistFuncNames = [ + [T_STRING, '__construct'], + [T_STRING, '__destruct'], + [T_STRING, '__clone'], + ]; + + /** + * @var array + */ + private $versionSpecificTypes = [ + 'void' => 70100, + 'iterable' => 70100, + 'object' => 70200, + ]; + + /** + * @var array + */ + private $scalarTypes = [ + 'bool' => 'bool', + 'true' => 'bool', + 'false' => 'bool', + 'float' => 'float', + 'int' => 'int', + 'string' => 'string', + ]; + + /** + * @var array + */ + private $skippedTypes = [ + 'mixed' => true, + 'resource' => true, + 'null' => true, + ]; + + /** + * @var string + */ + private $classRegex = '/^\\\\?[a-zA-Z_\\x7f-\\xff](?:\\\\?[a-zA-Z0-9_\\x7f-\\xff]+)*(?\[\])*$/'; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'EXPERIMENTAL: Takes `@return` annotation of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0.', + [ + new VersionSpecificCodeSample( + '= 70400 && $tokens->isTokenKindFound(T_FN)) { + return true; + } + + return \PHP_VERSION_ID >= 70000 && $tokens->isTokenKindFound(T_FUNCTION); + } + + /** + * {@inheritdoc} + * + * Must run before FullyQualifiedStrictTypesFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer, ReturnTypeDeclarationFixer. + * Must run after CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer, PhpdocTypesFixer. + */ + public function getPriority() + { + return 13; + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; 0 < $index; --$index) { + if ( + !$tokens[$index]->isGivenKind(T_FUNCTION) + && (\PHP_VERSION_ID < 70400 || !$tokens[$index]->isGivenKind(T_FN)) + ) { + continue; + } + + $funcName = $tokens->getNextMeaningfulToken($index); + if ($tokens[$funcName]->equalsAny($this->blacklistFuncNames, false)) { + continue; + } + + $returnTypeAnnotation = $this->findReturnAnnotations($tokens, $index); + if (1 !== \count($returnTypeAnnotation)) { + continue; + } + + $returnTypeAnnotation = current($returnTypeAnnotation); + $types = array_values($returnTypeAnnotation->getTypes()); + $typesCount = \count($types); + + if (1 > $typesCount || 2 < $typesCount) { + continue; + } + + $isNullable = false; + $returnType = current($types); + + if (2 === $typesCount) { + $null = $types[0]; + $returnType = $types[1]; + if ('null' !== $null) { + $null = $types[1]; + $returnType = $types[0]; + } + + if ('null' !== $null) { + continue; + } + + $isNullable = true; + + if (\PHP_VERSION_ID < 70100) { + continue; + } + + if ('void' === $returnType) { + continue; + } + } + + if ('static' === $returnType) { + $returnType = 'self'; + } + + if (isset($this->skippedTypes[$returnType])) { + continue; + } + + if (isset($this->versionSpecificTypes[$returnType]) && \PHP_VERSION_ID < $this->versionSpecificTypes[$returnType]) { + continue; + } + + if (isset($this->scalarTypes[$returnType])) { + if (false === $this->configuration['scalar_types']) { + continue; + } + + $returnType = $this->scalarTypes[$returnType]; + } else { + if (1 !== Preg::match($this->classRegex, $returnType, $matches)) { + continue; + } + + if (isset($matches['array'])) { + $returnType = 'array'; + } + } + + $startIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); + + if ($this->hasReturnTypeHint($tokens, $startIndex)) { + continue; + } + + $this->fixFunctionDefinition($tokens, $startIndex, $isNullable, $returnType); + } + } + + /** + * 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 ; + * + * @return bool + */ + private function hasReturnTypeHint(Tokens $tokens, $index) + { + $endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']); + $nextIndex = $tokens->getNextMeaningfulToken($endFuncIndex); + + return $tokens[$nextIndex]->isGivenKind(CT::T_TYPE_COLON); + } + + /** + * @param int $index The index of the end of the function definition line, EG at { or ; + * @param bool $isNullable + * @param string $returnType + */ + private function fixFunctionDefinition(Tokens $tokens, $index, $isNullable, $returnType) + { + static $specialTypes = [ + 'array' => [CT::T_ARRAY_TYPEHINT, 'array'], + 'callable' => [T_CALLABLE, 'callable'], + ]; + $newTokens = [ + new Token([CT::T_TYPE_COLON, ':']), + new Token([T_WHITESPACE, ' ']), + ]; + if (true === $isNullable) { + $newTokens[] = new Token([CT::T_NULLABLE_TYPE, '?']); + } + + if (isset($specialTypes[$returnType])) { + $newTokens[] = new Token($specialTypes[$returnType]); + } else { + foreach (explode('\\', $returnType) as $nsIndex => $value) { + if (0 === $nsIndex && '' === $value) { + continue; + } + + if (0 < $nsIndex) { + $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); + } + $newTokens[] = new Token([T_STRING, $value]); + } + } + + $endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']); + $tokens->insertAt($endFuncIndex + 1, $newTokens); + } + + /** + * Find all the return annotations in the function's PHPDoc comment. + * + * @param int $index The index of the function token + * + * @return Annotation[] + */ + private function findReturnAnnotations(Tokens $tokens, $index) + { + 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 []; + } + + $doc = new DocBlock($tokens[$index]->getContent()); + + return $doc->getAnnotationsOfType('return'); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..78340a45c28798108c8eeed4d86420b0b0baeea8 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.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\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class ReturnTypeDeclarationFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + $versionSpecification = new VersionSpecification(70000); + + return new FixerDefinition( + 'There should be one or no space before colon, and one space after it in return type declarations, according to configuration.', + [ + new VersionSpecificCodeSample( + " 'none'] + ), + new VersionSpecificCodeSample( + " 'one'] + ), + ], + 'Rule is applied only in a PHP 7+ environment.' + ); + } + + /** + * {@inheritdoc} + * + * Must run after PhpdocToReturnTypeFixer, VoidReturnFixer. + */ + public function getPriority() + { + return -17; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return \PHP_VERSION_ID >= 70000 && $tokens->isTokenKindFound(CT::T_TYPE_COLON); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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; + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('space_before', 'Spacing to apply before colon.')) + ->setAllowedValues(['one', 'none']) + ->setDefault('none') + ->getOption(), + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..31350a64a9188183c796162915f65925b5946fdc --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.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\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class SingleLineThrowFixer extends AbstractFixer +{ + /** + * @internal + */ + const REMOVE_WHITESPACE_AFTER_TOKENS = ['[']; + + /** + * @internal + */ + const REMOVE_WHITESPACE_AROUND_TOKENS = ['(', [T_OBJECT_OPERATOR], [T_DOUBLE_COLON]]; + + /** + * @internal + */ + const REMOVE_WHITESPACE_BEFORE_TOKENS = [')', ']', ',', ';']; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Throwing exception must be done in single line.', + [ + new CodeSample("isTokenKindFound(T_THROW); + } + + /** + * {@inheritdoc} + * + * Must run before ConcatSpaceFixer. + */ + public function getPriority() + { + // must be fun before ConcatSpaceFixer + return 1; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { + if (!$tokens[$index]->isGivenKind(T_THROW)) { + continue; + } + + /** @var int $openingBraceCandidateIndex */ + $openingBraceCandidateIndex = $tokens->getNextTokenOfKind($index, [';', '(']); + + while ($tokens[$openingBraceCandidateIndex]->equals('(')) { + /** @var int $closingBraceIndex */ + $closingBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingBraceCandidateIndex); + /** @var int $openingBraceCandidateIndex */ + $openingBraceCandidateIndex = $tokens->getNextTokenOfKind($closingBraceIndex, [';', '(']); + } + + $this->trimNewLines($tokens, $index, $openingBraceCandidateIndex); + } + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function trimNewLines(Tokens $tokens, $startIndex, $endIndex) + { + for ($index = $startIndex; $index < $endIndex; ++$index) { + $content = $tokens[$index]->getContent(); + + if ($tokens[$index]->isGivenKind(T_COMMENT)) { + if (0 === strpos($content, '//')) { + $content = '/*'.substr($content, 2).' */'; + $tokens->clearAt($index + 1); + } elseif (0 === strpos($content, '#')) { + $content = '/*'.substr($content, 1).' */'; + $tokens->clearAt($index + 1); + } elseif (false !== 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 (0 === Preg::match('/\R/', $content)) { + continue; + } + + $prevIndex = $tokens->getNonEmptySibling($index, -1); + if ($tokens[$prevIndex]->equalsAny(array_merge(self::REMOVE_WHITESPACE_AFTER_TOKENS, self::REMOVE_WHITESPACE_AROUND_TOKENS))) { + $tokens->clearAt($index); + + continue; + } + + $nextIndex = $tokens->getNonEmptySibling($index, 1); + if ($tokens[$nextIndex]->equalsAny(array_merge(self::REMOVE_WHITESPACE_AROUND_TOKENS, self::REMOVE_WHITESPACE_BEFORE_TOKENS))) { + if (!$tokens[$prevIndex]->isGivenKind(T_FUNCTION)) { + $tokens->clearAt($index); + + continue; + } + } + + $tokens[$index] = new Token([T_WHITESPACE, ' ']); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..f2da248d24435a18ffe77e15b93b7b7454d4c220 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.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\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author SpacePossum + */ +final class StaticLambdaFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Lambdas not (indirect) referencing `$this` must be declared `static`.', + [new CodeSample("bindTo` on lambdas without referencing to `$this`.' + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + if (\PHP_VERSION_ID >= 70400 && $tokens->isTokenKindFound(T_FN)) { + return true; + } + + return $tokens->isTokenKindFound(T_FUNCTION); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $analyzer = new TokensAnalyzer($tokens); + + $expectedFunctionKinds = [T_FUNCTION]; + if (\PHP_VERSION_ID >= 70400) { + $expectedFunctionKinds[] = 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 ... + $lambdaOpenIndex = $tokens->getNextTokenOfKind($argumentsEndIndex, ['{', [T_DOUBLE_ARROW]]); + + // ... and where it ends + if ($tokens[$lambdaOpenIndex]->isGivenKind(T_DOUBLE_ARROW)) { + $lambdaEndIndex = $tokens->getNextTokenOfKind($lambdaOpenIndex, [';']); + } else { + $lambdaEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $lambdaOpenIndex); + } + + 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. + * + * @param int $startIndex + * @param int $endIndex + * + * @return bool + */ + private function hasPossibleReferenceToThis(Tokens $tokens, $startIndex, $endIndex) + { + 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 + } + } + } + + return false; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..9784d0f57283322d1a4752828d6af9fd36d19292 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php @@ -0,0 +1,258 @@ + + * Dariusz Rumiń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\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Mark Nielsen + */ +final class VoidReturnFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 VersionSpecificCodeSample( + "= 70100 && $tokens->isTokenKindFound(T_FUNCTION); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + // These cause syntax errors. + static $blacklistFuncNames = [ + [T_STRING, '__construct'], + [T_STRING, '__destruct'], + [T_STRING, '__clone'], + ]; + + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + continue; + } + + $funcName = $tokens->getNextMeaningfulToken($index); + if ($tokens[$funcName]->equalsAny($blacklistFuncNames, 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 + * + * @return bool + */ + private function hasReturnAnnotation(Tokens $tokens, $index) + { + 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 + * + * @return bool + */ + private function hasVoidReturnAnnotation(Tokens $tokens, $index) + { + 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 ; + * + * @return bool + */ + private function hasReturnTypeHint(Tokens $tokens, $index) + { + $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 + * + * @return bool + */ + private function hasVoidReturn(Tokens $tokens, $startIndex, $endIndex) + { + $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, $index) + { + $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 Annotation[] + */ + private function findReturnAnnotations(Tokens $tokens, $index) + { + do { + $index = $tokens->getPrevNonWhitespace($index); + } while ($tokens[$index]->isGivenKind([ + T_ABSTRACT, + T_FINAL, + T_PRIVATE, + T_PROTECTED, + T_PUBLIC, + T_STATIC, + ])); + + if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) { + return []; + } + + $doc = new DocBlock($tokens[$index]->getContent()); + + return $doc->getAnnotationsOfType('return'); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..56206f720123e649de7472b651dc8b640663fe78 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.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\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Generator\NamespacedStringTokenGenerator; +use PhpCsFixer\Tokenizer\Resolver\TypeShortNameResolver; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author VeeWee + */ +final class FullyQualifiedStrictTypesFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Transforms imported FQCN parameters and return types in function arguments to short version.', + [ + new CodeSample( + 'isTokenKindFound(T_FUNCTION) && ( + \count((new NamespacesAnalyzer())->getDeclarations($tokens)) || + \count((new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens)) + ); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $lastIndex = $tokens->count() - 1; + for ($index = $lastIndex; $index >= 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + continue; + } + + // Return types are only available since PHP 7.0 + $this->fixFunctionReturnType($tokens, $index); + $this->fixFunctionArguments($tokens, $index); + } + } + + /** + * @param int $index + */ + private function fixFunctionArguments(Tokens $tokens, $index) + { + $arguments = (new FunctionsAnalyzer())->getFunctionArguments($tokens, $index); + + foreach ($arguments as $argument) { + if (!$argument->hasTypeAnalysis()) { + continue; + } + + $this->detectAndReplaceTypeWithShortType($tokens, $argument->getTypeAnalysis()); + } + } + + /** + * @param int $index + */ + private function fixFunctionReturnType(Tokens $tokens, $index) + { + if (\PHP_VERSION_ID < 70000) { + return; + } + + $returnType = (new FunctionsAnalyzer())->getFunctionReturnType($tokens, $index); + if (!$returnType) { + return; + } + + $this->detectAndReplaceTypeWithShortType($tokens, $returnType); + } + + private function detectAndReplaceTypeWithShortType( + Tokens $tokens, + TypeAnalysis $type + ) { + if ($type->isReservedType()) { + return; + } + + $typeName = $type->getName(); + + if (0 !== strpos($typeName, '\\')) { + return; + } + + $shortType = (new TypeShortNameResolver())->resolve($tokens, $typeName); + if ($shortType === $typeName) { + return; + } + + $shortType = (new NamespacedStringTokenGenerator())->generate($shortType); + + if (true === $type->isNullable()) { + array_unshift($shortType, new Token([CT::T_NULLABLE_TYPE, '?'])); + } + + $tokens->overrideRange( + $type->getStartIndex(), + $type->getEndIndex(), + $shortType + ); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..2a0fe44684ce7b87e1aafb44a3ebf39a7f440ca2 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php @@ -0,0 +1,751 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\ClassyAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +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; + +/** + * @author Gregor Harlan + */ +final class GlobalNamespaceImportFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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. + * Must run after NativeConstantInvocationFixer, NativeFunctionInvocationFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound([T_DOC_COMMENT, T_NS_SEPARATOR, T_USE]) + && $tokens->isTokenKindFound(T_NAMESPACE) + && (Tokens::isLegacyMode() || 1 === $tokens->countTokenKind(T_NAMESPACE)) + && $tokens->isMonolithicPhp(); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $namespaceAnalyses = (new NamespacesAnalyzer())->getDeclarations($tokens); + + if (1 !== \count($namespaceAnalyses) || '' === $namespaceAnalyses[0]->getFullName()) { + 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 ($newImports) { + $this->insertImports($tokens, $newImports, $useDeclarations); + } + } + + protected function createConfigurationDefinition() + { + 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) + { + list($global, $other) = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { + return $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); + + $indexes = []; + + 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; + } + + $indexes[] = $index; + } + + return $this->prepareImports($tokens, $indexes, $global, $other, true); + } + + /** + * @param NamespaceUseAnalysis[] $useDeclarations + * + * @return array + */ + private function importFunctions(Tokens $tokens, array $useDeclarations) + { + list($global, $other) = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { + return $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(); + + $indexes = []; + + 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; + } + + $indexes[] = $index; + } + + return $this->prepareImports($tokens, $indexes, $global, $other, false); + } + + /** + * @param NamespaceUseAnalysis[] $useDeclarations + * + * @return array + */ + private function importClasses(Tokens $tokens, array $useDeclarations) + { + list($global, $other) = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { + return $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 ($type) use ($global, &$other) { + if (false !== strpos($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(); + + $indexes = []; + + 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; + } + + $indexes[] = $index; + } + + $imports = []; + + foreach ($docBlocks as $index => $docBlock) { + $changed = $this->traverseDocBlockTypes($docBlock, static function ($type) use ($global, $other, &$imports) { + if ('\\' !== $type[0]) { + return $type; + } + + $name = substr($type, 1); + $checkName = strtolower($name); + + if (false !== strpos($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 $imports + $this->prepareImports($tokens, $indexes, $global, $other, false); + } + + /** + * Removes the leading slash at the given indexes (when the name is not already used). + * + * @param int[] $indexes + * @param bool $caseSensitive + * + * @return array array keys contain the names that must be imported + */ + private function prepareImports(Tokens $tokens, array $indexes, array $global, array $other, $caseSensitive) + { + $imports = []; + + foreach ($indexes 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 insertImports(Tokens $tokens, array $imports, array $useDeclarations) + { + if ($useDeclarations) { + $useDeclaration = end($useDeclarations); + $index = $useDeclaration->getEndIndex() + 1; + } else { + $namespace = (new NamespacesAnalyzer())->getDeclarations($tokens)[0]; + $index = $namespace->getEndIndex() + 1; + } + + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + if (!$tokens[$index]->isWhitespace() || false === strpos($tokens[$index]->getContent(), "\n")) { + $tokens->insertAt($index, new Token([T_WHITESPACE, $lineEnding])); + } + + foreach ($imports as $type => $typeImports) { + foreach ($typeImports as $name) { + $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[] = new Token([T_STRING, $name]); + $items[] = new Token(';'); + + $tokens->insertAt($index, $items); + } + } + } + + /** + * @param NamespaceUseAnalysis[] $useDeclarations + */ + private function fullyQualifyConstants(Tokens $tokens, array $useDeclarations) + { + if (!$tokens->isTokenKindFound(CT::T_CONST_IMPORT)) { + return; + } + + list($global) = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { + return $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) + { + if (!$tokens->isTokenKindFound(CT::T_FUNCTION_IMPORT)) { + return; + } + + list($global) = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { + return $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) + { + if (!$tokens->isTokenKindFound(T_USE)) { + return; + } + + list($global) = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { + return $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 ($type) use ($global) { + 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 + * @param bool $caseSensitive + * + * @return array + */ + private function filterUseDeclarations(array $declarations, callable $callback, $caseSensitive) + { + $global = []; + $other = []; + + foreach ($declarations as $declaration) { + if (!$callback($declaration)) { + continue; + } + + $fullName = ltrim($declaration->getFullName(), '\\'); + + if (false !== strpos($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]; + } + + private function findFunctionDeclarations(Tokens $tokens, $start, $end) + { + 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) + { + $annotations = $doc->getAnnotationsOfType(Annotation::getTagsWithTypes()); + + if (!$annotations) { + return false; + } + + $changed = false; + + foreach ($annotations as $annotation) { + $types = $new = $annotation->getTypes(); + + foreach ($types as $i => $fullType) { + $newFullType = $fullType; + + Preg::matchAll('/[\\\\\w]+/', $fullType, $matches, PREG_OFFSET_CAPTURE); + + foreach (array_reverse($matches[0]) as list($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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..a668ffa4a9d138ce5b1a211dddadddb56131bc02 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.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\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Carlos Cirello + */ +final class NoLeadingImportSlashFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Remove leading slashes in `use` clauses.', + [new CodeSample("isTokenKindFound(T_USE); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $usesIndexes = $tokensAnalyzer->getImportUseIndexes(); + + foreach ($usesIndexes 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); + } + } + } + } + + /** + * @param int $index + */ + private function removeLeadingImportSlash(Tokens $tokens, $index) + { + $previousIndex = $tokens->getPrevNonWhitespace($index); + + if ( + $previousIndex < $index - 1 + || $tokens[$previousIndex]->isComment() + ) { + $tokens->clearAt($index); + + return; + } + + $tokens[$index] = new Token([T_WHITESPACE, ' ']); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..52c81a2707f539176628f8ca403f8071d4cd8075 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php @@ -0,0 +1,277 @@ + + * Dariusz Rumiń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\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class NoUnusedImportsFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Unused `use` statements must be removed.', + [new CodeSample("isTokenKindFound(T_USE); + } + + /** + * {@inheritdoc} + */ + public function supports(\SplFileInfo $file) + { + $path = $file->getPathname(); + + /* + * @deprecated this exception will be removed on 3.0 + * some fixtures are auto-generated by Symfony and may contain unused use statements + */ + if (false !== strpos($path, \DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR) + && false === strpos($path, \DIRECTORY_SEPARATOR.'tests'.\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR) + ) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); + + if (0 === \count($useDeclarations)) { + return; + } + + foreach ((new NamespacesAnalyzer())->getDeclarations($tokens) as $namespace) { + $currentNamespaceUseDeclarations = array_filter( + $useDeclarations, + static function (NamespaceUseAnalysis $useDeclaration) use ($namespace) { + return + $useDeclaration->getStartIndex() >= $namespace->getScopeStartIndex() + && $useDeclaration->getEndIndex() <= $namespace->getScopeEndIndex() + ; + } + ); + + $usagesSearchIgnoredIndexes = []; + + foreach ($currentNamespaceUseDeclarations as $useDeclaration) { + $usagesSearchIgnoredIndexes[$useDeclaration->getStartIndex()] = $useDeclaration->getEndIndex(); + } + + foreach ($currentNamespaceUseDeclarations as $useDeclaration) { + if (!$this->isImportUsed($tokens, $namespace, $usagesSearchIgnoredIndexes, $useDeclaration->getShortName())) { + $this->removeUseDeclaration($tokens, $useDeclaration); + } + } + + $this->removeUsesInSameNamespace($tokens, $currentNamespaceUseDeclarations, $namespace); + } + } + + /** + * @param array $ignoredIndexes + * @param string $shortName + * + * @return bool + */ + private function isImportUsed(Tokens $tokens, NamespaceAnalysis $namespace, array $ignoredIndexes, $shortName) + { + $namespaceEndIndex = $namespace->getScopeEndIndex(); + for ($index = $namespace->getScopeStartIndex(); $index <= $namespaceEndIndex; ++$index) { + if (isset($ignoredIndexes[$index])) { + $index = $ignoredIndexes[$index]; + + continue; + } + + $token = $tokens[$index]; + + if ($token->isGivenKind(T_STRING)) { + $prevMeaningfulToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + + if ($prevMeaningfulToken->isGivenKind(T_NAMESPACE)) { + $index = $tokens->getNextTokenOfKind($index, [';', '{', [T_CLOSE_TAG]]); + + continue; + } + + if ( + 0 === strcasecmp($shortName, $token->getContent()) + && !$prevMeaningfulToken->isGivenKind([T_NS_SEPARATOR, T_CONST, T_OBJECT_OPERATOR, T_DOUBLE_COLON]) + ) { + return true; + } + + continue; + } + + if ($token->isComment() + && Preg::match( + '/(?getContent() + ) + ) { + return true; + } + } + + return false; + } + + private function removeUseDeclaration(Tokens $tokens, NamespaceUseAnalysis $useDeclaration) + { + for ($index = $useDeclaration->getEndIndex() - 1; $index >= $useDeclaration->getStartIndex(); --$index) { + if ($tokens[$index]->isComment()) { + continue; + } + + if (!$tokens[$index]->isWhitespace() || false === strpos($tokens[$index]->getContent(), "\n")) { + $tokens->clearTokenAndMergeSurroundingWhitespace($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); + } + } + + if ($tokens[$useDeclaration->getEndIndex()]->equals(';')) { // do not remove `? >` + $tokens->clearAt($useDeclaration->getEndIndex()); + } + + // remove white space above and below where the `use` statement was + + $prevIndex = $useDeclaration->getStartIndex() - 1; + $prevToken = $tokens[$prevIndex]; + + if ($prevToken->isWhitespace()) { + $content = rtrim($prevToken->getContent(), " \t"); + + if ('' === $content) { + $tokens->clearAt($prevIndex); + } else { + $tokens[$prevIndex] = new Token([T_WHITESPACE, $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 + ); + + if ('' !== $content) { + $tokens[$nextIndex] = new Token([T_WHITESPACE, $content]); + } else { + $tokens->clearAt($nextIndex); + } + + $nextToken = $tokens[$nextIndex]; + } + + if ($prevToken->isWhitespace() && $nextToken->isWhitespace()) { + $content = $prevToken->getContent().$nextToken->getContent(); + + if ('' !== $content) { + $tokens[$nextIndex] = new Token([T_WHITESPACE, $content]); + } else { + $tokens->clearAt($nextIndex); + } + + $tokens->clearAt($prevIndex); + } + } + + private function removeUsesInSameNamespace(Tokens $tokens, array $useDeclarations, NamespaceAnalysis $namespaceDeclaration) + { + $namespace = $namespaceDeclaration->getFullName(); + $nsLength = \strlen($namespace.'\\'); + + foreach ($useDeclarations as $useDeclaration) { + if ($useDeclaration->isAliased()) { + continue; + } + + $useDeclarationFullName = ltrim($useDeclaration->getFullName(), '\\'); + + if (0 !== strpos($useDeclarationFullName, $namespace.'\\')) { + continue; + } + + $partName = substr($useDeclarationFullName, $nsLength); + + if (false === strpos($partName, '\\')) { + $this->removeUseDeclaration($tokens, $useDeclaration); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..be2d884b3cbc8c193e490c836b108dd9147720dd --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.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\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AliasedFixerOptionBuilder; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Sebastiaan Stok + * @author Dariusz Rumiński + * @author SpacePossum + * @author Darius Matulionis + * @author Adriano Pilger + */ +final class OrderedImportsFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + const IMPORT_TYPE_CLASS = 'class'; + + const IMPORT_TYPE_CONST = 'const'; + + const IMPORT_TYPE_FUNCTION = 'function'; + + const SORT_ALPHA = 'alpha'; + + const SORT_LENGTH = 'length'; + + const SORT_NONE = 'none'; + + /** + * Array of supported sort types in configuration. + * + * @var string[] + */ + private $supportedSortTypes = [self::IMPORT_TYPE_CLASS, self::IMPORT_TYPE_CONST, self::IMPORT_TYPE_FUNCTION]; + + /** + * Array of supported sort algorithms in configuration. + * + * @var string[] + */ + private $supportedSortAlgorithms = [self::SORT_ALPHA, self::SORT_LENGTH, self::SORT_NONE]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Ordering `use` statements.', + [ + new CodeSample(" self::SORT_LENGTH] + ), + new VersionSpecificCodeSample( + " self::SORT_LENGTH, + 'imports_order' => [ + self::IMPORT_TYPE_CONST, + self::IMPORT_TYPE_CLASS, + self::IMPORT_TYPE_FUNCTION, + ], + ] + ), + new VersionSpecificCodeSample( + ' self::SORT_ALPHA, + 'imports_order' => [ + self::IMPORT_TYPE_CONST, + self::IMPORT_TYPE_CLASS, + self::IMPORT_TYPE_FUNCTION, + ], + ] + ), + new VersionSpecificCodeSample( + ' self::SORT_NONE, + 'imports_order' => [ + self::IMPORT_TYPE_CONST, + self::IMPORT_TYPE_CLASS, + self::IMPORT_TYPE_FUNCTION, + ], + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after GlobalNamespaceImportFixer, NoLeadingImportSlashFixer. + */ + public function getPriority() + { + return -30; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_USE); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $namespacesImports = $tokensAnalyzer->getImportUseIndexes(true); + + if (0 === \count($namespacesImports)) { + return; + } + + $usesOrder = []; + foreach ($namespacesImports as $uses) { + $usesOrder[] = $this->getNewOrder(array_reverse($uses), $tokens); + } + $usesOrder = array_replace(...$usesOrder); + + $usesOrder = array_reverse($usesOrder, true); + $mapStartToEnd = []; + + foreach ($usesOrder as $use) { + $mapStartToEnd[$use['startIndex']] = $use['endIndex']; + } + + // Now insert the new tokens, starting from the end + foreach ($usesOrder as $index => $use) { + $declarationTokens = Tokens::fromCode( + sprintf( + 'clearRange(0, 2); // clear `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, ' '])); + } + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $supportedSortTypes = $this->supportedSortTypes; + + return new FixerConfigurationResolver([ + (new AliasedFixerOptionBuilder( + new FixerOptionBuilder('sort_algorithm', 'whether the statements should be sorted alphabetically or by length, or not sorted'), + 'sortAlgorithm' + )) + ->setAllowedValues($this->supportedSortAlgorithms) + ->setDefault(self::SORT_ALPHA) + ->getOption(), + (new AliasedFixerOptionBuilder( + new FixerOptionBuilder('imports_order', 'Defines the order of import types.'), + 'importsOrder' + )) + ->setAllowedTypes(['array', 'null']) + ->setAllowedValues([static function ($value) use ($supportedSortTypes) { + if (null !== $value) { + $missing = array_diff($supportedSortTypes, $value); + if (\count($missing)) { + throw new InvalidOptionsException(sprintf( + 'Missing sort %s "%s".', + 1 === \count($missing) ? 'type' : 'types', + implode('", "', $missing) + )); + } + + $unknown = array_diff($value, $supportedSortTypes); + if (\count($unknown)) { + throw new InvalidOptionsException(sprintf( + 'Unknown sort %s "%s".', + 1 === \count($unknown) ? 'type' : 'types', + implode('", "', $unknown) + )); + } + } + + return true; + }]) + ->setDefault(null) + ->getOption(), + ]); + } + + /** + * This method is used for sorting the uses in a namespace. + * + * @param array $first + * @param array $second + * + * @return int + * + * @internal + */ + private function sortAlphabetically(array $first, array $second) + { + // 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 strcasecmp($firstNamespace, $secondNamespace); + } + + /** + * This method is used for sorting the uses statements in a namespace by length. + * + * @param array $first + * @param array $second + * + * @return int + * + * @internal + */ + private function sortByLength(array $first, array $second) + { + $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 = strcasecmp($firstNamespace, $secondNamespace); + } else { + $sortResult = $firstNamespaceLength > $secondNamespaceLength ? 1 : -1; + } + + return $sortResult; + } + + /** + * @param string $namespace + * + * @return string + */ + private function prepareNamespace($namespace) + { + return trim(Preg::replace('%/\*(.*)\*/%s', '', $namespace)); + } + + /** + * @param int[] $uses + * + * @return array + */ + private function getNewOrder(array $uses, Tokens $tokens) + { + $indexes = []; + $originalIndexes = []; + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + for ($i = \count($uses) - 1; $i >= 0; --$i) { + $index = $uses[$i]; + + $startIndex = $tokens->getTokenNotOfKindSibling($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 = ''; + + 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() && + false !== strpos($namespaceTokens[$k2]->getContent(), $lineEnding) + ) { + $lastIndent = $lineEnding; + $firstIndent = $lineEnding.$this->whitespacesConfig->getIndent(); + $separator = ','.$firstIndent; + } + + $namespacePart .= $namespaceTokens[$k2]->getContent(); + } + + $namespacePart = trim($namespacePart); + $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).$lastIndent.'}'; + } + } else { + $namespace = Tokens::fromArray($namespaceTokens)->generateCode(); + } + + $indexes[$startIndex] = [ + 'namespace' => $namespace, + 'startIndex' => $startIndex, + 'endIndex' => $index - 1, + 'importType' => $type, + 'group' => $group, + ]; + + $originalIndexes[] = $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 indexes by import type. + $groupedByTypes = []; + foreach ($indexes as $startIndex => $item) { + $groupedByTypes[$item['importType']][$startIndex] = $item; + } + + // Sorting each group by algorithm. + foreach ($groupedByTypes as $type => $indexes) { + $groupedByTypes[$type] = $this->sortByAlgorithm($indexes); + } + + // Ordering groups + $sortedGroups = []; + foreach ($this->configuration['imports_order'] as $type) { + if (isset($groupedByTypes[$type]) && !empty($groupedByTypes[$type])) { + foreach ($groupedByTypes[$type] as $startIndex => $item) { + $sortedGroups[$startIndex] = $item; + } + } + } + $indexes = $sortedGroups; + } else { + // Sorting only by algorithm + $indexes = $this->sortByAlgorithm($indexes); + } + + $index = -1; + $usesOrder = []; + + // Loop trough the index but use original index order + foreach ($indexes as $v) { + $usesOrder[$originalIndexes[++$index]] = $v; + } + + return $usesOrder; + } + + /** + * @param array[] $indexes + * + * @return array + */ + private function sortByAlgorithm(array $indexes) + { + if (self::SORT_ALPHA === $this->configuration['sort_algorithm']) { + uasort($indexes, [$this, 'sortAlphabetically']); + } elseif (self::SORT_LENGTH === $this->configuration['sort_algorithm']) { + uasort($indexes, [$this, 'sortByLength']); + } + + return $indexes; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..109a900c8fdc8474364f0e4c955cce526d42104d --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.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\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 + * @author SpacePossum + */ +final class SingleImportPerStatementFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'There MUST be one use keyword per declaration.', + [new CodeSample("isTokenKindFound(T_USE); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $uses = array_reverse($tokensAnalyzer->getImportUseIndexes()); + + foreach ($uses as $index) { + $endIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); + $groupClose = $tokens->getPrevMeaningfulToken($endIndex); + + if ($tokens[$groupClose]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { + $this->fixGroupUse($tokens, $index, $endIndex); + } else { + $this->fixMultipleUse($tokens, $index, $endIndex); + } + } + } + + /** + * @param int $index + * + * @return string + */ + private function detectIndent(Tokens $tokens, $index) + { + if (!$tokens[$index - 1]->isWhitespace()) { + return ''; // cannot detect indent + } + + $explodedContent = explode("\n", $tokens[$index - 1]->getContent()); + + return end($explodedContent); + } + + /** + * @param int $index + * + * @return array + */ + private function getGroupDeclaration(Tokens $tokens, $index) + { + $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, + ]; + } + + /** + * @param string $groupPrefix + * @param int $groupOpenIndex + * @param int $groupCloseIndex + * @param string $comment + * + * @return string[] + */ + private function getGroupStatements(Tokens $tokens, $groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment) + { + $statements = []; + $statement = $groupPrefix; + + for ($i = $groupOpenIndex + 1; $i <= $groupCloseIndex; ++$i) { + $token = $tokens[$i]; + + if ($token->equals(',') && $tokens[$tokens->getNextMeaningfulToken($i)]->equals([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]->equals([T_AS])) { + $statement .= ' as '; + $i += 2; + } elseif ($tokens[$j]->equals([T_FUNCTION])) { + $statement = ' function'.$statement; + $i += 2; + } elseif ($tokens[$j]->equals([T_CONST])) { + $statement = ' const'.$statement; + $i += 2; + } + + if ($token->isWhitespace(" \t") || '//' !== substr($tokens[$i - 1]->getContent(), 0, 2)) { + continue; + } + } + + $statement .= $token->getContent(); + } + + if ('' !== $comment) { + $statements[0] .= ' '.$comment; + } + + return $statements; + } + + /** + * @param int $index + * @param int $endIndex + */ + private function fixGroupUse(Tokens $tokens, $index, $endIndex) + { + list($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); + } + + /** + * @param int $index + * @param int $endIndex + */ + private function fixMultipleUse(Tokens $tokens, $index, $endIndex) + { + $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, ' '])); + + $indent = $this->detectIndent($tokens, $index); + if ($tokens[$i - 1]->isWhitespace()) { + $tokens[$i - 1] = new Token([T_WHITESPACE, $ending.$indent]); + + continue; + } + + if (false === strpos($tokens[$i - 1]->getContent(), "\n")) { + $tokens->insertAt($i, new Token([T_WHITESPACE, $ending.$indent])); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..739ab7ed0b2b2fff2e8b72d83165c800ace9639e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.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\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_USE); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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]; + $nextMeaningfulAfterUseIndex = $tokens->getNextMeaningfulToken($insertIndex); + if (null !== $nextMeaningfulAfterUseIndex && $tokens[$nextMeaningfulAfterUseIndex]->isGivenKind(T_USE)) { + if (substr_count($nextToken->getContent(), "\n") < 2) { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..fa6d7b59790e2af0bf3691c4e6191dbaa3a9ca4c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php @@ -0,0 +1,248 @@ + + * Dariusz Rumiń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\Tokenizer\Analyzer\NamespacesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Sullivan Senechal + */ +final class ClassKeywordRemoveFixer extends AbstractFixer +{ + /** + * @var string[] + */ + private $imports = []; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Converts `::class` keywords to FQCN strings.', + [ + new CodeSample( + 'isTokenKindFound(CT::T_CLASS_CONSTANT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $namespacesAnalyzer = new NamespacesAnalyzer(); + + $previousNamespaceScopeEndIndex = 0; + foreach ($namespacesAnalyzer->getDeclarations($tokens) 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); + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function storeImports(Tokens $tokens, $startIndex, $endIndex) + { + $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 function ($import) { + return trim($import); + }, + explode(',', $tokens->generatePartialCode($index + 1, $groupEndIndex - 1)) + ); + foreach ($groupImports as $groupImport) { + $groupImportParts = array_map(static function ($import) { + return 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; + } + } + } + + /** + * @param string $namespace + * @param int $startIndex + * @param int $endIndex + */ + private function replaceClassKeywordsSection(Tokens $tokens, $namespace, $startIndex, $endIndex) + { + 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); + } + } + + /** + * @param string $namespace + * @param int $classIndex + */ + private function replaceClassKeyword(Tokens $tokens, $namespace, $classIndex) + { + $classEndIndex = $tokens->getPrevMeaningfulToken($classIndex); + $classEndIndex = $tokens->getPrevMeaningfulToken($classEndIndex); + + 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; + foreach ($this->imports as $alias => $import) { + if ($classString === $alias) { + $classImport = $import; + + break; + } + + $classStringArray = explode('\\', $classString); + $namespaceToTest = $classStringArray[0]; + + if (0 === strcmp($namespaceToTest, substr($import, -\strlen($namespaceToTest)))) { + $classImport = $import; + + break; + } + } + + for ($i = $classBeginIndex; $i <= $classIndex; ++$i) { + if (!$tokens[$i]->isComment() && !($tokens[$i]->isWhitespace() && false !== strpos($tokens[$i]->getContent(), "\n"))) { + $tokens->clearAt($i); + } + } + + $tokens->insertAt($classBeginIndex, new Token([ + T_CONSTANT_ENCAPSED_STRING, + "'".$this->makeClassFQN($namespace, $classImport, $classString)."'", + ])); + } + + /** + * @param string $namespace + * @param false|string $classImport + * @param string $classString + * + * @return string + */ + private function makeClassFQN($namespace, $classImport, $classString) + { + if (false === $classImport) { + return ('' !== $namespace ? ($namespace.'\\') : '').$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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..8f40390c6a363a9f605d01b7170537cb67cff1f2 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.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\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class CombineConsecutiveIssetsFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Using `isset($var) &&` multiple times should be done in one call.', + [new CodeSample("isAllTokenKindsFound([T_ISSET, T_BOOLEAN_AND]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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 no 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 int[] $indexes + */ + private function clearTokens(Tokens $tokens, array $indexes) + { + foreach ($indexes as $index) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } + + /** + * @param int $index of T_ISSET + * + * @return int[] indexes of meaningful tokens belonging to the isset statement + */ + private function getIssetInfo(Tokens $tokens, $index) + { + $openIndex = $tokens->getNextMeaningfulToken($index); + + $braceOpenCount = 1; + $meaningfulTokenIndexes = [$openIndex]; + + for ($i = $openIndex + 1;; ++$i) { + if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { + continue; + } + + $meaningfulTokenIndexes[] = $i; + + if ($tokens[$i]->equals(')')) { + --$braceOpenCount; + if (0 === $braceOpenCount) { + break; + } + } elseif ($tokens[$i]->equals('(')) { + ++$braceOpenCount; + } + } + + return $meaningfulTokenIndexes; + } + + /** + * @param int[] $indexes + * + * @return Token[] + */ + private function getTokenClones(Tokens $tokens, array $indexes) + { + $clones = []; + + foreach ($indexes as $i) { + $clones[] = clone $tokens[$i]; + } + + return $clones; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..7c995173cfb533316f1819cb60f067a910b99831 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.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\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class CombineConsecutiveUnsetsFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Calling `unset` on multiple items should be done in one call.', + [new CodeSample("isTokenKindFound(T_UNSET); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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; + } + + list($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 $offset + * @param int[] $indices + */ + private function clearOffsetTokens(Tokens $tokens, $offset, array $indices) + { + 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 an call. + * + * @param int $index + * + * @return int|int[] + */ + private function getPreviousUnsetCall(Tokens $tokens, $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, $start, $end, $to) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..2879e62bdd1c3a5daf02cf82c5cb696cb3a77dac --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.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\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + * @author SpacePossum + */ +final class DeclareEqualNormalizeFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @var string + */ + private $callback; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->callback = 'none' === $this->configuration['space'] ? 'removeWhitespaceAroundToken' : 'ensureWhitespaceAroundToken'; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DECLARE); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $callback = $this->callback; + for ($index = 0, $count = $tokens->count(); $index < $count - 6; ++$index) { + if (!$tokens[$index]->isGivenKind(T_DECLARE)) { + continue; + } + + while (!$tokens[++$index]->equals('=')); + + $this->{$callback}($tokens, $index); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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, $index) + { + 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, $index) + { + if (!$tokens[$tokens->getPrevNonWhitespace($index)]->isComment()) { + $tokens->removeLeadingWhitespace($index); + } + + $tokens->removeTrailingWhitespace($index); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..c9567b65ea2efe593a28ea2b50f379efaff09b57 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.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\LanguageConstruct; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vladimir Reznichenko + */ +final class DirConstantFixer extends AbstractFunctionReferenceFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replaces `dirname(__FILE__)` expression with equivalent `__DIR__` constant.', + [new CodeSample("isTokenKindFound(T_FILE); + } + + /** + * {@inheritdoc} + * + * Must run before CombineNestedDirnameFixer. + */ + public function getPriority() + { + return 4; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $currIndex = 0; + while (null !== $currIndex) { + $boundaries = $this->find('dirname', $tokens, $currIndex, $tokens->count() - 1); + if (null === $boundaries) { + return; + } + + list($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); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..0fb2a11043b7e1045db06cd6b9886fd9840e0b89 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php @@ -0,0 +1,175 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 ConfigurationDefinitionFixerInterface +{ + const OPTION_MUTE_DEPRECATION_ERROR = 'mute_deprecation_error'; + const OPTION_NOISE_REMAINING_USAGES = 'noise_remaining_usages'; + const OPTION_NOISE_REMAINING_USAGES_EXCLUDE = 'noise_remaining_usages_exclude'; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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.' + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(['@', T_STRING]); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $functionsAnalyzer = new FunctionsAnalyzer(); + $excludedFunctions = array_map(static function ($function) { + return strtolower($function); + }, $this->configuration[self::OPTION_NOISE_REMAINING_USAGES_EXCLUDE]); + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if ($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 (!$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 ($this->configuration[self::OPTION_NOISE_REMAINING_USAGES] && !\in_array($tokens[$functionIndex]->getContent(), $excludedFunctions, true)) { + $tokens->clearAt($index); + } + } + } + + /** + * @param int $index + * + * @return bool + */ + private function isDeprecationErrorCall(Tokens $tokens, $index) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..e28e848f523f73f874a0bf7121ff90c16625a65e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php @@ -0,0 +1,91 @@ + + * Dariusz Rumiń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\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class ExplicitIndirectVariableFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Add curly braces to indirect variables to make them clear to understand. Requires PHP >= 7.0.', + [ + new VersionSpecificCodeSample( + <<<'EOT' +$bar['baz']; +echo $foo->$callback($baz); + +EOT +, + new VersionSpecification(70000) + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return \PHP_VERSION_ID >= 70000 && $tokens->isTokenKindFound(T_VARIABLE); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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->isGivenKind(T_OBJECT_OPERATOR)) { + continue; + } + + $openingBrace = CT::T_DYNAMIC_VAR_BRACE_OPEN; + $closingBrace = CT::T_DYNAMIC_VAR_BRACE_CLOSE; + if ($prevToken->isGivenKind(T_OBJECT_OPERATOR)) { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..81127badefad3214bc8e87a7802498a46046ce03 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.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\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class FunctionToConstantFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @var array + */ + private static $availableFunctions; + + /** + * @var array + */ + private $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_CLASS_C, '__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(); + } + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->functionsFixMap = []; + foreach ($this->configuration['functions'] as $key) { + $this->functionsFixMap[$key] = self::$availableFunctions[$key]; + } + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 NativeFunctionCasingFixer, NoExtraBlankLinesFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer, SelfStaticAccessorFixer. + * Must run after NoSpacesAfterFunctionNameFixer, NoSpacesInsideParenthesisFixer. + */ + public function getPriority() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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 + ); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $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_class', + 'php_sapi_name', + 'phpversion', + 'pi', + // TODO on v3.0 add 'get_called_class' and `get_class_this` here + ]) + ->getOption(), + ]); + } + + /** + * @param int $index + * @param int $braceOpenIndex + * @param int $braceCloseIndex + * @param Token[] $replacements + */ + private function fixFunctionCallToConstant(Tokens $tokens, $index, $braceOpenIndex, $braceCloseIndex, array $replacements) + { + for ($i = $braceCloseIndex; $i >= $braceOpenIndex; --$i) { + if ($tokens[$i]->equalsAny([[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT]])) { + continue; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } + + if ($replacements[0]->isGivenKind([T_CLASS_C, T_STATIC])) { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevIndex]; + if ($prevToken->isGivenKind(T_NS_SEPARATOR)) { + $tokens->clearAt($prevIndex); + } + } + + $tokens->clearAt($index); + $tokens->insertAt($index, $replacements); + } + + /** + * @param int $index + * + * @return null|array + */ + private function getReplaceCandidate( + Tokens $tokens, + FunctionsAnalyzer $functionAnalyzer, + $index + ) { + 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); + } + + /** + * @param int $index + * + * @return null|array + */ + private function fixGetClassCall( + Tokens $tokens, + FunctionsAnalyzer $functionAnalyzer, + $index + ) { + 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); + } + } else { + if (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; + } + + /** + * @param string $lowerContent + * @param int $braceOpenIndex + * @param int $braceCloseIndex + * + * @return array + */ + private function getReplacementTokenClones($lowerContent, $braceOpenIndex, $braceCloseIndex) + { + $clones = []; + foreach ($this->functionsFixMap[$lowerContent] as $token) { + $clones[] = clone $token; + } + + return [ + $braceOpenIndex, + $braceCloseIndex, + $clones, + ]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..410358d0b5b7b27ac16cbf651a984249ace7f385 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.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\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vladimir Reznichenko + */ +final class IsNullFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replaces `is_null($var)` expression with `null === $var`.', + [ + new CodeSample("isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + static $sequenceNeeded = [[T_STRING, 'is_null'], '(']; + $functionsAnalyzer = new FunctionsAnalyzer(); + + $currIndex = 0; + while (null !== $currIndex) { + $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 + list($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 ==, ===, !=, !==, <> + $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->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 (true === $this->configuration['use_yoda_style']) { + if ($wrapIntoParentheses) { + array_unshift($replacement, new Token('(')); + $tokens->insertAt($referenceEnd + 1, new Token(')')); + } + + $tokens->overrideRange($isNullIndex, $isNullIndex, $replacement); + } else { + $replacement = array_reverse($replacement); + if ($wrapIntoParentheses) { + $replacement[] = new Token(')'); + $tokens[$isNullIndex] = new Token('('); + } else { + $tokens->clearAt($isNullIndex); + } + + $tokens->insertAt($referenceEnd + 1, $replacement); + } + + // nested is_null calls support + $currIndex = $isNullIndex; + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + // @todo 3.0 drop `ConfigurationDefinitionFixerInterface` + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('use_yoda_style', 'Whether Yoda style conditions should be used.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->setDeprecationMessage('Use `yoda_style` fixer instead.') + ->getOption(), + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..5de1daafdafa970cfb3f7cb5bc47c0e4edfa9bda --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.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\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gert de Pagter + */ +final class NoUnsetOnPropertyFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Properties should be set to `null` instead of using `unset`.', + [new CodeSample("a);\n")], + null, + 'Changing variables to `null` instead of unsetting them will mean they still show up '. + 'when looping over class variables. With PHP 7.4, this rule might introduce `null` assignments to '. + 'property whose type declaration does not allow it.' + ); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_UNSET) + && $tokens->isAnyTokenKindsFound([T_OBJECT_OPERATOR, T_PAAMAYIM_NEKUDOTAYIM]); + } + + /** + * {@inheritdoc} + * + * Must run before CombineConsecutiveUnsetsFixer. + */ + public function getPriority() + { + return 25; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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; // yes, last - we reverse the array below + foreach (array_reverse($unsetsInfo) as $unsetInfo) { + $this->updateTokens($tokens, $unsetInfo, $isLastUnset); + $isLastUnset = false; + } + } + } + + /** + * @param int $index + * + * @return array> + */ + private function getUnsetsInfo(Tokens $tokens, $index) + { + $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; + } + + /** + * @param int $index + * @param int $endIndex + * + * @return bool + */ + private function isProperty(Tokens $tokens, $index, $endIndex) + { + 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->getTokenNotOfKindSibling($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 + * + * @return bool + */ + private function isAnyUnsetToTransform(array $unsetsInfo) + { + foreach ($unsetsInfo as $unsetInfo) { + if ($unsetInfo['isToTransform']) { + return true; + } + } + + return false; + } + + /** + * @param array $unsetInfo + * @param bool $isLastUnset + */ + private function updateTokens(Tokens $tokens, array $unsetInfo, $isLastUnset) + { + // if entry is first and to be transform 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SilencedDeprecationErrorFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SilencedDeprecationErrorFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b1199121c7e7821c578ad50c6559d9c69cdfd673 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SilencedDeprecationErrorFixer.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\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * @author Jules Pietri + * + * @deprecated + */ +final class SilencedDeprecationErrorFixer extends AbstractProxyFixer implements DeprecatedFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Ensures deprecation notices are silenced.', + [new CodeSample("proxyFixers); + } + + /** + * {@inheritdoc} + */ + protected function createProxyFixers() + { + return [new ErrorSuppressionFixer()]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..89039462059f43cd95d3c2b2430f3edeb982c128 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.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\ListNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class ListSyntaxFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + private $candidateTokenKind; + + /** + * Use 'syntax' => 'long'|'short'. + * + * @param null|array $configuration + * + * @throws InvalidFixerConfigurationException + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->candidateTokenKind = 'long' === $this->configuration['syntax'] ? CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN : T_LIST; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'List (`array` destructuring) assignment should be declared using the configured syntax. Requires PHP >= 7.1.', + [ + new VersionSpecificCodeSample( + " 'short'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BinaryOperatorSpacesFixer, TernaryOperatorSpacesFixer. + */ + public function getPriority() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return \PHP_VERSION_ID >= 70100 && $tokens->isTokenKindFound($this->candidateTokenKind); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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); + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('syntax', 'Whether to use the `long` or `short` `list` syntax.')) + ->setAllowedValues(['long', 'short']) + ->setDefault('long') + ->getOption(), + ]); + } + + /** + * @param int $index + */ + private function fixToLongSyntax(Tokens $tokens, $index) + { + 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'])); + } + + /** + * @param int $index + */ + private function fixToShortSyntax(Tokens $tokens, $index) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..a209b89abcdc9261775b704cb41c9bae2bfc90d3 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php @@ -0,0 +1,144 @@ + + * Dariusz Rumiń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\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 +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'There MUST be one blank line after the namespace declaration.', + [ + new CodeSample("isTokenKindFound(T_NAMESPACE); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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)); + } + } + } + + /** + * @param int $index + * + * @return int + */ + private function getIndexToEnsureBlankLineAfter(Tokens $tokens, $index) + { + $indexToEnsureBlankLine = $index; + $nextIndex = $tokens->getNonEmptySibling($indexToEnsureBlankLine, 1); + + while (null !== $nextIndex) { + $token = $tokens[$nextIndex]; + + if ($token->isWhitespace()) { + if (1 === 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; + } + + /** + * @param string $currentContent + * @param bool $isLastIndex + * + * @return Token + */ + private function getTokenToInsert($currentContent, $isLastIndex) + { + $ending = $this->whitespacesConfig->getLineEnding(); + + $emptyLines = $isLastIndex ? $ending : $ending.$ending; + $indent = 1 === Preg::match('/^.*\R( *)$/s', $currentContent, $matches) ? $matches[1] : ''; + + return new Token([T_WHITESPACE, $emptyLines.$indent]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..c516a01842d37bd32a1637a9b59db771a9956e8b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.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\NamespaceNotation; + +use PhpCsFixer\AbstractLinesBeforeNamespaceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class NoBlankLinesBeforeNamespaceFixer extends AbstractLinesBeforeNamespaceFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_NAMESPACE); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'There should be no blank lines before a namespace declaration.', + [ + new CodeSample( + "count(); $index < $limit; ++$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_NAMESPACE)) { + continue; + } + + $this->fixLinesBeforeNamespace($tokens, $index, 0, 1); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..ddb67adf77cc47870a11344e11223ee0a6aa2c39 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.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\NamespaceNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Bram Gotink + * @author Dariusz Rumiński + */ +final class NoLeadingNamespaceWhitespaceFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_NAMESPACE); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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($str) + { + if ('' === $str) { + return false; + } + + return '' === trim(substr($str, -1)); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..4b0a9a7a589dda0da6bc6c291796b279659cba84 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.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\NamespaceNotation; + +use PhpCsFixer\AbstractLinesBeforeNamespaceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class SingleBlankLineBeforeNamespaceFixer extends AbstractLinesBeforeNamespaceFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'There should be exactly one blank line before a namespace declaration.', + [ + new CodeSample("isTokenKindFound(T_NAMESPACE); + } + + /** + * {@inheritdoc} + * + * Must run after NoBlankLinesAfterPhpdocFixer. + */ + public function getPriority() + { + return -21; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_NAMESPACE)) { + $this->fixLinesBeforeNamespace($tokens, $index, 2, 2); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..9f4951ef37b5de443dfe487e46dbbd052eac152a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php @@ -0,0 +1,244 @@ + + * Dariusz Rumiń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\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 $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', + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replace accidental usage of homoglyphs (non ascii characters) in names.', + [new CodeSample("isAnyTokenKindsFound([T_VARIABLE, T_STRING]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind([T_VARIABLE, T_STRING])) { + continue; + } + + $replaced = Preg::replaceCallback('/[^[:ascii:]]/u', static function ($matches) { + return isset(self::$replacements[$matches[0]]) + ? self::$replacements[$matches[0]] + : $matches[0] + ; + }, $token->getContent(), -1, $count); + + if ($count) { + $tokens->offsetSet($index, new Token([$token->getId(), $replaced])); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/AlignDoubleArrowFixerHelper.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/AlignDoubleArrowFixerHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..4a10e47684953b37775a2f9662c3637ae0dc45b7 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/AlignDoubleArrowFixerHelper.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\Operator; + +use PhpCsFixer\AbstractAlignFixerHelper; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Carlos Cirello + * @author Dariusz Rumiński + * @author Graham Campbell + * + * @deprecated + */ +final class AlignDoubleArrowFixerHelper extends AbstractAlignFixerHelper +{ + /** + * Level counter of the current nest level. + * So one level alignments are not mixed with + * other level ones. + * + * @var int + */ + private $currentLevel = 0; + + public function __construct() + { + @trigger_error( + sprintf( + 'The "%s" class is deprecated. You should stop using it, as it will be removed in 3.0 version.', + __CLASS__ + ), + E_USER_DEPRECATED + ); + } + + /** + * {@inheritdoc} + */ + protected function injectAlignmentPlaceholders(Tokens $tokens, $startAt, $endAt) + { + for ($index = $startAt; $index < $endAt; ++$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind([T_FOREACH, T_FOR, T_WHILE, T_IF, T_SWITCH])) { + $index = $tokens->getNextMeaningfulToken($index); + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + continue; + } + + if ($token->isGivenKind(T_ARRAY)) { // don't use "$tokens->isArray()" here, short arrays are handled in the next case + $from = $tokens->getNextMeaningfulToken($index); + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $from); + $index = $until; + + $this->injectArrayAlignmentPlaceholders($tokens, $from, $until); + + continue; + } + + if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + if ($prevToken->isGivenKind([T_STRING, T_VARIABLE])) { + continue; + } + + $from = $index; + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $from); + $index = $until; + + $this->injectArrayAlignmentPlaceholders($tokens, $from + 1, $until - 1); + + continue; + } + + if ($token->isGivenKind(T_DOUBLE_ARROW)) { + $tokenContent = sprintf(self::ALIGNABLE_PLACEHOLDER, $this->currentLevel).$token->getContent(); + + $nextIndex = $index + 1; + $nextToken = $tokens[$nextIndex]; + if (!$nextToken->isWhitespace()) { + $tokenContent .= ' '; + } elseif ($nextToken->isWhitespace(" \t")) { + $tokens[$nextIndex] = new Token([T_WHITESPACE, ' ']); + } + + $tokens[$index] = new Token([T_DOUBLE_ARROW, $tokenContent]); + + continue; + } + + if ($token->equals(';')) { + ++$this->deepestLevel; + ++$this->currentLevel; + + continue; + } + + if ($token->equals(',')) { + for ($i = $index; $i < $endAt - 1; ++$i) { + if (false !== strpos($tokens[$i - 1]->getContent(), "\n")) { + 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; + } + } + } + } + + /** + * @param int $from + * @param int $until + */ + private function injectArrayAlignmentPlaceholders(Tokens $tokens, $from, $until) + { + // Only inject placeholders for multi-line arrays + if ($tokens->isPartialCodeMultiline($from, $until)) { + ++$this->deepestLevel; + ++$this->currentLevel; + $this->injectAlignmentPlaceholders($tokens, $from, $until); + --$this->currentLevel; + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/AlignEqualsFixerHelper.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/AlignEqualsFixerHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..9aecf4cb163ca1c80502ad9ea7b45988b1bc1322 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/AlignEqualsFixerHelper.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\Fixer\Operator; + +use PhpCsFixer\AbstractAlignFixerHelper; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Carlos Cirello + * @author Graham Campbell + * + * @deprecated + */ +final class AlignEqualsFixerHelper extends AbstractAlignFixerHelper +{ + public function __construct() + { + @trigger_error( + sprintf( + 'The "%s" class is deprecated. You should stop using it, as it will be removed in 3.0 version.', + __CLASS__ + ), + E_USER_DEPRECATED + ); + } + + /** + * {@inheritdoc} + */ + protected function injectAlignmentPlaceholders(Tokens $tokens, $startAt, $endAt) + { + for ($index = $startAt; $index < $endAt; ++$index) { + $token = $tokens[$index]; + + if ($token->equals('=')) { + $tokens[$index] = new Token(sprintf(self::ALIGNABLE_PLACEHOLDER, $this->deepestLevel).$token->getContent()); + + continue; + } + + if ($token->isGivenKind(T_FUNCTION)) { + ++$this->deepestLevel; + + continue; + } + + if ($token->equals('(')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + continue; + } + + if ($token->equals('[')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); + + continue; + } + + if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); + + continue; + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..e79a0cff6aaa98626cf86de9bc504600fe87c21e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php @@ -0,0 +1,833 @@ + + * Dariusz Rumiń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\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Console\Command\HelpCommand; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Dariusz Rumiński + * @author SpacePossum + */ +final class BinaryOperatorSpacesFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @internal + */ + const SINGLE_SPACE = 'single_space'; + + /** + * @internal + */ + const NO_SPACE = 'no_space'; + + /** + * @internal + */ + const ALIGN = 'align'; + + /** + * @internal + */ + const ALIGN_SINGLE_SPACE = 'align_single_space'; + + /** + * @internal + */ + const ALIGN_SINGLE_SPACE_MINIMAL = 'align_single_space_minimal'; + + /** + * @internal + * @const Placeholder used as anchor for right alignment. + */ + const ALIGN_PLACEHOLDER = "\x2 ALIGNABLE%d \x3"; + + /** + * Keep track of the deepest level ever achieved while + * parsing the code. Used later to replace alignment + * placeholders with spaces. + * + * @var int + */ + private $deepestLevel; + + /** + * Level counter of the current nest level. + * So one level alignments are not mixed with + * other level ones. + * + * @var int + */ + private $currentLevel; + + private static $allowedValues = [ + self::ALIGN, + self::ALIGN_SINGLE_SPACE, + self::ALIGN_SINGLE_SPACE_MINIMAL, + self::SINGLE_SPACE, + self::NO_SPACE, + null, + ]; + + /** + * @var string[] + */ + private static $supportedOperators = [ + '=', + '*', + '/', + '%', + '<', + '>', + '|', + '^', + '+', + '-', + '&', + '&=', + '&&', + '||', + '.=', + '/=', + '=>', + '==', + '>=', + '===', + '!=', + '<>', + '!==', + '<=', + 'and', + 'or', + 'xor', + '-=', + '%=', + '*=', + '|=', + '+=', + '<<', + '<<=', + '>>', + '>>=', + '^=', + '**', + '**=', + '<=>', + '??', + '??=', + ]; + + /** + * @var TokensAnalyzer + */ + private $tokensAnalyzer; + + /** + * @var array + */ + private $alignOperatorTokens = []; + + /** + * @var array + */ + private $operators = []; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + if ( + null !== $configuration && + (\array_key_exists('align_equals', $configuration) || \array_key_exists('align_double_arrow', $configuration)) + ) { + $configuration = $this->resolveOldConfig($configuration); + } + + parent::configure($configuration); + + $this->operators = $this->resolveOperatorsFromConfig(); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after ArrayIndentationFixer, ArraySyntaxFixer, ListSyntaxFixer, NoMultilineWhitespaceAroundDoubleArrowFixer, PowToExponentiationFixer, StandardizeNotEqualsFixer, StrictComparisonFixer. + */ + public function getPriority() + { + return -31; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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)) { + $this->fixAlignment($tokens, $this->alignOperatorTokens); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function ($option) { + foreach ($option as $operator => $value) { + if (!\in_array($operator, self::$supportedOperators, true)) { + throw new InvalidOptionsException( + sprintf( + 'Unexpected "operators" key, expected any of "%s", got "%s".', + implode('", "', self::$supportedOperators), + \is_object($operator) ? \get_class($operator) : \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, + implode('", "', self::$allowedValues), + \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value) + ) + ); + } + } + + return true; + }]) + ->setDefault([]) + ->getOption(), + // add deprecated options as BC layer + (new FixerOptionBuilder('align_double_arrow', 'Whether to apply, remove or ignore double arrows alignment.')) + ->setDefault(false) + ->setAllowedValues([true, false, null]) + ->setDeprecationMessage('Use options `operators` and `default` instead.') + ->getOption(), + (new FixerOptionBuilder('align_equals', 'Whether to apply, remove or ignore equals alignment.')) + ->setDefault(false) + ->setAllowedValues([true, false, null]) + ->setDeprecationMessage('Use options `operators` and `default` instead.') + ->getOption(), + ]); + } + + /** + * @param int $index + */ + private function fixWhiteSpaceAroundOperator(Tokens $tokens, $index) + { + $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::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]) { + return; + } + + // fix white space after operator + if ($tokens[$index + 1]->isWhitespace()) { + if (self::ALIGN_SINGLE_SPACE_MINIMAL === $this->operators[$tokenContent]) { + $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); + } + + return; + } + + $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); + } + + /** + * @param int $index + */ + private function fixWhiteSpaceAroundOperatorToSingleSpace(Tokens $tokens, $index) + { + // fix white space after operator + if ($tokens[$index + 1]->isWhitespace()) { + $content = $tokens[$index + 1]->getContent(); + if (' ' !== $content && false === strpos($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 && false === strpos($content, "\n") && !$tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { + $tokens[$index - 1] = new Token([T_WHITESPACE, ' ']); + } + } else { + $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); + } + } + + /** + * @param int $index + */ + private function fixWhiteSpaceAroundOperatorToNoSpace(Tokens $tokens, $index) + { + // fix white space after operator + if ($tokens[$index + 1]->isWhitespace()) { + $content = $tokens[$index + 1]->getContent(); + if (false === strpos($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 (false === strpos($content, "\n") && !$tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { + $tokens->clearAt($index - 1); + } + } + } + + /** + * @param int $index + * + * @return false|int index of T_DECLARE where the `=` belongs to or `false` + */ + private function isEqualPartOfDeclareStatement(Tokens $tokens, $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() + { + $operators = []; + + if (null !== $this->configuration['default']) { + foreach (self::$supportedOperators as $operator) { + $operators[$operator] = $this->configuration['default']; + } + } + + foreach ($this->configuration['operators'] as $operator => $value) { + if (null === $value) { + unset($operators[$operator]); + } else { + $operators[$operator] = $value; + } + } + + if (!\defined('T_SPACESHIP')) { + unset($operators['<=>']); + } + + if (!\defined('T_COALESCE')) { + unset($operators['??']); + } + + if (!\defined('T_COALESCE_EQUAL')) { + unset($operators['??=']); + } + + return $operators; + } + + /** + * @return array + */ + private function resolveOldConfig(array $configuration) + { + $newConfig = [ + 'operators' => [], + ]; + + foreach ($configuration as $name => $setting) { + if ('align_double_arrow' === $name) { + if (true === $configuration[$name]) { + $newConfig['operators']['=>'] = self::ALIGN; + } elseif (false === $configuration[$name]) { + $newConfig['operators']['=>'] = self::SINGLE_SPACE; + } elseif (null !== $configuration[$name]) { + throw new InvalidFixerConfigurationException( + $this->getName(), + sprintf( + 'Invalid configuration: The option "align_double_arrow" with value %s is invalid. Accepted values are: true, false, null.', + $configuration[$name] + ) + ); + } + } elseif ('align_equals' === $name) { + if (true === $configuration[$name]) { + $newConfig['operators']['='] = self::ALIGN; + } elseif (false === $configuration[$name]) { + $newConfig['operators']['='] = self::SINGLE_SPACE; + } elseif (null !== $configuration[$name]) { + throw new InvalidFixerConfigurationException( + $this->getName(), + sprintf( + 'Invalid configuration: The option "align_equals" with value %s is invalid. Accepted values are: true, false, null.', + $configuration[$name] + ) + ); + } + } else { + throw new InvalidFixerConfigurationException($this->getName(), 'Mixing old configuration with new configuration is not allowed.'); + } + } + + $message = sprintf( + 'Given configuration is deprecated and will be removed in 3.0. Use configuration %s as replacement for %s.', + HelpCommand::toString($newConfig), + HelpCommand::toString($configuration) + ); + + if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + throw new InvalidFixerConfigurationException($this->getName(), "{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); + } + + @trigger_error($message, E_USER_DEPRECATED); + + return $newConfig; + } + + // Alignment logic related methods + + /** + * @param array $toAlign + */ + private function fixAlignment(Tokens $tokens, array $toAlign) + { + $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->injectAlignmentPlaceholders($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) { + 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)); + } + } + + /** + * @param int $startAt + * @param int $endAt + * @param string $tokenContent + */ + private function injectAlignmentPlaceholders(Tokens $tokens, $startAt, $endAt, $tokenContent) + { + for ($index = $startAt; $index < $endAt; ++$index) { + $token = $tokens[$index]; + + $content = $token->getContent(); + if ( + strtolower($content) === $tokenContent + && $this->tokensAnalyzer->isBinaryOperator($index) + && ('=' !== $content || !$this->isEqualPartOfDeclareStatement($tokens, $index)) + ) { + $tokens[$index] = new Token(sprintf(self::ALIGN_PLACEHOLDER, $this->deepestLevel).$content); + + continue; + } + + if ($token->isGivenKind(T_FUNCTION)) { + ++$this->deepestLevel; + + continue; + } + + if ($token->equals('(')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + continue; + } + + if ($token->equals('[')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); + + continue; + } + + if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); + + continue; + } + } + } + + /** + * @param int $startAt + * @param int $endAt + */ + private function injectAlignmentPlaceholdersForArrow(Tokens $tokens, $startAt, $endAt) + { + for ($index = $startAt; $index < $endAt; ++$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind([T_FOREACH, T_FOR, T_WHILE, T_IF, T_SWITCH])) { + $index = $tokens->getNextMeaningfulToken($index); + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + continue; + } + + if ($token->isGivenKind(T_ARRAY)) { // don't use "$tokens->isArray()" here, short arrays are handled in the next case + $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)) { + $from = $index; + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $from); + $index = $until; + + $this->injectArrayAlignmentPlaceholders($tokens, $from + 1, $until - 1); + + continue; + } + + if ($token->isGivenKind(T_DOUBLE_ARROW)) { // no need to analyze for `isBinaryOperator` (always true), nor if part of declare statement (not valid PHP) + $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]); + + continue; + } + + if ($token->equals(';')) { + ++$this->deepestLevel; + ++$this->currentLevel; + + continue; + } + + if ($token->equals(',')) { + for ($i = $index; $i < $endAt - 1; ++$i) { + if (false !== strpos($tokens[$i - 1]->getContent(), "\n")) { + 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; + } + } + } + } + + /** + * @param int $from + * @param int $until + */ + private function injectArrayAlignmentPlaceholders(Tokens $tokens, $from, $until) + { + // Only inject placeholders for multi-line arrays + if ($tokens->isPartialCodeMultiline($from, $until)) { + ++$this->deepestLevel; + ++$this->currentLevel; + $this->injectAlignmentPlaceholdersForArrow($tokens, $from, $until); + --$this->currentLevel; + } + } + + /** + * @param int $index + * @param string $alignStrategy + */ + private function fixWhiteSpaceBeforeOperator(Tokens $tokens, $index, $alignStrategy) + { + // 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 || $tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { + return; + } + + $content = $tokens[$index - 1]->getContent(); + if (' ' !== $content && false === strpos($content, "\n")) { + $tokens[$index - 1] = new Token([T_WHITESPACE, ' ']); + } + } + + /** + * Look for group of placeholders and provide vertical alignment. + * + * @param string $alignStrategy + * + * @return string + */ + private function replacePlaceholders(Tokens $tokens, $alignStrategy) + { + $tmpCode = $tokens->generateCode(); + + for ($j = 0; $j <= $this->deepestLevel; ++$j) { + $placeholder = sprintf(self::ALIGN_PLACEHOLDER, $j); + + if (false === strpos($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; + } else { + ++$groupIndex; + $groups[$groupIndex] = []; + } + } + + foreach ($groups as $group) { + if (\count($group) < 1) { + continue; + } + + if (self::ALIGN !== $alignStrategy) { + // move place holders to match strategy + foreach ($group as $index) { + $currentPosition = strpos($lines[$index], $placeholder); + $before = substr($lines[$index], 0, $currentPosition); + + if (self::ALIGN_SINGLE_SPACE === $alignStrategy) { + if (1 > \strlen($before) || ' ' !== substr($before, -1)) { // if last char of before-content is not ' '; add it + $before .= ' '; + } + } elseif (self::ALIGN_SINGLE_SPACE_MINIMAL === $alignStrategy) { + if (1 !== 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, strpos(utf8_decode($lines[$index]), $placeholder)); + } + + foreach ($group as $index) { + $line = $lines[$index]; + $currentSymbol = strpos(utf8_decode($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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..60a1a9621e539ab1e3c00540bdae10a0404f1fa4 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.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\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + * @author SpacePossum + */ +final class ConcatSpaceFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + private $fixCallback; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + if ('one' === $this->configuration['spacing']) { + $this->fixCallback = 'fixConcatenationToSingleSpace'; + } else { + $this->fixCallback = 'fixConcatenationToNoSpace'; + } + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Concatenation should be spaced according configuration.', + [ + new CodeSample( + " 'none'] + ), + new CodeSample( + " 'one'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after SingleLineThrowFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound('.'); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $callBack = $this->fixCallback; + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if ($tokens[$index]->equals('.')) { + $this->{$callBack}($tokens, $index); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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, $index) + { + $prevNonWhitespaceToken = $tokens[$tokens->getPrevNonWhitespace($index)]; + if (!$prevNonWhitespaceToken->isGivenKind([T_LNUMBER, T_COMMENT, T_DOC_COMMENT]) || '/*' === substr($prevNonWhitespaceToken->getContent(), 0, 2)) { + $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, $index) + { + $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, $index, $offset) + { + $offsetIndex = $index + $offset; + + if (!$tokens[$offsetIndex]->isWhitespace()) { + $tokens->insertAt($index + (1 === $offset ?: 0), new Token([T_WHITESPACE, ' '])); + + return; + } + + if (false !== strpos($tokens[$offsetIndex]->getContent(), "\n")) { + return; + } + + if ($tokens[$index + $offset * 2]->isComment()) { + return; + } + + $tokens[$offsetIndex] = new Token([T_WHITESPACE, ' ']); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..82972736a0133e3935932be5c4c2afe18f081886 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.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\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gregor Harlan + * @author Kuba Werłos + */ +final class IncrementStyleFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @internal + */ + const STYLE_PRE = 'pre'; + + /** + * @internal + */ + const STYLE_POST = 'post'; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Pre- or post-increment and decrement operators should be used if possible.', + [ + new CodeSample(" self::STYLE_POST] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after StandardizeIncrementFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound([T_INC, T_DEC]); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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); + } + } + } + } + + /** + * @param int $index + * + * @return int + */ + private function findEnd(Tokens $tokens, $index) + { + $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->isGivenKind(T_OBJECT_OPERATOR)) { + return $this->findEnd($tokens, $nextIndex); + } + + if ($nextToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { + return $this->findEnd($tokens, $tokens->getNextMeaningfulToken($nextIndex)); + } + + return $index; + } + + /** + * @param int $index + * + * @return int + */ + private function findStart(Tokens $tokens, $index) + { + 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('$')) { + $index = $prevIndex; + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevIndex]; + } + + if ($prevToken->isGivenKind(T_OBJECT_OPERATOR)) { + 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->getTokenNotOfKindSibling($prevIndex, -1, [[T_NS_SEPARATOR], [T_STATIC], [T_STRING]]); + $index = $tokens->getNextMeaningfulToken($index); + } + + return $index; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..30d4fc32830bf88d052ffa45c443e7b302335a28 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.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\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Haralan Dobrev + */ +final class LogicalOperatorsFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Use `&&` and `||` logical operators instead of `and` and `or`.', + [ + new CodeSample( + 'isAnyTokenKindsFound([T_LOGICAL_AND, T_LOGICAL_OR]); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..c3ef6177f56ae9715ad7d6e2a346050988127e3f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php @@ -0,0 +1,150 @@ + + * Dariusz Rumiń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\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class NewWithBracesFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'All instances created with new keyword must be followed by braces.', + [new CodeSample("isTokenKindFound(T_NEW); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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], + [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_SPACESHIP')) { + $nextTokenKinds[] = [T_SPACESHIP]; + } + } + + for ($index = $tokens->count() - 3; $index > 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_NEW)) { + continue; + } + + $nextIndex = $tokens->getNextTokenOfKind($index, $nextTokenKinds); + $nextToken = $tokens[$nextIndex]; + + // new anonymous class definition + if ($nextToken->isGivenKind(T_CLASS)) { + if (!$tokens[$tokens->getNextMeaningfulToken($nextIndex)]->equals('(')) { + $this->insertBracesAfter($tokens, $nextIndex); + } + + continue; + } + + // entrance into array index syntax - need to look for exit + while ($nextToken->equals('[') || $nextToken->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN)) { + $nextIndex = $tokens->findBlockEnd($tokens->detectBlockType($nextToken)['type'], $nextIndex) + 1; + $nextToken = $tokens[$nextIndex]; + } + + // new statement has a gap in it - advance to the next token + if ($nextToken->isWhitespace()) { + $nextIndex = $tokens->getNextNonWhitespace($nextIndex); + $nextToken = $tokens[$nextIndex]; + } + + // new statement with () - nothing to do + if ($nextToken->equals('(') || $nextToken->isGivenKind(T_OBJECT_OPERATOR)) { + continue; + } + + $this->insertBracesAfter($tokens, $tokens->getPrevMeaningfulToken($nextIndex)); + } + } + + /** + * @param int $index + */ + private function insertBracesAfter(Tokens $tokens, $index) + { + $tokens->insertAt(++$index, [new Token('('), new Token(')')]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d1beb31845a0dbe5cd46e044d25c9557e48ec5c5 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.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\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Javier Spagnoletti + */ +final class NotOperatorWithSpaceFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Logical NOT operators (`!`) should have leading and trailing whitespaces.', + [new CodeSample( + 'isTokenKindFound('!'); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..feb1b8cb4b3006131609fd35ebfdde68187fdb44 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.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\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Javier Spagnoletti + */ +final class NotOperatorWithSuccessorSpaceFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Logical NOT operators (`!`) should have one trailing whitespace.', + [new CodeSample( + 'isTokenKindFound('!'); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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, ' '])); + } else { + $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); + } + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..245791d1060ed791095f87320d419f68cf0464ab --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.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\Tokenizer\Tokens; + +/** + * @author Fabien Potencier + * @author Dariusz Rumiński + */ +final class ObjectOperatorWithoutWhitespaceFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'There should not be space before or after object `T_OBJECT_OPERATOR` `->`.', + [new CodeSample(" b;\n")] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_OBJECT_OPERATOR); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + // [Structure] there should not be space before or after T_OBJECT_OPERATOR + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_OBJECT_OPERATOR)) { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/PreIncrementFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/PreIncrementFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..04b94b64dcda7c54567e7ebb047751593100571f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/PreIncrementFixer.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\Operator; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * @author Gregor Harlan + * + * @deprecated in 2.8, proxy to IncrementStyleFixer + */ +final class PreIncrementFixer extends AbstractProxyFixer implements DeprecatedFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Pre incrementation/decrementation should be used if possible.', + [new CodeSample("proxyFixers); + } + + /** + * {@inheritdoc} + */ + protected function createProxyFixers() + { + $fixer = new IncrementStyleFixer(); + $fixer->configure(['style' => 'pre']); + + return [$fixer]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..39013ecdc01f7bff1b34c7156f9965fd0811c085 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.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\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author ntzm + */ +final class StandardizeIncrementFixer extends AbstractFixer +{ + /** + * @internal + */ + const EXPRESSION_END_TOKENS = [ + ';', + ')', + ']', + ',', + ':', + [CT::T_DYNAMIC_PROP_BRACE_CLOSE], + [CT::T_DYNAMIC_VAR_BRACE_CLOSE], + [T_CLOSE_TAG], + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Increment and decrement operators should be used if possible.', + [ + new CodeSample("isAnyTokenKindsFound([T_PLUS_EQUAL, T_MINUS_EQUAL]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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, $tokens->getPrevMeaningfulToken($operatorIndex)); + + $this->clearRangeLeaveComments( + $tokens, + $tokens->getPrevMeaningfulToken($operatorIndex) + 1, + $numberIndex + ); + + $tokens->insertAt( + $startIndex, + new Token($operator->isGivenKind(T_PLUS_EQUAL) ? [T_INC, '++'] : [T_DEC, '--']) + ); + } + } + + /** + * Find the start of a reference. + * + * @param int $index + * + * @return int + */ + private function findStart(Tokens $tokens, $index) + { + while (!$tokens[$index]->equalsAny(['$', [T_VARIABLE]])) { + if ($tokens[$index]->equals(']')) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); + } elseif ($tokens[$index]->isGivenKind(CT::T_DYNAMIC_PROP_BRACE_CLOSE)) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_DYNAMIC_PROP_BRACE, $index); + } elseif ($tokens[$index]->isGivenKind(CT::T_DYNAMIC_VAR_BRACE_CLOSE)) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE, $index); + } elseif ($tokens[$index]->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE)) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE, $index); + } else { + $index = $tokens->getPrevMeaningfulToken($index); + } + } + + while ($tokens[$tokens->getPrevMeaningfulToken($index)]->equals('$')) { + $index = $tokens->getPrevMeaningfulToken($index); + } + + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_OBJECT_OPERATOR)) { + return $this->findStart($tokens, $tokens->getPrevMeaningfulToken($index)); + } + + return $index; + } + + /** + * Clear tokens in the given range unless they are comments. + * + * @param int $indexStart + * @param int $indexEnd + */ + private function clearRangeLeaveComments(Tokens $tokens, $indexStart, $indexEnd) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d240e33f830686f78260566e16c7fe86767999fb --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.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\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class StandardizeNotEqualsFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replace all `<>` with `!=`.', + [new CodeSample(" \$c;\n")] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BinaryOperatorSpacesFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_IS_NOT_EQUAL); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(T_IS_NOT_EQUAL)) { + $tokens[$index] = new Token([T_IS_NOT_EQUAL, '!=']); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..6560adaf82cda6d2160b7e34f94a42fcaa59f0df --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php @@ -0,0 +1,121 @@ + + * Dariusz Rumiń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\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class TernaryOperatorSpacesFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Standardize spaces around ternary operator.', + [new CodeSample("isAllTokenKindsFound(['?', ':']); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $ternaryLevel = 0; + + foreach ($tokens as $index => $token) { + if ($token->equals('?')) { + ++$ternaryLevel; + + $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($index); + $nextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex]; + + if ($nextNonWhitespaceToken->equals(':')) { + // for `$a ?: $b` remove spaces between `?` and `:` + if ($tokens[$index + 1]->isWhitespace()) { + $tokens->clearAt($index + 1); + } + } 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 ($ternaryLevel && $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); + } + + --$ternaryLevel; + } + } + } + + /** + * @param int $index + * @param bool $after + */ + private function ensureWhitespaceExistence(Tokens $tokens, $index, $after) + { + if ($tokens[$index]->isWhitespace()) { + if ( + false === strpos($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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..28bf095206281d101ff6847bfeee63e044e893df --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php @@ -0,0 +1,215 @@ + + * Dariusz Rumiń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\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class TernaryToNullCoalescingFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Use `null` coalescing operator `??` where possible. Requires PHP >= 7.0.', + [ + new VersionSpecificCodeSample( + "= 70000 && $tokens->isTokenKindFound(T_ISSET); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $issetIndexes = array_keys($tokens->findGivenKind(T_ISSET)); + while ($issetIndex = array_pop($issetIndexes)) { + $this->fixIsset($tokens, $issetIndex); + } + } + + /** + * @param int $index of `T_ISSET` token + */ + private function fixIsset(Tokens $tokens, $index) + { + $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 + } + + // search what is inside the middle argument of ternary operator + $ternaryColonIndex = $tokens->getNextTokenOfKind($ternaryQuestionMarkIndex, [':']); + $ternaryFirstOperandTokens = $this->getMeaningfulSequence($tokens, $ternaryQuestionMarkIndex, $ternaryColonIndex); + + if ($issetTokens->generateCode() !== $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 + * + * @return Tokens + */ + private function getMeaningfulSequence(Tokens $tokens, $start, $end) + { + $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()`. + * + * @return bool + */ + private function isHigherPrecedenceAssociativityOperator(Token $token) + { + 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 + * + * @return bool + */ + private function hasChangingContent(Tokens $tokens) + { + static $operatorsPerId = [ + T_DEC, + T_INC, + T_STRING, + T_YIELD, + T_YIELD_FROM, + ]; + + foreach ($tokens as $token) { + if ($token->isGivenKind($operatorsPerId) || $token->equals('(')) { + return true; + } + } + + return false; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..697cc6dc5e5db3b09929d0f371f55e236c38ff3c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.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\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gregor Harlan + */ +final class UnaryOperatorSpacesFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Unary operators should be placed adjacent to their operands.', + [new CodeSample("count() - 1; $index >= 0; --$index) { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..871c14995621f1b43b837cf23100ac85c812c644 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.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\PhpTag; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Ceeram + */ +final class BlankLineAfterOpeningTagFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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("isTokenKindFound(T_OPEN_TAG); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + // ignore files with short open tag and ignore non-monolithic files + if (!$tokens[0]->isGivenKind(T_OPEN_TAG) || !$tokens->isMonolithicPhp()) { + return; + } + + $newlineFound = false; + /** @var Token $token */ + foreach ($tokens as $token) { + if ($token->isWhitespace() && false !== strpos($token->getContent(), "\n")) { + $newlineFound = true; + + break; + } + } + + // ignore one-line files + if (!$newlineFound) { + return; + } + + $token = $tokens[0]; + + if (false === strpos($token->getContent(), "\n")) { + $tokens[0] = new Token([$token->getId(), rtrim($token->getContent()).$lineEnding]); + } + + if (false === strpos($tokens[1]->getContent(), "\n")) { + if ($tokens[1]->isWhitespace()) { + $tokens[1] = new Token([T_WHITESPACE, $lineEnding.$tokens[1]->getContent()]); + } else { + $tokens->insertAt(1, new Token([T_WHITESPACE, $lineEnding])); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b6c3b10508fcbe973197865511e7d1130e309497 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.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\PhpTag; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHP code must use the long `generateCode(); + + // replace all echo ' echo ' $token) { + if ($token->isGivenKind(T_OPEN_TAG)) { + $tokenContent = $token->getContent(); + + if ('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 = $tokens[$index]; + } + + $tokensOldContentLength += \strlen($token->getContent()); + } + + $tokensOrg->overrideRange(0, $tokensOrg->count() - 1, $tokens); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..5d59e52f392ca8dbbf0580301399fb440417a1cb --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.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\PhpTag; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Ceeram + */ +final class LinebreakAfterOpeningTagFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Ensure there is no code on the same line as the PHP open tag.', + [new CodeSample("isTokenKindFound(T_OPEN_TAG); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + // ignore files with short open tag and ignore non-monolithic files + if (!$tokens[0]->isGivenKind(T_OPEN_TAG) || !$tokens->isMonolithicPhp()) { + return; + } + + $newlineFound = false; + foreach ($tokens as $token) { + if ($token->isWhitespace() && false !== strpos($token->getContent(), "\n")) { + $newlineFound = true; + + break; + } + } + + // ignore one-line files + if (!$newlineFound) { + return; + } + + $token = $tokens[0]; + $tokens[0] = new Token([$token->getId(), rtrim($token->getContent()).$this->whitespacesConfig->getLineEnding()]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..8b19366a69ce5e4d753b6c898c9e3067499852dd --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.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\Fixer\PhpTag; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'The closing `?>` tag MUST be omitted from files containing only PHP.', + [new CodeSample("\n")] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_CLOSE_TAG); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + if (\count($tokens) < 2 || !$tokens->isMonolithicPhp() || !$tokens->isTokenKindFound(T_CLOSE_TAG)) { + return; + } + + $closeTags = $tokens->findGivenKind(T_CLOSE_TAG); + $index = key($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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoShortEchoTagFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoShortEchoTagFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..5ff7ae8f53cd7db32d03dfb74b8d9c335a3c11da --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoShortEchoTagFixer.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\PhpTag; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vincent Klaiber + */ +final class NoShortEchoTagFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replace short-echo `isTokenKindFound(T_OPEN_TAG_WITH_ECHO); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $i = \count($tokens); + + while ($i--) { + $token = $tokens[$i]; + + if (!$token->isGivenKind(T_OPEN_TAG_WITH_ECHO)) { + continue; + } + + $nextIndex = $i + 1; + + $tokens[$i] = new Token([T_OPEN_TAG, 'isWhitespace()) { + $tokens->insertAt($nextIndex, new Token([T_WHITESPACE, ' '])); + } + + $tokens->insertAt($nextIndex, new Token([T_ECHO, 'echo'])); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..080cf5c9af73ed0ec7f3e82f2df09d953d35acb9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php @@ -0,0 +1,218 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitConstructFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + private static $assertionFixers = [ + 'assertSame' => 'fixAssertPositive', + 'assertEquals' => 'fixAssertPositive', + 'assertNotEquals' => 'fixAssertNegative', + 'assertNotSame' => 'fixAssertNegative', + ]; + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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() + { + return -10; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + // no assertions to be fixed - fast return + if (empty($this->configuration['assertions'])) { + return; + } + + foreach ($this->configuration['assertions'] as $assertionMethod) { + $assertionFixer = self::$assertionFixers[$assertionMethod]; + + for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { + $index = $this->{$assertionFixer}($tokens, $index, $assertionMethod); + + if (null === $index) { + break; + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolverRootless('assertions', [ + (new FixerOptionBuilder('assertions', 'List of assertion methods to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(array_keys(self::$assertionFixers))]) + ->setDefault([ + 'assertEquals', + 'assertSame', + 'assertNotEquals', + 'assertNotSame', + ]) + ->getOption(), + ], $this->getName()); + } + + /** + * @param int $index + * @param string $method + * + * @return null|int + */ + private function fixAssertNegative(Tokens $tokens, $index, $method) + { + static $map = [ + 'false' => 'assertNotFalse', + 'null' => 'assertNotNull', + 'true' => 'assertNotTrue', + ]; + + return $this->fixAssert($map, $tokens, $index, $method); + } + + /** + * @param int $index + * @param string $method + * + * @return null|int + */ + private function fixAssertPositive(Tokens $tokens, $index, $method) + { + static $map = [ + 'false' => 'assertFalse', + 'null' => 'assertNull', + 'true' => 'assertTrue', + ]; + + return $this->fixAssert($map, $tokens, $index, $method); + } + + /** + * @param array $map + * @param int $index + * @param string $method + * + * @return null|int + */ + private function fixAssert(array $map, Tokens $tokens, $index, $method) + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + $sequence = $tokens->findSequence( + [ + [T_STRING, $method], + '(', + ], + $index + ); + + if (null === $sequence) { + return null; + } + + $sequenceIndexes = array_keys($sequence); + if (!$functionsAnalyzer->isTheSameClassCall($tokens, $sequenceIndexes[0])) { + return null; + } + + $sequenceIndexes[2] = $tokens->getNextMeaningfulToken($sequenceIndexes[1]); + $firstParameterToken = $tokens[$sequenceIndexes[2]]; + + if (!$firstParameterToken->isNativeConstant()) { + return $sequenceIndexes[2]; + } + + $sequenceIndexes[3] = $tokens->getNextMeaningfulToken($sequenceIndexes[2]); + + // return if first method argument is an expression, not value + if (!$tokens[$sequenceIndexes[3]]->equals(',')) { + return $sequenceIndexes[3]; + } + + $tokens[$sequenceIndexes[0]] = new Token([T_STRING, $map[strtolower($firstParameterToken->getContent())]]); + $tokens->clearRange($sequenceIndexes[2], $tokens->getNextNonWhitespace($sequenceIndexes[3]) - 1); + + return $sequenceIndexes[3]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..0c3e2a901cfdb4552b0962bffe3fa34f11f867c2 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php @@ -0,0 +1,443 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + * @author Dariusz Rumiński + */ +final class PhpUnitDedicateAssertFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + private static $fixMap = [ + 'array_key_exists' => ['assertArrayNotHasKey', 'assertArrayHasKey'], + 'empty' => ['assertNotEmpty', 'assertEmpty'], + 'file_exists' => ['assertFileNotExists', 'assertFileExists'], + 'is_array' => true, + 'is_bool' => true, + 'is_callable' => true, + 'is_dir' => ['assertDirectoryNotExists', 'assertDirectoryExists'], + 'is_double' => true, + 'is_float' => true, + 'is_infinite' => ['assertFinite', 'assertInfinite'], + 'is_int' => true, + 'is_integer' => true, + 'is_long' => true, + 'is_nan' => [false, 'assertNan'], + 'is_null' => ['assertNotNull', 'assertNull'], + 'is_numeric' => true, + 'is_object' => true, + 'is_readable' => ['assertNotIsReadable', 'assertIsReadable'], + 'is_real' => true, + 'is_resource' => true, + 'is_scalar' => true, + 'is_string' => true, + 'is_writable' => ['assertNotIsWritable', 'assertIsWritable'], + ]; + + /** + * @var string[] + */ + private $functions = []; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + if (isset($this->configuration['functions'])) { + $this->functions = $this->configuration['functions']; + + return; + } + + // assertions added in 3.0: assertArrayNotHasKey assertArrayHasKey assertFileNotExists assertFileExists assertNotNull, assertNull + $this->functions = [ + 'array_key_exists', + 'file_exists', + 'is_null', + ]; + + 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_resource', + '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', + ]); + } + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 PhpUnitDedicateAssertInternalTypeFixer. + * Must run after NoAliasFunctionsFixer, PhpUnitConstructFixer. + */ + public function getPriority() + { + return -15; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($this->getPreviousAssertCall($tokens) as $assertCall) { + // test and fix for assertTrue/False to dedicated asserts + if ('asserttrue' === $assertCall['loweredName'] || 'assertfalse' === $assertCall['loweredName']) { + $this->fixAssertTrueFalse($tokens, $assertCall); + + continue; + } + + if ( + 'assertsame' === $assertCall['loweredName'] + || 'assertnotsame' === $assertCall['loweredName'] + || 'assertequals' === $assertCall['loweredName'] + || 'assertnotequals' === $assertCall['loweredName'] + ) { + $this->fixAssertSameEquals($tokens, $assertCall); + + continue; + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $values = [ + 'array_key_exists', + 'empty', + 'file_exists', + 'is_array', + 'is_bool', + 'is_callable', + 'is_double', + 'is_float', + 'is_infinite', + 'is_int', + 'is_integer', + 'is_long', + 'is_nan', + 'is_null', + 'is_numeric', + 'is_object', + 'is_real', + 'is_resource', + 'is_scalar', + 'is_string', + ]; + + sort($values); + + return new FixerConfigurationResolverRootless('functions', [ + (new FixerOptionBuilder('functions', 'List of assertions to fix (overrides `target`).')) + ->setAllowedTypes(['null', 'array']) + ->setAllowedValues([ + null, + new AllowedValueSubset($values), + ]) + ->setDefault(null) + ->setDeprecationMessage('Use option `target` instead.') + ->getOption(), + (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_5_0) // @TODO 3.x: change to `VERSION_NEWEST` + ->getOption(), + ], $this->getName()); + } + + private function fixAssertTrueFalse(Tokens $tokens, array $assertCall) + { + $testDefaultNamespaceTokenIndex = false; + $testIndex = $tokens->getNextMeaningfulToken($assertCall['openBraceIndex']); + + if (!$tokens[$testIndex]->isGivenKind([T_EMPTY, T_STRING])) { + 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; + } + + $isPositive = 'asserttrue' === $assertCall['loweredName']; + + $content = strtolower($tokens[$testIndex]->getContent()); + if (!\in_array($content, $this->functions, true)) { + return; + } + + if (\is_array(self::$fixMap[$content])) { + if (false !== self::$fixMap[$content][$isPositive]) { + $tokens[$assertCall['index']] = new Token([T_STRING, self::$fixMap[$content][$isPositive]]); + $this->removeFunctionCall($tokens, $testDefaultNamespaceTokenIndex, $testIndex, $testOpenIndex, $testCloseIndex); + } + + 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 (false !== $testDefaultNamespaceTokenIndex) { + $tokens->clearTokenAndMergeSurroundingWhitespace($testDefaultNamespaceTokenIndex); + } + } + + private function fixAssertSameEquals(Tokens $tokens, array $assertCall) + { + // @ $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_LNUMBER)) { + 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 = false; + } + + 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', + ]); + } + + private function getPreviousAssertCall(Tokens $tokens) + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + for ($index = $tokens->count(); $index > 0; --$index) { + $index = $tokens->getPrevTokenOfKind($index, [[T_STRING]]); + if (null === $index) { + return; + } + + // test if "assert" something call + $loweredContent = strtolower($tokens[$index]->getContent()); + if ('assert' !== substr($loweredContent, 0, 6)) { + 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), + ]; + } + } + + /** + * @param false|int $callNSIndex + * @param int $callIndex + * @param int $openIndex + * @param int $closeIndex + */ + private function removeFunctionCall(Tokens $tokens, $callNSIndex, $callIndex, $openIndex, $closeIndex) + { + $tokens->clearTokenAndMergeSurroundingWhitespace($callIndex); + if (false !== $callNSIndex) { + $tokens->clearTokenAndMergeSurroundingWhitespace($callNSIndex); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($openIndex); + $commaIndex = $tokens->getPrevMeaningfulToken($closeIndex); + if ($tokens[$commaIndex]->equals(',')) { + $tokens->removeTrailingWhitespace($commaIndex); + $tokens->clearAt($commaIndex); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..a497376e290eaa3387e41ce378cf9b337dad1dcf --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.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\PhpUnit; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Filippo Tessarotto + */ +final class PhpUnitDedicateAssertInternalTypeFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @var array + */ + private $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', + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHPUnit assertions like `assertIsArray` should be used over `assertInternalType`.', + [ + new CodeSample( + 'assertInternalType("array", $var); + $this->assertInternalType("boolean", $var); + } +} +' + ), + ], + null, + 'Risky when PHPUnit methods are overridden or when project has PHPUnit incompatibilities.' + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_CLASS, T_FUNCTION]); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + * + * Must run after PhpUnitDedicateAssertFixer. + */ + public function getPriority() + { + return -16; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $this->updateAssertInternalTypeMethods($tokens, $indexes[0], $indexes[1]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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(), + ]); + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function updateAssertInternalTypeMethods(Tokens $tokens, $startIndex, $endIndex) + { + $anonymousClassIndexes = []; + $tokenAnalyzer = new TokensAnalyzer($tokens); + for ($index = $startIndex; $index < $endIndex; ++$index) { + if (!$tokens[$index]->isClassy() || !$tokenAnalyzer->isAnonymousClass($index)) { + continue; + } + + $openingBraceIndex = $tokens->getNextTokenOfKind($index, ['{']); + $closingBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openingBraceIndex); + + $anonymousClassIndexes[$closingBraceIndex] = $openingBraceIndex; + } + + for ($index = $endIndex - 1; $index > $startIndex; --$index) { + if (isset($anonymousClassIndexes[$index])) { + $index = $anonymousClassIndexes[$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->equals([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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d718c657bada4351f9207de7fb7fe6ee53497e50 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.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\PhpUnit; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitExpectationFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var array + */ + private $methodMap = []; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->methodMap = [ + 'setExpectedException' => 'expectExceptionMessage', + ]; + + if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_6)) { + $this->methodMap['setExpectedExceptionRegExp'] = 'expectExceptionMessageRegExp'; + } + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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_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() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_CLASS); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $this->fixExpectation($tokens, $indexes[0], $indexes[1]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) + ->setAllowedTypes(['string']) + ->setAllowedValues([PhpUnitTargetVersion::VERSION_5_2, PhpUnitTargetVersion::VERSION_5_6, PhpUnitTargetVersion::VERSION_NEWEST]) + ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST) + ->getOption(), + ]); + } + + private function fixExpectation(Tokens $tokens, $startIndex, $endIndex) + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + $oldMethodSequence = [ + new Token([T_VARIABLE, '$this']), + new Token([T_OBJECT_OPERATOR, '->']), + [T_STRING], + ]; + + for ($index = $startIndex; $startIndex < $endIndex; ++$index) { + $match = $tokens->findSequence($oldMethodSequence, $index); + + if (null === $match) { + return; + } + + list($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().$this->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) { + array_push($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); + } + + $tokens[$index] = new Token([T_STRING, 'expectException']); + } + } + + /** + * @param int $index + * + * @return string + */ + private function detectIndent(Tokens $tokens, $index) + { + if (!$tokens[$index - 1]->isWhitespace()) { + return ''; // cannot detect indent + } + + $explodedContent = explode("\n", $tokens[$index - 1]->getContent()); + + return end($explodedContent); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..335163f25724f8c2c21abc9985a20fe704defb48 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.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\PhpUnit; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Roland Franssen + */ +final class PhpUnitFqcnAnnotationFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHPUnit annotations should be a FQCNs including a root namespace.', + [new CodeSample( + 'isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $startIndex = $indexes[0]; + $prevDocCommentIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_DOC_COMMENT]]); + if (null !== $prevDocCommentIndex) { + $startIndex = $prevDocCommentIndex; + } + $this->fixPhpUnitClass($tokens, $startIndex, $indexes[1]); + } + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function fixPhpUnitClass(Tokens $tokens, $startIndex, $endIndex) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..013cec410434d12946e35fcd396c964c7077e626 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php @@ -0,0 +1,271 @@ + + * Dariusz Rumiń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\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gert de Pagter + */ +final class PhpUnitInternalClassFixer extends AbstractFixer implements WhitespacesAwareFixerInterface, ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'All PHPUnit test classes should be marked as internal.', + [new CodeSample("isTokenKindFound(T_CLASS); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $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 applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens, true) as $indexes) { + $this->markClassInternal($tokens, $indexes[0]); + } + } + + /** + * @param int $startIndex + */ + private function markClassInternal(Tokens $tokens, $startIndex) + { + $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); + + if (!$this->isAllowedByConfiguration($tokens, $classIndex)) { + return; + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex); + + if ($this->hasDocBlock($tokens, $classIndex)) { + $this->updateDocBlockIfNeeded($tokens, $docBlockIndex); + + return; + } + + $this->createDocBlock($tokens, $docBlockIndex); + } + + /** + * @param int $i + * + * @return bool + */ + private function isAllowedByConfiguration(Tokens $tokens, $i) + { + $typeIndex = $tokens->getPrevMeaningfulToken($i); + if ($tokens[$typeIndex]->isGivenKind(T_FINAL)) { + return \in_array('final', $this->configuration['types'], true); + } + + if ($tokens[$typeIndex]->isGivenKind(T_ABSTRACT)) { + return \in_array('abstract', $this->configuration['types'], true); + } + + return \in_array('normal', $this->configuration['types'], true); + } + + private function createDocBlock(Tokens $tokens, $docBlockIndex) + { + $lineEnd = $this->whitespacesConfig->getLineEnding(); + $originalIndent = $this->detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); + $toInsert = [ + new Token([T_DOC_COMMENT, '/**'.$lineEnd."{$originalIndent} * @internal".$lineEnd."{$originalIndent} */"]), + new Token([T_WHITESPACE, $lineEnd.$originalIndent]), + ]; + $index = $tokens->getNextMeaningfulToken($docBlockIndex); + $tokens->insertAt($index, $toInsert); + } + + private function updateDocBlockIfNeeded(Tokens $tokens, $docBlockIndex) + { + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + if (!empty($doc->getAnnotationsOfType('internal'))) { + return; + } + $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex); + $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex); + $lines = implode('', $lines); + + $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); + } + + /** + * @param int $index + * + * @return bool + */ + private function hasDocBlock(Tokens $tokens, $index) + { + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + return $tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT); + } + + /** + * @param int $index + * + * @return int + */ + private function getDocBlockIndex(Tokens $tokens, $index) + { + do { + $index = $tokens->getPrevNonWhitespace($index); + } while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT])); + + return $index; + } + + /** + * @param int $index + * + * @return string + */ + private function detectIndent(Tokens $tokens, $index) + { + if (!$tokens[$index - 1]->isWhitespace()) { + return ''; // cannot detect indent + } + + $explodedContent = explode($this->whitespacesConfig->getLineEnding(), $tokens[$index - 1]->getContent()); + + return end($explodedContent); + } + + /** + * @param int $docBlockIndex + * + * @return Line[] + */ + private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, $docBlockIndex) + { + $lines = $docBlock->getLines(); + $originalIndent = $this->detectIndent($tokens, $docBlockIndex); + $lineEnd = $this->whitespacesConfig->getLineEnding(); + array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @internal'.$lineEnd); + + return $lines; + } + + /** + * @param int $docBlockIndex + * + * @return DocBlock + */ + private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, $docBlockIndex) + { + $lines = $doc->getLines(); + if (1 === \count($lines) && empty($doc->getAnnotationsOfType('internal'))) { + $lines = $this->splitUpDocBlock($lines, $tokens, $docBlockIndex); + + return new DocBlock(implode('', $lines)); + } + + return $doc; + } + + /** + * Take a one line doc block, and turn it into a multi line doc block. + * + * @param Line[] $lines + * @param int $docBlockIndex + * + * @return Line[] + */ + private function splitUpDocBlock($lines, Tokens $tokens, $docBlockIndex) + { + $lineContent = $this->getSingleLineDocBlockEntry($lines); + $lineEnd = $this->whitespacesConfig->getLineEnding(); + $originalIndent = $this->detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); + + return [ + new Line('/**'.$lineEnd), + new Line($originalIndent.' * '.$lineContent.$lineEnd), + new Line($originalIndent.' */'), + ]; + } + + /** + * @param Line[] $line + * + * @return string + */ + private function getSingleLineDocBlockEntry($line) + { + $line = $line[0]; + $line = str_replace('*/', '', $line); + $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); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b42338be9e3e01882d8fc32b42630cf0ea32eb69 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php @@ -0,0 +1,278 @@ + + * Dariusz Rumiń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\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +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 AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @internal + */ + const CAMEL_CASE = 'camel_case'; + + /** + * @internal + */ + const SNAKE_CASE = 'snake_case'; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_CLASS, T_FUNCTION]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $this->applyCasing($tokens, $indexes[0], $indexes[1]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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(), + ]); + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function applyCasing(Tokens $tokens, $startIndex, $endIndex) + { + 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->hasDocBlock($tokens, $index)) { + $this->updateDocBlock($tokens, $docBlockIndex); + } + } + } + + /** + * @param string $functionName + * + * @return string + */ + private function updateMethodCasing($functionName) + { + if (self::CAMEL_CASE === $this->configuration['case']) { + $newFunctionName = $functionName; + $newFunctionName = ucwords($newFunctionName, '_'); + $newFunctionName = str_replace('_', '', $newFunctionName); + $newFunctionName = lcfirst($newFunctionName); + } else { + $newFunctionName = Utils::camelCaseToUnderscore($functionName); + } + + return $newFunctionName; + } + + /** + * @param int $index + * + * @return bool + */ + private function isTestMethod(Tokens $tokens, $index) + { + // 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 ($this->startsWith('test', $functionName)) { + return true; + } + // If the function doesn't have test in its name, and no doc block, it's not a test + if (!$this->hasDocBlock($tokens, $index)) { + return false; + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + $doc = $tokens[$docBlockIndex]->getContent(); + if (false === strpos($doc, '@test')) { + return false; + } + + return true; + } + + /** + * @param int $index + * + * @return bool + */ + private function isMethod(Tokens $tokens, $index) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + return $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index); + } + + /** + * @param string $needle + * @param string $haystack + * + * @return bool + */ + private function startsWith($needle, $haystack) + { + return substr($haystack, 0, \strlen($needle)) === $needle; + } + + /** + * @param int $index + * + * @return bool + */ + private function hasDocBlock(Tokens $tokens, $index) + { + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + return $tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT); + } + + /** + * @param int $index + * + * @return int + */ + private function getDocBlockIndex(Tokens $tokens, $index) + { + do { + $index = $tokens->getPrevNonWhitespace($index); + } while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT])); + + return $index; + } + + /** + * @param int $docBlockIndex + */ + private function updateDocBlock(Tokens $tokens, $docBlockIndex) + { + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + $lines = $doc->getLines(); + + $docBlockNeedsUpdate = false; + for ($inc = 0; $inc < \count($lines); ++$inc) { + $lineContent = $lines[$inc]->getContent(); + if (false === strpos($lineContent, '@depends')) { + continue; + } + + $newLineContent = Preg::replaceCallback('/(@depends\s+)(.+)(\b)/', function (array $matches) { + return 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..02d29e512e71c0965d7048d0d3086999c02cb41b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php @@ -0,0 +1,150 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitMockFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @var bool + */ + private $fixCreatePartialMock; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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.' + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_CLASS); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->fixCreatePartialMock = PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_5); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + for ($index = $indexes[0]; $index < $indexes[1]; ++$index) { + if (!$tokens[$index]->isGivenKind(T_OBJECT_OPERATOR)) { + 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']); + } + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..91a955ae34c057363c6e68b697c857f39395abef --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php @@ -0,0 +1,145 @@ + + * Dariusz Rumiń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\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Michał Adamski + * @author Kuba Werłos + */ +final class PhpUnitMockShortWillReturnFixer extends AbstractFixer +{ + /** + * @internal + */ + const RETURN_METHODS_MAP = [ + 'returnargument' => 'willReturnArgument', + 'returncallback' => 'willReturnCallback', + 'returnself' => 'willReturnSelf', + 'returnvalue' => 'willReturn', + 'returnvaluemap' => 'willReturnMap', + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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.' + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_CLASS, T_OBJECT_OPERATOR, T_STRING]); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $this->fixWillReturn($tokens, $indexes[0], $indexes[1]); + } + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function fixWillReturn(Tokens $tokens, $startIndex, $endIndex) + { + for ($index = $startIndex; $index < $endIndex; ++$index) { + if (!$tokens[$index]->isGivenKind(T_OBJECT_OPERATOR)) { + 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); + if ( + !($tokens[$classReferenceIndex]->equals([T_VARIABLE, '$this'], false) && $tokens[$objectOperatorIndex]->equals([T_OBJECT_OPERATOR, '->'])) + && !($tokens[$classReferenceIndex]->equals([T_STRING, 'self'], false) && $tokens[$objectOperatorIndex]->equals([T_DOUBLE_COLON, '::'])) + && !($tokens[$classReferenceIndex]->equals([T_STATIC, 'static'], false) && $tokens[$objectOperatorIndex]->equals([T_DOUBLE_COLON, '::'])) + ) { + continue; + } + + $functionToRemoveIndex = $tokens->getNextMeaningfulToken($objectOperatorIndex); + if (!$tokens[$functionToRemoveIndex]->isGivenKind(T_STRING) || !\array_key_exists(strtolower($tokens[$functionToRemoveIndex]->getContent()), self::RETURN_METHODS_MAP)) { + continue; + } + + $openingBraceIndex = $tokens->getNextMeaningfulToken($functionToRemoveIndex); + if (!$tokens[$openingBraceIndex]->equals('(')) { + 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); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..a017279e5a931178830b86b10faa71f7cf6c6ffc --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.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\PhpUnit; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitNamespacedFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @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|string[] Class Mappings + */ + private $classMap; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHPUnit classes MUST be used in namespaced version, e.g. `\PHPUnit\Framework\TestCase` instead of `\PHPUnit_Framework_TestCase`.', + [ + new CodeSample( + 'isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + 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|PHPUnit_Framework_Assert|PHPUnit_Framework_BaseTestListener|PHPUnit_Framework_TestListener$/i'; + $this->classMap = []; + } else { + $this->originalClassRegEx = '/^PHPUnit_Framework_TestCase$/i'; + $this->classMap = []; + } + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $importedOriginalClassesMap = []; + $currIndex = 0; + + while (null !== $currIndex) { + $currIndex = $tokens->getNextTokenOfKind($currIndex, [[T_STRING]]); + + if (null === $currIndex) { + break; + } + + $originalClass = $tokens[$currIndex]->getContent(); + + if (1 !== 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; + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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(), + ]); + } + + /** + * @param string $originalClassName + * + * @return Tokens + */ + private function generateReplacement($originalClassName) + { + $delimiter = '_'; + $string = $originalClassName; + + if (isset($this->classMap[$originalClassName])) { + $delimiter = '\\'; + $string = $this->classMap[$originalClassName]; + } + + $parts = explode($delimiter, $string); + + $tokensArray = []; + while (!empty($parts)) { + $tokensArray[] = new Token([T_STRING, array_shift($parts)]); + if (!empty($parts)) { + $tokensArray[] = new Token([T_NS_SEPARATOR, '\\']); + } + } + + return Tokens::fromArray($tokensArray); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..ecc34d287961ccb933bd5250006b9b526e42d81b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php @@ -0,0 +1,302 @@ + + * Dariusz Rumiń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\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitNoExpectationAnnotationFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var bool + */ + private $fixMessageRegExp; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->fixMessageRegExp = PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_4_3); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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() + { + return 10; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $this->fixPhpUnitClass($tokens, $indexes[0], $indexes[1]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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(), + ]); + } + + /** + * @param int $index + * + * @return string + */ + private function detectIndent(Tokens $tokens, $index) + { + if (!$tokens[$index - 1]->isWhitespace()) { + return ''; // cannot detect indent + } + + $explodedContent = explode("\n", $tokens[$index - 1]->getContent()); + + return end($explodedContent); + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function fixPhpUnitClass(Tokens $tokens, $startIndex, $endIndex) + { + $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 = $this->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 + $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $doc->getContent()]); + $tokens->insertAt($braceIndex + 1, $newMethods); + + $whitespaceIndex = $braceIndex + $newMethods->getSize() + 1; + $tokens[$whitespaceIndex] = new Token([ + T_WHITESPACE, + $this->whitespacesConfig->getLineEnding().$tokens[$whitespaceIndex]->getContent(), + ]); + + $i = $docBlockIndex; + } + } + + /** + * @return string + */ + private function extractContentFromAnnotation(Annotation $annotation) + { + $tag = $annotation->getTag()->getName(); + + if (1 !== Preg::match('/@'.$tag.'\s+(.+)$/s', $annotation->getContent(), $matches)) { + return ''; + } + + $content = $matches[1]; + + if (Preg::match('/\R/u', $content)) { + $content = Preg::replace('/\s*\R+\s*\*\s*/u', ' ', $content); + } + + return rtrim($content); + } + + private function annotationsToParamList(array $annotations) + { + $params = []; + $exceptionClass = ltrim($annotations['expectedException'], '\\'); + + if ($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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..81678ae6a7cf39461780eba2e34de0259ab72e0e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.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\PhpUnit; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class PhpUnitOrderedCoversFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Order `@covers` annotation of PHPUnit tests.', + [ + new CodeSample( + 'isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT) || 0 === Preg::match('/@covers\s.+@covers\s/s', $tokens[$index]->getContent())) { + continue; + } + + $docBlock = new DocBlock($tokens[$index]->getContent()); + $covers = $docBlock->getAnnotationsOfType('covers'); + + $coversMap = []; + foreach ($covers as $annotation) { + $rawContent = $annotation->getContent(); + + $comparableContent = Preg::replace('/\*\s*@covers\s+(.+)/', '\1', strtolower(trim($rawContent))); + $coversMap[$comparableContent] = $rawContent; + } + $orderedCoversMap = $coversMap; + ksort($orderedCoversMap, SORT_STRING); + if ($orderedCoversMap === $coversMap) { + continue; + } + + $lines = $docBlock->getLines(); + foreach (array_reverse($covers) as $annotation) { + array_splice( + $lines, + $annotation->getStart(), + $annotation->getEnd() - $annotation->getStart() + 1, + array_pop($orderedCoversMap) + ); + } + + $tokens[$index] = new Token([T_DOC_COMMENT, implode('', $lines)]); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..5890777b6a59eaf44385f88dda560148f023695c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.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\PhpUnit; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gert de Pagter + */ +final class PhpUnitSetUpTearDownVisibilityFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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.' + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_CLASS, T_FUNCTION]); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $this->fixSetUpAndTearDown($tokens, $indexes[0], $indexes[1]); + } + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function fixSetUpAndTearDown(Tokens $tokens, $startIndex, $endIndex) + { + $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, ' '])]); + } + } + } + + /** + * @param int $index + * + * @return bool + */ + private function isSetupOrTearDownMethod(Tokens $tokens, $index) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..3e4a5c2b31523f67175a708a6a389882373736dd --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php @@ -0,0 +1,270 @@ + + * Dariusz Rumiń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\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use SplFileInfo; + +/** + * @author Jefersson Nathan + */ +final class PhpUnitSizeClassFixer extends AbstractFixer implements WhitespacesAwareFixerInterface, ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_CLASS, T_STRING]); + } + + protected function applyFix(SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens, true) as $indexes) { + $this->markClassSize($tokens, $indexes[0]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('group', 'Define a specific group to be used in case no group is already in use')) + ->setAllowedValues(['small', 'medium', 'large']) + ->setDefault('small') + ->getOption(), + ]); + } + + /** + * @param int $startIndex + */ + private function markClassSize(Tokens $tokens, $startIndex) + { + $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); + + if ($this->isAbstractClass($tokens, $classIndex)) { + return; + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex); + + if ($this->hasDocBlock($tokens, $classIndex)) { + $this->updateDocBlockIfNeeded($tokens, $docBlockIndex); + + return; + } + + $this->createDocBlock($tokens, $docBlockIndex); + } + + /** + * @param int $i + * + * @return bool + */ + private function isAbstractClass(Tokens $tokens, $i) + { + $typeIndex = $tokens->getPrevMeaningfulToken($i); + + return $tokens[$typeIndex]->isGivenKind(T_ABSTRACT); + } + + private function createDocBlock(Tokens $tokens, $docBlockIndex) + { + $lineEnd = $this->whitespacesConfig->getLineEnding(); + $originalIndent = $this->detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); + $group = $this->configuration['group']; + $toInsert = [ + new Token([T_DOC_COMMENT, '/**'.$lineEnd."{$originalIndent} * @".$group.$lineEnd."{$originalIndent} */"]), + new Token([T_WHITESPACE, $lineEnd.$originalIndent]), + ]; + $index = $tokens->getNextMeaningfulToken($docBlockIndex); + $tokens->insertAt($index, $toInsert); + } + + private function updateDocBlockIfNeeded(Tokens $tokens, $docBlockIndex) + { + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + if (!empty($this->filterDocBlock($doc))) { + return; + } + $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex); + $lines = $this->addSizeAnnotation($doc, $tokens, $docBlockIndex); + $lines = implode('', $lines); + + $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); + } + + /** + * @param int $index + * + * @return bool + */ + private function hasDocBlock(Tokens $tokens, $index) + { + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + return $tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT); + } + + /** + * @param int $index + * + * @return int + */ + private function getDocBlockIndex(Tokens $tokens, $index) + { + do { + $index = $tokens->getPrevNonWhitespace($index); + } while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT])); + + return $index; + } + + /** + * @param int $index + * + * @return string + */ + private function detectIndent(Tokens $tokens, $index) + { + if (!$tokens[$index - 1]->isWhitespace()) { + return ''; // cannot detect indent + } + + $explodedContent = explode($this->whitespacesConfig->getLineEnding(), $tokens[$index - 1]->getContent()); + + return end($explodedContent); + } + + /** + * @param int $docBlockIndex + * + * @return Line[] + */ + private function addSizeAnnotation(DocBlock $docBlock, Tokens $tokens, $docBlockIndex) + { + $lines = $docBlock->getLines(); + $originalIndent = $this->detectIndent($tokens, $docBlockIndex); + $lineEnd = $this->whitespacesConfig->getLineEnding(); + $group = $this->configuration['group']; + array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @'.$group.$lineEnd); + + return $lines; + } + + /** + * @param int $docBlockIndex + * + * @return DocBlock + */ + private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, $docBlockIndex) + { + $lines = $doc->getLines(); + if (1 === \count($lines) && empty($this->filterDocBlock($doc))) { + $lines = $this->splitUpDocBlock($lines, $tokens, $docBlockIndex); + + return new DocBlock(implode('', $lines)); + } + + return $doc; + } + + /** + * Take a one line doc block, and turn it into a multi line doc block. + * + * @param Line[] $lines + * @param int $docBlockIndex + * + * @return Line[] + */ + private function splitUpDocBlock($lines, Tokens $tokens, $docBlockIndex) + { + $lineContent = $this->getSingleLineDocBlockEntry($lines); + $lineEnd = $this->whitespacesConfig->getLineEnding(); + $originalIndent = $this->detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); + + return [ + new Line('/**'.$lineEnd), + new Line($originalIndent.' * '.$lineContent.$lineEnd), + new Line($originalIndent.' */'), + ]; + } + + /** + * @param Line|Line[]|string $line + * + * @return string + */ + private function getSingleLineDocBlockEntry($line) + { + $line = $line[0]; + $line = str_replace('*/', '', $line); + $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); + } + + /** + * @return Annotation[][] + */ + private function filterDocBlock(DocBlock $doc) + { + return array_filter([ + $doc->getAnnotationsOfType('small'), + $doc->getAnnotationsOfType('large'), + $doc->getAnnotationsOfType('medium'), + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..98d99bb022f81ab0d7bb51e3395d0ef3e4f74dc0 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php @@ -0,0 +1,154 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + private static $assertionMap = [ + 'assertAttributeEquals' => 'assertAttributeSame', + 'assertAttributeNotEquals' => 'assertAttributeNotSame', + 'assertEquals' => 'assertSame', + 'assertNotEquals' => 'assertNotSame', + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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.' + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + $functionsAnalyzer = new FunctionsAnalyzer(); + + foreach ($this->configuration['assertions'] as $methodBefore) { + $methodAfter = self::$assertionMap[$methodBefore]; + + for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$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; + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolverRootless('assertions', [ + (new FixerOptionBuilder('assertions', 'List of assertion methods to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(array_keys(self::$assertionMap))]) + ->setDefault([ + 'assertAttributeEquals', + 'assertAttributeNotEquals', + 'assertEquals', + 'assertNotEquals', + ]) + ->getOption(), + ], $this->getName()); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php new file mode 100644 index 0000000000000000000000000000000000000000..7e5bdf802d8858899bb0eb6e6cb6836a9a40d73e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php @@ -0,0 +1,61 @@ + + * Dariusz Rumiń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 +{ + const VERSION_3_0 = '3.0'; + const VERSION_3_2 = '3.2'; + const VERSION_3_5 = '3.5'; + const VERSION_4_3 = '4.3'; + const VERSION_4_8 = '4.8'; + const VERSION_5_0 = '5.0'; + const VERSION_5_2 = '5.2'; + const VERSION_5_4 = '5.4'; + const VERSION_5_5 = '5.5'; + const VERSION_5_6 = '5.6'; + const VERSION_5_7 = '5.7'; + const VERSION_6_0 = '6.0'; + const VERSION_7_5 = '7.5'; + const VERSION_NEWEST = 'newest'; + + private function __construct() + { + } + + /** + * @param string $candidate + * @param string $target + * + * @return bool + */ + public static function fulfills($candidate, $target) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..10cdcd9af52ac77657de14613a88009045fe3b2f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php @@ -0,0 +1,543 @@ + + * Dariusz Rumiń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\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gert de Pagter + */ +final class PhpUnitTestAnnotationFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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() + { + return 10; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_CLASS, T_FUNCTION]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + if ('annotation' === $this->configuration['style']) { + $this->applyTestAnnotation($tokens, $indexes[0], $indexes[1]); + } else { + $this->applyTestPrefix($tokens, $indexes[0], $indexes[1]); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('style', 'Whether to use the @test annotation or not.')) + ->setAllowedValues(['prefix', 'annotation']) + ->setDefault('prefix') + ->getOption(), + (new FixerOptionBuilder('case', 'Whether to camel or snake case when adding the test prefix')) + ->setAllowedValues(['camel', 'snake']) + ->setDefault('camel') + ->setDeprecationMessage('Use `php_unit_method_casing` fixer instead.') + ->getOption(), + ]); + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function applyTestAnnotation(Tokens $tokens, $startIndex, $endIndex) + { + 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)) { + $newFunctionName = $this->removeTestPrefix($functionName); + $tokens[$functionNameIndex] = new Token([T_STRING, $newFunctionName]); + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $i); + + // Create a new docblock if it didn't have one before; + if (!$this->hasDocBlock($tokens, $i)) { + $this->createDocBlock($tokens, $docBlockIndex); + + continue; + } + $lines = $this->updateDocBlock($tokens, $docBlockIndex); + + $lines = $this->addTestAnnotation($lines, $tokens, $docBlockIndex); + + $lines = implode('', $lines); + $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); + } + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function applyTestPrefix(Tokens $tokens, $startIndex, $endIndex) + { + 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) || !$this->hasDocBlock($tokens, $i)) { + continue; + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $i); + + $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]); + } + } + + /** + * @param int$index + * + * @return bool + */ + private function isTestMethod(Tokens $tokens, $index) + { + // 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 its a test + $functionNameIndex = $tokens->getNextMeaningfulToken($index); + $functionName = $tokens[$functionNameIndex]->getContent(); + + if ($this->startsWith('test', $functionName)) { + return true; + } + // If the function doesn't have test in its name, and no doc block, its not a test + if (!$this->hasDocBlock($tokens, $index)) { + return false; + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + $doc = $tokens[$docBlockIndex]->getContent(); + if (false === strpos($doc, '@test')) { + return false; + } + + return true; + } + + /** + * @param int $index + * + * @return bool + */ + private function isMethod(Tokens $tokens, $index) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + return $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index); + } + + /** + * @param string $needle + * @param string $haystack + * + * @return bool + */ + private function startsWith($needle, $haystack) + { + $len = \strlen($needle); + + return substr($haystack, 0, $len) === $needle; + } + + /** + * @param int $index + * + * @return bool + */ + private function hasDocBlock(Tokens $tokens, $index) + { + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + return $tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT); + } + + /** + * @param int $index + * + * @return int + */ + private function getDocBlockIndex(Tokens $tokens, $index) + { + do { + $index = $tokens->getPrevNonWhitespace($index); + } while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT])); + + return $index; + } + + /** + * @param string $functionName + * + * @return bool + */ + private function hasTestPrefix($functionName) + { + if (!$this->startsWith('test', $functionName)) { + return false; + } + + if ('test' === $functionName) { + return true; + } + + $nextCharacter = $functionName[4]; + + return $nextCharacter === strtoupper($nextCharacter); + } + + /** + * @param string $functionName + * + * @return string + */ + private function removeTestPrefix($functionName) + { + $remainder = Preg::replace('/^test_?/', '', $functionName); + + if ('' === $remainder || is_numeric($remainder[0])) { + return $functionName; + } + + return lcfirst($remainder); + } + + /** + * @param string $functionName + * + * @return string + */ + private function addTestPrefix($functionName) + { + if ('camel' !== $this->configuration['case']) { + return 'test_'.$functionName; + } + + return'test'.ucfirst($functionName); + } + + /** + * @param int $index + * + * @return string + */ + private function detectIndent(Tokens $tokens, $index) + { + if (!$tokens[$index - 1]->isWhitespace()) { + return ''; // cannot detect indent + } + + $explodedContent = explode($this->whitespacesConfig->getLineEnding(), $tokens[$index - 1]->getContent()); + + return end($explodedContent); + } + + /** + * @param int $docBlockIndex + */ + private function createDocBlock(Tokens $tokens, $docBlockIndex) + { + $lineEnd = $this->whitespacesConfig->getLineEnding(); + $originalIndent = $this->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); + } + + /** + * @param int $docBlockIndex + * + * @return Line[] + */ + private function updateDocBlock(Tokens $tokens, $docBlockIndex) + { + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + $lines = $doc->getLines(); + + return $this->updateLines($lines, $tokens, $docBlockIndex); + } + + /** + * @param Line[] $lines + * @param int $docBlockIndex + * + * @return Line[] + */ + private function updateLines($lines, Tokens $tokens, $docBlockIndex) + { + $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 && + false !== strpos($lines[$i]->getContent(), ' @test') && + false === strpos($lines[$i]->getContent(), '@testWith') && + false === strpos($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 (false === strpos($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 + * @param int $docBlockIndex + * + * @return Line[] + */ + private function splitUpDocBlock($lines, Tokens $tokens, $docBlockIndex) + { + $lineContent = $this->getSingleLineDocBlockEntry($lines); + $lineEnd = $this->whitespacesConfig->getLineEnding(); + $originalIndent = $this->detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); + + return [ + new Line('/**'.$lineEnd), + new Line($originalIndent.' * '.$lineContent.$lineEnd), + new Line($originalIndent.' */'), + ]; + } + + /** + * @param Line []$line + * + * @return string + */ + private function getSingleLineDocBlockEntry($line) + { + $line = $line[0]; + $line = str_replace('*/', '', $line); + $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. + * + * @return Line + */ + private function updateDependsAnnotation(Line $line) + { + if ('annotation' === $this->configuration['style']) { + return $this->removeTestPrefixFromDependsAnnotation($line); + } + + return $this->addTestPrefixToDependsAnnotation($line); + } + + /** + * @return Line + */ + private function removeTestPrefixFromDependsAnnotation(Line $line) + { + $line = str_split($line->getContent()); + + $dependsIndex = $this->findWhereDependsFunctionNameStarts($line); + $dependsFunctionName = implode('', \array_slice($line, $dependsIndex)); + + if ($this->startsWith('test', $dependsFunctionName)) { + $dependsFunctionName = $this->removeTestPrefix($dependsFunctionName); + } + array_splice($line, $dependsIndex); + + return new Line(implode('', $line).$dependsFunctionName); + } + + /** + * @return Line + */ + private function addTestPrefixToDependsAnnotation(Line $line) + { + $line = str_split($line->getContent()); + $dependsIndex = $this->findWhereDependsFunctionNameStarts($line); + $dependsFunctionName = implode('', \array_slice($line, $dependsIndex)); + + if (!$this->startsWith('test', $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. + * + * @return int + */ + private function findWhereDependsFunctionNameStarts(array $line) + { + $counter = \count($line); + + do { + --$counter; + } while (' ' !== $line[$counter]); + + return $counter + 1; + } + + /** + * @param Line[] $lines + * @param int $docBlockIndex + * + * @return Line[] + */ + private function addTestAnnotation($lines, Tokens $tokens, $docBlockIndex) + { + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + + if (!$this->doesDocBlockContainTest($doc)) { + $originalIndent = $this->detectIndent($tokens, $docBlockIndex); + $lineEnd = $this->whitespacesConfig->getLineEnding(); + + array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @test'.$lineEnd); + } + + return $lines; + } + + /** + * @return bool + */ + private function doesDocBlockContainTest(DocBlock $doc) + { + return !empty($doc->getAnnotationsOfType('test')); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b05ef27b5734aeb94af238aa7ee93c5c404e8855 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php @@ -0,0 +1,473 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Filippo Tessarotto + */ +final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * @internal + */ + const CALL_TYPE_THIS = 'this'; + + /** + * @internal + */ + const CALL_TYPE_SELF = 'self'; + + /** + * @internal + */ + const CALL_TYPE_STATIC = 'static'; + + private $allowedValues = [ + self::CALL_TYPE_THIS => true, + self::CALL_TYPE_SELF => true, + self::CALL_TYPE_STATIC => true, + ]; + + private $staticMethods = [ + // Assert methods + 'anything' => true, + 'arrayHasKey' => true, + 'assertArrayHasKey' => 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, + 'assertDirectoryExists' => true, + 'assertDirectoryIsReadable' => true, + 'assertDirectoryIsWritable' => true, + 'assertDirectoryNotExists' => true, + 'assertDirectoryNotIsReadable' => true, + 'assertDirectoryNotIsWritable' => true, + 'assertEmpty' => true, + 'assertEqualXMLStructure' => true, + 'assertEquals' => true, + 'assertEqualsCanonicalizing' => true, + 'assertEqualsIgnoringCase' => true, + 'assertEqualsWithDelta' => true, + 'assertFalse' => true, + 'assertFileEquals' => true, + 'assertFileExists' => true, + 'assertFileIsReadable' => true, + 'assertFileIsWritable' => true, + 'assertFileNotEquals' => 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, + 'assertIsFloat' => true, + 'assertIsInt' => true, + 'assertIsIterable' => true, + 'assertIsNotArray' => true, + 'assertIsNotBool' => true, + 'assertIsNotCallable' => true, + 'assertIsNotFloat' => true, + 'assertIsNotInt' => true, + 'assertIsNotIterable' => true, + 'assertIsNotNumeric' => true, + 'assertIsNotObject' => true, + 'assertIsNotResource' => true, + 'assertIsNotScalar' => true, + 'assertIsNotString' => 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, + '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, + 'assertObjectHasAttribute' => true, + 'assertObjectNotHasAttribute' => true, + 'assertRegExp' => true, + 'assertSame' => true, + 'assertSameSize' => true, + 'assertStringContainsString' => true, + 'assertStringContainsStringIgnoringCase' => true, + 'assertStringEndsNotWith' => true, + 'assertStringEndsWith' => true, + 'assertStringEqualsFile' => true, + 'assertStringMatchesFormat' => true, + 'assertStringMatchesFormatFile' => true, + 'assertStringNotContainsString' => true, + 'assertStringNotContainsStringIgnoringCase' => true, + 'assertStringNotEqualsFile' => 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, + 'containsOnly' => true, + 'containsOnlyInstancesOf' => true, + 'countOf' => true, + 'directoryExists' => true, + 'equalTo' => 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, + '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, + 'objectHasAttribute' => true, + 'readAttribute' => true, + 'resetCount' => true, + 'stringContains' => true, + 'stringEndsWith' => true, + 'stringStartsWith' => true, + + // TestCase methods + 'any' => true, + 'at' => true, + 'atLeast' => true, + 'atLeastOnce' => true, + 'atMost' => true, + 'exactly' => true, + 'never' => true, + 'onConsecutiveCalls' => true, + 'once' => true, + 'returnArgument' => true, + 'returnCallback' => true, + 'returnSelf' => true, + 'returnValue' => true, + 'returnValueMap' => true, + 'setUpBeforeClass' => true, + 'tearDownAfterClass' => true, + 'throwException' => true, + ]; + + private $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']], + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Calls to `PHPUnit\Framework\TestCase` static methods must all be of the same type, either `$this->`, `self::` or `static::`.', + [ + new CodeSample( + 'assertSame(1, 2); + self::assertSame(1, 2); + static::assertSame(1, 2); + } +} +' + ), + ], + null, + 'Risky when PHPUnit methods are overridden or not accessible, or when project has PHPUnit incompatibilities.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before FinalStaticAccessFixer, SelfStaticAccessorFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_CLASS, T_STRING]); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $this->fixPhpUnitClass($tokens, $indexes[0], $indexes[1]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $thisFixer = $this; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('call_type', 'The call type to use for referring to PHPUnit methods.')) + ->setAllowedTypes(['string']) + ->setAllowedValues(array_keys($this->allowedValues)) + ->setDefault('static') + ->getOption(), + (new FixerOptionBuilder('methods', 'Dictionary of `method` => `call_type` values that differ from the default strategy.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function ($option) use ($thisFixer) { + foreach ($option as $method => $value) { + if (!isset($thisFixer->staticMethods[$method])) { + throw new InvalidOptionsException( + sprintf( + 'Unexpected "methods" key, expected any of "%s", got "%s".', + implode('", "', array_keys($thisFixer->staticMethods)), + \is_object($method) ? \get_class($method) : \gettype($method).'#'.$method + ) + ); + } + + if (!isset($thisFixer->allowedValues[$value])) { + throw new InvalidOptionsException( + sprintf( + 'Unexpected value for method "%s", expected any of "%s", got "%s".', + $method, + implode('", "', array_keys($thisFixer->allowedValues)), + \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value) + ) + ); + } + } + + return true; + }]) + ->setDefault([]) + ->getOption(), + ]); + } + + /** + * @param int $startIndex + * @param int $endIndex + */ + private function fixPhpUnitClass(Tokens $tokens, $startIndex, $endIndex) + { + $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($this->staticMethods[$tokens[$index]->getContent()])) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + if (!$tokens[$nextIndex]->equals('(')) { + $index = $nextIndex; + + 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]); + } + } + + /** + * @param int $index + * @param int $referenceIndex + * @param string $callType + * + * @return bool + */ + private function needsConversion(Tokens $tokens, $index, $referenceIndex, $callType) + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + return $functionsAnalyzer->isTheSameClassCall($tokens, $index) + && !$tokens[$referenceIndex]->equals($this->conversionMap[$callType][1], false); + } + + /** + * @param int $index + * + * @return int + */ + private function findEndOfNextBlock(Tokens $tokens, $index) + { + $index = $tokens->getNextTokenOfKind($index, ['{']); + + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..8ca4c9f576d0344af26c5f20384f069b907a8d1d --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php @@ -0,0 +1,150 @@ + + * Dariusz Rumiń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\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitTestClassRequiresCoversFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Adds a default `@coversNothing` annotation to PHPUnit test classes that have no `@covers*` annotation.', + [ + new CodeSample( + 'assertSame(a(), b()); + } +} +' + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_CLASS); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $this->addRequiresCover($tokens, $indexes[0]); + } + } + + private function addRequiresCover(Tokens $tokens, $startIndex) + { + $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); + $prevIndex = $tokens->getPrevMeaningfulToken($classIndex); + + // don't add `@covers` annotation for abstract base classes + if ($tokens[$prevIndex]->isGivenKind(T_ABSTRACT)) { + return; + } + + $index = $tokens[$prevIndex]->isGivenKind(T_FINAL) ? $prevIndex : $classIndex; + + $indent = $tokens[$index - 1]->isGivenKind(T_WHITESPACE) + ? Preg::replace('/^.*\R*/', '', $tokens[$index - 1]->getContent()) + : ''; + + $prevIndex = $tokens->getPrevNonWhitespace($index); + + if ($tokens[$prevIndex]->isGivenKind(T_DOC_COMMENT)) { + $docIndex = $prevIndex; + $docContent = $tokens[$docIndex]->getContent(); + + // ignore one-line phpdocs like `/** foo */`, as there is no place to put new annotations + if (false === strpos($docContent, "\n")) { + return; + } + + $doc = new DocBlock($docContent); + + // skip if already has annotation + if (!empty($doc->getAnnotationsOfType([ + 'covers', + 'coversDefaultClass', + 'coversNothing', + ]))) { + return; + } + } else { + $docIndex = $index; + $tokens->insertAt($docIndex, [ + new Token([T_DOC_COMMENT, sprintf('/**%s%s */', $this->whitespacesConfig->getLineEnding(), $indent)]), + new Token([T_WHITESPACE, sprintf('%s%s', $this->whitespacesConfig->getLineEnding(), $indent)]), + ]); + + if (!$tokens[$docIndex - 1]->isGivenKind(T_WHITESPACE)) { + $extraNewLines = $this->whitespacesConfig->getLineEnding(); + + if (!$tokens[$docIndex - 1]->isGivenKind(T_OPEN_TAG)) { + $extraNewLines .= $this->whitespacesConfig->getLineEnding(); + } + + $tokens->insertAt($docIndex, [ + new Token([T_WHITESPACE, $extraNewLines.$indent]), + ]); + ++$docIndex; + } + + $doc = new DocBlock($tokens[$docIndex]->getContent()); + } + + $lines = $doc->getLines(); + array_splice( + $lines, + \count($lines) - 1, + 0, + [ + new Line(sprintf( + '%s * @coversNothing%s', + $indent, + $this->whitespacesConfig->getLineEnding() + )), + ] + ); + + $tokens[$docIndex] = new Token([T_DOC_COMMENT, implode('', $lines)]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..3ac63a8d5a430371e2cb8212ace8e0ba36d311cb --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.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\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + * @author Julien Falque + */ +final class AlignMultilineCommentFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + private $tokenKinds; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->tokenKinds = [T_DOC_COMMENT]; + if ('phpdocs_only' !== $this->configuration['comment_type']) { + $this->tokenKinds[] = T_COMMENT; + } + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 after ArrayIndentationFixer. + */ + public function getPriority() + { + return -40; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound($this->tokenKinds); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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 (1 !== Preg::match('/\R(\h*)$/', $whitespace, $matches)) { + continue; + } + + if ($token->isGivenKind(T_COMMENT) && 'all_multiline' !== $this->configuration['comment_type'] && 1 === 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)]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..979227cc87809eabb7338df655e067eaba334390 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.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\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + * @author Dariusz Rumiński + */ +final class GeneralPhpdocAnnotationRemoveFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Configured annotations should be omitted from PHPDoc.', + [ + new CodeSample( + ' ['author']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoEmptyPhpdocFixer, PhpdocAlignFixer, PhpdocLineSpanFixer, PhpdocSeparationFixer, PhpdocTrimFixer. + * Must run after CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority() + { + return 10; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + if (!\count($this->configuration['annotations'])) { + return; + } + + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $annotations = $doc->getAnnotationsOfType($this->configuration['annotations']); + + // nothing to do if there are no annotations + if (empty($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + $annotation->remove(); + } + + if ('' === $doc->getContent()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } else { + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolverRootless('annotations', [ + (new FixerOptionBuilder('annotations', 'List of annotations to remove, e.g. `["author"]`.')) + ->setAllowedTypes(['array']) + ->setDefault([]) + ->getOption(), + ], $this->getName()); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..451f8e1e5517c2657fce8172eb1845d88fbe4417 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php @@ -0,0 +1,113 @@ + + * Dariusz Rumiń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\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class NoBlankLinesAfterPhpdocFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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. + * + * @param int $index + */ + private function fixWhitespace(Tokens $tokens, $index) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..9545ff437f25504ef3c71f30b524d7433ab2ec96 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.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\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class NoEmptyPhpdocFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'There should not be empty PHPDoc blocks.', + [new CodeSample("isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + if (Preg::match('#^/\*\*[\s\*]*\*/$#', $token->getContent())) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..74b25798c5171b54fa70af984e9981d0e065dce3 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php @@ -0,0 +1,506 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoSuperfluousPhpdocTagsFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Removes `@param`, `@return` and `@var` tags that don\'t provide any useful information.', + [ + new CodeSample(' true]), + new VersionSpecificCodeSample(' true]), + new CodeSample(' true]), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoEmptyPhpdocFixer, PhpdocAlignFixer. + * Must run after CommentToPhpdocFixer, FullyQualifiedStrictTypesFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocIndentFixer, PhpdocReturnSelfReferenceFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToReturnTypeFixer, PhpdocTypesFixer. + */ + public function getPriority() + { + return 6; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $namespaceUseAnalyzer = new NamespaceUsesAnalyzer(); + + $shortNames = []; + foreach ($namespaceUseAnalyzer->getDeclarationsFromTokens($tokens) as $namespaceUseAnalysis) { + $shortNames[strtolower($namespaceUseAnalysis->getShortName())] = '\\'.strtolower($namespaceUseAnalysis->getFullName()); + } + + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $content = $initialContent = $token->getContent(); + + $documentedElementIndex = $this->findDocumentedElement($tokens, $index); + + if (null === $documentedElementIndex) { + continue; + } + + $token = $tokens[$documentedElementIndex]; + + if ($token->isGivenKind(T_FUNCTION)) { + $content = $this->fixFunctionDocComment($content, $tokens, $index, $shortNames); + } elseif ($token->isGivenKind(T_VARIABLE)) { + $content = $this->fixPropertyDocComment($content, $tokens, $index, $shortNames); + } + + if ($this->configuration['remove_inheritdoc']) { + $content = $this->removeSuperfluousInheritDoc($content); + } + + if ($content !== $initialContent) { + $tokens[$index] = new Token([T_DOC_COMMENT, $content]); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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_unused_params', 'Whether `param` annotation without actual signature is allowed (`true`) or considered superfluous (`false`)')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * @param int $docCommentIndex + * + * @return null|int + */ + private function findDocumentedElement(Tokens $tokens, $docCommentIndex) + { + $index = $docCommentIndex; + + do { + $index = $tokens->getNextMeaningfulToken($index); + + if (null === $index || $tokens[$index]->isGivenKind([T_FUNCTION, T_CLASS, T_INTERFACE])) { + return $index; + } + } while ($tokens[$index]->isGivenKind([T_ABSTRACT, T_FINAL, T_STATIC, T_PRIVATE, T_PROTECTED, T_PUBLIC])); + + $index = $tokens->getNextMeaningfulToken($docCommentIndex); + + $kindsBeforeProperty = [T_STATIC, T_PRIVATE, T_PROTECTED, T_PUBLIC, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, T_STRING, T_NS_SEPARATOR]; + + if (!$tokens[$index]->isGivenKind($kindsBeforeProperty)) { + return null; + } + + do { + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(T_VARIABLE)) { + return $index; + } + } while ($tokens[$index]->isGivenKind($kindsBeforeProperty)); + + return null; + } + + /** + * @param string $content + * @param int $functionIndex + * + * @return string + */ + private function fixFunctionDocComment($content, Tokens $tokens, $functionIndex, array $shortNames) + { + $docBlock = new DocBlock($content); + + $openingParenthesisIndex = $tokens->getNextTokenOfKind($functionIndex, ['(']); + $closingParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesisIndex); + + $argumentsInfo = $this->getArgumentsInfo( + $tokens, + $openingParenthesisIndex + 1, + $closingParenthesisIndex - 1 + ); + + foreach ($docBlock->getAnnotationsOfType('param') as $annotation) { + if (0 === Preg::match('/@param(?:\s+[^\$]\S+)?\s+(\$\S+)/', $annotation->getContent(), $matches)) { + continue; + } + + $argumentName = $matches[1]; + + if (!isset($argumentsInfo[$argumentName]) && $this->configuration['allow_unused_params']) { + continue; + } + + if (!isset($argumentsInfo[$argumentName]) || $this->annotationIsSuperfluous($annotation, $argumentsInfo[$argumentName], $shortNames)) { + $annotation->remove(); + } + } + + $returnTypeInfo = $this->getReturnTypeInfo($tokens, $closingParenthesisIndex); + + foreach ($docBlock->getAnnotationsOfType('return') as $annotation) { + if ($this->annotationIsSuperfluous($annotation, $returnTypeInfo, $shortNames)) { + $annotation->remove(); + } + } + + return $docBlock->getContent(); + } + + /** + * @param string $content + * @param int $index Index of the DocComment token + * + * @return string + */ + private function fixPropertyDocComment($content, Tokens $tokens, $index, array $shortNames) + { + $docBlock = new DocBlock($content); + + do { + $index = $tokens->getNextMeaningfulToken($index); + } while ($tokens[$index]->isGivenKind([T_STATIC, T_PRIVATE, T_PROTECTED, T_PUBLIC])); + + $propertyTypeInfo = $this->getPropertyTypeInfo($tokens, $index); + + foreach ($docBlock->getAnnotationsOfType('var') as $annotation) { + if ($this->annotationIsSuperfluous($annotation, $propertyTypeInfo, $shortNames)) { + $annotation->remove(); + } + } + + return $docBlock->getContent(); + } + + /** + * @param int $start + * @param int $end + * + * @return array + */ + private function getArgumentsInfo(Tokens $tokens, $start, $end) + { + $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 = [ + 'type' => null, + 'allows_null' => true, + ]; + } + + if (!$info['allows_null']) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + if ( + $tokens[$nextIndex]->equals('=') + && $tokens[$tokens->getNextMeaningfulToken($nextIndex)]->equals([T_STRING, 'null']) + ) { + $info['allows_null'] = true; + } + } + + $argumentsInfo[$token->getContent()] = $info; + } + + return $argumentsInfo; + } + + private function getReturnTypeInfo(Tokens $tokens, $closingParenthesisIndex) + { + $colonIndex = $tokens->getNextMeaningfulToken($closingParenthesisIndex); + if ($tokens[$colonIndex]->isGivenKind(CT::T_TYPE_COLON)) { + return $this->parseTypeHint($tokens, $tokens->getNextMeaningfulToken($colonIndex)); + } + + return [ + 'type' => null, + 'allows_null' => true, + ]; + } + + /** + * @param int $index The index of the first token of the type hint + * + * @return array + */ + private function getPropertyTypeInfo(Tokens $tokens, $index) + { + if ($tokens[$index]->isGivenKind(T_VARIABLE)) { + return [ + 'type' => null, + 'allows_null' => true, + ]; + } + + return $this->parseTypeHint($tokens, $index); + } + + /** + * @param int $index The index of the first token of the type hint + * + * @return array + */ + private function parseTypeHint(Tokens $tokens, $index) + { + $allowsNull = false; + if ($tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) { + $allowsNull = true; + $index = $tokens->getNextMeaningfulToken($index); + } + + $type = ''; + while ($tokens[$index]->isGivenKind([T_NS_SEPARATOR, T_STRING, CT::T_ARRAY_TYPEHINT, T_CALLABLE])) { + $type .= $tokens[$index]->getContent(); + + $index = $tokens->getNextMeaningfulToken($index); + } + + return [ + 'type' => '' === $type ? null : $type, + 'allows_null' => $allowsNull, + ]; + } + + /** + * @param array $symbolShortNames + * + * @return bool + */ + private function annotationIsSuperfluous(Annotation $annotation, array $info, array $symbolShortNames) + { + if ('param' === $annotation->getTag()->getName()) { + $regex = '/@param\s+(?:\S|\s(?!\$))++\s\$\S+\s+\S/'; + } elseif ('var' === $annotation->getTag()->getName()) { + $regex = '/@var\s+\S+(\s+\$\S+)?(\s+)([^$\s]+)/'; + } else { + $regex = '/@return\s+\S+\s+\S/'; + } + + if (Preg::match($regex, $annotation->getContent())) { + return false; + } + + $annotationTypes = $this->toComparableNames($annotation->getTypes(), $symbolShortNames); + + if (['null'] === $annotationTypes) { + return false; + } + + if (['mixed'] === $annotationTypes && null === $info['type']) { + return !$this->configuration['allow_mixed']; + } + + $actualTypes = null === $info['type'] ? [] : [$info['type']]; + if ($info['allows_null']) { + $actualTypes[] = 'null'; + } + + return $annotationTypes === $this->toComparableNames($actualTypes, $symbolShortNames); + } + + /** + * Normalizes types to make them comparable. + * + * Converts given types to lowercase, replaces imports aliases with + * their matching FQCN, and finally sorts the result. + * + * @param string[] $types The types to normalize + * @param array $symbolShortNames The imports aliases + * + * @return array The normalized types + */ + private function toComparableNames(array $types, array $symbolShortNames) + { + $normalized = array_map( + static function ($type) use ($symbolShortNames) { + $type = strtolower($type); + + if (isset($symbolShortNames[$type])) { + return $symbolShortNames[$type]; + } + + return $type; + }, + $types + ); + + sort($normalized); + + return $normalized; + } + + /** + * @param string $docComment + * + * @return string + */ + private function removeSuperfluousInheritDoc($docComment) + { + 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); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..3ed1b325a686b589639bf0447359f99b1504783d --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.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\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpdocAddMissingParamAnnotationFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHPDoc should contain `@param` for all params.', + [ + new CodeSample( + ' true] + ), + new CodeSample( + ' false] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer, PhpdocAlignFixer, PhpdocOrderFixer. + * Must run after CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocNoAliasTagFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority() + { + return 10; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return false; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { + $mainIndex = $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 (false === strpos($tokenContent, "\n")) { + continue; + } + + $index = $tokens->getNextMeaningfulToken($index); + + if (null === $index) { + return; + } + + while ($tokens[$index]->isGivenKind([ + T_ABSTRACT, + T_FINAL, + T_PRIVATE, + T_PROTECTED, + T_PUBLIC, + T_STATIC, + T_VAR, + ])) { + $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 (!$this->configuration['only_untyped'] || '' === $argumentInfo['type']) { + $arguments[$argumentInfo['name']] = $argumentInfo; + } + } + + if (!\count($arguments)) { + continue; + } + + $doc = new DocBlock($tokenContent); + $lastParamLine = null; + + foreach ($doc->getAnnotationsOfType('param') as $annotation) { + $pregMatched = Preg::match('/^[^$]+(\$\w+).*$/s', $annotation->getContent(), $matches); + + if (1 === $pregMatched) { + unset($arguments[$matches[1]]); + } + + $lastParamLine = max($lastParamLine, $annotation->getEnd()); + } + + if (!\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'] ?: 'mixed'; + + if ('?' !== $type[0] && '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 ? $lastParamLine + 1 : $linesCount - 1, + 0, + $newLines + ); + + $tokens[$mainIndex] = new Token([T_DOC_COMMENT, implode('', $lines)]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('only_untyped', 'Whether to add missing `@param` annotations for untyped parameters only.')) + ->setDefault(true) + ->setAllowedTypes(['bool']) + ->getOption(), + ]); + } + + /** + * @param int $start + * @param int $end + * + * @return array + */ + private function prepareArgumentInformation(Tokens $tokens, $start, $end) + { + $info = [ + 'default' => '', + 'name' => '', + 'type' => '', + ]; + + $sawName = false; + + for ($index = $start; $index <= $end; ++$index) { + $token = $tokens[$index]; + + if ($token->isComment() || $token->isWhitespace()) { + 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->getContent()) { + if ($token->isGivenKind(T_ELLIPSIS)) { + if ('' === $info['type']) { + $info['type'] = 'array'; + } else { + $info['type'] .= '[]'; + } + } else { + $info['type'] .= $token->getContent(); + } + } + } + + return $info; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b814a12c0fdf2bb66def7bcefbf97d03981ae4c7 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php @@ -0,0 +1,434 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Fabien Potencier + * @author Jordi Boggiano + * @author Sebastiaan Stok + * @author Graham Campbell + * @author Dariusz Rumiński + */ +final class PhpdocAlignFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @internal + */ + const ALIGN_LEFT = 'left'; + + /** + * @internal + */ + const ALIGN_VERTICAL = 'vertical'; + + /** + * @var string + */ + private $regex; + + /** + * @var string + */ + private $regexCommentLine; + + /** + * @var string + */ + private $align; + + private static $alignableTags = [ + 'param', + 'property', + 'property-read', + 'property-write', + 'return', + 'throws', + 'type', + 'var', + 'method', + ]; + + private static $tagsWithName = [ + 'param', + 'property', + ]; + + private static $tagsWithMethodSignature = [ + 'method', + ]; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $tagsWithNameToAlign = array_intersect($this->configuration['tags'], self::$tagsWithName); + $tagsWithMethodSignatureToAlign = array_intersect($this->configuration['tags'], self::$tagsWithMethodSignature); + $tagsWithoutNameToAlign = array_diff($this->configuration['tags'], $tagsWithNameToAlign, $tagsWithMethodSignatureToAlign); + $types = []; + + $indent = '(?P(?: {2}|\t)*)'; + // e.g. @param <$var> + if (!empty($tagsWithNameToAlign)) { + $types[] = '(?P'.implode('|', $tagsWithNameToAlign).')\s+(?P[^$]+?)\s+(?P(?:&|\.{3})?\$[^\s]+)'; + } + + // e.g. @return + if (!empty($tagsWithoutNameToAlign)) { + $types[] = '(?P'.implode('|', $tagsWithoutNameToAlign).')\s+(?P[^\s]+?)'; + } + + // e.g. @method + if (!empty($tagsWithMethodSignatureToAlign)) { + $types[] = '(?P'.implode('|', $tagsWithMethodSignatureToAlign).')(\s+(?P[^\s(]+)|)\s+(?P.+\))'; + } + + // optional + $desc = '(?:\s+(?P\V*))'; + + $this->regex = '/^'.$indent.' \* @(?:'.implode('|', $types).')'.$desc.'\s*$/u'; + $this->regexCommentLine = '/^'.$indent.' \*(?! @)(?:\s+(?P\V+))(?align = $this->configuration['align']; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + $code = <<<'EOF' + self::ALIGN_VERTICAL]), + new CodeSample($code, ['align' => self::ALIGN_LEFT]), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after CommentToPhpdocFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAnnotationWithoutDotFixer, PhpdocIndentFixer, PhpdocIndentFixer, PhpdocInlineTagFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocScalarFixer, PhpdocScalarFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocToCommentFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesFixer, PhpdocTypesFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + */ + public function getPriority() + { + /* + * 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 -21; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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]); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $tags = new FixerOptionBuilder('tags', 'The tags that should be aligned.'); + $tags + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(self::$alignableTags)]) + /* + * By default, all tags apart from @property and @method will be aligned for backwards compatibility + * @TODO 3.0 Align all available tags by default + */ + ->setDefault([ + 'param', + 'return', + 'throws', + 'type', + 'var', + ]) + ; + + $align = new FixerOptionBuilder('align', 'Align comments'); + $align + ->setAllowedTypes(['string']) + ->setAllowedValues([self::ALIGN_LEFT, self::ALIGN_VERTICAL]) + ->setDefault(self::ALIGN_VERTICAL) + ; + + return new FixerConfigurationResolver([$tags->getOption(), $align->getOption()]); + } + + private function fixDocBlock(DocBlock $docBlock) + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + for ($i = 0, $l = \count($docBlock->getLines()); $i < $l; ++$i) { + $items = []; + $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 + $tagMax = 0; + $hintMax = 0; + $varMax = 0; + + foreach ($items as $item) { + if (null === $item['tag']) { + continue; + } + + $tagMax = max($tagMax, \strlen($item['tag'])); + $hintMax = max($hintMax, \strlen($item['hint'])); + $varMax = max($varMax, \strlen($item['var'])); + } + + $currTag = null; + + // update + foreach ($items as $j => $item) { + if (null === $item['tag']) { + if ('@' === $item['desc'][0]) { + $docBlock->getLine($current + $j)->setContent($item['indent'].' * '.$item['desc'].$lineEnding); + + continue; + } + + $extraIndent = 2; + + if (\in_array($currTag, self::$tagsWithName, true) || \in_array($currTag, self::$tagsWithMethodSignature, true)) { + $extraIndent = 3; + } + + $line = + $item['indent'] + .' * ' + .$this->getIndent( + $tagMax + $hintMax + $varMax + $extraIndent, + $this->getLeftAlignedDescriptionIndent($items, $j) + ) + .$item['desc'] + .$lineEnding; + + $docBlock->getLine($current + $j)->setContent($line); + + continue; + } + + $currTag = $item['tag']; + + $line = + $item['indent'] + .' * @' + .$item['tag'] + .$this->getIndent( + $tagMax - \strlen($item['tag']) + 1, + $item['hint'] ? 1 : 0 + ) + .$item['hint'] + ; + + if (!empty($item['var'])) { + $line .= + $this->getIndent(($hintMax ?: -1) - \strlen($item['hint']) + 1) + .$item['var'] + .( + !empty($item['desc']) + ? $this->getIndent($varMax - \strlen($item['var']) + 1).$item['desc'].$lineEnding + : $lineEnding + ) + ; + } elseif (!empty($item['desc'])) { + $line .= $this->getIndent($hintMax - \strlen($item['hint']) + 1).$item['desc'].$lineEnding; + } else { + $line .= $lineEnding; + } + + $docBlock->getLine($current + $j)->setContent($line); + } + } + } + + /** + * @param string $line + * @param bool $matchCommentOnly + * + * @return null|array + */ + private function getMatches($line, $matchCommentOnly = false) + { + if (Preg::match($this->regex, $line, $matches)) { + if (!empty($matches['tag2'])) { + $matches['tag'] = $matches['tag2']; + $matches['hint'] = $matches['hint2']; + $matches['var'] = ''; + } + + if (!empty($matches['tag3'])) { + $matches['tag'] = $matches['tag3']; + $matches['hint'] = $matches['hint3']; + $matches['var'] = $matches['signature']; + } + + if (isset($matches['hint'])) { + $matches['hint'] = trim($matches['hint']); + } + + return $matches; + } + + if ($matchCommentOnly && Preg::match($this->regexCommentLine, $line, $matches)) { + $matches['tag'] = null; + $matches['var'] = ''; + $matches['hint'] = ''; + + return $matches; + } + + return null; + } + + /** + * @param int $verticalAlignIndent + * @param int $leftAlignIndent + * + * @return string + */ + private function getIndent($verticalAlignIndent, $leftAlignIndent = 1) + { + $indent = self::ALIGN_VERTICAL === $this->align ? $verticalAlignIndent : $leftAlignIndent; + + return str_repeat(' ', $indent); + } + + /** + * @param array[] $items + * @param int $index + * + * @return int + */ + private function getLeftAlignedDescriptionIndent(array $items, $index) + { + 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; + } + + // Indent according to existing values: + return + $this->getSentenceIndent($item['tag']) + + $this->getSentenceIndent($item['hint']) + + $this->getSentenceIndent($item['var']); + } + + /** + * Get indent for sentence. + * + * @param null|string $sentence + * + * @return int + */ + private function getSentenceIndent($sentence) + { + if (null === $sentence) { + return 0; + } + + $length = \strlen($sentence); + + return 0 === $length ? 0 : $length + 1; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..77760a42362458563c2e49dba4968d90fba687a9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.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\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpdocAnnotationWithoutDotFixer extends AbstractFixer +{ + private $tags = ['throws', 'return', 'param', 'internal', 'deprecated', 'var', 'type']; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHPDoc annotation descriptions should not be a sentence.', + [new CodeSample('isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $annotations = $doc->getAnnotations(); + + if (empty($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 || '*' !== $lineAfterAnnotationTrimmed[0]) { + // malformed PHPDoc, missing asterisk ! + continue; + } + } + + $content = $annotation->getContent(); + + if ( + 1 !== Preg::match('/[.。]\h*$/u', $content) + || 0 !== 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 function (array $matches) { + return $matches[1].strtolower($matches[2]).$matches[3]; + }, + $startLine->getContent(), + 1 + ); + $startLine->setContent($content); + } + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..00f6c4baa27391ca3909b6b89792471745140f23 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.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\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; + +/** + * @author Ceeram + * @author Graham Campbell + */ +final class PhpdocIndentFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Docblocks should have the same indentation as the documented subject.', + [new CodeSample('isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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; + } + + $indent = ''; + if ($tokens[$nextIndex - 1]->isWhitespace()) { + $indent = Utils::calculateTrailingWhitespaceIndent($tokens[$nextIndex - 1]); + } + + $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($content, $indent) + { + 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($content, $indent) + { + return rtrim($content, " \t").$indent; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..2c9f6159e030298d99d6aade8bec8b6ca2b7ce73 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagFixer.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\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fix inline tags and make inheritdoc tag always inline. + */ +final class PhpdocInlineTagFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Fix PHPDoc inline tags, make `@inheritdoc` always inline.', + [new CodeSample( + 'isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $content = $token->getContent(); + + // Move `@` inside tag, for example @{tag} -> {@tag}, replace multiple curly brackets, + // remove spaces between '{' and '@', remove 's' at the end of tag. + // Make sure the tags are written in lower case, remove white space between end + // of text and closing bracket and between the tag and inline comment. + $content = Preg::replaceCallback( + '#(?:@{+|{+\h*@)[ \t]*(example|id|internal|inheritdoc|link|source|toc|tutorial)s?([^}]*)(?:}+)#i', + static function (array $matches) { + $doc = trim($matches[2]); + + if ('' === $doc) { + return '{@'.strtolower($matches[1]).'}'; + } + + return '{@'.strtolower($matches[1]).' '.$doc.'}'; + }, + $content + ); + + // Always make inheritdoc inline using with '{' '}' when needed, + // make sure lowercase. + $content = 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\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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, ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 PhpdocAlignFixer. + * Must run after CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('const', 'Whether const blocks should be single or multi line')) + ->setAllowedValues(['single', 'multi']) + ->setDefault('multi') + ->getOption(), + (new FixerOptionBuilder('property', 'Whether property doc blocks should be single or multi line')) + ->setAllowedValues(['single', 'multi']) + ->setDefault('multi') + ->getOption(), + (new FixerOptionBuilder('method', 'Whether method doc blocks should be single or multi line')) + ->setAllowedValues(['single', 'multi']) + ->setDefault('multi') + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $analyzer = new TokensAnalyzer($tokens); + + $elements = $analyzer->getClassyElements(); + + foreach ($elements as $index => $element) { + if (!$this->hasDocBlock($tokens, $index)) { + continue; + } + + $type = $element['type']; + $docIndex = $this->getDocBlockIndex($tokens, $index); + $doc = new DocBlock($tokens[$docIndex]->getContent()); + + if ('multi' === $this->configuration[$type]) { + $doc->makeMultiLine($originalIndent = $this->detectIndent($tokens, $docIndex), $this->whitespacesConfig->getLineEnding()); + } else { + $doc->makeSingleLine(); + } + + $tokens->offsetSet($docIndex, new Token([T_DOC_COMMENT, $doc->getContent()])); + } + } + + /** + * @param int $index + * + * @return bool + */ + private function hasDocBlock(Tokens $tokens, $index) + { + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + return $tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT); + } + + /** + * @param int $index + * + * @return int + */ + private function getDocBlockIndex(Tokens $tokens, $index) + { + do { + $index = $tokens->getPrevNonWhitespace($index); + } while ($tokens[$index]->isGivenKind([ + T_PUBLIC, + T_PROTECTED, + T_PRIVATE, + T_FINAL, + T_ABSTRACT, + T_COMMENT, + T_VAR, + T_STATIC, + T_STRING, + T_NS_SEPARATOR, + CT::T_NULLABLE_TYPE, + ])); + + return $index; + } + + /** + * @param int $index + * + * @return string + */ + private function detectIndent(Tokens $tokens, $index) + { + if (!$tokens[$index - 1]->isWhitespace()) { + return ''; // cannot detect indent + } + + $explodedContent = explode($this->whitespacesConfig->getLineEnding(), $tokens[$index - 1]->getContent()); + + return end($explodedContent); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..02f1f67336352f8ebd645b2b2b6cd01664ab7b76 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.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\AbstractProxyFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * @author Graham Campbell + * @author Dariusz Rumiński + */ +final class PhpdocNoAccessFixer extends AbstractProxyFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + '`@access` annotations should be omitted from PHPDoc.', + [ + new CodeSample( + 'configure(['annotations' => ['access']]); + + return [$fixer]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..0e7151f609931b68818457573de1e2db43ffcef6 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.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\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Options; + +/** + * Case sensitive tag replace fixer (does not process inline tags like {@inheritdoc}). + * + * @author Graham Campbell + * @author Dariusz Rumiński + * @author SpacePossum + */ +final class PhpdocNoAliasTagFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'No alias PHPDoc tags should be used.', + [ + new CodeSample( + ' ['link' => 'website']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocSingleLineVarSpacingFixer. + * Must run after CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority() + { + return 11; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $searchFor = array_keys($this->configuration['replacements']); + + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $annotations = $doc->getAnnotationsOfType($searchFor); + + if (empty($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + $annotation->getTag()->setName($this->configuration['replacements'][$annotation->getTag()->getName()]); + } + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolverRootless('replacements', [ + (new FixerOptionBuilder('replacements', 'Mapping between replaced annotations with new ones.')) + ->setAllowedTypes(['array']) + ->setNormalizer(static function (Options $options, $value) { + $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 (1 !== Preg::match('#^\S+$#', $to) || false !== strpos($to, '*/')) { + throw new InvalidOptionsException(sprintf( + 'Tag "%s" cannot be replaced by invalid tag "%s".', + $from, + $to + )); + } + + $normalizedValue[trim($from)] = trim($to); + } + + foreach ($normalizedValue as $from => $to) { + if (isset($normalizedValue[$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([ + 'property-read' => 'property', + 'property-write' => 'property', + 'type' => 'var', + 'link' => 'see', + ]) + ->getOption(), + ], $this->getName()); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..774f8ec8f3eb49a88cac2c819e559500251b82f2 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.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\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class PhpdocNoEmptyReturnFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 (empty($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + $this->fixAnnotation($doc, $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(DocBlock $doc, Annotation $annotation) + { + $types = $annotation->getNormalizedTypes(); + + if (1 === \count($types) && ('null' === $types[0] || 'void' === $types[0])) { + $annotation->remove(); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b04295c95dd954c4f5449bfac27061fa3154489f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.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\AbstractProxyFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * @author Graham Campbell + * @author Dariusz Rumiński + */ +final class PhpdocNoPackageFixer extends AbstractProxyFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + '`@package` and `@subpackage` annotations should be omitted from PHPDoc.', + [ + new CodeSample( + 'configure(['annotations' => ['package', 'subpackage']]); + + return [$fixer]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..cfd67353b3c526b2abe79270f13ae0546a1a815c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php @@ -0,0 +1,190 @@ + + * Dariusz Rumiń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\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Remove inheritdoc tags from classy that does not inherit. + * + * @author SpacePossum + */ +final class PhpdocNoUselessInheritdocFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + // min. offset 4 as minimal candidate is @: isGivenKind([T_CLASS, T_INTERFACE])) { + $index = $this->fixClassy($tokens, $index); + } + } + } + + /** + * @param int $index + * + * @return int + */ + private function fixClassy(Tokens $tokens, $index) + { + // 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; + } + + /** + * @param int $classOpenIndex + * @param int $classEndIndex + * @param bool $fixThisLevel + */ + private function fixClassyInside(Tokens $tokens, $classOpenIndex, $classEndIndex, $fixThisLevel) + { + 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); + } + } + } + + /** + * @param int $classIndex + */ + private function fixClassyOutside(Tokens $tokens, $classIndex) + { + $previousIndex = $tokens->getPrevNonWhitespace($classIndex); + if ($tokens[$previousIndex]->isGivenKind(T_DOC_COMMENT)) { + $this->fixToken($tokens, $previousIndex); + } + } + + /** + * @param int $tokenIndex + */ + private function fixToken(Tokens $tokens, $tokenIndex) + { + $count = 0; + $content = Preg::replaceCallback( + '#(\h*(?:@{*|{*\h*@)\h*inheritdoc\h*)([^}]*)((?:}*)\h*)#i', + static function ($matches) { + return ' '.$matches[2]; + }, + $tokens[$tokenIndex]->getContent(), + -1, + $count + ); + + if ($count) { + $tokens[$tokenIndex] = new Token([T_DOC_COMMENT, $content]); + } + } + + /** + * @param int $classIndex + * @param int $classOpenIndex + * + * @return bool + */ + private function isExtendingOrImplementing(Tokens $tokens, $classIndex, $classOpenIndex) + { + for ($index = $classIndex; $index < $classOpenIndex; ++$index) { + if ($tokens[$index]->isGivenKind([T_EXTENDS, T_IMPLEMENTS])) { + return true; + } + } + + return false; + } + + /** + * @param int $classIndex + * @param int $classOpenIndex + * @param int $classCloseIndex + * + * @return bool + */ + private function isUsingTrait(Tokens $tokens, $classIndex, $classOpenIndex, $classCloseIndex) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..9c718ff4686ead38cd72cb026e561031bd765676 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.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\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class PhpdocOrderFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Annotations in PHPDoc should be ordered so that `@param` annotations come first, then `@throws` annotations, then `@return` annotations.', + [ + new CodeSample( + ' $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $content = $token->getContent(); + // move param to start, return to end, leave throws in the middle + $content = $this->moveParamAnnotations($content); + // we're parsing the content again to make sure the internal + // state of the dockblock is correct after the modifications + $content = $this->moveReturnAnnotations($content); + // persist the content at the end + $tokens[$index] = new Token([T_DOC_COMMENT, $content]); + } + } + + /** + * Move all param annotations in before throws and return annotations. + * + * @param string $content + * + * @return string + */ + private function moveParamAnnotations($content) + { + $doc = new DocBlock($content); + $params = $doc->getAnnotationsOfType('param'); + + // nothing to do if there are no param annotations + if (empty($params)) { + return $content; + } + + $others = $doc->getAnnotationsOfType(['throws', 'return']); + + if (empty($others)) { + return $content; + } + + // get the index of the final line of the final param annotation + $end = end($params)->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 indexes + $line->setContent($line->getContent().$other->getContent()); + $other->remove(); + } + } + + return $doc->getContent(); + } + + /** + * Move all return annotations after param and throws annotations. + * + * @param string $content + * + * @return string + */ + private function moveReturnAnnotations($content) + { + $doc = new DocBlock($content); + $returns = $doc->getAnnotationsOfType('return'); + + // nothing to do if there are no return annotations + if (empty($returns)) { + return $content; + } + + $others = $doc->getAnnotationsOfType(['param', 'throws']); + + // nothing to do if there are no other annotations + if (empty($others)) { + return $content; + } + + // get the index of the first line of the first return annotation + $start = $returns[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 indexes + $line->setContent($other->getContent().$line->getContent()); + $other->remove(); + } + } + + return $doc->getContent(); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..5f6c8a4b0b63ff2ef5fd438ca79253b43f80ea2e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.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\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author SpacePossum + */ +final class PhpdocReturnSelfReferenceFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + private static $toTypes = [ + '$this', + 'static', + 'self', + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'The type of `@return` annotations of methods returning a reference to itself must the configured one.', + [ + new CodeSample( + ' ['this' => 'self']] + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return \count($tokens) > 10 && $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]); + } + + /** + * {@inheritdoc} + * + * Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer. + * Must run after CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority() + { + return 10; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + foreach ($tokensAnalyzer->getClassyElements() as $index => $element) { + if ('method' === $element['type']) { + $this->fixMethod($tokens, $index); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $default = [ + 'this' => '$this', + '@this' => '$this', + '$self' => 'self', + '@self' => 'self', + '$static' => 'static', + '@static' => 'static', + ]; + + return new FixerConfigurationResolverRootless('replacements', [ + (new FixerOptionBuilder('replacements', 'Mapping between replaced return types with new ones.')) + ->setAllowedTypes(['array']) + ->setNormalizer(static function (Options $options, $value) use ($default) { + $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".', + \is_object($from) ? \get_class($from) : \gettype($from).(\is_resource($from) ? '' : '#'.$from), + implode('", "', 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), + implode('", "', self::$toTypes) + )); + } + + $normalizedValue[$from] = $to; + } + + return $normalizedValue; + }) + ->setDefault($default) + ->getOption(), + ], $this->getName()); + } + + /** + * @param int $index + */ + private function fixMethod(Tokens $tokens, $index) + { + static $methodModifiers = [T_STATIC, T_FINAL, T_ABSTRACT, T_PRIVATE, T_PROTECTED, T_PUBLIC]; + + // find PHPDoc of method (if any) + do { + $tokenIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$tokenIndex]->isGivenKind($methodModifiers)) { + break; + } + + $index = $tokenIndex; + } while (true); + + $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 (!\count($returnsBlock)) { + return; // no return annotation found + } + + $returnsBlock = $returnsBlock[0]; + $types = $returnsBlock->getTypes(); + + if (!\count($types)) { + return; // no return type(s) found + } + + $newTypes = []; + foreach ($types as $type) { + $lower = strtolower($type); + $newTypes[] = isset($this->configuration['replacements'][$lower]) ? $this->configuration['replacements'][$lower] : $type; + } + + if ($types === $newTypes) { + return; + } + + $returnsBlock->setTypes($newTypes); + $tokens[$docIndex] = new Token([T_DOC_COMMENT, $docBlock->getContent()]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..5ab9f99d578982d009f177fa360262ccb160648a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.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\Phpdoc; + +use PhpCsFixer\AbstractPhpdocTypesFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * @author Graham Campbell + */ +final class PhpdocScalarFixer extends AbstractPhpdocTypesFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * The types to fix. + * + * @var array + */ + private static $types = [ + 'boolean' => 'bool', + 'callback' => 'callable', + 'double' => 'float', + 'integer' => 'int', + 'real' => 'float', + 'str' => 'string', + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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('setAllowedValues([new AllowedValueSubset(array_keys(self::$types))]) + ->setDefault(['boolean', 'double', 'integer', 'real', 'str']) // TODO 3.0 add "callback" + ->getOption(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function normalize($type) + { + if (\in_array($type, $this->configuration['types'], true)) { + return self::$types[$type]; + } + + return $type; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..2ecb34db283c4db0d017bbe1472f9089838ec6f1 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.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\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\TagComparator; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class PhpdocSeparationFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Annotations in PHPDoc should be grouped together so that annotations of the same type immediately follow each other, and annotations of a different type are separated by a single blank line.', + [ + new CodeSample( + 'isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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()]); + } + } + + /** + * Make sure the description is separated from the annotations. + */ + private function fixDescription(DocBlock $doc) + { + 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. + * + * @return string + */ + private function fixAnnotations(DocBlock $doc) + { + foreach ($doc->getAnnotations() as $index => $annotation) { + $next = $doc->getAnnotation($index + 1); + + if (null === $next) { + break; + } + + if (true === $next->getTag()->valid()) { + if (TagComparator::shouldBeTogether($annotation->getTag(), $next->getTag())) { + $this->ensureAreTogether($doc, $annotation, $next); + } else { + $this->ensureAreSeparate($doc, $annotation, $next); + } + } + } + + return $doc->getContent(); + } + + /** + * Force the given annotations to immediately follow each other. + */ + private function ensureAreTogether(DocBlock $doc, Annotation $first, Annotation $second) + { + $pos = $first->getEnd(); + $final = $second->getStart(); + + for ($pos = $pos + 1; $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) + { + $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 + 1; $pos < $final; ++$pos) { + $doc->getLine($pos)->remove(); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..3f4a34d5f8a4c6a3a8d9d8ffbd6c652c3bc173d8 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.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\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for part of rule defined in PSR5 ¶7.22. + * + * @author SpacePossum + */ +final class PhpdocSingleLineVarSpacingFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Single line `@var` PHPDoc should have proper spacing.', + [new CodeSample("isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + /** @var Token $token */ + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(T_DOC_COMMENT)) { + $tokens[$index] = new Token([T_DOC_COMMENT, $this->fixTokenContent($token->getContent())]); + + continue; + } + + if (!$token->isGivenKind(T_COMMENT)) { + continue; + } + + $content = $token->getContent(); + $fixedContent = $this->fixTokenContent($content); + if ($content !== $fixedContent) { + $tokens[$index] = new Token([T_DOC_COMMENT, $fixedContent]); + } + } + } + + /** + * @param string $content + * + * @return string + */ + private function fixTokenContent($content) + { + 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]; + } + } + + $content = rtrim($content); + + return $content.' */'; + }, + $content + ); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..3192f28c1026ba44ef92d0a7462a6ba63e4a44ad --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.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\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\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class PhpdocSummaryFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'PHPDoc summary should end in either a full stop, exclamation mark, or question mark.', + [new CodeSample('isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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 (!$this->isCorrectlyFormatted($content)) { + $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? + * + * @param string $content + * + * @return bool + */ + private function isCorrectlyFormatted($content) + { + if (false !== stripos($content, '{@inheritdoc}')) { + return true; + } + + return $content !== rtrim($content, '.。!?¡¿!?'); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..f89853d739c3af78cacbaca5b59c7a3cac96597a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.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\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\CommentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Ceeram + * @author Dariusz Rumiński + */ +final class PhpdocToCommentFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + * + * Must run before GeneralPhpdocAnnotationRemoveFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocIndentFixer, PhpdocInlineTagFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocToParamTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + * Must run after CommentToPhpdocFixer. + */ + public function getPriority() + { + /* + * 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; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Docblocks should only be used on structural elements.', + [ + new CodeSample( + ' $sqlite) { + $sqlite->open($path); +} +' + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $commentsAnalyzer = new CommentsAnalyzer(); + + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + if ($commentsAnalyzer->isHeaderComment($tokens, $index)) { + continue; + } + + if ($commentsAnalyzer->isBeforeStructuralElement($tokens, $index)) { + continue; + } + + $tokens[$index] = new Token([T_COMMENT, '/*'.ltrim($token->getContent(), '/*')]); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..1c633a82698e1f46b6a63a134a84cf0c0b0f068e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.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\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\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Nobu Funaki + * @author Dariusz Rumiński + */ +final class PhpdocTrimConsecutiveBlankLineSeparationFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Removes extra blank lines after summary and after description in PHPDoc.', + [ + new CodeSample( + 'isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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()]); + } + } + + /** + * @param int $summaryEnd + */ + private function fixSummary(DocBlock $doc, $summaryEnd) + { + $nonBlankLineAfterSummary = $this->findNonBlankLine($doc, $summaryEnd); + + $this->removeExtraBlankLinesBetween($doc, $summaryEnd, $nonBlankLineAfterSummary); + } + + /** + * @param int $summaryEnd + */ + private function fixDescription(DocBlock $doc, $summaryEnd) + { + $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) + { + $annotationStart = $this->findFirstAnnotationOrEnd($doc); + $lastLine = $this->reverseFindLastUsefulContent($doc, \count($doc->getLines()) - 1); + + if (null !== $lastLine && $annotationStart !== $lastLine) { + $this->removeExtraBlankLinesBetween($doc, $annotationStart, $lastLine); + } + } + + /** + * @param int $from + * @param int $to + */ + private function removeExtraBlankLinesBetween(DocBlock $doc, $from, $to) + { + 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) + { + if (!$current->isTheEnd() && !$current->containsUsefulContent() + && !$next->isTheEnd() && !$next->containsUsefulContent()) { + $current->remove(); + } + } + + /** + * @param int $after + * + * @return null|int + */ + private function findNonBlankLine(DocBlock $doc, $after) + { + foreach ($doc->getLines() as $index => $line) { + if ($index <= $after) { + continue; + } + + if ($line->containsATag() || $line->containsUsefulContent() || $line->isTheEnd()) { + return $index; + } + } + + return null; + } + + /** + * @return int + */ + private function findFirstAnnotationOrEnd(DocBlock $doc) + { + $index = null; + foreach ($doc->getLines() as $index => $line) { + if ($line->containsATag()) { + return $index; + } + } + + return $index; // no Annotation, return the last line + } + + /** + * @param int $from + * + * @return null|int + */ + private function reverseFindLastUsefulContent(DocBlock $doc, $from) + { + for ($index = $from - 1; $index >= 0; --$index) { + if ($doc->getLine($index)->containsUsefulContent()) { + return $index; + } + } + + return null; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..6fd5512ef5fb1bf42f4cff7de88fa648a247eaa1 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.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\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class PhpdocTrimFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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. + * + * @param string $content + * + * @return string + */ + private function fixStart($content) + { + 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. + * + * @param string $content + * + * @return string + */ + private function fixEnd($content) + { + 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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * @author Graham Campbell + * @author Dariusz Rumiński + */ +final class PhpdocTypesFixer extends AbstractPhpdocTypesFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * Available types, grouped. + * + * @var array + */ + private static $possibleTypes = [ + 'simple' => [ + 'array', + 'bool', + 'callable', + 'float', + 'int', + 'iterable', + 'null', + 'object', + 'string', + ], + 'alias' => [ + 'boolean', + 'callback', + 'double', + 'integer', + 'real', + ], + 'meta' => [ + '$this', + 'false', + 'mixed', + 'parent', + 'resource', + 'scalar', + 'self', + 'static', + 'true', + 'void', + ], + ]; + + /** + * @var array string[] + */ + private $typesToFix = []; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->typesToFix = array_merge(...array_map(static function ($group) { + return self::$possibleTypes[$group]; + }, $this->configuration['groups'])); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'The correct case must be used for standard PHP types in PHPDoc.', + [ + new CodeSample( + 'typesToFix, true)) { + return $lower; + } + + return $type; + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $possibleGroups = array_keys(self::$possibleTypes); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('groups', 'Type groups to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($possibleGroups)]) + ->setDefault($possibleGroups) + ->getOption(), + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..6d8b05f0b0d8baba34c208d72234ecddb613f045 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php @@ -0,0 +1,223 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; + +final class PhpdocTypesOrderFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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', + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer. + * Must run after CommentToPhpdocFixer, PhpdocAnnotationWithoutDotFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $annotations = $doc->getAnnotationsOfType(Annotation::getTagsWithTypes()); + + if (!\count($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + $types = $annotation->getTypes(); + + // fix main types + $annotation->setTypes($this->sortTypes($types)); + + // fix @method parameters types + $line = $doc->getLine($annotation->getStart()); + $line->setContent(Preg::replaceCallback('/(@method\s+.+?\s+\w+\()(.*)\)/', function (array $matches) { + $sorted = Preg::replaceCallback('/([^\s,]+)([\s]+\$[^\s,]+)/', function (array $matches) { + return $this->sortJoinedTypes($matches[1]).$matches[2]; + }, $matches[2]); + + return $matches[1].$sorted.')'; + }, $line->getContent())); + } + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + + /** + * @param string[] $types + * + * @return string[] + */ + private function sortTypes(array $types) + { + foreach ($types as $index => $type) { + $types[$index] = Preg::replaceCallback('/^([^<]+)<(?:([\w\|]+?)(,\s*))?(.*)>$/', function (array $matches) { + return $matches[1].'<'.$this->sortJoinedTypes($matches[2]).$matches[3].$this->sortJoinedTypes($matches[4]).'>'; + }, $type); + } + + if ('alpha' === $this->configuration['sort_algorithm']) { + $types = Utils::stableSort( + $types, + static function ($type) { return $type; }, + static function ($typeA, $typeB) { + $regexp = '/^\\??\\\?/'; + + return strcasecmp( + Preg::replace($regexp, '', $typeA), + Preg::replace($regexp, '', $typeB) + ); + } + ); + } + + if ('none' !== $this->configuration['null_adjustment']) { + $nulls = []; + foreach ($types as $index => $type) { + if (Preg::match('/^\\\?null$/i', $type)) { + $nulls[$index] = $type; + unset($types[$index]); + } + } + + if (\count($nulls)) { + if ('always_last' === $this->configuration['null_adjustment']) { + array_push($types, ...$nulls); + } else { + array_unshift($types, ...$nulls); + } + } + } + + return $types; + } + + /** + * @param string $types + * + * @return string + */ + private function sortJoinedTypes($types) + { + $types = array_filter( + Preg::split('/([^|<]+(?:<.*>)?)/', $types, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY), + static function ($value) { + return '|' !== $value; + } + ); + + return implode('|', $this->sortTypes($types)); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..403e64753859e871b7adebaf84f571341098a762 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.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\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class PhpdocVarAnnotationCorrectOrderFixer extends AbstractFixer +{ + public function getDefinition() + { + 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) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..54b820e53a9e992fce9576846f36bf479387649f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.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\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + * @author Dave van der Brugge + */ +final class PhpdocVarWithoutNameFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + '`@var` and `@type` annotations of classy properties should not contain the name.', + [new CodeSample('isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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 + if (!$tokens[$nextIndex]->isGivenKind([T_PRIVATE, T_PROTECTED, T_PUBLIC, T_VAR])) { + 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) + { + $content = $line->getContent(); + + Preg::matchAll('/ \$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $content, $matches); + + if (isset($matches[0][0])) { + $line->setContent(str_replace($matches[0][0], '', $content)); + } + } + + /** + * @return Line[] + */ + private function getFirstLevelLines(DocBlock $docBlock) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/BlankLineBeforeReturnFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/BlankLineBeforeReturnFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..ce8eec371ab908dd4af17f9e8ab9d30128493a8e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/BlankLineBeforeReturnFixer.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\ReturnNotation; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\Whitespace\BlankLineBeforeStatementFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * @deprecated since 2.4, replaced by BlankLineBeforeStatementFixer + * + * @todo To be removed at 3.0 + * + * @author Dariusz Rumiński + * @author Andreas Möller + */ +final class BlankLineBeforeReturnFixer extends AbstractProxyFixer implements DeprecatedFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'An empty line feed should precede a return statement.', + [new CodeSample("proxyFixers); + } + + /** + * {@inheritdoc} + */ + protected function createProxyFixers() + { + $fixer = new BlankLineBeforeStatementFixer(); + $fixer->configure(['statements' => ['return']]); + + return [$fixer]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..0452f2bfb3449a0564043ee1509f85efdab97112 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php @@ -0,0 +1,112 @@ + + * Dariusz Rumiń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\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class NoUselessReturnFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_FUNCTION, T_RETURN]); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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, $start, $end) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..2792eb3ea62b9b5307cf2379af837209751cc1aa --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php @@ -0,0 +1,374 @@ + + * Dariusz Rumiń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\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class ReturnAssignmentFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $tokenCount = \count($tokens); + + for ($index = 1; $index < $tokenCount; ++$index) { + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + 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 + ); + + $totalTokensAdded += $tokensAdded; + } while ($tokensAdded > 0); + + $index = $functionCloseIndex + $totalTokensAdded; + $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, $functionIndex, $functionOpenIndex, $functionCloseIndex) + { + 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, // " + T_STATIC, + ]; + + $inserted = 0; + $candidates = []; + $isRisky = false; + + // 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 this 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]->equals('$')) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { + $isRisky = true; // "$$a" case + + continue; + } + } + + if ($this->isSuperGlobal($tokens[$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 ? >") + do { + $prevMeaningFul = $tokens->getPrevMeaningfulToken($assignVarEndIndex); + + if (!$tokens[$prevMeaningFul]->equals(')')) { + break; + } + + $assignVarEndIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevMeaningFul); + } while (true); + + $assignVarOperatorIndex = $tokens->getPrevTokenOfKind( + $assignVarEndIndex, + ['=', ';', '{', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]] + ); + + if (null === $assignVarOperatorIndex || !$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; + } + + // Note: here we are @ "[;{}] $a = [^;{] ; return $a;" + $inserted += $this->simplifyReturnStatement( + $tokens, + $assignVarIndex, + $assignVarOperatorIndex, + $index, + $endReturnVarIndex + ); + } + + return $inserted; + } + + /** + * @param int $assignVarIndex + * @param int $assignVarOperatorIndex + * @param int $returnIndex + * @param int $returnVarEndIndex + * + * @return int >= 0 number of tokens inserted into the Tokens collection + */ + private function simplifyReturnStatement( + Tokens $tokens, + $assignVarIndex, + $assignVarOperatorIndex, + $returnIndex, + $returnVarEndIndex + ) { + $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 clean up 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, $index) + { + if ($tokens[$index]->isComment()) { + return; + } + + if ($tokens[$index]->isWhitespace() && $tokens[$tokens->getPrevNonWhitespace($index)]->isComment()) { + return; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + + /** + * @return bool + */ + private function isSuperGlobal(Token $token) + { + static $superNames = [ + '$_COOKIE' => true, + '$_ENV' => true, + '$_FILES' => true, + '$_GET' => true, + '$_POST' => true, + '$_REQUEST' => true, + '$_SERVER' => true, + '$_SESSION' => true, + '$GLOBALS' => true, + ]; + + if (!$token->isGivenKind(T_VARIABLE)) { + return false; + } + + return isset($superNames[strtoupper($token->getContent())]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..486a9de4935a33f49a47fdf0f69ee3f93b109247 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.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\ReturnNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class SimplifiedNullReturnFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'A return statement wishing to return `void` should not return `null`.', + [ + new CodeSample("isTokenKindFound(T_RETURN); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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. + * + * @param int $index + */ + private function clear(Tokens $tokens, $index) + { + while (!$tokens[++$index]->equals(';')) { + if ($this->shouldClearToken($tokens, $index)) { + $tokens->clearAt($index); + } + } + } + + /** + * Does the return statement located at a given index need fixing? + * + * @param int $index + * + * @return bool + */ + private function needFixing(Tokens $tokens, $index) + { + 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 + * + * @return bool + */ + private function isStrictOrNullableReturnTypeFunction(Tokens $tokens, $returnIndex) + { + $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) && '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. + * + * @param int $index + * + * @return bool + */ + private function shouldClearToken(Tokens $tokens, $index) + { + $token = $tokens[$index]; + + return !$token->isComment() && !($token->isWhitespace() && $tokens[$index + 1]->isComment()); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..57241d0bbd7f27769c5c721bc004e24649ec8bd0 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php @@ -0,0 +1,304 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + * @author Egidijus Girčys + */ +final class MultilineWhitespaceBeforeSemicolonsFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @internal + */ + const STRATEGY_NO_MULTI_LINE = 'no_multi_line'; + + /** + * @internal + */ + const STRATEGY_NEW_LINE_FOR_CHAINED_CALLS = 'new_line_for_chained_calls'; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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, NoEmptyStatementFixer, SingleImportPerStatementFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(';'); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + if (self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS === $this->configuration['strategy']) { + $this->applyChainedCallsFix($tokens); + + return; + } + + if (self::STRATEGY_NO_MULTI_LINE === $this->configuration['strategy']) { + $this->applyNoMultiLineFix($tokens); + } + } + + private function applyNoMultiLineFix(Tokens $tokens) + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + foreach ($tokens as $index => $token) { + if (!$token->equals(';')) { + continue; + } + + $previousIndex = $index - 1; + $previous = $tokens[$previousIndex]; + if (!$previous->isWhitespace() || false === strpos($previous->getContent(), "\n")) { + continue; + } + + $content = $previous->getContent(); + if (0 === strpos($content, $lineEnding) && $tokens[$index - 2]->isComment()) { + $tokens->ensureWhitespaceAtIndex($previousIndex, 0, $lineEnding); + } else { + $tokens->clearAt($previousIndex); + } + } + } + + private function applyChainedCallsFix(Tokens $tokens) + { + for ($index = \count($tokens) - 1; $index >= 0; --$index) { + // continue if token is not a semicolon + if (!$tokens[$index]->equals(';')) { + continue; + } + + // get the indent of the chained call, null in case it's not a chained call + $indent = $this->findWhitespaceBeforeFirstCall($index - 1, $tokens); + + if (null === $indent) { + continue; + } + + // unset semicolon + $tokens->clearAt($index); + + // find the line ending token index after the semicolon + $index = $this->getNewLineIndex($index, $tokens); + + // line ending string of the last method call + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + // 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(';')]); + } + } + + /** + * Find the index for the new line. Return the given index when there's no new line. + * + * @param int $index + * + * @return int + */ + private function getNewLineIndex($index, Tokens $tokens) + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + for ($index, $count = \count($tokens); $index < $count; ++$index) { + if (false !== strstr($tokens[$index]->getContent(), $lineEnding)) { + return $index; + } + } + + return $index; + } + + /** + * Checks if the semicolon closes a chained 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(); + * .. + * + * @param int $index + * + * @return null|string + */ + private function findWhitespaceBeforeFirstCall($index, Tokens $tokens) + { + // semicolon followed by a closing bracket? + if (!$tokens[$index]->equals(')')) { + return null; + } + + // find opening bracket + $openingBrackets = 1; + for (--$index; $index > 0; --$index) { + if ($tokens[$index]->equals(')')) { + ++$openingBrackets; + + continue; + } + + if ($tokens[$index]->equals('(')) { + if (1 === $openingBrackets) { + break; + } + --$openingBrackets; + } + } + + // method name + if (!$tokens[--$index]->isGivenKind(T_STRING)) { + return null; + } + + // -> or :: + if (!$tokens[--$index]->isGivenKind([T_OBJECT_OPERATOR, T_DOUBLE_COLON])) { + return null; + } + + // white space + if (!$tokens[--$index]->isGivenKind(T_WHITESPACE)) { + return null; + } + + $closingBrackets = 0; + for ($index; $index >= 0; --$index) { + if ($tokens[$index]->equals(')')) { + ++$closingBrackets; + } + + if ($tokens[$index]->equals('(')) { + --$closingBrackets; + } + + // must be the variable of the first call in the chain + if ($tokens[$index]->isGivenKind([T_VARIABLE, T_RETURN, T_STRING]) && 0 === $closingBrackets) { + if ($tokens[--$index]->isGivenKind(T_WHITESPACE) + || $tokens[$index]->isGivenKind(T_OPEN_TAG)) { + return $this->getIndentAt($tokens, $index); + } + } + } + + return null; + } + + /** + * @param int $index + * + * @return null|string + */ + private function getIndentAt(Tokens $tokens, $index) + { + $content = ''; + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + // find line ending token + for ($index; $index > 0; --$index) { + if (false !== strstr($tokens[$index]->getContent(), $lineEnding)) { + break; + } + } + + if ($tokens[$index]->isWhitespace()) { + $content = $tokens[$index]->getContent(); + --$index; + } + + if ($tokens[$index]->isGivenKind(T_OPEN_TAG)) { + $content = $tokens[$index]->getContent().$content; + } + + if (1 === Preg::match('/\R{1}(\h*)$/', $content, $matches)) { + return $matches[1]; + } + + return null; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..027eca3ba8a77522351a12768b3216c848769d38 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.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\Semicolon; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author SpacePossum + * @author Dariusz Rumiński + */ +final class NoEmptyStatementFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Remove useless semicolon statements.', + [new CodeSample("isTokenKindFound(';'); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { + // skip T_FOR parenthesis to ignore duplicated `;` 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); + } + } + } + + /** + * 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 + * @param int $curlyCloseIndex + */ + private function fixSemicolonAfterCurlyBraceClose(Tokens $tokens, $index, $curlyCloseIndex) + { + 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); + $beforeCurlyOpening = $tokens->getPrevMeaningfulToken($curlyOpeningIndex); + if ($tokens[$beforeCurlyOpening]->isGivenKind($beforeCurlyOpeningKinds) || $tokens[$beforeCurlyOpening]->equalsAny([';', '{', '}'])) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + return; + } + + // check for namespaces and class, interface and trait definitions + if ($tokens[$beforeCurlyOpening]->isGivenKind(T_STRING)) { + $classyTest = $tokens->getPrevMeaningfulToken($beforeCurlyOpening); + while ($tokens[$classyTest]->equals(',') || $tokens[$classyTest]->isGivenKind([T_STRING, T_NS_SEPARATOR, T_EXTENDS, T_IMPLEMENTS])) { + $classyTest = $tokens->getPrevMeaningfulToken($classyTest); + } + + $tokensAnalyzer = new TokensAnalyzer($tokens); + + if ( + $tokens[$classyTest]->isGivenKind(T_NAMESPACE) || + ($tokens[$classyTest]->isClassy() && !$tokensAnalyzer->isAnonymousClass($classyTest)) + ) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + + return; + } + + // early return check, below only control structures with conditions are fixed + if (!$tokens[$beforeCurlyOpening]->equals(')')) { + return; + } + + $openingBrace = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $beforeCurlyOpening); + $beforeOpeningBrace = $tokens->getPrevMeaningfulToken($openingBrace); + + if ($tokens[$beforeOpeningBrace]->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[$beforeOpeningBrace]->isGivenKind(T_STRING)) { + $beforeString = $tokens->getPrevMeaningfulToken($beforeOpeningBrace); + if ($tokens[$beforeString]->isGivenKind(T_FUNCTION)) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); // implicit return + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoMultilineWhitespaceBeforeSemicolonsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoMultilineWhitespaceBeforeSemicolonsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d35bb10b7d59abdecf3771a0c0946e1fe3d0ac1b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoMultilineWhitespaceBeforeSemicolonsFixer.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\Semicolon; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; + +/** + * @deprecated since 2.9.1, replaced by MultilineWhitespaceBeforeSemicolonsFixer + * + * @todo To be removed at 3.0 + * + * @author Graham Campbell + */ +final class NoMultilineWhitespaceBeforeSemicolonsFixer extends AbstractProxyFixer implements DeprecatedFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Multi-line whitespace before closing semicolon are prohibited.', + [ + new CodeSample( + 'proxyFixers); + } + + /** + * {@inheritdoc} + */ + protected function createProxyFixers() + { + $fixer = new MultilineWhitespaceBeforeSemicolonsFixer(); + $fixer->configure(['strategy' => MultilineWhitespaceBeforeSemicolonsFixer::STRATEGY_NO_MULTI_LINE]); + + return [$fixer]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..dc4cf4a7ca714314979b44b7b04ef430d2f09c53 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.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\Semicolon; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class NoSinglelineWhitespaceBeforeSemicolonsFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Single-line whitespace before closing semicolon are prohibited.', + [new CodeSample("foo() ;\n")] + ); + } + + /** + * {@inheritdoc} + * + * Must run after CombineConsecutiveIssetsFixer, FunctionToConstantFixer, NoEmptyStatementFixer, SingleImportPerStatementFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(';'); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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 + if (!$tokens[$index - 1]->equals(' ')) { + $tokens[$index - 1] = new Token([T_WHITESPACE, ' ']); + } + } elseif (!$tokens[$index - 2]->isComment()) { + $tokens->clearAt($index - 1); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..35ee221af5cdf2b0689487264791b1823c3a7f99 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.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\Semicolon; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class SemicolonAfterInstructionFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Instructions must be terminated with a semicolon.', + [new CodeSample("\n")] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_CLOSE_TAG); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..e11c1f15d42d9ce30f1e7bd3d069889a99bc46e3 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php @@ -0,0 +1,145 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class SpaceAfterSemicolonFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Fix whitespace after a semicolon.', + [ + new CodeSample( + " true, + ]), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after CombineConsecutiveUnsetsFixer, MultilineWhitespaceBeforeSemicolonsFixer, NoEmptyStatementFixer, OrderedClassElementsFixer, SingleImportPerStatementFixer, SingleTraitInsertPerStatementFixer. + */ + public function getPriority() + { + return -1; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(';'); + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('remove_in_empty_for_expressions', 'Whether spaces should be removed for empty `for` expressions.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $insideForParenthesesUntil = null; + + for ($index = 0, $max = \count($tokens) - 1; $index < $max; ++$index) { + if ($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]]) && ( + !$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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..ef11e6c70591a659b218dd70e84347a8458e09bc --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.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\Strict; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jordi Boggiano + * @author SpacePossum + */ +final class DeclareStrictTypesFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Force strict types declaration in all files. Requires PHP >= 7.0.', + [ + new VersionSpecificCodeSample( + "= 70000 && isset($tokens[0]) && $tokens[0]->isGivenKind(T_OPEN_TAG); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + // check if the declaration is already done + $searchIndex = $tokens->getNextMeaningfulToken(0); + if (null === $searchIndex) { + $this->insertSequence($tokens); // declaration not found, insert one + + return; + } + + $sequenceLocation = $tokens->findSequence([[T_DECLARE, 'declare'], '(', [T_STRING, 'strict_types'], '=', [T_LNUMBER], ')'], $searchIndex, null, false); + if (null === $sequenceLocation) { + $this->insertSequence($tokens); // declaration not found, insert one + + return; + } + + $this->fixStrictTypesCasingAndValue($tokens, $sequenceLocation); + } + + /** + * @param array $sequence + */ + private function fixStrictTypesCasingAndValue(Tokens $tokens, array $sequence) + { + /** @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(Tokens $tokens) + { + $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(';'), + ]; + $endIndex = \count($sequence); + + $tokens->insertAt(1, $sequence); + + // start index of the sequence is always 1 here, 0 is always open tag + // transform "getContent(), "\n")) { + $tokens[0] = new Token([$tokens[0]->getId(), trim($tokens[0]->getContent()).' ']); + } + + if ($endIndex === \count($tokens) - 1) { + return; // no more tokens afters sequence, single_blank_line_at_eof might add a line + } + + $lineEnding = $this->whitespacesConfig->getLineEnding(); + if (!$tokens[1 + $endIndex]->isWhitespace()) { + $tokens->insertAt(1 + $endIndex, new Token([T_WHITESPACE, $lineEnding])); + + return; + } + + $content = $tokens[1 + $endIndex]->getContent(); + $tokens[1 + $endIndex] = new Token([T_WHITESPACE, $lineEnding.ltrim($content, " \t")]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..7a706a648908e55bbeccded0038685dbfa5091ba --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.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\Strict; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class StrictComparisonFixer extends AbstractFixer +{ + public function getDefinition() + { + return new FixerDefinition( + 'Comparisons should be strict.', + [new CodeSample("isAnyTokenKindsFound([T_IS_EQUAL, T_IS_NOT_EQUAL]); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b8c5c17b9fdc4d5eb07c9bd02b45bfa0d8104644 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php @@ -0,0 +1,154 @@ + + * Dariusz Rumiń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\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class StrictParamFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Functions should be used with `$strict` param set to `true`.', + [new CodeSample("isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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 ($token->isGivenKind(T_STRING) && isset($map[$lowercaseContent])) { + $this->fixFunction($tokens, $index, $map[$lowercaseContent]); + } + } + } + + private function fixFunction(Tokens $tokens, $functionIndex, array $functionParams) + { + $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 (!$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->getTokenNotOfKindSibling($endBraceIndex, -1, [[T_WHITESPACE], ',']); + $tokens->insertAt($beforeEndBraceIndex + 1, $tokensToInsert); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..8cd23bdeace3da5ce3097bfb6fcfb91a53fe6ce7 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.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\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class EscapeImplicitBackslashesFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + $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} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING]); + } + + /** + * {@inheritdoc} + * + * Must run before HeredocToNowdocFixer, SingleQuoteFixer. + * Must run after BacktickToShellExecFixer. + */ + public function getPriority() + { + return 1; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + static $singleQuotedRegex = '/(? $token) { + $content = $token->getContent(); + if ($token->equalsAny(['"', 'b"', 'B"'])) { + $doubleQuoteOpened = !$doubleQuoteOpened; + } + if (!$token->isGivenKind([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING]) || false === strpos($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) + ; + $isHeredocSyntax = !$isSingleQuotedString && !$isDoubleQuotedString; + if ( + (false === $this->configuration['single_quoted'] && $isSingleQuotedString) + || (false === $this->configuration['double_quoted'] && $isDoubleQuotedString) + || (false === $this->configuration['heredoc_syntax'] && $isHeredocSyntax) + ) { + continue; + } + + $regex = $heredocSyntaxRegex; + if ($isSingleQuotedString) { + $regex = $singleQuotedRegex; + } elseif ($isDoubleQuotedString) { + $regex = $doubleQuotedRegex; + } + + $newContent = Preg::replace($regex, '\\\\\\\\$1', $content); + if ($newContent !== $content) { + $tokens[$index] = new Token([$token->getId(), $newContent]); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..caa69502c62410879884607e7db0ffeda66d951d --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.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\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class ExplicitStringVariableFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 before SimpleToComplexStringVariableFixer. + * Must run after BacktickToShellExecFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_VARIABLE); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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 = key($distinctVariableSet['tokens']); + $singleVariableToken = current($distinctVariableSet['tokens']); + $tokens->overrideRange($singleVariableIndex, $singleVariableIndex, [ + new Token([T_DOLLAR_OPEN_CURLY_BRACES, '${']), + new Token([T_STRING_VARNAME, substr($singleVariableToken->getContent(), 1)]), + new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']), + ]); + } 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 + * + * @return bool + */ + private function isStringPartToken(Token $token) + { + return $token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) + || $token->isGivenKind(T_START_HEREDOC) + || '"' === $token->getContent() + || 'b"' === strtolower($token->getContent()) + ; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..b54dbd9cf4b3be6571a330d2b487b7966c217ef5 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.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\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class HeredocToNowdocFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Convert `heredoc` to `nowdoc` where possible.', + [ + new CodeSample( + <<<'EOF' +isTokenKindFound(T_START_HEREDOC); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_START_HEREDOC) || false !== strpos($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. + * + * @return Token + */ + private function convertToNowdoc(Token $token) + { + return new Token([ + $token->getId(), + Preg::replace('/^([Bb]?<<<)(\h*)"?([^\s"]+)"?/', '$1$2\'$3\'', $token->getContent()), + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..08a2561b974723ec3fc0099d577943551c309469 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.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\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author ntzm + */ +final class NoBinaryStringFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_START_HEREDOC]); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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])) { + continue; + } + + $content = $token->getContent(); + + if ('b' === strtolower($content[0])) { + $tokens[$index] = new Token([$token->getId(), substr($content, 1)]); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..14cb6354390d75c63c0354a14d964de87235df87 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php @@ -0,0 +1,113 @@ + + * Dariusz Rumiń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\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dave van der Brugge + */ +final class SimpleToComplexStringVariableFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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) + { + 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 ('$' === substr($stringContent, -1) && '\\$' !== substr($stringContent, -2)) { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..46055b478fe3c6d7069e8e6cbe572abacd596b0f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.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\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class SingleQuoteFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + $codeSample = <<<'EOF' + true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after EscapeImplicitBackslashesFixer. + */ + public function getPriority() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_CONSTANT_ENCAPSED_STRING); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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'] || false === strpos($content, "'")) && + // regex: odd number of backslashes, not followed by double quote or dollar + !Preg::match('/(?setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..33a50f7663894d6a3bc3d61ba8a2691a79179654 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.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\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML]); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..d6d5dc2eafd9a9a807ae7118d22e19abbd0c4e03 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php @@ -0,0 +1,400 @@ + + * Dariusz Rumiń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\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class ArrayIndentationFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Each element of an array must be indented exactly once.', + [ + new CodeSample(" [\n 'baz' => true,\n ],\n];\n"), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + } + + /** + * {@inheritdoc} + * + * Must run before AlignMultilineCommentFixer, BinaryOperatorSpacesFixer. + * Must run after BracesFixer, MethodChainingIndentationFixer. + */ + public function getPriority() + { + return -30; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($this->findArrays($tokens) as $array) { + $indentLevel = 1; + $scopes = [[ + 'opening_braces' => $array['start_braces']['opening'], + 'unindented' => false, + ]]; + $currentScope = 0; + + $arrayIndent = $this->getLineIndentation($tokens, $array['start']); + $previousLineInitialIndent = $arrayIndent; + $previousLineNewIndent = $arrayIndent; + + foreach ($array['braces'] as $index => $braces) { + $currentIndentLevel = $indentLevel; + if ( + $braces['starts_with_closing'] + && !$scopes[$currentScope]['unindented'] + && !$this->isClosingLineWithMeaningfulContent($tokens, $index) + ) { + --$currentIndentLevel; + } + + $token = $tokens[$index]; + if ($this->newlineIsInArrayScope($tokens, $index, $array)) { + $content = Preg::replace( + '/(\R+)\h*$/', + '$1'.$arrayIndent.str_repeat($this->whitespacesConfig->getIndent(), $currentIndentLevel), + $token->getContent() + ); + + $previousLineInitialIndent = $this->extractIndent($token->getContent()); + $previousLineNewIndent = $this->extractIndent($content); + } else { + $content = Preg::replace( + '/(\R)'.preg_quote($previousLineInitialIndent, '/').'(\h*)$/', + '$1'.$previousLineNewIndent.'$2', + $token->getContent() + ); + } + + $closingBraces = $braces['closing']; + while ($closingBraces-- > 0) { + if (!$scopes[$currentScope]['unindented']) { + --$indentLevel; + $scopes[$currentScope]['unindented'] = true; + } + + if (0 === --$scopes[$currentScope]['opening_braces']) { + array_pop($scopes); + --$currentScope; + } + } + + if ($braces['opening'] > 0) { + $scopes[] = [ + 'opening_braces' => $braces['opening'], + 'unindented' => false, + ]; + ++$indentLevel; + ++$currentScope; + } + + $tokens[$index] = new Token([T_WHITESPACE, $content]); + } + } + } + + private function findArrays(Tokens $tokens) + { + $arrays = []; + + foreach ($this->findArrayTokenRanges($tokens, 0, \count($tokens) - 1) as $arrayTokenRanges) { + $array = [ + 'start' => $arrayTokenRanges[0][0], + 'end' => $arrayTokenRanges[\count($arrayTokenRanges) - 1][1], + 'token_ranges' => $arrayTokenRanges, + ]; + + $array['start_braces'] = $this->getLineSignificantBraces($tokens, $array['start'] - 1, $array); + $array['braces'] = $this->computeArrayLineSignificantBraces($tokens, $array); + + $arrays[] = $array; + } + + return $arrays; + } + + private function findArrayTokenRanges(Tokens $tokens, $from, $to) + { + $arrayTokenRanges = []; + $currentArray = null; + $valueSinceIndex = null; + + for ($index = $from; $index <= $to; ++$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + $arrayStartIndex = $index; + + if ($token->isGivenKind(T_ARRAY)) { + $index = $tokens->getNextTokenOfKind($index, ['(']); + } + + $endIndex = $tokens->findBlockEnd( + $tokens[$index]->equals('(') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, + $index + ); + + if (null === $currentArray) { + $currentArray = [ + 'start' => $index, + 'end' => $endIndex, + 'ignored_tokens_ranges' => [], + ]; + } else { + if (null === $valueSinceIndex) { + $valueSinceIndex = $arrayStartIndex; + } + + $index = $endIndex; + } + + continue; + } + + if (null === $currentArray || $token->isWhitespace() || $token->isComment()) { + continue; + } + + if ($currentArray['end'] === $index) { + if (null !== $valueSinceIndex) { + $currentArray['ignored_tokens_ranges'][] = [$valueSinceIndex, $tokens->getPrevMeaningfulToken($index)]; + $valueSinceIndex = null; + } + + $rangeIndexes = [$currentArray['start']]; + foreach ($currentArray['ignored_tokens_ranges'] as list($start, $end)) { + $rangeIndexes[] = $start - 1; + $rangeIndexes[] = $end + 1; + } + $rangeIndexes[] = $currentArray['end']; + + $arrayTokenRanges[] = array_chunk($rangeIndexes, 2); + + foreach ($currentArray['ignored_tokens_ranges'] as list($start, $end)) { + foreach ($this->findArrayTokenRanges($tokens, $start, $end) as $nestedArray) { + $arrayTokenRanges[] = $nestedArray; + } + } + + $currentArray = null; + + continue; + } + + if (null === $valueSinceIndex) { + $valueSinceIndex = $index; + } + + if ( + ($token->equals('(') && !$tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_ARRAY)) + || $token->equals('{') + ) { + $index = $tokens->findBlockEnd( + $token->equals('{') ? Tokens::BLOCK_TYPE_CURLY_BRACE : Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $index + ); + } + + if ($token->equals(',')) { + $currentArray['ignored_tokens_ranges'][] = [$valueSinceIndex, $tokens->getPrevMeaningfulToken($index)]; + $valueSinceIndex = null; + } + } + + return $arrayTokenRanges; + } + + private function computeArrayLineSignificantBraces(Tokens $tokens, array $array) + { + $braces = []; + + for ($index = $array['start']; $index <= $array['end']; ++$index) { + if (!$this->isNewLineToken($tokens, $index)) { + continue; + } + + $braces[$index] = $this->getLineSignificantBraces($tokens, $index, $array); + } + + return $braces; + } + + private function getLineSignificantBraces(Tokens $tokens, $index, array $array) + { + $deltas = []; + + for (++$index; $index <= $array['end']; ++$index) { + if ($this->isNewLineToken($tokens, $index)) { + break; + } + + if (!$this->indexIsInArrayTokenRanges($index, $array)) { + continue; + } + + $token = $tokens[$index]; + if ($token->equals('(') && !$tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_ARRAY)) { + continue; + } + + if ($token->equals(')')) { + $openBraceIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + if (!$tokens[$tokens->getPrevMeaningfulToken($openBraceIndex)]->isGivenKind(T_ARRAY)) { + continue; + } + } + + if ($token->equalsAny(['(', [CT::T_ARRAY_SQUARE_BRACE_OPEN]])) { + $deltas[] = 1; + + continue; + } + + if ($token->equalsAny([')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]])) { + $deltas[] = -1; + } + } + + $braces = [ + 'opening' => 0, + 'closing' => 0, + 'starts_with_closing' => -1 === reset($deltas), + ]; + + foreach ($deltas as $delta) { + if (1 === $delta) { + ++$braces['opening']; + } elseif ($braces['opening'] > 0) { + --$braces['opening']; + } else { + ++$braces['closing']; + } + } + + return $braces; + } + + private function isClosingLineWithMeaningfulContent(Tokens $tokens, $newLineIndex) + { + $nextMeaningfulIndex = $tokens->getNextMeaningfulToken($newLineIndex); + + return !$tokens[$nextMeaningfulIndex]->equalsAny([')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]]); + } + + private function getLineIndentation(Tokens $tokens, $index) + { + $newlineTokenIndex = $this->getPreviousNewlineTokenIndex($tokens, $index); + + if (null === $newlineTokenIndex) { + return ''; + } + + return $this->extractIndent($this->computeNewLineContent($tokens, $newlineTokenIndex)); + } + + private function extractIndent($content) + { + if (Preg::match('/\R(\h*)[^\r\n]*$/D', $content, $matches)) { + return $matches[1]; + } + + return ''; + } + + private function getPreviousNewlineTokenIndex(Tokens $tokens, $index) + { + 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 newlineIsInArrayScope(Tokens $tokens, $index, array $array) + { + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny(['.', '?', ':'])) { + return false; + } + + $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; + if ($nextToken->isGivenKind(T_OBJECT_OPERATOR) || $nextToken->equalsAny(['.', '?', ':'])) { + return false; + } + + return $this->indexIsInArrayTokenRanges($index, $array); + } + + private function indexIsInArrayTokenRanges($index, array $array) + { + foreach ($array['token_ranges'] as list($start, $end)) { + if ($index < $start) { + return false; + } + + if ($index <= $end) { + return true; + } + } + + return false; + } + + private function isNewLineToken(Tokens $tokens, $index) + { + if (!$tokens[$index]->equalsAny([[T_WHITESPACE], [T_INLINE_HTML]])) { + return false; + } + + return (bool) Preg::match('/\R/', $this->computeNewLineContent($tokens, $index)); + } + + private function computeNewLineContent(Tokens $tokens, $index) + { + $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; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..08b1852c983b33744262350f8680487164d1cdbb --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php @@ -0,0 +1,331 @@ + + * Dariusz Rumiń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\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Dariusz Rumiński + * @author Andreas Möller + * @author SpacePossum + */ +final class BlankLineBeforeStatementFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var array + */ + private static $tokenMap = [ + 'break' => T_BREAK, + 'case' => T_CASE, + 'continue' => T_CONTINUE, + 'declare' => T_DECLARE, + 'default' => T_DEFAULT, + 'die' => T_EXIT, + '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, + '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, + ]; + + /** + * @var array + */ + private $fixTokenMap = []; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->fixTokenMap = []; + + foreach ($this->configuration['statements'] as $key) { + $this->fixTokenMap[$key] = self::$tokenMap[$key]; + } + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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( + ' ['die'], + ] + ), + 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, NoUselessReturnFixer, ReturnAssignmentFixer. + */ + public function getPriority() + { + return -21; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(array_values($this->fixTokenMap)); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + $tokenKinds = array_values($this->fixTokenMap); + $analyzer = new TokensAnalyzer($tokens); + + for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind($tokenKinds) || ($token->isGivenKind(T_WHILE) && $analyzer->isWhilePartOfDoWhile($index))) { + continue; + } + + $prevNonWhitespaceToken = $tokens[$tokens->getPrevNonWhitespace($index)]; + + if (!$prevNonWhitespaceToken->equalsAny([';', '}'])) { + continue; + } + + $prevIndex = $index - 1; + $prevToken = $tokens[$prevIndex]; + + if ($prevToken->isWhitespace()) { + $countParts = substr_count($prevToken->getContent(), "\n"); + + if (0 === $countParts) { + $tokens[$prevIndex] = new Token([T_WHITESPACE, rtrim($prevToken->getContent(), " \t").$lineEnding.$lineEnding]); + } elseif (1 === $countParts) { + $tokens[$prevIndex] = new Token([T_WHITESPACE, $lineEnding.$prevToken->getContent()]); + } + } else { + $tokens->insertAt($index, new Token([T_WHITESPACE, $lineEnding.$lineEnding])); + + ++$index; + ++$limit; + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + 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(), + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..8cb48c36df2df1f5cdd837691367fbce5a0a3d7e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.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\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jack Cherng + */ +final class CompactNullableTypehintFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Remove extra spaces in a nullable typehint.', + [ + new VersionSpecificCodeSample( + "= 70100 && $tokens->isTokenKindFound(CT::T_NULLABLE_TYPE); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + static $typehintKinds = [ + CT::T_ARRAY_TYPEHINT, + T_CALLABLE, + T_NS_SEPARATOR, + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/HeredocIndentationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/HeredocIndentationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..22d6bccc7a56a34e47385ab1800ebece266d595e --- /dev/null +++ b/lib/composer/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\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class HeredocIndentationFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Heredoc/nowdoc content must be properly indented. Requires PHP >= 7.3.', + [ + new VersionSpecificCodeSample( + <<<'SAMPLE' += 70300 && $tokens->isTokenKindFound(T_START_HEREDOC); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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); + } + } + + /** + * @param int $start + * @param int $end + */ + private function fixIndentation(Tokens $tokens, $start, $end) + { + $indent = $this->getIndentAt($tokens, $start).$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; + } + + for ($index = $end - 1, $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 || $currentIndent === substr($content, 0, $currentIndentLength))) { + $content = $indent.substr($content, $currentIndentLength); + } elseif ($currentIndent) { + $content = Preg::replace('/^(?!'.$currentIndent.')\h+/', '', $content); + } + + $tokens[$index] = new Token([T_ENCAPSED_AND_WHITESPACE, $content]); + } + + /** + * @param int $index + * + * @return string + */ + private function getIndentAt(Tokens $tokens, $index) + { + for (; $index >= 0; --$index) { + if (!$tokens[$index]->isGivenKind([T_WHITESPACE, T_INLINE_HTML, T_OPEN_TAG])) { + continue; + } + + $content = $tokens[$index]->getContent(); + + if ($tokens[$index]->isWhitespace() && $tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) { + $content = $tokens[$index - 1]->getContent().$content; + } + + if (1 === Preg::match('/\R(\h*)$/', $content, $matches)) { + return $matches[1]; + } + } + + return ''; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..ae16ae526c28be86a7140b68b37a22d2a579a0c1 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.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\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Code MUST use configured indentation type.', + [ + new CodeSample("isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT, T_WHITESPACE]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $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; + } + } + } + + /** + * @param int $index + * + * @return Token + */ + private function fixIndentInComment(Tokens $tokens, $index) + { + $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', function ($matches) use ($indent) { + return $this->getExpectedIndent($matches[0], $indent); + }, $content); + + return new Token([$tokens[$index]->getId(), $content]); + } + + /** + * @param int $index + * + * @return Token + */ + private function fixIndentToken(Tokens $tokens, $index) + { + $content = $tokens[$index]->getContent(); + $previousTokenHasTrailingLinebreak = false; + + // @TODO 3.0 this can be removed when we have a transformer for "T_OPEN_TAG" to "T_OPEN_TAG + T_WHITESPACE" + if (false !== strpos($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) { + // normalize mixed indent + $content = Preg::replace('/(?:(?getExpectedIndent($content, $indent); + }, + $content + ); + + if ($previousTokenHasTrailingLinebreak) { + $newContent = substr($newContent, 1); + } + + return new Token([T_WHITESPACE, $newContent]); + } + + /** + * @param string $content + * @param string $indent + * + * @return string mixed + */ + private function getExpectedIndent($content, $indent) + { + if ("\t" === $indent) { + $content = str_replace(' ', $indent, $content); + } + + return $content; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..0157c8482deb131ef00dabfeb6b5b67a12928ffd --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.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\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶2.2. + * + * @author Fabien Potencier + * @author SpacePossum + * @author Dariusz Rumiński + */ +final class LineEndingFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..ac38e6931cec2bf675d8811fbb5a973b8060fac9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php @@ -0,0 +1,202 @@ + + * Dariusz Rumiń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\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vladimir Boliev + */ +final class MethodChainingIndentationFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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 before ArrayIndentationFixer. + * Must run after BracesFixer. + */ + public function getPriority() + { + return -29; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_OBJECT_OPERATOR); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { + if (!$tokens[$index]->isGivenKind(T_OBJECT_OPERATOR)) { + 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; + } + } + + $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]); + } + } + } + + /** + * @param int $index index of the first token on the line to indent + * + * @return string + */ + private function getExpectedIndentAt(Tokens $tokens, $index) + { + $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 T_OBJECT_OPERATOR token + * + * @return bool + */ + private function canBeMovedToNextLine($index, Tokens $tokens) + { + $prevMeaningful = $tokens->getPrevMeaningfulToken($index); + $hasCommentBefore = false; + + for ($i = $index - 1; $i > $prevMeaningful; --$i) { + if ($tokens[$i]->isComment()) { + $hasCommentBefore = true; + + continue; + } + + if ($tokens[$i]->isWhitespace() && 1 === Preg::match('/\R/', $tokens[$i]->getContent())) { + return $hasCommentBefore; + } + } + + return false; + } + + /** + * @param int $index index of the indentation token + * + * @return null|string + */ + private function getIndentAt(Tokens $tokens, $index) + { + if (1 === Preg::match('/\R{1}(\h*)$/', $this->getIndentContentAt($tokens, $index), $matches)) { + return $matches[1]; + } + + return null; + } + + private function getIndentContentAt(Tokens $tokens, $index) + { + 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 + * + * @return bool + */ + private function currentLineRequiresExtraIndentLevel(Tokens $tokens, $start, $end) + { + if ($tokens[$start + 1]->isGivenKind(T_OBJECT_OPERATOR)) { + return false; + } + + if ($tokens[$end]->isGivenKind(CT::T_BRACE_CLASS_INSTANTIATION_CLOSE)) { + return true; + } + + return + !$tokens[$end]->equals(')') + || $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $end) >= $start + ; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..a1b560b40739be603f0e7553c9c72a5824c3ca75 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php @@ -0,0 +1,484 @@ + + * Dariusz Rumiń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\ConfigurationException\InvalidConfigurationException; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author Dariusz Rumiński + * @author SpacePossum + */ +final class NoExtraBlankLinesFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var string[] + */ + private static $availableTokens = [ + 'break', + 'case', + 'continue', + 'curly_brace_block', + 'default', + 'extra', + 'parenthesis_brace_block', + 'return', + 'square_brace_block', + 'switch', + 'throw', + 'use', + 'useTrait', + 'use_trait', + ]; + + /** + * @var array key is token id, value is name of callback + */ + private $tokenKindCallbackMap; + + /** + * @var array token prototype, value is name of callback + */ + private $tokenEqualsMap; + + /** + * @var Tokens + */ + private $tokens; + + /** + * @var TokensAnalyzer + */ + private $tokensAnalyzer; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + static $reprToTokenMap = [ + 'break' => T_BREAK, + 'case' => T_CASE, + 'continue' => T_CONTINUE, + 'curly_brace_block' => '{', + 'default' => T_DEFAULT, + 'extra' => T_WHITESPACE, + 'parenthesis_brace_block' => '(', + 'return' => T_RETURN, + 'square_brace_block' => CT::T_ARRAY_SQUARE_BRACE_OPEN, + 'switch' => T_SWITCH, + 'throw' => T_THROW, + 'use' => T_USE, + 'use_trait' => CT::T_USE_TRAIT, + ]; + + static $tokenKindCallbackMap = [ + T_BREAK => 'fixAfterToken', + T_CASE => 'fixAfterToken', + T_CONTINUE => 'fixAfterToken', + T_DEFAULT => 'fixAfterToken', + T_RETURN => 'fixAfterToken', + T_SWITCH => 'fixAfterToken', + T_THROW => 'fixAfterToken', + T_USE => 'removeBetweenUse', + T_WHITESPACE => 'removeMultipleBlankLines', + CT::T_USE_TRAIT => 'removeBetweenUse', + CT::T_ARRAY_SQUARE_BRACE_OPEN => 'fixStructureOpenCloseIfMultiLine', // typeless '[' tokens should not be fixed (too rare) + ]; + + static $tokenEqualsMap = [ + '{' => 'fixStructureOpenCloseIfMultiLine', // i.e. not: CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN + '(' => 'fixStructureOpenCloseIfMultiLine', // i.e. not: CT::T_BRACE_CLASS_INSTANTIATION_OPEN + ]; + + $tokensAssoc = array_flip(array_intersect_key($reprToTokenMap, array_flip($this->configuration['tokens']))); + + $this->tokenKindCallbackMap = array_intersect_key($tokenKindCallbackMap, $tokensAssoc); + $this->tokenEqualsMap = array_intersect_key($tokenEqualsMap, $tokensAssoc); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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( + ' ['use_trait']] + ), + new CodeSample( + ' ['switch', 'case', 'default']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BlankLineBeforeStatementFixer. + * Must run after CombineConsecutiveUnsetsFixer, FunctionToConstantFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoEmptyStatementFixer, NoUnusedImportsFixer, NoUselessElseFixer, NoUselessReturnFixer. + */ + public function getPriority() + { + return -20; + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $this->tokens = $tokens; + $this->tokensAnalyzer = new TokensAnalyzer($this->tokens); + for ($index = $tokens->getSize() - 1; $index > 0; --$index) { + $this->fixByToken($tokens[$index], $index); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $that = $this; + + return new FixerConfigurationResolverRootless('tokens', [ + (new FixerOptionBuilder('tokens', 'List of tokens to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(self::$availableTokens)]) + ->setNormalizer(static function (Options $options, $tokens) use ($that) { + foreach ($tokens as &$token) { + if ('useTrait' === $token) { + $message = "Token \"useTrait\" in option \"tokens\" for rule \"{$that->getName()}\" is deprecated and will be removed in 3.0, use \"use_trait\" instead."; + + if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + throw new InvalidConfigurationException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); + } + + @trigger_error($message, E_USER_DEPRECATED); + $token = 'use_trait'; + + break; + } + } + + return $tokens; + }) + ->setDefault(['extra']) + ->getOption(), + ], $this->getName()); + } + + private function fixByToken(Token $token, $index) + { + 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($index) + { + $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; + } + + return $this->removeEmptyLinesAfterLineWithTokenAt($next); + } + + private function removeMultipleBlankLines($index) + { + $expected = $this->tokens[$index - 1]->isGivenKind(T_OPEN_TAG) && 1 === 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($index) + { + 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() && false !== strpos($this->tokens[$i]->getContent(), "\n")) { + break; + } + } + + $this->removeEmptyLinesAfterLineWithTokenAt($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($index) + { + $blockTypeInfo = Tokens::detectBlockType($this->tokens[$index]); + $bodyEnd = $this->tokens->findBlockEnd($blockTypeInfo['type'], $index); + + for ($i = $bodyEnd - 1; $i >= $index; --$i) { + if (false !== strpos($this->tokens[$i]->getContent(), "\n")) { + $this->removeEmptyLinesAfterLineWithTokenAt($i); + $this->removeEmptyLinesAfterLineWithTokenAt($index); + + break; + } + } + } + + private function removeEmptyLinesAfterLineWithTokenAt($index) + { + // find the line break + $tokenCount = \count($this->tokens); + for ($end = $index; $end < $tokenCount; ++$end) { + if ( + $this->tokens[$end]->equals('}') + || false !== strpos($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; + } + + $pos = strrpos($content, "\n"); + if ($pos + 2 <= \strlen($content)) { // preserve indenting where possible + $newContent = $ending.substr($content, $pos + 1); + } else { + $newContent = $ending; + } + + $this->tokens[$i] = new Token([T_WHITESPACE, $newContent]); + } + } + + /** + * @param int $startIndex + * @param int $endIndex + * + * @return bool + */ + private function containsLinebreak($startIndex, $endIndex) + { + for ($i = $endIndex; $i > $startIndex; --$i) { + if (Preg::match('/\R/', $this->tokens[$i]->getContent())) { + return true; + } + } + + return false; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraConsecutiveBlankLinesFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraConsecutiveBlankLinesFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..07be31916d4f5a937b86fd5bf27a25873b0bdc6b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraConsecutiveBlankLinesFixer.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\Whitespace; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; + +/** + * @author Dariusz Rumiński + * @author SpacePossum + * + * @deprecated in 2.10, proxy to NoExtraBlankLinesFixer + */ +final class NoExtraConsecutiveBlankLinesFixer extends AbstractProxyFixer implements ConfigurationDefinitionFixerInterface, DeprecatedFixerInterface, WhitespacesAwareFixerInterface +{ + private $fixer; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return $this->getFixer()->getDefinition(); + } + + public function configure(array $configuration = null) + { + $this->getFixer()->configure($configuration); + $this->configuration = $configuration; + } + + public function getConfigurationDefinition() + { + return $this->getFixer()->getConfigurationDefinition(); + } + + /** + * {@inheritdoc} + */ + public function getSuccessorsNames() + { + return array_keys($this->proxyFixers); + } + + /** + * {@inheritdoc} + */ + protected function createProxyFixers() + { + return [$this->getFixer()]; + } + + private function getFixer() + { + if (null === $this->fixer) { + $this->fixer = new NoExtraBlankLinesFixer(); + } + + return $this->fixer; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..1ffc2ed6d40fa97b12dbfe0b69b2f16bd4b39e87 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.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\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Javier Spagnoletti + */ +final class NoSpacesAroundOffsetFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'There MUST NOT be spaces around offset braces.', + [ + new CodeSample(" ['inside']]), + new CodeSample(" ['outside']]), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAnyTokenKindsFound(['[', CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + 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); + } + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $values = ['inside', 'outside']; + + return new FixerConfigurationResolverRootless('positions', [ + (new FixerOptionBuilder('positions', 'Whether spacing should be fixed inside and/or outside the offset braces.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($values)]) + ->setDefault($values) + ->getOption(), + ], $this->getName()); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..25c8d09ae8a73c4c1744ff5e48744481f5dd13f8 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.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\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶4.3, ¶4.6, ¶5. + * + * @author Marc Aubé + * @author Dariusz Rumiński + */ +final class NoSpacesInsideParenthesisFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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("isTokenKindFound('('); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->equals('(')) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + // ignore parenthesis for T_ARRAY + if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_ARRAY)) { + continue; + } + + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $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); + } + } + } + + /** + * Remove spaces from token at a given index. + * + * @param int $index + */ + private function removeSpaceAroundToken(Tokens $tokens, $index) + { + $token = $tokens[$index]; + + if ($token->isWhitespace() && false === strpos($token->getContent(), "\n")) { + $tokens->clearAt($index); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..06c79ebb1a07eb67da522092bbc7e888e9262350 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php @@ -0,0 +1,114 @@ + + * Dariusz Rumiń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\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 +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + 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() + && 1 === Preg::match('/(.*)\h$/', $token->getContent(), $openTagMatches) + && 1 === Preg::match('/^(\R)(.*)$/s', $tokens[$index + 1]->getContent(), $whitespaceMatches) + ) { + $tokens[$index] = new Token([T_OPEN_TAG, $openTagMatches[1].$whitespaceMatches[1]]); + if ('' === $whitespaceMatches[2]) { + $tokens->clearAt($index + 1); + } else { + $tokens[$index + 1] = new Token([T_WHITESPACE, $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) || 1 !== 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/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..139afa24aabee534ced9dea60e4fb75bf8003a8e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.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\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class NoWhitespaceInBlankLineFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Remove trailing whitespace at the end of blank lines.', + [new CodeSample("isWhitespace()) { + $this->fixWhitespaceToken($tokens, $i); + } + } + } + + /** + * @param int $index + */ + private function fixWhitespaceToken(Tokens $tokens, $index) + { + $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); + if ('' !== $content) { + $tokens[$index] = new Token([T_WHITESPACE, $content]); + } else { + $tokens->clearAt($index); + } + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php new file mode 100644 index 0000000000000000000000000000000000000000..a8a59dd9aca4b75ac908f621c6471ba004e882ec --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.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\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +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 +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'A PHP file without end tag must always end with a single empty line feed.', + [ + new CodeSample("count(); + + if ($count && !$tokens[$count - 1]->isGivenKind([T_INLINE_HTML, T_CLOSE_TAG, T_OPEN_TAG])) { + $tokens->ensureWhitespaceAtIndex($count - 1, 1, $this->whitespacesConfig->getLineEnding()); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/WhitespacesAwareFixerInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/WhitespacesAwareFixerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..7f1c22c48cf8998fab92b5abf47acf1d7b05795a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Fixer/WhitespacesAwareFixerInterface.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\Fixer; + +use PhpCsFixer\WhitespacesFixerConfig; + +/** + * @author Dariusz Rumiński + */ +interface WhitespacesAwareFixerInterface extends FixerInterface +{ + public function setWhitespacesConfig(WhitespacesFixerConfig $config); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.php new file mode 100644 index 0000000000000000000000000000000000000000..38bc92e31b0028f41df9b7b6de15ba84f715427f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.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\FixerConfiguration; + +/** + * @author ntzm + * + * @internal + * + * @todo 3.0 Drop this class + */ +final class AliasedFixerOption implements FixerOptionInterface +{ + /** + * @var FixerOptionInterface + */ + private $fixerOption; + + /** + * @var string + */ + private $alias; + + public function __construct(FixerOptionInterface $fixerOption, $alias) + { + $this->fixerOption = $fixerOption; + $this->alias = $alias; + } + + /** + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->fixerOption->getName(); + } + + /** + * {@inheritdoc} + */ + public function getDescription() + { + return $this->fixerOption->getDescription(); + } + + /** + * {@inheritdoc} + */ + public function hasDefault() + { + return $this->fixerOption->hasDefault(); + } + + /** + * {@inheritdoc} + */ + public function getDefault() + { + return $this->fixerOption->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function getAllowedTypes() + { + return $this->fixerOption->getAllowedTypes(); + } + + /** + * {@inheritdoc} + */ + public function getAllowedValues() + { + return $this->fixerOption->getAllowedValues(); + } + + /** + * {@inheritdoc} + */ + public function getNormalizer() + { + return $this->fixerOption->getNormalizer(); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..61e814c484f370561c2c6768717acf0564d58e8c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.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\FixerConfiguration; + +/** + * @author ntzm + * + * @internal + * + * @todo 3.0 Drop this class + */ +final class AliasedFixerOptionBuilder +{ + /** + * @var FixerOptionBuilder + */ + private $optionBuilder; + + /** + * @var string + */ + private $alias; + + public function __construct(FixerOptionBuilder $optionBuilder, $alias) + { + $this->optionBuilder = $optionBuilder; + $this->alias = $alias; + } + + /** + * @param mixed $default + * + * @return $this + */ + public function setDefault($default) + { + $this->optionBuilder->setDefault($default); + + return $this; + } + + /** + * @param string[] $allowedTypes + * + * @return $this + */ + public function setAllowedTypes(array $allowedTypes) + { + $this->optionBuilder->setAllowedTypes($allowedTypes); + + return $this; + } + + /** + * @return $this + */ + public function setAllowedValues(array $allowedValues) + { + $this->optionBuilder->setAllowedValues($allowedValues); + + return $this; + } + + /** + * @return $this + */ + public function setNormalizer(\Closure $normalizer) + { + $this->optionBuilder->setNormalizer($normalizer); + + return $this; + } + + /** + * @return AliasedFixerOption + */ + public function getOption() + { + return new AliasedFixerOption( + $this->optionBuilder->getOption(), + $this->alias + ); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.php new file mode 100644 index 0000000000000000000000000000000000000000..bc4410b78215d6f0d905382fcd7bbd98be49b01d --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.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\FixerConfiguration; + +/** + * @internal + */ +final class AllowedValueSubset +{ + private $allowedValues; + + public function __construct(array $allowedValues) + { + $this->allowedValues = $allowedValues; + } + + /** + * Checks whether the given values are a subset of the allowed ones. + * + * @param mixed $values the value to validate + * + * @return bool + */ + public function __invoke($values) + { + if (!\is_array($values)) { + return false; + } + + foreach ($values as $value) { + if (!\in_array($value, $this->allowedValues, true)) { + return false; + } + } + + return true; + } + + public function getAllowedValues() + { + return $this->allowedValues; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.php new file mode 100644 index 0000000000000000000000000000000000000000..f498ea3fcd0c3e4281f1b9ab8bac09408eb2e64b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.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\FixerConfiguration; + +final class DeprecatedFixerOption implements DeprecatedFixerOptionInterface +{ + /** + * @var FixerOptionInterface + */ + private $option; + + /** + * @var string + */ + private $deprecationMessage; + + /** + * @param string $deprecationMessage + */ + public function __construct(FixerOptionInterface $option, $deprecationMessage) + { + $this->option = $option; + $this->deprecationMessage = $deprecationMessage; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->option->getName(); + } + + /** + * {@inheritdoc} + */ + public function getDescription() + { + return $this->option->getDescription(); + } + + /** + * {@inheritdoc} + */ + public function hasDefault() + { + return $this->option->hasDefault(); + } + + /** + * {@inheritdoc} + */ + public function getDefault() + { + return $this->option->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function getAllowedTypes() + { + return $this->option->getAllowedTypes(); + } + + /** + * {@inheritdoc} + */ + public function getAllowedValues() + { + return $this->option->getAllowedValues(); + } + + /** + * {@inheritdoc} + */ + public function getNormalizer() + { + return $this->option->getNormalizer(); + } + + /** + * @return string + */ + public function getDeprecationMessage() + { + return $this->deprecationMessage; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOptionInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..fc847985c69db75a0396efcdf7211e3a0ddf29fb --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOptionInterface.php @@ -0,0 +1,21 @@ + + * Dariusz 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 +{ + /** + * @return string + */ + public function getDeprecationMessage(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..29efdbd77bf0b7b7daab8f37891e1a3b4c698112 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.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\FixerConfiguration; + +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\OptionsResolver; + +final class FixerConfigurationResolver implements FixerConfigurationResolverInterface +{ + /** + * @var FixerOptionInterface[] + */ + private $options = []; + + /** + * @var string[] + */ + private $registeredNames = []; + + /** + * @param iterable $options + */ + public function __construct($options) + { + foreach ($options as $option) { + $this->addOption($option); + } + + if (empty($this->registeredNames)) { + throw new \LogicException('Options cannot be empty.'); + } + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->options; + } + + /** + * {@inheritdoc} + */ + public function resolve(array $options) + { + $resolver = new OptionsResolver(); + + foreach ($this->options as $option) { + $name = $option->getName(); + + if ($option instanceof AliasedFixerOption) { + $alias = $option->getAlias(); + + if (\array_key_exists($alias, $options)) { + if (\array_key_exists($name, $options)) { + throw new InvalidOptionsException(sprintf('Aliased option %s/%s is passed multiple times.', $name, $alias)); + } + + @trigger_error(sprintf('Option "%s" is deprecated, use "%s" instead.', $alias, $name), E_USER_DEPRECATED); + + $options[$name] = $options[$alias]; + unset($options[$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 function ($values) use ($allowedValue) { + return $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($options); + } + + /** + * @throws \LogicException when the option is already defined + * + * @return $this + */ + private function addOption(FixerOptionInterface $option) + { + $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; + + return $this; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..099f5f12390ec7a75f106b8eb40400803dad74f9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.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\FixerConfiguration; + +interface FixerConfigurationResolverInterface +{ + /** + * @return FixerOptionInterface[] + */ + public function getOptions(); + + /** + * @param array $configuration + * + * @return array + */ + public function resolve(array $configuration); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverRootless.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverRootless.php new file mode 100644 index 0000000000000000000000000000000000000000..32efd0b029d4ec92fe26910189dddeded4494042 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverRootless.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\FixerConfiguration; + +/** + * @internal + * + * @deprecated will be removed in 3.0 + */ +final class FixerConfigurationResolverRootless implements FixerConfigurationResolverInterface +{ + /** + * @var FixerConfigurationResolverInterface + */ + private $resolver; + + /** + * @var string + */ + private $root; + + /** + * @var string + */ + private $fixerName; + + /** + * @param string $root + * @param iterable $options + * @param string $fixerName + */ + public function __construct($root, $options, $fixerName) + { + $this->resolver = new FixerConfigurationResolver($options); + $this->fixerName = $fixerName; + + $names = array_map( + static function (FixerOptionInterface $option) { + return $option->getName(); + }, + $this->resolver->getOptions() + ); + + if (!\in_array($root, $names, true)) { + throw new \LogicException(sprintf('The "%s" option is not defined.', $root)); + } + + $this->root = $root; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->resolver->getOptions(); + } + + /** + * {@inheritdoc} + */ + public function resolve(array $options) + { + if (!empty($options) && !\array_key_exists($this->root, $options)) { + $names = array_map( + static function (FixerOptionInterface $option) { + return $option->getName(); + }, + $this->resolver->getOptions() + ); + + $passedNames = array_keys($options); + + if (!empty(array_diff($passedNames, $names))) { + $message = "Passing \"{$this->root}\" at the root of the configuration for rule \"{$this->fixerName}\" is deprecated and will not be supported in 3.0, use \"{$this->root}\" => array(...) option instead."; + + if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + throw new \RuntimeException("{$message}. This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); + } + + @trigger_error($message, E_USER_DEPRECATED); + + $options = [$this->root => $options]; + } + } + + return $this->resolver->resolve($options); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.php new file mode 100644 index 0000000000000000000000000000000000000000..55ada57812c9f5a98ad59db47839b2c9f58cbbd4 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.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\FixerConfiguration; + +final class FixerOption implements FixerOptionInterface +{ + /** + * @var string + */ + private $name; + + /** + * @var string + */ + private $description; + + /** + * @var mixed + */ + private $default; + + /** + * @var bool + */ + private $isRequired; + + /** + * @var null|string[] + */ + private $allowedTypes; + + /** + * @var null|array + */ + private $allowedValues; + + /** + * @var null|\Closure + */ + private $normalizer; + + /** + * @param string $name + * @param string $description + * @param bool $isRequired + * @param mixed $default + * @param null|string[] $allowedTypes + */ + public function __construct( + $name, + $description, + $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); + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getDescription() + { + return $this->description; + } + + /** + * {@inheritdoc} + */ + public function hasDefault() + { + return !$this->isRequired; + } + + /** + * {@inheritdoc} + */ + public function getDefault() + { + if (!$this->hasDefault()) { + throw new \LogicException('No default value defined.'); + } + + return $this->default; + } + + /** + * {@inheritdoc} + */ + public function getAllowedTypes() + { + return $this->allowedTypes; + } + + /** + * {@inheritdoc} + */ + public function getAllowedValues() + { + return $this->allowedValues; + } + + /** + * {@inheritdoc} + */ + public function getNormalizer() + { + 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. + * + * @return \Closure + */ + private function unbind(\Closure $closure) + { + return $closure->bindTo(null); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..d83957476fcb6bf6fd7fc84be89ed751ce53046c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php @@ -0,0 +1,145 @@ + + * Dariusz 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 +{ + /** + * @var string + */ + private $name; + + /** + * @var string + */ + private $description; + + /** + * @var mixed + */ + private $default; + + /** + * @var bool + */ + private $isRequired = true; + + /** + * @var null|string[] + */ + private $allowedTypes; + + /** + * @var null|array + */ + private $allowedValues; + + /** + * @var null|\Closure + */ + private $normalizer; + + /** + * @var null|string + */ + private $deprecationMessage; + + /** + * @param string $name + * @param string $description + */ + public function __construct($name, $description) + { + $this->name = $name; + $this->description = $description; + } + + /** + * @param mixed $default + * + * @return $this + */ + public function setDefault($default) + { + $this->default = $default; + $this->isRequired = false; + + return $this; + } + + /** + * @param string[] $allowedTypes + * + * @return $this + */ + public function setAllowedTypes(array $allowedTypes) + { + $this->allowedTypes = $allowedTypes; + + return $this; + } + + /** + * @return $this + */ + public function setAllowedValues(array $allowedValues) + { + $this->allowedValues = $allowedValues; + + return $this; + } + + /** + * @return $this + */ + public function setNormalizer(\Closure $normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * @param null|string $deprecationMessage + * + * @return $this + */ + public function setDeprecationMessage($deprecationMessage) + { + $this->deprecationMessage = $deprecationMessage; + + return $this; + } + + /** + * @return FixerOptionInterface + */ + public function getOption() + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c682db608f4d094dd5c7212c8ecf5b82fe7e6753 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.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\FixerConfiguration; + +interface FixerOptionInterface +{ + /** + * @return string + */ + public function getName(); + + /** + * @return string + */ + public function getDescription(); + + /** + * @return bool + */ + public function hasDefault(); + + /** + * @throws \LogicException when no default value is defined + * + * @return mixed + */ + public function getDefault(); + + /** + * @return null|string[] + */ + public function getAllowedTypes(); + + /** + * @return null|array + */ + public function getAllowedValues(); + + /** + * @return null|\Closure + */ + public function getNormalizer(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/InvalidOptionsForEnvException.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerConfiguration/InvalidOptionsForEnvException.php new file mode 100644 index 0000000000000000000000000000000000000000..755d0b02d0961751fa375b8409e4c34b0b9d2c10 --- /dev/null +++ b/lib/composer/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/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.php new file mode 100644 index 0000000000000000000000000000000000000000..4e7dab7c44a4617ce29ade3dd6e7f1128d23660b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.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 + */ +final class CodeSample implements CodeSampleInterface +{ + /** + * @var string + */ + private $code; + + /** + * @var null|array + */ + private $configuration; + + /** + * @param string $code + */ + public function __construct($code, array $configuration = null) + { + $this->code = $code; + $this->configuration = $configuration; + } + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @return null|array + */ + public function getConfiguration() + { + return $this->configuration; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..877214a0a0f1c1d0987bc09a793db0b1828cf6a0 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.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\FixerDefinition; + +/** + * @author Dariusz Rumiński + */ +interface CodeSampleInterface +{ + /** + * @return string + */ + public function getCode(); + + /** + * @return null|array + */ + public function getConfiguration(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.php new file mode 100644 index 0000000000000000000000000000000000000000..3f3c1e8a18ce1fa70289f78fdc4953814d0d56c2 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.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\FixerDefinition; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class FileSpecificCodeSample implements FileSpecificCodeSampleInterface +{ + /** + * @var CodeSampleInterface + */ + private $codeSample; + + /** + * @var \SplFileInfo + */ + private $splFileInfo; + + /** + * @param string $code + */ + public function __construct( + $code, + \SplFileInfo $splFileInfo, + array $configuration = null + ) { + $this->codeSample = new CodeSample($code, $configuration); + $this->splFileInfo = $splFileInfo; + } + + /** + * {@inheritdoc} + */ + public function getCode() + { + return $this->codeSample->getCode(); + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() + { + return $this->codeSample->getConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function getSplFileInfo() + { + return $this->splFileInfo; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSampleInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSampleInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b069e30c519fa0ce1dd72c80338c8bad91cdf1ff --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSampleInterface.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\FixerDefinition; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +interface FileSpecificCodeSampleInterface extends CodeSampleInterface +{ + /** + * @return \SplFileInfo + */ + public function getSplFileInfo(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.php new file mode 100644 index 0000000000000000000000000000000000000000..3a948ce979dd14b74b94356c1b44385fbf85f001 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.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\FixerDefinition; + +/** + * @author Dariusz Rumiński + */ +final class FixerDefinition implements FixerDefinitionInterface +{ + private $riskyDescription; + private $configurationDescription; + private $defaultConfiguration; + private $codeSamples; + private $summary; + private $description; + + /** + * @param string $summary + * @param CodeSampleInterface[] $codeSamples array of samples, where single sample is [code, configuration] + * @param null|string $description + * @param null|string $configurationDescription null for non-configurable fixer + * @param null|array $defaultConfiguration null for non-configurable fixer + * @param null|string $riskyDescription null for non-risky fixer + */ + public function __construct( + $summary, + array $codeSamples, + $description = null, + $configurationDescription = null, + array $defaultConfiguration = null, + $riskyDescription = null + ) { + if (6 === \func_num_args()) { + @trigger_error('Arguments #5 and #6 of FixerDefinition::__construct() are deprecated and will be removed in 3.0, use argument #4 instead.', E_USER_DEPRECATED); + } elseif (5 === \func_num_args()) { + @trigger_error('Argument #5 of FixerDefinition::__construct() is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); + } else { + $riskyDescription = $configurationDescription; + $configurationDescription = null; + } + + $this->summary = $summary; + $this->codeSamples = $codeSamples; + $this->description = $description; + $this->configurationDescription = $configurationDescription; + $this->defaultConfiguration = $defaultConfiguration; + $this->riskyDescription = $riskyDescription; + } + + public function getSummary() + { + return $this->summary; + } + + public function getDescription() + { + return $this->description; + } + + public function getConfigurationDescription() + { + @trigger_error(sprintf('%s is deprecated and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); + + return $this->configurationDescription; + } + + public function getDefaultConfiguration() + { + @trigger_error(sprintf('%s is deprecated and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); + + return $this->defaultConfiguration; + } + + public function getRiskyDescription() + { + return $this->riskyDescription; + } + + public function getCodeSamples() + { + return $this->codeSamples; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1b1e93ea10477def6a3c4e0b22b4af5a9cda6a65 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.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\FixerDefinition; + +/** + * @author Dariusz Rumiński + */ +interface FixerDefinitionInterface +{ + /** + * @return string + */ + public function getSummary(); + + /** + * @return null|string + */ + public function getDescription(); + + /** + * @return null|string null for non-configurable fixer + * + * @deprecated will be removed in 3.0 + */ + public function getConfigurationDescription(); + + /** + * @return null|array null for non-configurable fixer + * + * @deprecated will be removed in 3.0 + */ + public function getDefaultConfiguration(); + + /** + * @return null|string null for non-risky fixer + */ + public function getRiskyDescription(); + + /** + * Array of samples, where single sample is [code, configuration]. + * + * @return CodeSampleInterface[] + */ + public function getCodeSamples(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.php new file mode 100644 index 0000000000000000000000000000000000000000..01080e7a057a2acd25241129a91ea6eb03a704dc --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.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\FixerDefinition; + +/** + * @author Andreas Möller + */ +final class VersionSpecificCodeSample implements VersionSpecificCodeSampleInterface +{ + /** + * @var CodeSampleInterface + */ + private $codeSample; + + /** + * @var VersionSpecificationInterface + */ + private $versionSpecification; + + /** + * @param string $code + */ + public function __construct( + $code, + VersionSpecificationInterface $versionSpecification, + array $configuration = null + ) { + $this->codeSample = new CodeSample($code, $configuration); + $this->versionSpecification = $versionSpecification; + } + + /** + * {@inheritdoc} + */ + public function getCode() + { + return $this->codeSample->getCode(); + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() + { + return $this->codeSample->getConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function isSuitableFor($version) + { + return $this->versionSpecification->isSatisfiedBy($version); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSampleInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSampleInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..9612770ae191e7e386f78c7c2c87b0bae3c04e40 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSampleInterface.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\FixerDefinition; + +/** + * @author Andreas Moeller + */ +interface VersionSpecificCodeSampleInterface extends CodeSampleInterface +{ + /** + * @param int $version + * + * @return bool + */ + public function isSuitableFor($version); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.php new file mode 100644 index 0000000000000000000000000000000000000000..d1459d140083e917eb27666b0e3b7644ed159684 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.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\FixerDefinition; + +/** + * @author Andreas Möller + */ +final class VersionSpecification implements VersionSpecificationInterface +{ + /** + * @var null|int + */ + private $minimum; + + /** + * @var null|int + */ + private $maximum; + + /** + * @param null|int $minimum + * @param null|int $maximum + * + * @throws \InvalidArgumentException + */ + public function __construct($minimum = null, $maximum = null) + { + if (null === $minimum && null === $maximum) { + throw new \InvalidArgumentException('Minimum or maximum need to be specified.'); + } + + if (null !== $minimum && (!\is_int($minimum) || 1 > $minimum)) { + throw new \InvalidArgumentException('Minimum needs to be either null or an integer greater than 0.'); + } + + if (null !== $maximum) { + if (!\is_int($maximum) || 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; + } + + /** + * {@inheritdoc} + */ + public function isSatisfiedBy($version) + { + if (null !== $this->minimum && $version < $this->minimum) { + return false; + } + + if (null !== $this->maximum && $version > $this->maximum) { + return false; + } + + return true; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificationInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificationInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..36ae35436d685296c847dcba8579debb1c105afb --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificationInterface.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\FixerDefinition; + +/** + * @author Andreas Möller + */ +interface VersionSpecificationInterface +{ + /** + * @param int $version + * + * @return bool + */ + public function isSatisfiedBy($version); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerFactory.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..da0605a420759d460a13110f7a6b22c52182dc92 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerFactory.php @@ -0,0 +1,254 @@ + + * Dariusz 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 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 +{ + /** + * @var FixerNameValidator + */ + private $nameValidator; + + /** + * @var FixerInterface[] + */ + private $fixers = []; + + /** + * @var FixerInterface[] Associative array of fixers with names as keys + */ + private $fixersByName = []; + + public function __construct() + { + $this->nameValidator = new FixerNameValidator(); + } + + /** + * Create instance. + * + * @return FixerFactory + */ + public static function create() + { + return new self(); + } + + public function setWhitespacesConfig(WhitespacesFixerConfig $config) + { + foreach ($this->fixers as $fixer) { + if ($fixer instanceof WhitespacesAwareFixerInterface) { + $fixer->setWhitespacesConfig($config); + } + } + + return $this; + } + + /** + * @return FixerInterface[] + */ + public function getFixers() + { + $this->fixers = Utils::sortFixers($this->fixers); + + return $this->fixers; + } + + /** + * @return $this + */ + public function registerBuiltInFixers() + { + static $builtInFixers = null; + + if (null === $builtInFixers) { + $builtInFixers = []; + + /** @var SplFileInfo $file */ + foreach (SymfonyFinder::create()->files()->in(__DIR__.'/Fixer') as $file) { + $relativeNamespace = $file->getRelativePath(); + $fixerClass = 'PhpCsFixer\\Fixer\\'.($relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); + if ('Fixer' === substr($fixerClass, -5)) { + $builtInFixers[] = $fixerClass; + } + } + } + + foreach ($builtInFixers as $class) { + $this->registerFixer(new $class(), false); + } + + return $this; + } + + /** + * @param FixerInterface[] $fixers + * + * @return $this + */ + public function registerCustomFixers(array $fixers) + { + foreach ($fixers as $fixer) { + $this->registerFixer($fixer, true); + } + + return $this; + } + + /** + * @param bool $isCustom + * + * @return $this + */ + public function registerFixer(FixerInterface $fixer, $isCustom) + { + $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) + { + $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 (!\is_array($config) || !\count($config)) { + 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. + * + * @param string $name + * + * @return bool + */ + public function hasRule($name) + { + return isset($this->fixersByName[$name]); + } + + /** + * @return null|string[] + */ + private function getFixersConflicts(FixerInterface $fixer) + { + static $conflictMap = [ + 'no_blank_lines_before_namespace' => ['single_blank_line_before_namespace'], + ]; + + $fixerName = $fixer->getName(); + + return \array_key_exists($fixerName, $conflictMap) ? $conflictMap[$fixerName] : []; + } + + /** + * @param array $fixerConflicts + * + * @return string + */ + private function generateConflictMessage(array $fixerConflicts) + { + $message = 'Rule contains conflicting fixers:'; + $report = []; + foreach ($fixerConflicts as $fixer => $fixers) { + // filter mutual conflicts + $report[$fixer] = array_filter( + $fixers, + static function ($candidate) use ($report, $fixer) { + return !\array_key_exists($candidate, $report) || !\in_array($fixer, $report[$candidate], true); + } + ); + + if (\count($report[$fixer]) > 0) { + $message .= sprintf("\n- \"%s\" with \"%s\"", $fixer, implode('", "', $report[$fixer])); + } + } + + return $message; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..1fafb70f381b7619750b57d58b7006ec2dd31803 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.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; + +use PhpCsFixer\Event\Event; + +/** + * Event that is fired when file was processed by Fixer. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class FixerFileProcessedEvent extends Event +{ + /** + * Event name. + */ + const NAME = 'fixer.file_processed'; + + const STATUS_UNKNOWN = 0; + const STATUS_INVALID = 1; + const STATUS_SKIPPED = 2; + const STATUS_NO_CHANGES = 3; + const STATUS_FIXED = 4; + const STATUS_EXCEPTION = 5; + const STATUS_LINT = 6; + + /** + * @var int + */ + private $status; + + /** + * @param int $status + */ + public function __construct($status) + { + $this->status = $status; + } + + /** + * @return int + */ + public function getStatus() + { + return $this->status; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/FixerNameValidator.php b/lib/composer/friendsofphp/php-cs-fixer/src/FixerNameValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..bfa4477047a98c2e0578a8b98c73e1dafa9dbd4a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/FixerNameValidator.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; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class FixerNameValidator +{ + /** + * @param string $name + * @param bool $isCustom + * + * @return bool + */ + public function isValid($name, $isCustom) + { + if (!$isCustom) { + return 1 === Preg::match('/^[a-z][a-z0-9_]*$/', $name); + } + + return 1 === Preg::match('/^[A-Z][a-zA-Z0-9]*\/[a-z][a-z0-9_]*$/', $name); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.php b/lib/composer/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.php new file mode 100644 index 0000000000000000000000000000000000000000..4044cd7e652666b36f0c77d28e582ad129cfe8f9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.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\Indicator; + +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class PhpUnitTestCaseIndicator +{ + public function isPhpUnitClass(Tokens $tokens, $index) + { + 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 (0 !== 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 (0 !== Preg::match('/(?:Test|TestCase)(?:Interface)?$/', $tokens[$index]->getContent())) { + return true; + } + } + + return false; + } + + /** + * @param bool $beginAtBottom whether we should start yielding PHPUnit classes from the bottom of the file + * + * @return \Generator array of [int start, int end] indexes from sooner to later classes + */ + public function findPhpUnitClasses(Tokens $tokens, $beginAtBottom = true) + { + $direction = $beginAtBottom ? -1 : 1; + + for ($index = 1 === $direction ? 0 : $tokens->count() - 1; $tokens->offsetExists($index); $index += $direction) { + if (!$tokens[$index]->isGivenKind(T_CLASS) || !$this->isPhpUnitClass($tokens, $index)) { + continue; + } + + $startIndex = $tokens->getNextTokenOfKind($index, ['{'], false); + + if (null === $startIndex) { + return; + } + + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex); + yield [$startIndex, $endIndex]; + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.php b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.php new file mode 100644 index 0000000000000000000000000000000000000000..9367cf0515f4e03aca14a19e102fcd94d8e66edc --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.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\Linter; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class CachingLinter implements LinterInterface +{ + /** + * @var LinterInterface + */ + private $sublinter; + + /** + * @var array + */ + private $cache = []; + + public function __construct(LinterInterface $linter) + { + $this->sublinter = $linter; + } + + /** + * {@inheritdoc} + */ + public function isAsync() + { + return $this->sublinter->isAsync(); + } + + /** + * {@inheritdoc} + */ + public function lintFile($path) + { + $checksum = crc32(file_get_contents($path)); + + if (!isset($this->cache[$checksum])) { + $this->cache[$checksum] = $this->sublinter->lintFile($path); + } + + return $this->cache[$checksum]; + } + + /** + * {@inheritdoc} + */ + public function lintSource($source) + { + $checksum = crc32($source); + + if (!isset($this->cache[$checksum])) { + $this->cache[$checksum] = $this->sublinter->lintSource($source); + } + + return $this->cache[$checksum]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Linter/Linter.php b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/Linter.php new file mode 100644 index 0000000000000000000000000000000000000000..199eacc8cd939056646ebd29f278bd8ee45b5bfe --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/Linter.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\Linter; + +/** + * Handle PHP code linting process. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class Linter implements LinterInterface +{ + /** + * @var LinterInterface + */ + private $sublinter; + + /** + * @param null|string $executable PHP executable, null for autodetection + */ + public function __construct($executable = null) + { + try { + $this->sublinter = new TokenizerLinter(); + } catch (UnavailableLinterException $e) { + $this->sublinter = new ProcessLinter($executable); + } + } + + /** + * {@inheritdoc} + */ + public function isAsync() + { + return $this->sublinter->isAsync(); + } + + /** + * {@inheritdoc} + */ + public function lintFile($path) + { + return $this->sublinter->lintFile($path); + } + + /** + * {@inheritdoc} + */ + public function lintSource($source) + { + return $this->sublinter->lintSource($source); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Linter/LinterInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/LinterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..efaa2a4c7b3627378e308790b766dfa166e88b0a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/LinterInterface.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; + +/** + * Interface for PHP code linting process manager. + * + * @author Dariusz Rumiński + */ +interface LinterInterface +{ + /** + * @return bool + */ + public function isAsync(); + + /** + * Lint PHP file. + * + * @param string $path + * + * @return LintingResultInterface + */ + public function lintFile($path); + + /** + * Lint PHP code. + * + * @param string $source + * + * @return LintingResultInterface + */ + public function lintSource($source); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Linter/LintingException.php b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/LintingException.php new file mode 100644 index 0000000000000000000000000000000000000000..7ce51fe3d3e228e081058f666d2338a97b5e512d --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/LintingException.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\Linter; + +/** + * @author Dariusz Rumiński + */ +class LintingException extends \RuntimeException +{ +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Linter/LintingResultInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/LintingResultInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..75f56a190ea610cd8739cdc6252fc94408500922 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/LintingResultInterface.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 + */ +interface LintingResultInterface +{ + /** + * Check if linting process was successful and raise LintingException if not. + */ + public function check(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.php b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.php new file mode 100644 index 0000000000000000000000000000000000000000..05d820e4f8128f17521f596966dc48a8bdabefa8 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.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\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 +{ + /** + * @var FileRemoval + */ + private $fileRemoval; + + /** + * @var ProcessLinterProcessBuilder + */ + private $processBuilder; + + /** + * Temporary file for code linting. + * + * @var null|string + */ + private $temporaryFile; + + /** + * @param null|string $executable PHP executable, null for autodetection + */ + public function __construct($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 (false === strpos($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); + } + } + + /** + * {@inheritdoc} + */ + public function isAsync() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function lintFile($path) + { + return new ProcessLintingResult($this->createProcessForFile($path)); + } + + /** + * {@inheritdoc} + */ + public function lintSource($source) + { + return new ProcessLintingResult($this->createProcessForSource($source)); + } + + /** + * @param string $path path to file + * + * @return Process + */ + private function createProcessForFile($path) + { + // 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 + * + * @return Process + */ + private function createProcessForSource($source) + { + if (null === $this->temporaryFile) { + $this->temporaryFile = tempnam('.', '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/lib/composer/friendsofphp/php-cs-fixer/src/Linter/ProcessLinterProcessBuilder.php b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/ProcessLinterProcessBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..5cce9e12d48f234abcffd64d2bd9426adf051976 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/ProcessLinterProcessBuilder.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\Linter; + +use Symfony\Component\Process\Process; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ProcessLinterProcessBuilder +{ + /** + * @var string + */ + private $executable; + + /** + * @param string $executable PHP executable + */ + public function __construct($executable) + { + $this->executable = $executable; + } + + /** + * @param string $path + * + * @return Process + */ + public function build($path) + { + return new Process([ + $this->executable, + '-l', + $path, + ]); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.php b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.php new file mode 100644 index 0000000000000000000000000000000000000000..b04faccfb07a60b7920b4a48c967cb618c8476eb --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.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\Linter; + +use Symfony\Component\Process\Process; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ProcessLintingResult implements LintingResultInterface +{ + /** + * @var bool + */ + private $isSuccessful; + + /** + * @var Process + */ + private $process; + + public function __construct(Process $process) + { + $this->process = $process; + } + + /** + * {@inheritdoc} + */ + public function check() + { + if (!$this->isSuccessful()) { + // on some systems stderr is used, but on others, it's not + throw new LintingException($this->process->getErrorOutput() ?: $this->process->getOutput(), $this->process->getExitCode()); + } + } + + private function isSuccessful() + { + if (null === $this->isSuccessful) { + $this->process->wait(); + $this->isSuccessful = $this->process->isSuccessful(); + } + + return $this->isSuccessful; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.php b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.php new file mode 100644 index 0000000000000000000000000000000000000000..90d31d52ca44b85ba139a169afd8df4b503f88fd --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.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\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 __construct() + { + if (false === \defined('TOKEN_PARSE')) { + throw new UnavailableLinterException('Cannot use tokenizer as linter.'); + } + } + + /** + * {@inheritdoc} + */ + public function isAsync() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function lintFile($path) + { + return $this->lintSource(FileReader::createSingleton()->read($path)); + } + + /** + * {@inheritdoc} + */ + public function lintSource($source) + { + try { + // To lint, we will parse the source into Tokens. + // During that process, it might throw ParseError. + // 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 (\ParseError $e) { + return new TokenizerLintingResult($e); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.php b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.php new file mode 100644 index 0000000000000000000000000000000000000000..67c3510a5309de5f634eea8b5994897a16a2814a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.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\Linter; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class TokenizerLintingResult implements LintingResultInterface +{ + /** + * @var null|\ParseError + */ + private $error; + + public function __construct(\ParseError $error = null) + { + $this->error = $error; + } + + /** + * {@inheritdoc} + */ + public function check() + { + if (null !== $this->error) { + throw new LintingException( + sprintf('PHP Parse error: %s on line %d.', $this->error->getMessage(), $this->error->getLine()), + $this->error->getCode(), + $this->error + ); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.php b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.php new file mode 100644 index 0000000000000000000000000000000000000000..6e8cd63f2f84346fa4f3de906300fc00ace3c34e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.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\Linter; + +/** + * Exception that is thrown when the chosen linter is not available on the environment. + * + * @author Dariusz Rumiński + */ +class UnavailableLinterException extends \RuntimeException +{ +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/PharChecker.php b/lib/composer/friendsofphp/php-cs-fixer/src/PharChecker.php new file mode 100644 index 0000000000000000000000000000000000000000..81fae9fc0c65ad74724e72540f2dcbdc947159d2 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/PharChecker.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; + +/** + * @internal + */ +final class PharChecker implements PharCheckerInterface +{ + /** + * {@inheritdoc} + */ + public function checkFileValidity($filename) + { + 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/lib/composer/friendsofphp/php-cs-fixer/src/PharCheckerInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/PharCheckerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8de1395d3542359b996cbc1aea928cf2fe4e0b6f --- /dev/null +++ b/lib/composer/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 +{ + /** + * @param string $filename + * + * @return null|string the invalidity reason if any, null otherwise + */ + public function checkFileValidity($filename); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Preg.php b/lib/composer/friendsofphp/php-cs-fixer/src/Preg.php new file mode 100644 index 0000000000000000000000000000000000000000..d4161ad64cecb2520cd883be76ba4d3c858df5a4 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Preg.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; + +/** + * 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 string $pattern + * @param string $subject + * @param null|string[] $matches + * @param int $flags + * @param int $offset + * + * @throws PregException + * + * @return int + */ + public static function match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0) + { + $result = @preg_match(self::addUtf8Modifier($pattern), $subject, $matches, $flags, $offset); + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { + return $result; + } + + $result = @preg_match(self::removeUtf8Modifier($pattern), $subject, $matches, $flags, $offset); + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { + return $result; + } + + throw self::newPregException(preg_last_error(), __METHOD__, (array) $pattern); + } + + /** + * @param string $pattern + * @param string $subject + * @param null|string[] $matches + * @param int $flags + * @param int $offset + * + * @throws PregException + * + * @return int + */ + public static function matchAll($pattern, $subject, &$matches = null, $flags = PREG_PATTERN_ORDER, $offset = 0) + { + $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(), __METHOD__, (array) $pattern); + } + + /** + * @param string|string[] $pattern + * @param string|string[] $replacement + * @param string|string[] $subject + * @param int $limit + * @param null|int $count + * + * @throws PregException + * + * @return string|string[] + */ + public static function replace($pattern, $replacement, $subject, $limit = -1, &$count = null) + { + $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(), __METHOD__, (array) $pattern); + } + + /** + * @param string|string[] $pattern + * @param callable $callback + * @param string|string[] $subject + * @param int $limit + * @param null|int $count + * + * @throws PregException + * + * @return string|string[] + */ + public static function replaceCallback($pattern, $callback, $subject, $limit = -1, &$count = null) + { + $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(), __METHOD__, (array) $pattern); + } + + /** + * @param string $pattern + * @param string $subject + * @param int $limit + * @param int $flags + * + * @throws PregException + * + * @return string[] + */ + public static function split($pattern, $subject, $limit = -1, $flags = 0) + { + $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(), __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 int $error + * @param string $method + * @param string[] $patterns + * + * @return PregException + */ + private static function newPregException($error, $method, array $patterns) + { + foreach ($patterns as $pattern) { + $last = error_get_last(); + $result = @preg_match($pattern, ''); + + if (false !== $result) { + continue; + } + + $code = preg_last_error(); + $next = error_get_last(); + + if ($last !== $next) { + $message = sprintf( + '(code: %d) %s', + $code, + preg_replace('~preg_[a-z_]+[()]{2}: ~', '', $next['message']) + ); + } else { + $message = sprintf('(code: %d)', $code); + } + + 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.', $method), $error); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/PregException.php b/lib/composer/friendsofphp/php-cs-fixer/src/PregException.php new file mode 100644 index 0000000000000000000000000000000000000000..79ff1e04b8d70ffc05e32e2a6fc3e081fecb74e1 --- /dev/null +++ b/lib/composer/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/lib/composer/friendsofphp/php-cs-fixer/src/Report/CheckstyleReporter.php b/lib/composer/friendsofphp/php-cs-fixer/src/Report/CheckstyleReporter.php new file mode 100644 index 0000000000000000000000000000000000000000..6ce5ab2285995f81eefc83253684ade3935f24ff --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Report/CheckstyleReporter.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\Report; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Kévin Gomez + * + * @internal + */ +final class CheckstyleReporter implements ReporterInterface +{ + /** + * {@inheritdoc} + */ + public function getFormat() + { + return 'checkstyle'; + } + + /** + * {@inheritdoc} + */ + public function generate(ReportSummary $reportSummary) + { + if (!\extension_loaded('dom')) { + throw new \RuntimeException('Cannot generate report! `ext-dom` is not available!'); + } + + $dom = new \DOMDocument('1.0', 'UTF-8'); + $checkstyles = $dom->appendChild($dom->createElement('checkstyle')); + + 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(); + } + + /** + * @param string $appliedFixer + * + * @return \DOMElement + */ + private function createError(\DOMDocument $dom, $appliedFixer) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Report/GitlabReporter.php b/lib/composer/friendsofphp/php-cs-fixer/src/Report/GitlabReporter.php new file mode 100644 index 0000000000000000000000000000000000000000..7845ca74aa113d1ff47116035782cba2bbd70029 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Report/GitlabReporter.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\Report; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * Generates a report according to gitlabs subset of codeclimate json files. + * + * @see https://github.com/codeclimate/spec/blob/master/SPEC.md#data-types + * + * @author Hans-Christian Otto + * + * @internal + */ +final class GitlabReporter implements ReporterInterface +{ + public function getFormat() + { + return 'gitlab'; + } + + /** + * Process changed files array. Returns generated report. + * + * @return string + */ + public function generate(ReportSummary $reportSummary) + { + $report = []; + foreach ($reportSummary->getChanged() as $fileName => $change) { + foreach ($change['appliedFixers'] as $fixerName) { + $report[] = [ + 'description' => $fixerName, + 'fingerprint' => md5($fileName.$fixerName), + 'location' => [ + 'path' => $fileName, + 'lines' => [ + 'begin' => 0, // line numbers are required in the format, but not available to reports + ], + ], + ]; + } + } + + $jsonString = json_encode($report); + + return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($jsonString) : $jsonString; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Report/JsonReporter.php b/lib/composer/friendsofphp/php-cs-fixer/src/Report/JsonReporter.php new file mode 100644 index 0000000000000000000000000000000000000000..2772b2d81351409927aa065d372ef2473465307b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Report/JsonReporter.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\Report; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class JsonReporter implements ReporterInterface +{ + /** + * {@inheritdoc} + */ + public function getFormat() + { + return 'json'; + } + + /** + * {@inheritdoc} + */ + public function generate(ReportSummary $reportSummary) + { + $jFiles = []; + + foreach ($reportSummary->getChanged() as $file => $fixResult) { + $jfile = ['name' => $file]; + + if ($reportSummary->shouldAddAppliedFixers()) { + $jfile['appliedFixers'] = $fixResult['appliedFixers']; + } + + if (!empty($fixResult['diff'])) { + $jfile['diff'] = $fixResult['diff']; + } + + $jFiles[] = $jfile; + } + + $json = [ + 'files' => $jFiles, + ]; + + if (null !== $reportSummary->getTime()) { + $json['time'] = [ + 'total' => round($reportSummary->getTime() / 1000, 3), + ]; + } + + if (null !== $reportSummary->getMemory()) { + $json['memory'] = round($reportSummary->getMemory() / 1024 / 1024, 3); + } + + $json = json_encode($json); + + return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($json) : $json; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Report/JunitReporter.php b/lib/composer/friendsofphp/php-cs-fixer/src/Report/JunitReporter.php new file mode 100644 index 0000000000000000000000000000000000000000..fa747b35a089a5f4675dd18e0d3c8e0f298bc743 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Report/JunitReporter.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\Report; + +use PhpCsFixer\Preg; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class JunitReporter implements ReporterInterface +{ + /** + * {@inheritdoc} + */ + public function getFormat() + { + return 'junit'; + } + + /** + * {@inheritdoc} + */ + public function generate(ReportSummary $reportSummary) + { + 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'); + + if (\count($reportSummary->getChanged())) { + $this->createFailedTestCases($dom, $testsuite, $reportSummary); + } else { + $this->createSuccessTestCase($dom, $testsuite); + } + + if ($reportSummary->getTime()) { + $testsuite->setAttribute( + 'time', + sprintf( + '%.3f', + $reportSummary->getTime() / 1000 + ) + ); + } + + $dom->formatOutput = true; + + return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML(); + } + + private function createSuccessTestCase(\DOMDocument $dom, \DOMElement $testsuite) + { + $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) + { + $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 string $file + * @param bool $shouldAddAppliedFixers + * + * @return \DOMElement + */ + private function createFailedTestCase(\DOMDocument $dom, $file, array $fixResult, $shouldAddAppliedFixers) + { + $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 (!empty($fixResult['diff'])) { + $failureContent .= "\nDiff:\n---------------\n\n".$fixResult['diff']; + } + + $failure->appendChild($dom->createCDATASection(trim($failureContent))); + + return $testcase; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Report/ReportSummary.php b/lib/composer/friendsofphp/php-cs-fixer/src/Report/ReportSummary.php new file mode 100644 index 0000000000000000000000000000000000000000..a276649e751a6163b3e8f166ad9962a862060e6c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Report/ReportSummary.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\Report; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ReportSummary +{ + /** + * @var bool + */ + private $addAppliedFixers; + + /** + * @var array + */ + private $changed; + + /** + * @var bool + */ + private $isDecoratedOutput; + + /** + * @var bool + */ + private $isDryRun; + + /** + * @var int + */ + private $memory; + + /** + * @var int + */ + private $time; + + /** + * @param int $time duration in milliseconds + * @param int $memory memory usage in bytes + * @param bool $addAppliedFixers + * @param bool $isDryRun + * @param bool $isDecoratedOutput + */ + public function __construct( + array $changed, + $time, + $memory, + $addAppliedFixers, + $isDryRun, + $isDecoratedOutput + ) { + $this->changed = $changed; + $this->time = $time; + $this->memory = $memory; + $this->addAppliedFixers = $addAppliedFixers; + $this->isDryRun = $isDryRun; + $this->isDecoratedOutput = $isDecoratedOutput; + } + + /** + * @return bool + */ + public function isDecoratedOutput() + { + return $this->isDecoratedOutput; + } + + /** + * @return bool + */ + public function isDryRun() + { + return $this->isDryRun; + } + + /** + * @return array + */ + public function getChanged() + { + return $this->changed; + } + + /** + * @return int + */ + public function getMemory() + { + return $this->memory; + } + + /** + * @return int + */ + public function getTime() + { + return $this->time; + } + + /** + * @return bool + */ + public function shouldAddAppliedFixers() + { + return $this->addAppliedFixers; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Report/ReporterFactory.php b/lib/composer/friendsofphp/php-cs-fixer/src/Report/ReporterFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..fce909952444481337df4aaf11022c2530ecf386 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Report/ReporterFactory.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\Report; + +use Symfony\Component\Finder\Finder as SymfonyFinder; +use Symfony\Component\Finder\SplFileInfo; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class ReporterFactory +{ + /** @var ReporterInterface[] */ + private $reporters = []; + + public static function create() + { + return new self(); + } + + public function registerBuiltInReporters() + { + /** @var null|string[] $builtInReporters */ + static $builtInReporters; + + if (null === $builtInReporters) { + $builtInReporters = []; + + /** @var SplFileInfo $file */ + 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) + { + $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 string[] + */ + public function getFormats() + { + $formats = array_keys($this->reporters); + sort($formats); + + return $formats; + } + + /** + * @param string $format + * + * @return ReporterInterface + */ + public function getReporter($format) + { + if (!isset($this->reporters[$format])) { + throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is not registered.', $format)); + } + + return $this->reporters[$format]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Report/ReporterInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Report/ReporterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..6138558b31da8bafc4529c127a4d38011b9ed8d5 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Report/ReporterInterface.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\Report; + +/** + * @author Boris Gorbylev + */ +interface ReporterInterface +{ + /** + * @return string + */ + public function getFormat(); + + /** + * Process changed files array. Returns generated report. + * + * @return string + */ + public function generate(ReportSummary $reportSummary); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Report/TextReporter.php b/lib/composer/friendsofphp/php-cs-fixer/src/Report/TextReporter.php new file mode 100644 index 0000000000000000000000000000000000000000..c473d84ce9daddf4b398772ba5f066ce1b4d080c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Report/TextReporter.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\Report; + +use PhpCsFixer\Differ\DiffConsoleFormatter; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class TextReporter implements ReporterInterface +{ + /** + * {@inheritdoc} + */ + public function getFormat() + { + return 'txt'; + } + + /** + * {@inheritdoc} + */ + public function generate(ReportSummary $reportSummary) + { + $output = ''; + + $i = 0; + foreach ($reportSummary->getChanged() as $file => $fixResult) { + ++$i; + $output .= sprintf('%4d) %s', $i, $file); + + if ($reportSummary->shouldAddAppliedFixers()) { + $output .= $this->getAppliedFixers($reportSummary->isDecoratedOutput(), $fixResult); + } + + $output .= $this->getDiff($reportSummary->isDecoratedOutput(), $fixResult); + $output .= PHP_EOL; + } + + return $output.$this->getFooter($reportSummary->getTime(), $reportSummary->getMemory(), $reportSummary->isDryRun()); + } + + /** + * @param bool $isDecoratedOutput + * + * @return string + */ + private function getAppliedFixers($isDecoratedOutput, array $fixResult) + { + return sprintf( + $isDecoratedOutput ? ' (%s)' : ' (%s)', + implode(', ', $fixResult['appliedFixers']) + ); + } + + /** + * @param bool $isDecoratedOutput + * + * @return string + */ + private function getDiff($isDecoratedOutput, array $fixResult) + { + if (empty($fixResult['diff'])) { + return ''; + } + + $diffFormatter = new DiffConsoleFormatter($isDecoratedOutput, sprintf( + ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', + PHP_EOL, + PHP_EOL + )); + + return PHP_EOL.$diffFormatter->format($fixResult['diff']).PHP_EOL; + } + + /** + * @param int $time + * @param int $memory + * @param bool $isDryRun + * + * @return string + */ + private function getFooter($time, $memory, $isDryRun) + { + if (0 === $time || 0 === $memory) { + return ''; + } + + return PHP_EOL.sprintf( + '%s all files in %.3f seconds, %.3f MB memory used'.PHP_EOL, + $isDryRun ? 'Checked' : 'Fixed', + $time / 1000, + $memory / 1024 / 1024 + ); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Report/XmlReporter.php b/lib/composer/friendsofphp/php-cs-fixer/src/Report/XmlReporter.php new file mode 100644 index 0000000000000000000000000000000000000000..080715aada916bdce15e7441e813a4fe10e1a3ed --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Report/XmlReporter.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\Report; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class XmlReporter implements ReporterInterface +{ + /** + * {@inheritdoc} + */ + public function getFormat() + { + return 'xml'; + } + + /** + * {@inheritdoc} + */ + public function generate(ReportSummary $reportSummary) + { + 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); + + $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)); + } + + if (!empty($fixResult['diff'])) { + $fileXML->appendChild($this->createDiffElement($dom, $fixResult)); + } + } + + 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 \DOMDocument $dom + * + * @return \DOMElement + */ + private function createAppliedFixersElement($dom, array $fixResult) + { + $appliedFixersXML = $dom->createElement('applied_fixers'); + + foreach ($fixResult['appliedFixers'] as $appliedFixer) { + $appliedFixerXML = $dom->createElement('applied_fixer'); + $appliedFixerXML->setAttribute('name', $appliedFixer); + $appliedFixersXML->appendChild($appliedFixerXML); + } + + return $appliedFixersXML; + } + + /** + * @return \DOMElement + */ + private function createDiffElement(\DOMDocument $dom, array $fixResult) + { + $diffXML = $dom->createElement('diff'); + $diffXML->appendChild($dom->createCDATASection($fixResult['diff'])); + + return $diffXML; + } + + /** + * @param float $time + * + * @return \DOMElement + */ + private function createTimeElement($time, \DOMDocument $dom) + { + $time = round($time / 1000, 3); + + $timeXML = $dom->createElement('time'); + $timeXML->setAttribute('unit', 's'); + $timeTotalXML = $dom->createElement('total'); + $timeTotalXML->setAttribute('value', (string) $time); + $timeXML->appendChild($timeTotalXML); + + return $timeXML; + } + + /** + * @param float $memory + * + * @return \DOMElement + */ + private function createMemoryElement($memory, \DOMDocument $dom) + { + $memory = round($memory / 1024 / 1024, 3); + + $memoryXML = $dom->createElement('memory'); + $memoryXML->setAttribute('value', (string) $memory); + $memoryXML->setAttribute('unit', 'MB'); + + return $memoryXML; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/RuleSet.php b/lib/composer/friendsofphp/php-cs-fixer/src/RuleSet.php new file mode 100644 index 0000000000000000000000000000000000000000..51b65ffa9845299eb031800305a0d7e5383319b8 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/RuleSet.php @@ -0,0 +1,534 @@ + + * Dariusz 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\FunctionNotation\NativeFunctionInvocationFixer; +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; + +/** + * Set of rules to be used by fixer. + * + * @author Dariusz Rumiński + * @author SpacePossum + * + * @internal + */ +final class RuleSet implements RuleSetInterface +{ + private $setDefinitions = [ + '@PSR1' => [ + 'encoding' => true, + 'full_opening_tag' => true, + ], + '@PSR2' => [ + '@PSR1' => true, + 'blank_line_after_namespace' => true, + 'braces' => true, + 'class_definition' => true, + 'constant_case' => true, + 'elseif' => true, + 'function_declaration' => true, + 'indentation_type' => true, + 'line_ending' => true, + 'lowercase_keywords' => true, + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], + 'no_break_comment' => true, + 'no_closing_tag' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_inside_parenthesis' => 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, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'visibility_required' => true, + ], + '@Symfony' => [ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => [ + 'statements' => ['return'], + ], + 'braces' => [ + 'allow_single_line_closure' => true, + ], + 'cast_spaces' => true, + 'class_attributes_separation' => ['elements' => ['method']], + 'class_definition' => ['single_line' => true], + 'concat_space' => true, + 'declare_equal_normalize' => true, + 'function_typehint_space' => true, + 'include' => true, + 'increment_style' => true, + 'lowercase_cast' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'method_argument_space' => true, + 'native_function_casing' => true, + 'native_function_type_declaration_casing' => true, + 'new_with_braces' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => ['tokens' => [ + 'curly_brace_block', + 'extra', + 'parenthesis_brace_block', + 'square_brace_block', + 'throw', + 'use', + ]], + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => true, + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_around_offset' => true, + 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'allow_unused_params' => true], + 'no_trailing_comma_in_list_call' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_curly_braces' => ['namespaces' => true], + 'no_unused_imports' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'normalize_index_brace' => true, + 'object_operator_without_whitespace' => true, + 'ordered_imports' => true, + 'php_unit_fqcn_annotation' => true, + 'phpdoc_align' => [ + // @TODO: on 3.0 switch whole rule to `=> true`, currently we use custom config that will be default on 3.0 + 'tags' => [ + 'method', + 'param', + 'property', + 'return', + 'throws', + 'type', + 'var', + ], + ], + 'phpdoc_annotation_without_dot' => true, + 'phpdoc_indent' => true, + 'phpdoc_inline_tag' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_alias_tag' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + '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, + 'return_type_declaration' => true, + 'semicolon_after_instruction' => true, + 'short_scalar_cast' => true, + 'single_blank_line_before_namespace' => true, + 'single_class_element_per_statement' => true, + 'single_line_comment_style' => [ + 'comment_types' => ['hash'], + ], + 'single_line_throw' => true, + 'single_quote' => true, + 'single_trait_insert_per_statement' => true, + 'space_after_semicolon' => [ + 'remove_in_empty_for_expressions' => true, + ], + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'ternary_operator_spaces' => true, + 'trailing_comma_in_multiline_array' => true, + 'trim_array_spaces' => true, + 'unary_operator_spaces' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => true, + ], + '@Symfony:risky' => [ + 'dir_constant' => true, + 'ereg_to_preg' => true, + 'error_suppression' => true, + 'fopen_flag_order' => true, + 'fopen_flags' => ['b_mode' => false], + 'function_to_constant' => [ + 'functions' => [ + 'get_called_class', + 'get_class', + 'get_class_this', + 'php_sapi_name', + 'phpversion', + 'pi', + ], + ], + 'implode_call' => true, + 'is_null' => true, + 'modernize_types_casting' => true, + 'native_constant_invocation' => [ + 'fix_built_in' => false, + 'include' => [ + 'DIRECTORY_SEPARATOR', + 'PHP_SAPI', + 'PHP_VERSION_ID', + ], + 'scope' => 'namespaced', + ], + 'native_function_invocation' => [ + 'include' => [NativeFunctionInvocationFixer::SET_COMPILER_OPTIMIZED], + 'scope' => 'namespaced', + 'strict' => true, + ], + 'no_alias_functions' => true, + 'no_homoglyph_names' => true, + 'no_unneeded_final_method' => true, + 'non_printable_character' => true, + 'php_unit_construct' => true, + 'php_unit_mock_short_will_return' => true, + 'psr4' => true, + 'self_accessor' => true, + 'set_type_to_cast' => true, + ], + '@PhpCsFixer' => [ + '@Symfony' => true, + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'blank_line_before_statement' => true, + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'compact_nullable_typehint' => true, + 'escape_implicit_backslashes' => true, + 'explicit_indirect_variable' => true, + 'explicit_string_variable' => true, + 'fully_qualified_strict_types' => 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_alternative_syntax' => true, + 'no_binary_string' => true, + 'no_extra_blank_lines' => ['tokens' => [ + 'break', + 'continue', + 'curly_brace_block', + 'extra', + 'parenthesis_brace_block', + 'return', + 'square_brace_block', + 'throw', + 'use', + ]], + 'no_null_property_initialization' => true, + 'no_short_echo_tag' => true, + 'no_superfluous_elseif' => true, + 'no_unset_cast' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_class_elements' => true, + 'php_unit_internal_class' => true, + 'php_unit_method_casing' => true, + 'php_unit_ordered_covers' => true, + 'php_unit_test_class_requires_covers' => true, + 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_order' => true, + 'phpdoc_types_order' => true, + 'phpdoc_var_annotation_correct_order' => true, + 'protected_to_private' => true, + 'return_assignment' => true, + 'simple_to_complex_string_variable' => true, + 'single_line_comment_style' => true, + 'single_line_throw' => false, + ], + '@PhpCsFixer:risky' => [ + '@Symfony:risky' => true, + 'comment_to_phpdoc' => true, + 'final_internal_class' => true, + 'logical_operators' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unset_on_property' => true, + 'php_unit_set_up_tear_down_visibility' => true, + 'php_unit_strict' => true, + 'php_unit_test_annotation' => true, + 'php_unit_test_case_static_method_calls' => true, + 'strict_comparison' => true, + 'strict_param' => true, + 'string_line_ending' => true, + ], + '@DoctrineAnnotation' => [ + 'doctrine_annotation_array_assignment' => [ + 'operator' => ':', + ], + 'doctrine_annotation_braces' => true, + 'doctrine_annotation_indentation' => true, + 'doctrine_annotation_spaces' => [ + 'before_array_assignments_colon' => false, + ], + ], + '@PHP56Migration' => [], + '@PHP56Migration:risky' => [ + 'pow_to_exponentiation' => true, + ], + '@PHP70Migration' => [ + '@PHP56Migration' => true, + 'ternary_to_null_coalescing' => true, + ], + '@PHP70Migration:risky' => [ + '@PHP56Migration:risky' => true, + 'combine_nested_dirname' => true, + 'declare_strict_types' => true, + 'non_printable_character' => [ + 'use_escape_sequences_in_strings' => true, + ], + 'random_api_migration' => ['replacements' => [ + 'mt_rand' => 'random_int', + 'rand' => 'random_int', + ]], + ], + '@PHP71Migration' => [ + '@PHP70Migration' => true, + 'visibility_required' => ['elements' => [ + 'const', + 'method', + 'property', + ]], + ], + '@PHP71Migration:risky' => [ + '@PHP70Migration:risky' => true, + 'void_return' => true, + ], + '@PHP73Migration' => [ + '@PHP71Migration' => true, + 'heredoc_indentation' => true, + ], + '@PHPUnit30Migration:risky' => [ + 'php_unit_dedicate_assert' => ['target' => PhpUnitTargetVersion::VERSION_3_0], + ], + '@PHPUnit32Migration:risky' => [ + '@PHPUnit30Migration:risky' => true, + 'php_unit_no_expectation_annotation' => ['target' => PhpUnitTargetVersion::VERSION_3_2], + ], + '@PHPUnit35Migration:risky' => [ + '@PHPUnit32Migration:risky' => true, + 'php_unit_dedicate_assert' => ['target' => PhpUnitTargetVersion::VERSION_3_5], + ], + '@PHPUnit43Migration:risky' => [ + '@PHPUnit35Migration:risky' => true, + 'php_unit_no_expectation_annotation' => ['target' => PhpUnitTargetVersion::VERSION_4_3], + ], + '@PHPUnit48Migration:risky' => [ + '@PHPUnit43Migration:risky' => true, + 'php_unit_namespaced' => ['target' => PhpUnitTargetVersion::VERSION_4_8], + ], + '@PHPUnit50Migration:risky' => [ + '@PHPUnit48Migration:risky' => true, + 'php_unit_dedicate_assert' => true, + ], + '@PHPUnit52Migration:risky' => [ + '@PHPUnit50Migration:risky' => true, + 'php_unit_expectation' => ['target' => PhpUnitTargetVersion::VERSION_5_2], + ], + '@PHPUnit54Migration:risky' => [ + '@PHPUnit52Migration:risky' => true, + 'php_unit_mock' => ['target' => PhpUnitTargetVersion::VERSION_5_4], + ], + '@PHPUnit55Migration:risky' => [ + '@PHPUnit54Migration:risky' => true, + 'php_unit_mock' => ['target' => PhpUnitTargetVersion::VERSION_5_5], + ], + '@PHPUnit56Migration:risky' => [ + '@PHPUnit55Migration:risky' => true, + 'php_unit_dedicate_assert' => ['target' => PhpUnitTargetVersion::VERSION_5_6], + 'php_unit_expectation' => ['target' => PhpUnitTargetVersion::VERSION_5_6], + ], + '@PHPUnit57Migration:risky' => [ + '@PHPUnit56Migration:risky' => true, + 'php_unit_namespaced' => ['target' => PhpUnitTargetVersion::VERSION_5_7], + ], + '@PHPUnit60Migration:risky' => [ + '@PHPUnit57Migration:risky' => true, + 'php_unit_namespaced' => ['target' => PhpUnitTargetVersion::VERSION_6_0], + ], + '@PHPUnit75Migration:risky' => [ + '@PHPUnit60Migration:risky' => true, + 'php_unit_dedicate_assert_internal_type' => ['target' => PhpUnitTargetVersion::VERSION_7_5], + ], + ]; + + /** + * Set that was used to generate group of rules. + * + * The key is name of rule or set, value is bool if the rule/set should be used. + * + * @var array + */ + private $set; + + /** + * Group of rules generated from input set. + * + * The key is name of rule, value is bool if the rule/set should be used. + * The key must not point to any set. + * + * @var array + */ + private $rules; + + public function __construct(array $set = []) + { + foreach ($set as $key => $value) { + if (\is_int($key)) { + throw new \InvalidArgumentException(sprintf('Missing value for "%s" rule/set.', $value)); + } + } + + $this->set = $set; + $this->resolveSet(); + } + + public static function create(array $set = []) + { + return new self($set); + } + + /** + * {@inheritdoc} + */ + public function hasRule($rule) + { + return \array_key_exists($rule, $this->rules); + } + + /** + * {@inheritdoc} + */ + public function getRuleConfiguration($rule) + { + 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]; + } + + /** + * {@inheritdoc} + */ + public function getRules() + { + return $this->rules; + } + + /** + * {@inheritdoc} + */ + public function getSetDefinitionNames() + { + return array_keys($this->setDefinitions); + } + + /** + * @param string $name name of set + * + * @return array + */ + private function getSetDefinition($name) + { + if (!isset($this->setDefinitions[$name])) { + throw new \InvalidArgumentException(sprintf('Set "%s" does not exist.', $name)); + } + + return $this->setDefinitions[$name]; + } + + /** + * Resolve input set into group of rules. + * + * @return $this + */ + private function resolveSet() + { + $rules = $this->set; + $resolvedRules = []; + + // expand sets + foreach ($rules as $name => $value) { + if ('@' === $name[0]) { + 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; + + return $this; + } + + /** + * 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. + * + * @param string $setName + * @param bool $setValue + * + * @return array + */ + private function resolveSubset($setName, $setValue) + { + $rules = $this->getSetDefinition($setName); + foreach ($rules as $name => $value) { + if ('@' === $name[0]) { + $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/lib/composer/friendsofphp/php-cs-fixer/src/RuleSetInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/RuleSetInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c52eccc0ce60af05511ba34b239d6f878dc8238b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/RuleSetInterface.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; + +/** + * Set of rules to be used by fixer. + * + * Example of set: ["@PSR2" => true, "@PSR1" => false, "strict" => true]. + * + * @author Dariusz Rumiński + */ +interface RuleSetInterface +{ + public function __construct(array $set = []); + + public static function create(array $set = []); + + /** + * Get configuration for given rule. + * + * @param string $rule name of rule + * + * @return null|array + */ + public function getRuleConfiguration($rule); + + /** + * Get all rules from rules set. + * + * @return array + */ + public function getRules(); + + /** + * Get names of all set definitions. + * + * @return string[] + */ + public function getSetDefinitionNames(); + + /** + * Check given rule is in rules set. + * + * @param string $rule name of rule + * + * @return bool + */ + public function hasRule($rule); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingIterator.php b/lib/composer/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..cd6bcb608376aa894eff40c3d51f46abbdc3ed54 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingIterator.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\Runner; + +use PhpCsFixer\Linter\LinterInterface; +use PhpCsFixer\Linter\LintingResultInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class FileCachingLintingIterator extends \CachingIterator +{ + /** + * @var LintingResultInterface + */ + private $currentResult; + + /** + * @var LinterInterface + */ + private $linter; + + /** + * @var LintingResultInterface + */ + private $nextResult; + + public function __construct(\Iterator $iterator, LinterInterface $linter) + { + parent::__construct($iterator); + + $this->linter = $linter; + } + + public function currentLintingResult() + { + return $this->currentResult; + } + + public function next() + { + parent::next(); + + $this->currentResult = $this->nextResult; + + if ($this->hasNext()) { + $this->nextResult = $this->handleItem($this->getInnerIterator()->current()); + } + } + + public function rewind() + { + 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) + { + return $this->linter->lintFile($file->getRealPath()); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.php b/lib/composer/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..7a4ac229afa42cc502277310dcfec75b72d69b3f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.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\Runner; + +use PhpCsFixer\Cache\CacheManagerInterface; +use PhpCsFixer\Event\Event; +use PhpCsFixer\FileReader; +use PhpCsFixer\FixerFileProcessedEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class FileFilterIterator extends \FilterIterator +{ + /** + * @var null|EventDispatcherInterface + */ + private $eventDispatcher; + + /** + * @var CacheManagerInterface + */ + private $cacheManager; + + /** + * @var array + */ + private $visitedElements = []; + + public function __construct( + \Traversable $iterator, + EventDispatcherInterface $eventDispatcher = null, + CacheManagerInterface $cacheManager + ) { + if (!$iterator instanceof \Iterator) { + $iterator = new \IteratorIterator($iterator); + } + + parent::__construct($iterator); + + $this->eventDispatcher = $eventDispatcher; + $this->cacheManager = $cacheManager; + } + + public function accept() + { + $file = $this->current(); + if (!$file instanceof \SplFileInfo) { + throw new \RuntimeException( + sprintf( + 'Expected instance of "\SplFileInfo", got "%s".', + \is_object($file) ? \get_class($file) : \gettype($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; + } + + /** + * @param string $name + */ + private function dispatchEvent($name, Event $event) + { + if (null === $this->eventDispatcher) { + return; + } + + // BC compatibility < Sf 4.3 + if ( + !$this->eventDispatcher instanceof \Symfony\Contracts\EventDispatcher\EventDispatcherInterface + ) { + $this->eventDispatcher->dispatch($name, $event); + + return; + } + + $this->eventDispatcher->dispatch($event, $name); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Runner/FileLintingIterator.php b/lib/composer/friendsofphp/php-cs-fixer/src/Runner/FileLintingIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..fbd9b34008deaf6aac4ce12d7c5facfe07303639 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Runner/FileLintingIterator.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\Runner; + +use PhpCsFixer\Linter\LinterInterface; +use PhpCsFixer\Linter\LintingResultInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class FileLintingIterator extends \IteratorIterator +{ + /** + * @var LintingResultInterface + */ + private $currentResult; + + /** + * @var null|LinterInterface + */ + private $linter; + + public function __construct(\Iterator $iterator, LinterInterface $linter) + { + parent::__construct($iterator); + + $this->linter = $linter; + } + + /** + * @return null|LintingResultInterface + */ + public function currentLintingResult() + { + return $this->currentResult; + } + + public function next() + { + parent::next(); + + $this->currentResult = $this->valid() ? $this->handleItem($this->current()) : null; + } + + public function rewind() + { + parent::rewind(); + + $this->currentResult = $this->valid() ? $this->handleItem($this->current()) : null; + } + + private function handleItem(\SplFileInfo $file) + { + return $this->linter->lintFile($file->getRealPath()); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Runner/Runner.php b/lib/composer/friendsofphp/php-cs-fixer/src/Runner/Runner.php new file mode 100644 index 0000000000000000000000000000000000000000..088be8da064817530e5f499264c002dc2671e325 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Runner/Runner.php @@ -0,0 +1,307 @@ + + * Dariusz 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\Event\Event; +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; + +/** + * @author Dariusz Rumiński + */ +final class Runner +{ + /** + * @var DifferInterface + */ + private $differ; + + /** + * @var DirectoryInterface + */ + private $directory; + + /** + * @var null|EventDispatcherInterface + */ + private $eventDispatcher; + + /** + * @var ErrorsManager + */ + private $errorsManager; + + /** + * @var CacheManagerInterface + */ + private $cacheManager; + + /** + * @var bool + */ + private $isDryRun; + + /** + * @var LinterInterface + */ + private $linter; + + /** + * @var \Traversable + */ + private $finder; + + /** + * @var FixerInterface[] + */ + private $fixers; + + /** + * @var bool + */ + private $stopOnViolation; + + public function __construct( + $finder, + array $fixers, + DifferInterface $differ, + EventDispatcherInterface $eventDispatcher = null, + ErrorsManager $errorsManager, + LinterInterface $linter, + $isDryRun, + CacheManagerInterface $cacheManager, + DirectoryInterface $directory = null, + $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 + */ + public function fix() + { + $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 ($fixInfo) { + $name = $this->directory->getRelativePathTo($file); + $changed[$name] = $fixInfo; + + if ($this->stopOnViolation) { + break; + } + } + } + + return $changed; + } + + private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResult) + { + $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; + } + + $old = FileReader::createSingleton()->read($file->getRealPath()); + + Tokens::setLegacyMode(false); + + $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 (\Exception $e) { + $this->processException($name, $e); + + return; + } catch (\ParseError $e) { + $this->dispatchEvent( + FixerFileProcessedEvent::NAME, + new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_LINT) + ); + + $this->errorsManager->report(new Error(Error::TYPE_LINT, $name, $e)); + + return; + } catch (\Throwable $e) { + $this->processException($name, $e); + + return; + } + + $fixInfo = null; + + if (!empty($appliedFixers)) { + $new = $tokens->generateCode(); + $newHash = $tokens->getCodeHash(); + } + + // We need to check if content was changed and then applied changes. + // But we can't simple 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), + ]; + + 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; + } + + if (!$this->isDryRun) { + if (false === @file_put_contents($file->getRealPath(), $new)) { + $error = error_get_last(); + + throw new IOException( + sprintf('Failed to write file "%s", "%s".', $file->getPathname(), $error ? $error['message'] : 'no reason available'), + 0, + null, + $file->getRealPath() + ); + } + } + } + + $this->cacheManager->setFile($name, $new); + + $this->dispatchEvent( + FixerFileProcessedEvent::NAME, + new FixerFileProcessedEvent($fixInfo ? FixerFileProcessedEvent::STATUS_FIXED : FixerFileProcessedEvent::STATUS_NO_CHANGES) + ); + + return $fixInfo; + } + + /** + * Process an exception that occurred. + * + * @param string $name + * @param \Throwable $e + */ + private function processException($name, $e) + { + $this->dispatchEvent( + FixerFileProcessedEvent::NAME, + new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_EXCEPTION) + ); + + $this->errorsManager->report(new Error(Error::TYPE_EXCEPTION, $name, $e)); + } + + /** + * @param string $name + */ + private function dispatchEvent($name, Event $event) + { + if (null === $this->eventDispatcher) { + return; + } + + // BC compatibility < Sf 4.3 + if ( + !$this->eventDispatcher instanceof \Symfony\Contracts\EventDispatcher\EventDispatcherInterface + ) { + $this->eventDispatcher->dispatch($name, $event); + + return; + } + + $this->eventDispatcher->dispatch($event, $name); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/StdinFileInfo.php b/lib/composer/friendsofphp/php-cs-fixer/src/StdinFileInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..2eedfa341a56c4bdfcda552511be335e8d164ff9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/StdinFileInfo.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; + +/** + * @author Davi Koscianski Vidal + * + * @internal + */ +final class StdinFileInfo extends \SplFileInfo +{ + public function __construct() + { + } + + public function __toString() + { + return $this->getRealPath(); + } + + public function getRealPath() + { + // 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() + { + return 0; + } + + public function getBasename($suffix = null) + { + return $this->getFilename(); + } + + public function getCTime() + { + return 0; + } + + public function getExtension() + { + return '.php'; + } + + public function getFileInfo($className = null) + { + throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); + } + + public function getFilename() + { + /* + * 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() + { + return 0; + } + + public function getInode() + { + return 0; + } + + public function getLinkTarget() + { + return ''; + } + + public function getMTime() + { + return 0; + } + + public function getOwner() + { + return 0; + } + + public function getPath() + { + return ''; + } + + public function getPathInfo($className = null) + { + throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); + } + + public function getPathname() + { + return $this->getFilename(); + } + + public function getPerms() + { + return 0; + } + + public function getSize() + { + return 0; + } + + public function getType() + { + return 'file'; + } + + public function isDir() + { + return false; + } + + public function isExecutable() + { + return false; + } + + public function isFile() + { + return true; + } + + public function isLink() + { + return false; + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return false; + } + + public function openFile($openMode = 'r', $useIncludePath = false, $context = null) + { + throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); + } + + public function setFileClass($className = null) + { + } + + public function setInfoClass($className = null) + { + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Test/AbstractFixerTestCase.php b/lib/composer/friendsofphp/php-cs-fixer/src/Test/AbstractFixerTestCase.php new file mode 100644 index 0000000000000000000000000000000000000000..748c10f57f61525917222ee8f71f151d24023e78 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Test/AbstractFixerTestCase.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\Test; + +use PhpCsFixer\Tests\Test\AbstractFixerTestCase as BaseAbstractFixerTestCase; + +/** + * @TODO 3.0 While removing, remove loading `tests/Test` from `autoload` section of `composer.json`. + * + * @deprecated since v2.4 + */ +abstract class AbstractFixerTestCase extends BaseAbstractFixerTestCase +{ + public function __construct($name = null, array $data = [], $dataName = '') + { + @trigger_error( + sprintf( + 'The "%s" class is deprecated. You should stop using it, as it will be removed in 3.0 version.', + __CLASS__ + ), + E_USER_DEPRECATED + ); + + parent::__construct($name, $data, $dataName); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Test/AbstractIntegrationTestCase.php b/lib/composer/friendsofphp/php-cs-fixer/src/Test/AbstractIntegrationTestCase.php new file mode 100644 index 0000000000000000000000000000000000000000..fe7c20cfb59fe71c337a379034266fbea42f2114 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Test/AbstractIntegrationTestCase.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\Test; + +use PhpCsFixer\Tests\Test\AbstractIntegrationTestCase as BaseAbstractIntegrationTestCase; + +/** + * @TODO 3.0 While removing, remove loading `tests/Test` from `autoload` section of `composer.json`. + * + * @deprecated since v2.4 + */ +abstract class AbstractIntegrationTestCase extends BaseAbstractIntegrationTestCase +{ + public function __construct($name = null, array $data = [], $dataName = '') + { + @trigger_error( + sprintf( + 'The "%s" class is deprecated. You should stop using it, as it will be removed in 3.0 version.', + __CLASS__ + ), + E_USER_DEPRECATED + ); + + parent::__construct($name, $data, $dataName); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Test/AccessibleObject.php b/lib/composer/friendsofphp/php-cs-fixer/src/Test/AccessibleObject.php new file mode 100644 index 0000000000000000000000000000000000000000..3a6c8d4f152eecd4d90994e6642d4921865884a9 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Test/AccessibleObject.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\Test; + +/** + * @author Dariusz Rumiński + * + * @deprecated since v2.5. Use "php-cs-fixer/accessible-object" package instead. + */ +final class AccessibleObject +{ + private $object; + private $reflection; + + /** + * @param object $object + */ + public function __construct($object) + { + @trigger_error( + sprintf( + 'The "%s" class is deprecated and will be removed in 3.0 version. Use "php-cs-fixer/accessible-object" package instead.', + __CLASS__ + ), + E_USER_DEPRECATED + ); + + $this->object = $object; + $this->reflection = new \ReflectionClass($object); + } + + public function __call($name, array $arguments) + { + if (!method_exists($this->object, $name)) { + throw new \LogicException(sprintf('Cannot call non existing method %s->%s.', \get_class($this->object), $name)); + } + + $method = $this->reflection->getMethod($name); + $method->setAccessible(true); + + return $method->invokeArgs($this->object, $arguments); + } + + public function __isset($name) + { + try { + $value = $this->{$name}; + } catch (\LogicException $e) { + return false; + } + + return isset($value); + } + + public function __get($name) + { + if (!property_exists($this->object, $name)) { + throw new \LogicException(sprintf('Cannot get non existing property %s->%s.', \get_class($this->object), $name)); + } + + $property = $this->reflection->getProperty($name); + $property->setAccessible(true); + + return $property->getValue($this->object); + } + + public function __set($name, $value) + { + if (!property_exists($this->object, $name)) { + throw new \LogicException(sprintf('Cannot set non existing property %s->%s = %s.', \get_class($this->object), $name, var_export($value, true))); + } + + $property = $this->reflection->getProperty($name); + $property->setAccessible(true); + + $property->setValue($this->object, $value); + } + + public static function create($object) + { + return new self($object); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Test/IntegrationCase.php b/lib/composer/friendsofphp/php-cs-fixer/src/Test/IntegrationCase.php new file mode 100644 index 0000000000000000000000000000000000000000..59cfbce3e37f53d24a553959c8c3c844b0073874 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Test/IntegrationCase.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\Test; + +use PhpCsFixer\RuleSet; +use PhpCsFixer\Tests\Test\IntegrationCase as BaseIntegrationCase; + +/** + * @author Dariusz Rumiński + * + * @TODO 3.0 While removing, remove loading `tests/Test` from `autoload` section of `composer.json`. + * + * @deprecated since v2.4 + */ +final class IntegrationCase +{ + /** + * @var BaseIntegrationCase + */ + private $base; + + /** + * @param string $fileName + * @param string $title + * @param string $expectedCode + * @param null|string $inputCode + */ + public function __construct( + $fileName, + $title, + array $settings, + array $requirements, + array $config, + RuleSet $ruleset, + $expectedCode, + $inputCode + ) { + $this->base = new BaseIntegrationCase( + $fileName, + $title, + $settings, + $requirements, + $config, + $ruleset, + $expectedCode, + $inputCode + ); + @trigger_error( + sprintf( + 'The "%s" class is deprecated. You should stop using it, as it will be removed in 3.0 version.', + __CLASS__ + ), + E_USER_DEPRECATED + ); + } + + public function hasInputCode() + { + return $this->base->hasInputCode(); + } + + public function getConfig() + { + return $this->base->getConfig(); + } + + public function getExpectedCode() + { + return $this->base->getExpectedCode(); + } + + public function getFileName() + { + return $this->base->getFileName(); + } + + public function getInputCode() + { + return $this->base->getInputCode(); + } + + public function getRequirement($name) + { + return $this->base->getRequirement($name); + } + + public function getRequirements() + { + return $this->base->getRequirements(); + } + + public function getRuleset() + { + return $this->base->getRuleset(); + } + + public function getSettings() + { + return $this->base->getSettings(); + } + + public function getTitle() + { + return $this->base->getTitle(); + } + + /** + * @return bool + * + * @deprecated since v2.1, on ~2.1 line IntegrationTest check whether different priorities are required is done automatically, this method will be removed on v3.0 + */ + public function shouldCheckPriority() + { + @trigger_error( + sprintf( + 'The "%s" method is deprecated. You should stop using it, as it will be removed in 3.0 version.', + __METHOD__ + ), + E_USER_DEPRECATED + ); + + $settings = $this->base->getSettings(); + + return isset($settings['checkPriority']) ? $settings['checkPriority'] : true; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..5cca9a81d6b7b36d4a2ccdf48c58be7bc852c1e7 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.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\Tokenizer; + +use PhpCsFixer\Utils; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +abstract class AbstractTransformer implements TransformerInterface +{ + /** + * {@inheritdoc} + */ + public function getName() + { + $nameParts = explode('\\', static::class); + $name = substr(end($nameParts), 0, -\strlen('Transformer')); + + return Utils::camelCaseToUnderscore($name); + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 0; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.php new file mode 100644 index 0000000000000000000000000000000000000000..4b747fb162566f4431a1ef2511579c32a35d1a63 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.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\Tokenizer\Analyzer\Analysis; + +/** + * @internal + */ +final class ArgumentAnalysis +{ + /** + * The default value of the argument. + * + * @var null|string + */ + private $default; + + /** + * The name of the argument. + * + * @var string + */ + private $name; + + /** + * The index where the name is located in the supplied Tokens object. + * + * @var int + */ + private $nameIndex; + + /** + * The type analysis of the argument. + * + * @var null|TypeAnalysis + */ + private $typeAnalysis; + + /** + * @param string $name + * @param int $nameIndex + * @param null|string $default + */ + public function __construct($name, $nameIndex, $default, TypeAnalysis $typeAnalysis = null) + { + $this->name = $name; + $this->nameIndex = $nameIndex; + $this->default = $default ?: null; + $this->typeAnalysis = $typeAnalysis ?: null; + } + + /** + * @return null|string + */ + public function getDefault() + { + return $this->default; + } + + /** + * @return bool + */ + public function hasDefault() + { + return null !== $this->default; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return int + */ + public function getNameIndex() + { + return $this->nameIndex; + } + + /** + * @return null|TypeAnalysis + */ + public function getTypeAnalysis() + { + return $this->typeAnalysis; + } + + /** + * @return bool + */ + public function hasTypeAnalysis() + { + return null !== $this->typeAnalysis; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceAnalysis.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceAnalysis.php new file mode 100644 index 0000000000000000000000000000000000000000..4457db7d3c3c8308b0a9f040d6687e4bfca388c8 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceAnalysis.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\Tokenizer\Analyzer\Analysis; + +/** + * @internal + */ +final class NamespaceAnalysis implements StartEndTokenAwareAnalysis +{ + /** + * The fully qualified namespace name. + * + * @var string + */ + private $fullName; + + /** + * The short version of the namespace. + * + * @var string + */ + private $shortName; + + /** + * The start index of the namespace declaration in the analyzed Tokens. + * + * @var int + */ + private $startIndex; + + /** + * The end index of the namespace declaration in the analyzed Tokens. + * + * @var int + */ + private $endIndex; + + /** + * The start index of the scope of the namespace in the analyzed Tokens. + * + * @var int + */ + private $scopeStartIndex; + + /** + * The end index of the scope of the namespace in the analyzed Tokens. + * + * @var int + */ + private $scopeEndIndex; + + /** + * @param string $fullName + * @param string $shortName + * @param int $startIndex + * @param int $endIndex + * @param int $scopeStartIndex + * @param int $scopeEndIndex + */ + public function __construct($fullName, $shortName, $startIndex, $endIndex, $scopeStartIndex, $scopeEndIndex) + { + $this->fullName = $fullName; + $this->shortName = $shortName; + $this->startIndex = $startIndex; + $this->endIndex = $endIndex; + $this->scopeStartIndex = $scopeStartIndex; + $this->scopeEndIndex = $scopeEndIndex; + } + + /** + * @return string + */ + public function getFullName() + { + return $this->fullName; + } + + /** + * @return string + */ + public function getShortName() + { + return $this->shortName; + } + + /** + * @return int + */ + public function getStartIndex() + { + return $this->startIndex; + } + + /** + * @return int + */ + public function getEndIndex() + { + return $this->endIndex; + } + + /** + * @return int + */ + public function getScopeStartIndex() + { + return $this->scopeStartIndex; + } + + /** + * @return int + */ + public function getScopeEndIndex() + { + return $this->scopeEndIndex; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php new file mode 100644 index 0000000000000000000000000000000000000000..687aeff09834f73e45b11698a295bb78ebe8c02a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php @@ -0,0 +1,147 @@ + + * Dariusz 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 NamespaceUseAnalysis implements StartEndTokenAwareAnalysis +{ + const TYPE_CLASS = 1; + const TYPE_FUNCTION = 2; + const TYPE_CONSTANT = 3; + + /** + * The fully qualified use namespace. + * + * @var string + */ + private $fullName; + + /** + * The short version of use namespace or the alias name in case of aliased use statements. + * + * @var string + */ + private $shortName; + + /** + * Is the use statement being aliased? + * + * @var bool + */ + private $isAliased; + + /** + * The start index of the namespace declaration in the analyzed Tokens. + * + * @var int + */ + private $startIndex; + + /** + * The end index of the namespace declaration in the analyzed Tokens. + * + * @var int + */ + private $endIndex; + + /** + * The type of import: class, function or constant. + * + * @var int + */ + private $type; + + /** + * @param string $fullName + * @param string $shortName + * @param bool $isAliased + * @param int $startIndex + * @param int $endIndex + * @param int $type + */ + public function __construct($fullName, $shortName, $isAliased, $startIndex, $endIndex, $type) + { + $this->fullName = $fullName; + $this->shortName = $shortName; + $this->isAliased = $isAliased; + $this->startIndex = $startIndex; + $this->endIndex = $endIndex; + $this->type = $type; + } + + /** + * @return string + */ + public function getFullName() + { + return $this->fullName; + } + + /** + * @return string + */ + public function getShortName() + { + return $this->shortName; + } + + /** + * @return bool + */ + public function isAliased() + { + return $this->isAliased; + } + + /** + * @return int + */ + public function getStartIndex() + { + return $this->startIndex; + } + + /** + * @return int + */ + public function getEndIndex() + { + return $this->endIndex; + } + + /** + * @return bool + */ + public function isClass() + { + return self::TYPE_CLASS === $this->type; + } + + /** + * @return bool + */ + public function isFunction() + { + return self::TYPE_FUNCTION === $this->type; + } + + /** + * @return bool + */ + public function isConstant() + { + return self::TYPE_CONSTANT === $this->type; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/StartEndTokenAwareAnalysis.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/StartEndTokenAwareAnalysis.php new file mode 100644 index 0000000000000000000000000000000000000000..1cadfff488e9829a15ec532330652e8b73528d2f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/StartEndTokenAwareAnalysis.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\Tokenizer\Analyzer\Analysis; + +interface StartEndTokenAwareAnalysis +{ + /** + * The start index of the analyzed subject inside of the Tokens. + * + * @return int + */ + public function getStartIndex(); + + /** + * The end index of the analyzed subject inside of the Tokens. + * + * @return int + */ + public function getEndIndex(); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php new file mode 100644 index 0000000000000000000000000000000000000000..ed1b9272dd78997026c66cef23b1a7712ce8fc8c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.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\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 array + */ + private static $reservedTypes = [ + 'array', + 'bool', + 'callable', + 'int', + 'iterable', + 'float', + 'mixed', + 'numeric', + 'object', + 'resource', + 'self', + 'string', + 'void', + ]; + + /** + * @var string + */ + private $name; + + /** + * @var int + */ + private $startIndex; + + /** + * @var int + */ + private $endIndex; + + /** + * @var bool + */ + private $nullable; + + /** + * @param string $name + * @param int $startIndex + * @param int $endIndex + */ + public function __construct($name, $startIndex, $endIndex) + { + $this->name = $name; + $this->nullable = false; + + if (0 === strpos($name, '?')) { + $this->name = substr($name, 1); + $this->nullable = true; + } + + $this->startIndex = $startIndex; + $this->endIndex = $endIndex; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return int + */ + public function getStartIndex() + { + return $this->startIndex; + } + + /** + * @return int + */ + public function getEndIndex() + { + return $this->endIndex; + } + + /** + * @return bool + */ + public function isReservedType() + { + return \in_array($this->name, self::$reservedTypes, true); + } + + /** + * @return bool + */ + public function isNullable() + { + return $this->nullable; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..d463e8931e5b3a85be376f13ac5fdd492f0be1df --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.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\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + * @author Vladimir Reznichenko + * + * @internal + */ +final class ArgumentsAnalyzer +{ + /** + * Count amount of parameters in a function/method reference. + * + * @param int $openParenthesis + * @param int $closeParenthesis + * + * @return int + */ + public function countArguments(Tokens $tokens, $openParenthesis, $closeParenthesis) + { + return \count($this->getArguments($tokens, $openParenthesis, $closeParenthesis)); + } + + /** + * Returns start and end token indexes 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 ')'. + * + * @param int $openParenthesis + * @param int $closeParenthesis + * + * @return array + */ + public function getArguments(Tokens $tokens, $openParenthesis, $closeParenthesis) + { + $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; + } + + /** + * @param int $argumentStart + * @param int $argumentEnd + * + * @return ArgumentAnalysis + */ + public function getArgumentInfo(Tokens $tokens, $argumentStart, $argumentEnd) + { + $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 ($token->isComment() || $token->isWhitespace() || $token->isGivenKind(T_ELLIPSIS) || $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(); + } + } + + return new ArgumentAnalysis( + $info['name'], + $info['name_index'], + $info['default'], + $info['type'] ? new TypeAnalysis($info['type'], $info['type_index_start'], $info['type_index_end']) : null + ); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..8a1eb91324b3659a6171828699ed97256e26a22d --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.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\Analyzer; + +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + * + * @internal + */ +final class BlocksAnalyzer +{ + /** + * @param null|int $openIndex + * @param null|int $closeIndex + * + * @return bool + */ + public function isBlock(Tokens $tokens, $openIndex, $closeIndex) + { + if (null === $openIndex || null === $closeIndex) { + return false; + } + + if (!$tokens->offsetExists($openIndex)) { + return false; + } + + if (!$tokens->offsetExists($closeIndex)) { + return false; + } + + $blockType = $this->getBlockType($tokens[$openIndex]); + + if (null === $blockType) { + return false; + } + + return $closeIndex === $tokens->findBlockEnd($blockType, $openIndex); + } + + /** + * @return null|int + */ + private function getBlockType(Token $token) + { + foreach (Tokens::getBlockEdgeDefinitions() as $blockType => $definition) { + if ($token->equals($definition['start'])) { + return $blockType; + } + } + + return null; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..a374d97f0a727029a71f3607703742e9be8e071e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.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\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class ClassyAnalyzer +{ + /** + * @param int $index + * + * @return bool + */ + public function isClassyInvocation(Tokens $tokens, $index) + { + $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 (\in_array(strtolower($token->getContent()), ['bool', 'float', 'int', 'iterable', 'object', 'parent', 'self', 'string', 'void'], true)) { + 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, 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_COLON, CT::T_USE_TRAIT])) { + 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/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..d345f66d22018fabf2979199b855776cd1ea572d --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php @@ -0,0 +1,314 @@ + + * Dariusz 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 + * @author SpacePossum + * + * @internal + */ +final class CommentsAnalyzer +{ + const TYPE_HASH = 1; + const TYPE_DOUBLE_SLASH = 2; + const TYPE_SLASH_ASTERISK = 3; + + /** + * @param int $index + * + * @return bool + */ + public function isHeaderComment(Tokens $tokens, $index) + { + 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 + * + * @param int $index + * + * @return bool + */ + public function isBeforeStructuralElement(Tokens $tokens, $index) + { + $token = $tokens[$index]; + + if (!$token->isGivenKind([T_COMMENT, T_DOC_COMMENT])) { + throw new \InvalidArgumentException('Given index must point to a comment.'); + } + + $nextIndex = $index; + do { + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + } while (null !== $nextIndex && $tokens[$nextIndex]->equals('(')); + + if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) { + return false; + } + + $nextToken = $tokens[$nextIndex]; + + if ($this->isStructuralElement($nextToken)) { + return true; + } + + if ($this->isValidControl($tokens, $token, $nextIndex)) { + return true; + } + + if ($this->isValidVariable($tokens, $nextIndex)) { + return true; + } + + if ($this->isValidLanguageConstruct($tokens, $token, $nextIndex)) { + return true; + } + + return false; + } + + /** + * Return array of indices that are part of a comment started at given index. + * + * @param int $index T_COMMENT index + * + * @return null|array + */ + public function getCommentBlockIndices(Tokens $tokens, $index) + { + 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 + * + * @return bool + */ + private function isStructuralElement(Token $token) + { + static $skip = [ + T_PRIVATE, + T_PROTECTED, + T_PUBLIC, + T_VAR, + T_FUNCTION, + T_ABSTRACT, + T_CONST, + T_NAMESPACE, + T_REQUIRE, + T_REQUIRE_ONCE, + T_INCLUDE, + T_INCLUDE_ONCE, + T_FINAL, + T_STATIC, + ]; + + return $token->isClassy() || $token->isGivenKind($skip); + } + + /** + * 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 + * + * @return bool + */ + private function isValidControl(Tokens $tokens, Token $docsToken, $controlIndex) + { + static $controlStructures = [ + T_FOR, + T_FOREACH, + T_IF, + T_SWITCH, + T_WHILE, + ]; + + if (!$tokens[$controlIndex]->isGivenKind($controlStructures)) { + return false; + } + + $index = $tokens->getNextMeaningfulToken($controlIndex); + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $docsContent = $docsToken->getContent(); + + for ($index = $index + 1; $index < $endIndex; ++$index) { + $token = $tokens[$index]; + + if ( + $token->isGivenKind(T_VARIABLE) && + false !== strpos($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 + * + * @return bool + */ + private function isValidLanguageConstruct(Tokens $tokens, Token $docsToken, $languageConstructIndex) + { + 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) && false !== strpos($docsContent, $token->getContent())) { + return true; + } + } + + return false; + } + + /** + * Checks variable assignments for correct docblock usage. + * + * @param int $index index of variable Token + * + * @return bool + */ + private function isValidVariable(Tokens $tokens, $index) + { + if (!$tokens[$index]->isGivenKind(T_VARIABLE)) { + return false; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + + return $tokens[$nextIndex]->equals('='); + } + + /** + * @param string $content + * + * @return int + */ + private function getCommentType($content) + { + if ('#' === $content[0]) { + return self::TYPE_HASH; + } + + if ('*' === $content[1]) { + return self::TYPE_SLASH_ASTERISK; + } + + return self::TYPE_DOUBLE_SLASH; + } + + /** + * @param int $whiteStart + * @param int $whiteEnd + * + * @return int + */ + private function getLineBreakCount(Tokens $tokens, $whiteStart, $whiteEnd) + { + $lineCount = 0; + for ($i = $whiteStart; $i < $whiteEnd; ++$i) { + $lineCount += Preg::matchAll('/\R/u', $tokens[$i]->getContent()); + } + + return $lineCount; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..463e452e8959bcb49da13f49bd4445f27e0d0f20 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php @@ -0,0 +1,262 @@ + + * Dariusz 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 + */ + private $functionsAnalysis = ['tokens' => '', 'imports' => [], 'declarations' => []]; + + /** + * Important: risky because of the limited (file) scope of the tool. + * + * @param int $index + * + * @return bool + */ + public function isGlobalFunctionCall(Tokens $tokens, $index) + { + 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); + } + + if ($tokens[$prevIndex]->isGivenKind([T_DOUBLE_COLON, T_FUNCTION, CT::T_NAMESPACE_OPERATOR, T_NEW, T_OBJECT_OPERATOR, CT::T_RETURN_REF, T_STRING])) { + return false; + } + + if ($previousIsNamespaceSeparator) { + return true; + } + + if ($tokens->isChanged() || $tokens->getCodeHash() !== $this->functionsAnalysis['tokens']) { + $this->buildFunctionsAnalysis($tokens); + } + + // figure out in which namespace we are + $namespaceAnalyzer = new NamespacesAnalyzer(); + + $declarations = $namespaceAnalyzer->getDeclarations($tokens); + $scopeStartIndex = 0; + $scopeEndIndex = \count($tokens) - 1; + $inGlobalNamespace = false; + + foreach ($declarations as $declaration) { + $scopeStartIndex = $declaration->getScopeStartIndex(); + $scopeEndIndex = $declaration->getScopeEndIndex(); + + if ($index >= $scopeStartIndex && $index <= $scopeEndIndex) { + $inGlobalNamespace = '' === $declaration->getFullName(); + + 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(), '\\'); + } + + return true; + } + + /** + * @param int $methodIndex + * + * @return ArgumentAnalysis[] + */ + public function getFunctionArguments(Tokens $tokens, $methodIndex) + { + $argumentsStart = $tokens->getNextTokenOfKind($methodIndex, ['(']); + $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; + } + + /** + * @param int $methodIndex + * + * @return null|TypeAnalysis + */ + public function getFunctionReturnType(Tokens $tokens, $methodIndex) + { + $argumentsStart = $tokens->getNextTokenOfKind($methodIndex, ['(']); + $argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart); + $typeColonIndex = $tokens->getNextMeaningfulToken($argumentsEnd); + if (':' !== $tokens[$typeColonIndex]->getContent()) { + 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); + } + + /** + * @param int $index + * + * @return bool + */ + public function isTheSameClassCall(Tokens $tokens, $index) + { + if (!$tokens->offsetExists($index)) { + return false; + } + + $operatorIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens->offsetExists($operatorIndex)) { + return false; + } + + $referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex); + if (!$tokens->offsetExists($referenceIndex)) { + return false; + } + + return $tokens[$operatorIndex]->equals([T_OBJECT_OPERATOR, '->']) && $tokens[$referenceIndex]->equals([T_VARIABLE, '$this'], false) + || $tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STRING, 'self'], false) + || $tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STATIC, 'static'], false); + } + + private function buildFunctionsAnalysis(Tokens $tokens) + { + $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/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..3149f114335a94a2c842dd3a489317dede6157f7 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.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\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @internal + */ +final class NamespaceUsesAnalyzer +{ + /** + * @return NamespaceUseAnalysis[] + */ + public function getDeclarationsFromTokens(Tokens $tokens) + { + $tokenAnalyzer = new TokensAnalyzer($tokens); + $useIndexes = $tokenAnalyzer->getImportUseIndexes(); + + return $this->getDeclarations($tokens, $useIndexes); + } + + /** + * @return NamespaceUseAnalysis[] + */ + private function getDeclarations(Tokens $tokens, array $useIndexes) + { + $uses = []; + + foreach ($useIndexes as $index) { + $endIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); + $analysis = $this->parseDeclaration($tokens, $index, $endIndex); + if ($analysis) { + $uses[] = $analysis; + } + } + + return $uses; + } + + /** + * @param int $startIndex + * @param int $endIndex + * + * @return null|NamespaceUseAnalysis + */ + private function parseDeclaration(Tokens $tokens, $startIndex, $endIndex) + { + $fullName = $shortName = ''; + $aliased = false; + + $type = NamespaceUseAnalysis::TYPE_CLASS; + for ($i = $startIndex; $i <= $endIndex; ++$i) { + $token = $tokens[$i]; + if ($token->equals(',') || $token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { + // do not touch group use declarations until the logic of this is added (for example: `use some\a\{ClassD};`) + // ignore multiple use statements that should be split into few separate statements (for example: `use BarB, BarC as C;`) + return null; + } + + if ($token->isGivenKind(CT::T_FUNCTION_IMPORT)) { + $type = NamespaceUseAnalysis::TYPE_FUNCTION; + } elseif ($token->isGivenKind(CT::T_CONST_IMPORT)) { + $type = NamespaceUseAnalysis::TYPE_CONSTANT; + } + + if ($token->isWhitespace() || $token->isComment() || $token->isGivenKind(T_USE)) { + continue; + } + + 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; + } + } + + return new NamespaceUseAnalysis( + trim($fullName), + $shortName, + $aliased, + $startIndex, + $endIndex, + $type + ); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..36fd71413dc6d4edc620f4259bb882bd32c9067b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.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\Analyzer; + +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class NamespacesAnalyzer +{ + /** + * @return NamespaceAnalysis[] + */ + public function getDeclarations(Tokens $tokens) + { + $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; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/CT.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/CT.php new file mode 100644 index 0000000000000000000000000000000000000000..05a28c6dddeeffd78d1a0ae8b8856c2130389d8d --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/CT.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\Tokenizer; + +/** + * @author Dariusz Rumiński + */ +final class CT +{ + const T_ARRAY_INDEX_CURLY_BRACE_CLOSE = 10001; + const T_ARRAY_INDEX_CURLY_BRACE_OPEN = 10002; + const T_ARRAY_SQUARE_BRACE_CLOSE = 10003; + const T_ARRAY_SQUARE_BRACE_OPEN = 10004; + const T_ARRAY_TYPEHINT = 10005; + const T_BRACE_CLASS_INSTANTIATION_CLOSE = 10006; + const T_BRACE_CLASS_INSTANTIATION_OPEN = 10007; + const T_CLASS_CONSTANT = 10008; + const T_CONST_IMPORT = 10009; + const T_CURLY_CLOSE = 10010; + const T_DESTRUCTURING_SQUARE_BRACE_CLOSE = 10011; + const T_DESTRUCTURING_SQUARE_BRACE_OPEN = 10012; + const T_DOLLAR_CLOSE_CURLY_BRACES = 10013; + const T_DYNAMIC_PROP_BRACE_CLOSE = 10014; + const T_DYNAMIC_PROP_BRACE_OPEN = 10015; + const T_DYNAMIC_VAR_BRACE_CLOSE = 10016; + const T_DYNAMIC_VAR_BRACE_OPEN = 10017; + const T_FUNCTION_IMPORT = 10018; + const T_GROUP_IMPORT_BRACE_CLOSE = 10019; + const T_GROUP_IMPORT_BRACE_OPEN = 10020; + const T_NAMESPACE_OPERATOR = 10021; + const T_NULLABLE_TYPE = 10022; + const T_RETURN_REF = 10023; + const T_TYPE_ALTERNATION = 10024; + const T_TYPE_COLON = 10025; + const T_USE_LAMBDA = 10026; + const T_USE_TRAIT = 10027; + + private function __construct() + { + } + + /** + * Get name for custom token. + * + * @param int $value custom token value + * + * @return string + */ + public static function getName($value) + { + 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 + * + * @return bool + */ + public static function has($value) + { + $tokens = self::getMapById(); + + return isset($tokens[$value]); + } + + private static function getMapById() + { + static $constants; + + if (null === $constants) { + $reflection = new \ReflectionClass(__CLASS__); + $constants = array_flip($reflection->getConstants()); + } + + return $constants; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/CodeHasher.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/CodeHasher.php new file mode 100644 index 0000000000000000000000000000000000000000..27683c1dbd4ec0bccbfa40f97639f959fcb7d097 --- /dev/null +++ b/lib/composer/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. + * + * @param string $code + * + * @return string + */ + public static function calculateCodeHash($code) + { + return (string) crc32($code); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Generator/NamespacedStringTokenGenerator.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Generator/NamespacedStringTokenGenerator.php new file mode 100644 index 0000000000000000000000000000000000000000..aeb50c43c8498aa852c0812149f7e7032da98259 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Generator/NamespacedStringTokenGenerator.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\Generator; + +use PhpCsFixer\Tokenizer\Token; + +/** + * @internal + */ +final class NamespacedStringTokenGenerator +{ + /** + * Parse a string that contains a namespace into tokens. + * + * @param string $input + * + * @return Token[] + */ + public function generate($input) + { + $tokens = []; + $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; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Resolver/TypeShortNameResolver.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Resolver/TypeShortNameResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..65bbd0ec9647bac2f4c10f0e67ba1fac7e992b79 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Resolver/TypeShortNameResolver.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\Tokenizer\Resolver; + +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class TypeShortNameResolver +{ + /** + * This method will resolve the shortName of a FQCN if possible or otherwise return the inserted type name. + * E.g.: use Foo\Bar => "Bar". + * + * @param string $typeName + * + * @return string + */ + public function resolve(Tokens $tokens, $typeName) + { + // First match explicit imports: + $useMap = $this->getUseMapFromTokens($tokens); + foreach ($useMap as $shortName => $fullName) { + $regex = '/^\\\\?'.preg_quote($fullName, '/').'$/'; + if (Preg::match($regex, $typeName)) { + return $shortName; + } + } + + // Next try to match (partial) classes inside the same namespace + // For now only support one namespace per file: + $namespaces = $this->getNamespacesFromTokens($tokens); + if (1 === \count($namespaces)) { + foreach ($namespaces as $fullName) { + $matches = []; + $regex = '/^\\\\?'.preg_quote($fullName, '/').'\\\\(?P.+)$/'; + if (Preg::match($regex, $typeName, $matches)) { + return $matches['className']; + } + } + } + + // Next: Try to match partial use statements: + + foreach ($useMap as $shortName => $fullName) { + $matches = []; + $regex = '/^\\\\?'.preg_quote($fullName, '/').'\\\\(?P.+)$/'; + if (Preg::match($regex, $typeName, $matches)) { + return $shortName.'\\'.$matches['className']; + } + } + + return $typeName; + } + + /** + * @return array A list of all FQN namespaces in the file with the short name as key + */ + private function getNamespacesFromTokens(Tokens $tokens) + { + return array_map(static function (NamespaceAnalysis $info) { + return $info->getFullName(); + }, (new NamespacesAnalyzer())->getDeclarations($tokens)); + } + + /** + * @return array A list of all FQN use statements in the file with the short name as key + */ + private function getUseMapFromTokens(Tokens $tokens) + { + $map = []; + + foreach ((new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens) as $useDeclaration) { + $map[$useDeclaration->getShortName()] = $useDeclaration->getFullName(); + } + + return $map; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php new file mode 100644 index 0000000000000000000000000000000000000000..8fdf4fefc551a42fa5f659dbb1b751942ca0f017 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php @@ -0,0 +1,618 @@ + + * Dariusz 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 + */ +class Token +{ + /** + * Content of token prototype. + * + * @var string + */ + private $content; + + /** + * ID of token prototype, if available. + * + * @var null|int + */ + private $id; + + /** + * If token prototype is an array. + * + * @var bool + */ + private $isArray; + + /** + * Flag is token was changed. + * + * @var bool + */ + private $changed = false; + + /** + * @param array|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".', + \is_object($token[0]) ? \get_class($token[0]) : \gettype($token[0]) + )); + } + + if (!\is_string($token[1])) { + throw new \InvalidArgumentException(sprintf( + 'Content must be a string, got "%s".', + \is_object($token[1]) ? \get_class($token[1]) : \gettype($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]; + + if ($token[0] && '' === $token[1]) { + throw new \InvalidArgumentException('Cannot set empty content for id-based Token.'); + } + } 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".', + \is_object($token) ? \get_class($token) : \gettype($token) + )); + } + } + + /** + * @return int[] + */ + public static function getCastTokenKinds() + { + 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 int[] + */ + public static function getClassyTokenKinds() + { + static $classTokens = [T_CLASS, T_TRAIT, T_INTERFACE]; + + return $classTokens; + } + + /** + * Clear token at given index. + * + * Clearing means override token by empty string. + * + * @deprecated since 2.4 + */ + public function clear() + { + @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); + Tokens::setLegacyMode(true); + + $this->content = ''; + $this->id = null; + $this->isArray = false; + } + + /** + * Clear internal flag if token was changed. + * + * @deprecated since 2.4 + */ + public function clearChanged() + { + @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); + Tokens::setLegacyMode(true); + + $this->changed = false; + } + + /** + * Check if token is equals to given one. + * + * If tokens are arrays, then only keys defined in parameter token are checked. + * + * @param array|string|Token $other token or it's prototype + * @param bool $caseSensitive perform a case sensitive comparison + * + * @return bool + */ + public function equals($other, $caseSensitive = true) + { + 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 empty($otherPrototype); + } + + /** + * Check if token is equals to one of given. + * + * @param array $others array of tokens or token prototypes + * @param bool $caseSensitive perform a case sensitive comparison + * + * @return bool + */ + public function equalsAny(array $others, $caseSensitive = true) + { + foreach ($others as $other) { + if ($this->equals($other, $caseSensitive)) { + return true; + } + } + + return false; + } + + /** + * A helper method used to find out whether or not 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 $others. 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 + * + * @return bool + */ + public static function isKeyCaseSensitive($caseSensitive, $key) + { + if (\is_array($caseSensitive)) { + return isset($caseSensitive[$key]) ? $caseSensitive[$key] : true; + } + + return $caseSensitive; + } + + /** + * @return array|string token prototype + */ + 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 string + */ + public function getContent() + { + 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. + * + * @return null|int + */ + public function getId() + { + 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() + { + 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. + * + * @param int $id + * + * @return null|string token name + */ + public static function getNameForId($id) + { + 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() + { + 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_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', + ]) + [ + 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_FUNCTION_IMPORT => CT::T_FUNCTION_IMPORT, + CT::T_NAMESPACE_OPERATOR => CT::T_NAMESPACE_OPERATOR, + CT::T_USE_TRAIT => CT::T_USE_TRAIT, + CT::T_USE_LAMBDA => CT::T_USE_LAMBDA, + ]; + } + + 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() + { + 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() + { + return $this->isArray; + } + + /** + * Check if token is one of type cast tokens. + * + * @return bool + */ + public function isCast() + { + return $this->isGivenKind(self::getCastTokenKinds()); + } + + /** + * Check if token was changed. + * + * @return bool + * + * @deprecated since 2.4 + */ + public function isChanged() + { + @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); + + return $this->changed; + } + + /** + * Check if token is one of classy tokens: T_CLASS, T_INTERFACE or T_TRAIT. + * + * @return bool + */ + public function isClassy() + { + return $this->isGivenKind(self::getClassyTokenKinds()); + } + + /** + * Check if token is one of comment tokens: T_COMMENT or T_DOC_COMMENT. + * + * @return bool + */ + public function isComment() + { + static $commentTokens = [T_COMMENT, T_DOC_COMMENT]; + + return $this->isGivenKind($commentTokens); + } + + /** + * Check if token is empty, e.g. because of clearing. + * + * @return bool + * + * @deprecated since 2.4 + */ + public function isEmpty() + { + @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); + + return null === $this->id && ('' === $this->content || null === $this->content); + } + + /** + * Check if token is one of given kind. + * + * @param int|int[] $possibleKind kind or array of kinds + * + * @return bool + */ + public function isGivenKind($possibleKind) + { + return $this->isArray && (\is_array($possibleKind) ? \in_array($this->id, $possibleKind, true) : $this->id === $possibleKind); + } + + /** + * Check if token is a keyword. + * + * @return bool + */ + public function isKeyword() + { + $keywords = static::getKeywords(); + + return $this->isArray && isset($keywords[$this->id]); + } + + /** + * Check if token is a native PHP constant: true, false or null. + * + * @return bool + */ + public function isNativeConstant() + { + 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 + * + * @return bool + */ + public function isMagicConstant() + { + $magicConstants = static::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" + * + * @return bool + */ + public function isWhitespace($whitespaces = " \t\n\r\0\x0B") + { + if (null === $whitespaces) { + $whitespaces = " \t\n\r\0\x0B"; + } + + if ($this->isArray && !$this->isGivenKind(T_WHITESPACE)) { + return false; + } + + return '' === trim($this->content, $whitespaces); + } + + /** + * Override token. + * + * If called on Token inside Tokens collection please use `Tokens::overrideAt` instead. + * + * @param array|string|Token $other token prototype + * + * @deprecated since 2.4 + */ + public function override($other) + { + @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); + Tokens::setLegacyMode(true); + + $prototype = $other instanceof self ? $other->getPrototype() : $other; + + if ($this->equals($prototype)) { + return; + } + + $this->changed = true; + + if (\is_array($prototype)) { + $this->isArray = true; + $this->id = $prototype[0]; + $this->content = $prototype[1]; + + return; + } + + $this->isArray = false; + $this->id = null; + $this->content = $prototype; + } + + /** + * @param string $content + * + * @deprecated since 2.4 + */ + public function setContent($content) + { + @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); + Tokens::setLegacyMode(true); + + if ($this->content === $content) { + return; + } + + $this->changed = true; + $this->content = $content; + + // setting empty content is clearing the token + if ('' === $content) { + @trigger_error(__METHOD__.' shall not be used to clear token, use Tokens::clearAt instead.', E_USER_DEPRECATED); + $this->id = null; + $this->isArray = false; + } + } + + public function toArray() + { + return [ + 'id' => $this->id, + 'name' => $this->getName(), + 'content' => $this->content, + 'isArray' => $this->isArray, + 'changed' => $this->changed, + ]; + } + + /** + * @param null|string[] $options JSON encode option + * + * @return string + */ + public function toJson(array $options = null) + { + static $defaultOptions = null; + + if (null === $options) { + if (null === $defaultOptions) { + $defaultOptions = Utils::calculateBitmask(['JSON_PRETTY_PRINT', 'JSON_NUMERIC_CHECK']); + } + + $options = $defaultOptions; + } else { + $options = Utils::calculateBitmask($options); + } + + return json_encode($this->toArray(), $options); + } + + /** + * @param string[] $tokenNames + * + * @return array + */ + private static function getTokenKindsForNames(array $tokenNames) + { + $keywords = []; + foreach ($tokenNames as $keywordName) { + if (\defined($keywordName)) { + $keyword = \constant($keywordName); + $keywords[$keyword] = $keyword; + } + } + + return $keywords; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php new file mode 100644 index 0000000000000000000000000000000000000000..61d4d448af6814ecbf2e143b5993a116e6858411 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php @@ -0,0 +1,1453 @@ + + * Dariusz 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\Utils; + +/** + * 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 + */ +class Tokens extends \SplFixedArray +{ + const BLOCK_TYPE_PARENTHESIS_BRACE = 1; + const BLOCK_TYPE_CURLY_BRACE = 2; + const BLOCK_TYPE_INDEX_SQUARE_BRACE = 3; + const BLOCK_TYPE_ARRAY_SQUARE_BRACE = 4; + const BLOCK_TYPE_DYNAMIC_PROP_BRACE = 5; + const BLOCK_TYPE_DYNAMIC_VAR_BRACE = 6; + const BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE = 7; + const BLOCK_TYPE_GROUP_IMPORT_BRACE = 8; + const BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE = 9; + const BLOCK_TYPE_BRACE_CLASS_INSTANTIATION = 10; + + /** + * Static class cache. + * + * @var array + */ + private static $cache = []; + + /** + * Cache of block edges. Any change in collection will invalidate it. + * + * @var array + */ + private $blockEndCache = []; + + /** + * crc32 hash of code string. + * + * @var string + */ + private $codeHash; + + /** + * Flag is collection was changed. + * + * It doesn't know about change of collection's items. To check it run `isChanged` method. + * + * @var bool + */ + private $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 $foundTokenKinds = []; + + /** + * @var bool + * + * @todo remove at 3.0 + */ + private static $isLegacyMode = false; + + /** + * Clone tokens collection. + */ + public function __clone() + { + foreach ($this as $key => $val) { + $this[$key] = clone $val; + } + } + + /** + * @return bool + * + * @internal + * + * @todo remove at 3.0 + */ + public static function isLegacyMode() + { + return self::$isLegacyMode; + } + + /** + * @param bool $isLegacy + * + * @internal + * + * @todo remove at 3.0 + */ + public static function setLegacyMode($isLegacy) + { + if (getenv('PHP_CS_FIXER_FUTURE_MODE') && $isLegacy) { + throw new \RuntimeException('Cannot enable `legacy mode` when using `future mode`. This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set.'); + } + + self::$isLegacyMode = $isLegacy; + } + + /** + * Clear cache - one position or all of them. + * + * @param null|string $key position to clear, when null clear all + */ + public static function clearCache($key = null) + { + if (null === $key) { + self::$cache = []; + + return; + } + + if (self::hasCache($key)) { + unset(self::$cache[$key]); + } + } + + /** + * Detect type of block. + * + * @param Token $token token + * + * @return null|array array with 'type' and 'isStart' keys or null if not found + */ + public static function detectBlockType(Token $token) + { + foreach (self::getBlockEdgeDefinitions() as $type => $definition) { + if ($token->equals($definition['start'])) { + return ['type' => $type, 'isStart' => true]; + } + + if ($token->equals($definition['end'])) { + return ['type' => $type, 'isStart' => false]; + } + } + + return null; + } + + /** + * Create token collection from array. + * + * @param Token[] $array the array to import + * @param bool $saveIndexes save the numeric indexes used in the original array, default is yes + * + * @return Tokens + */ + public static function fromArray($array, $saveIndexes = null) + { + $tokens = new self(\count($array)); + + if (null === $saveIndexes || $saveIndexes) { + 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 + + return $tokens; + } + + /** + * Create token collection directly from code. + * + * @param string $code PHP code + * + * @return Tokens + */ + public static function fromCode($code) + { + $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() + { + return [ + 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, ')'], + ], + ]; + } + + /** + * Set new size of collection. + * + * @param int $size + */ + public function setSize($size) + { + if ($this->getSize() !== $size) { + $this->changed = true; + parent::setSize($size); + } + } + + /** + * Unset collection item. + * + * @param int $index + */ + public function offsetUnset($index) + { + $this->changed = true; + $this->unregisterFoundToken($this[$index]); + 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) + { + $this->blockEndCache = []; + + if (!isset($this[$index]) || !$this[$index]->equals($newval)) { + $this->changed = true; + + if (isset($this[$index])) { + $this->unregisterFoundToken($this[$index]); + } + + $this->registerFoundToken($newval); + } + + parent::offsetSet($index, $newval); + } + + /** + * Clear internal flag if collection was changed and flag for all collection's items. + */ + public function clearChanged() + { + $this->changed = false; + + if (self::isLegacyMode()) { + foreach ($this as $token) { + $token->clearChanged(); + } + } + } + + /** + * Clear empty tokens. + * + * Empty tokens can occur e.g. after calling clear on item of collection. + */ + public function clearEmptyTokens() + { + $limit = $this->count(); + $index = 0; + + for (; $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)) { + /** @var Token $token */ + $token = $this[$index]; + $this[$count++] = $token; + } + } + + $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($index, $indexOffset, $whitespace) + { + $removeLastCommentLine = static function (self $tokens, $index, $indexOffset, $whitespace) { + $token = $tokens[$index]; + + if (1 === $indexOffset && $token->isGivenKind(T_OPEN_TAG)) { + if (0 === strpos($whitespace, "\r\n")) { + $tokens[$index] = new Token([T_OPEN_TAG, rtrim($token->getContent())."\r\n"]); + + return \strlen($whitespace) > 2 // can be removed on PHP 7; 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 // can be removed on PHP 7; 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 int $type type of block, one of BLOCK_TYPE_* + * @param int $searchIndex index of opening brace + * @param bool $findEnd if method should find block's end, default true, otherwise method find block's start + * + * @return int index of closing brace + */ + public function findBlockEnd($type, $searchIndex, $findEnd = true) + { + if (3 === \func_num_args()) { + if ($findEnd) { + @trigger_error('Argument #3 of Tokens::findBlockEnd is deprecated and will be removed in 3.0, you can safely drop the argument.', E_USER_DEPRECATED); + } else { + @trigger_error('Argument #3 of Tokens::findBlockEnd is deprecated and will be removed in 3.0, use Tokens::findBlockStart instead.', E_USER_DEPRECATED); + } + } + + return $this->findOppositeBlockEdge($type, $searchIndex, $findEnd); + } + + /** + * @param int $type type of block, one of BLOCK_TYPE_* + * @param int $searchIndex index of closing brace + * + * @return int index of opening brace + */ + public function findBlockStart($type, $searchIndex) + { + return $this->findOppositeBlockEdge($type, $searchIndex, false); + } + + /** + * @param array|int $possibleKind kind or array of kind + * @param int $start optional offset + * @param null|int $end optional limit + * + * @return array array of tokens of given kinds or assoc array of arrays + */ + public function findGivenKind($possibleKind, $start = 0, $end = null) + { + $this->rewind(); + if (null === $end) { + $end = $this->count(); + } + + $elements = []; + $possibleKinds = (array) $possibleKind; + + foreach ($possibleKinds as $kind) { + $elements[$kind] = []; + } + + if (!self::isLegacyMode()) { + $possibleKinds = array_filter($possibleKinds, function ($kind) { + return $this->isTokenKindFound($kind); + }); + } + + if (\count($possibleKinds)) { + 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]; + } + + /** + * @return string + */ + public function generateCode() + { + $code = $this->generatePartialCode(0, \count($this) - 1); + $this->changeCodeHash(self::calculateCodeHash($code)); + + return $code; + } + + /** + * Generate code from tokens between given indexes. + * + * @param int $start start index + * @param int $end end index + * + * @return string + */ + public function generatePartialCode($start, $end) + { + $code = ''; + + for ($i = $start; $i <= $end; ++$i) { + $code .= $this[$i]->getContent(); + } + + return $code; + } + + /** + * Get hash of code. + * + * @return string + */ + public function getCodeHash() + { + 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 + * + * @return null|int + */ + public function getNextNonWhitespace($index, $whitespaces = null) + { + 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 array $tokens possible tokens + * @param bool $caseSensitive perform a case sensitive comparison + * + * @return null|int + */ + public function getNextTokenOfKind($index, array $tokens = [], $caseSensitive = true) + { + return $this->getTokenOfKindSibling($index, 1, $tokens, $caseSensitive); + } + + /** + * Get index for closest sibling token which is non whitespace. + * + * @param int $index token index + * @param int $direction direction for looking, +1 or -1 + * @param null|string $whitespaces whitespaces characters for Token::isWhitespace + * + * @return null|int + */ + public function getNonWhitespaceSibling($index, $direction, $whitespaces = null) + { + while (true) { + $index += $direction; + + if (!$this->offsetExists($index)) { + return null; + } + + $token = $this[$index]; + + if (!$token->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 + * + * @return null|int + */ + public function getPrevNonWhitespace($index, $whitespaces = null) + { + 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 array $tokens possible tokens + * @param bool $caseSensitive perform a case sensitive comparison + * + * @return null|int + */ + public function getPrevTokenOfKind($index, array $tokens = [], $caseSensitive = true) + { + return $this->getTokenOfKindSibling($index, -1, $tokens, $caseSensitive); + } + + /** + * Get index for closest sibling token of given kind. + * + * @param int $index token index + * @param int $direction direction for looking, +1 or -1 + * @param array $tokens possible tokens + * @param bool $caseSensitive perform a case sensitive comparison + * + * @return null|int + */ + public function getTokenOfKindSibling($index, $direction, array $tokens = [], $caseSensitive = true) + { + if (!self::isLegacyMode()) { + $tokens = array_filter($tokens, function ($token) { + return $this->isTokenKindFound($this->extractTokenKind($token)); + }); + } + + if (!\count($tokens)) { + return null; + } + + while (true) { + $index += $direction; + + if (!$this->offsetExists($index)) { + return null; + } + + $token = $this[$index]; + + if ($token->equalsAny($tokens, $caseSensitive)) { + return $index; + } + } + } + + /** + * Get index for closest sibling token not of given kind. + * + * @param int $index token index + * @param int $direction direction for looking, +1 or -1 + * @param array $tokens possible tokens + * + * @return null|int + */ + public function getTokenNotOfKindSibling($index, $direction, array $tokens = []) + { + while (true) { + $index += $direction; + + if (!$this->offsetExists($index)) { + return null; + } + + if ($this->isEmptyAt($index)) { + continue; + } + + if ($this[$index]->equalsAny($tokens)) { + continue; + } + + return $index; + } + } + + /** + * Get index for closest sibling token that is not a whitespace or comment. + * + * @param int $index token index + * @param int $direction direction for looking, +1 or -1 + * + * @return null|int + */ + public function getMeaningfulTokenSibling($index, $direction) + { + return $this->getTokenNotOfKindSibling( + $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 int $direction direction for looking, +1 or -1 + * + * @return null|int + */ + public function getNonEmptySibling($index, $direction) + { + 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 + * + * @return null|int + */ + public function getNextMeaningfulToken($index) + { + return $this->getMeaningfulTokenSibling($index, 1); + } + + /** + * Get index for closest previous token that is not a whitespace or comment. + * + * @param int $index token index + * + * @return null|int + */ + public function getPrevMeaningfulToken($index) + { + return $this->getMeaningfulTokenSibling($index, -1); + } + + /** + * Find a sequence of meaningful tokens and returns the array of their locations. + * + * @param array $sequence an array of tokens (kinds) (same format used by getNextTokenOfKind) + * @param int $start start index, defaulting to the start of the file + * @param int $end end index, defaulting to the end of the file + * @param array|bool $caseSensitive global case sensitiveness or an array of booleans, whose keys should match + * the ones used in $others. If any is missing, the default case-sensitive + * comparison is used + * + * @return null|array an array containing the tokens matching the sequence elements, indexed by their position + */ + public function findSequence(array $sequence, $start = 0, $end = null, $caseSensitive = true) + { + $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; + } + + // 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->isWhitespace() || $token->isComment() || '' === $token->getContent()) { + throw new \InvalidArgumentException(sprintf('Non-meaningful token at position: "%s".', $key)); + } + } + + if (!self::isLegacyMode()) { + 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 + $key = key($sequence); + $firstCs = Token::isKeyCaseSensitive($caseSensitive, $key); + $firstToken = $sequence[$key]; + unset($sequence[$key]); + + // begin searching for the first token in the sequence (start included) + $index = $start - 1; + while (null !== $index && $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, Token::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 array|Token|Tokens $items instances of Token to insert + */ + public function insertAt($index, $items) + { + $items = \is_array($items) || $items instanceof self ? $items : [$items]; + $itemsCnt = \count($items); + + if (0 === $itemsCnt) { + return; + } + + $oldSize = \count($this); + $this->changed = true; + $this->blockEndCache = []; + $this->setSize($oldSize + $itemsCnt); + + // 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. + for ($i = $oldSize + $itemsCnt - 1; $i >= $index; --$i) { + $oldItem = parent::offsetExists($i - $itemsCnt) ? parent::offsetGet($i - $itemsCnt) : new Token(''); + parent::offsetSet($i, $oldItem); + } + + for ($i = 0; $i < $itemsCnt; ++$i) { + if ('' === $items[$i]->getContent()) { + throw new \InvalidArgumentException('Must not add empty token to collection.'); + } + + $this->registerFoundToken($items[$i]); + parent::offsetSet($i + $index, $items[$i]); + } + } + + /** + * Check if collection was change: collection itself (like insert new tokens) or any of collection's elements. + * + * @return bool + */ + public function isChanged() + { + if ($this->changed) { + return true; + } + + if (self::isLegacyMode()) { + foreach ($this as $token) { + if ($token->isChanged()) { + return true; + } + } + } + + return false; + } + + /** + * @param int $index + * + * @return bool + */ + public function isEmptyAt($index) + { + $token = $this[$index]; + + return null === $token->getId() && '' === $token->getContent(); + } + + public function clearAt($index) + { + $this[$index] = new Token(''); + } + + /** + * Override token at given index and register it. + * + * @param int $index + * @param array|string|Token $token token prototype + * + * @deprecated since 2.4, use offsetSet instead + */ + public function overrideAt($index, $token) + { + @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0, use offsetSet instead.', E_USER_DEPRECATED); + self::$isLegacyMode = true; + + $this[$index]->override($token); + $this->registerFoundToken($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($indexStart, $indexEnd, $items) + { + $oldCode = $this->generatePartialCode($indexStart, $indexEnd); + + $newCode = ''; + foreach ($items as $item) { + $newCode .= $item->getContent(); + } + + // no changes, return + if ($oldCode === $newCode) { + return; + } + + $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 less tokens than passed range contains then clear + // not needed tokens. + if ($itemsCount < $indexToChange) { + $this->clearRange($indexStart + $itemsCount, $indexEnd); + } + } + + /** + * @param int $index + * @param null|string $whitespaces optional whitespaces characters for Token::isWhitespace + */ + public function removeLeadingWhitespace($index, $whitespaces = null) + { + $this->removeWhitespaceSafely($index, -1, $whitespaces); + } + + /** + * @param int $index + * @param null|string $whitespaces optional whitespaces characters for Token::isWhitespace + */ + public function removeTrailingWhitespace($index, $whitespaces = null) + { + $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($code) + { + // 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 = \defined('TOKEN_PARSE') + ? token_get_all($code, TOKEN_PARSE) + : token_get_all($code); + + $this->setSize(\count($tokens)); + + foreach ($tokens as $index => $token) { + $this[$index] = new Token($token); + } + + $transformers = Transformers::create(); + $transformers->transform($this); + + $this->foundTokenKinds = []; + foreach ($this as $token) { + $this->registerFoundToken($token); + } + + $this->rewind(); + $this->changeCodeHash(self::calculateCodeHash($code)); + $this->changed = true; + } + + public function toJson() + { + static $options = null; + + if (null === $options) { + $options = Utils::calculateBitmask(['JSON_PRETTY_PRINT', 'JSON_NUMERIC_CHECK']); + } + + $output = new \SplFixedArray(\count($this)); + + foreach ($this as $index => $token) { + $output[$index] = $token->toArray(); + } + + $this->rewind(); + + return json_encode($output, $options); + } + + /** + * Check if all token kinds given as argument are found. + * + * @return bool + */ + public function isAllTokenKindsFound(array $tokenKinds) + { + foreach ($tokenKinds as $tokenKind) { + if (empty($this->foundTokenKinds[$tokenKind])) { + return false; + } + } + + return true; + } + + /** + * Check if any token kind given as argument is found. + * + * @return bool + */ + public function isAnyTokenKindsFound(array $tokenKinds) + { + foreach ($tokenKinds as $tokenKind) { + if (!empty($this->foundTokenKinds[$tokenKind])) { + return true; + } + } + + return false; + } + + /** + * Check if token kind given as argument is found. + * + * @param int|string $tokenKind + * + * @return bool + */ + public function isTokenKindFound($tokenKind) + { + return !empty($this->foundTokenKinds[$tokenKind]); + } + + /** + * @param int|string $tokenKind + * + * @return int + */ + public function countTokenKind($tokenKind) + { + if (self::isLegacyMode()) { + throw new \RuntimeException(sprintf('"%s" is not available in legacy mode.', __METHOD__)); + } + + return isset($this->foundTokenKinds[$tokenKind]) ? $this->foundTokenKinds[$tokenKind] : 0; + } + + /** + * Clear tokens in the given range. + * + * @param int $indexStart + * @param int $indexEnd + */ + public function clearRange($indexStart, $indexEnd) + { + 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. + * + * @return bool + */ + public function isMonolithicPhp() + { + $size = $this->count(); + + if (0 === $size) { + return false; + } + + if (self::isLegacyMode()) { + // If code is not monolithic there is a great chance that first or last token is `T_INLINE_HTML`: + if ($this[0]->isGivenKind(T_INLINE_HTML) || $this[$size - 1]->isGivenKind(T_INLINE_HTML)) { + return false; + } + + for ($index = 1; $index < $size; ++$index) { + if ($this[$index]->isGivenKind([T_INLINE_HTML, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO])) { + return false; + } + } + + return true; + } + + if ($this->isTokenKindFound(T_INLINE_HTML)) { + return false; + } + + return 1 >= ($this->countTokenKind(T_OPEN_TAG) + $this->countTokenKind(T_OPEN_TAG_WITH_ECHO)); + } + + /** + * @param int $start start index + * @param int $end end index + * + * @return bool + */ + public function isPartialCodeMultiline($start, $end) + { + for ($i = $start; $i <= $end; ++$i) { + if (false !== strpos($this[$i]->getContent(), "\n")) { + return true; + } + } + + return false; + } + + /** + * @param int $index + */ + public function clearTokenAndMergeSurroundingWhitespace($index) + { + $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); + } + + private function removeWhitespaceSafely($index, $direction, $whitespaces = null) + { + $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() && '/*' !== substr($this[$whitespaceIndex - 1]->getContent(), 0, 2)) { + list($emptyString, $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 int $type type of block, one of BLOCK_TYPE_* + * @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($type, $searchIndex, $findEnd) + { + $blockEdgeDefinitions = self::getBlockEdgeDefinitions(); + + if (!isset($blockEdgeDefinitions[$type])) { + throw new \InvalidArgumentException(sprintf('Invalid param type: "%s".', $type)); + } + + if (!self::isLegacyMode() && 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) { + list($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; + } + + continue; + } + } + + if (!$this[$index]->equals($endEdge)) { + throw new \UnexpectedValueException(sprintf('Missing block "%s".', $findEnd ? 'end' : 'start')); + } + + $this->blockEndCache[$startIndex] = $index; + $this->blockEndCache[$index] = $startIndex; + + return $index; + } + + /** + * Calculate hash for code. + * + * @param string $code + * + * @return string + */ + private static function calculateCodeHash($code) + { + return CodeHasher::calculateCodeHash($code); + } + + /** + * Get cache value for given key. + * + * @param string $key item key + * + * @return Tokens + */ + private static function getCache($key) + { + 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 string $key item key + * + * @return bool + */ + private static function hasCache($key) + { + return isset(self::$cache[$key]); + } + + /** + * @param string $key item key + * @param Tokens $value item value + */ + private static function setCache($key, self $value) + { + self::$cache[$key] = $value; + } + + /** + * Change code hash. + * + * Remove old cache and set new one. + * + * @param string $codeHash new code hash + */ + private function changeCodeHash($codeHash) + { + if (null !== $this->codeHash) { + self::clearCache($this->codeHash); + } + + $this->codeHash = $codeHash; + self::setCache($this->codeHash, $this); + } + + /** + * Register token as found. + * + * @param array|string|Token $token token prototype + */ + private function registerFoundToken($token) + { + // inlined extractTokenKind() call on the hot path + $tokenKind = $token instanceof Token + ? ($token->isArray() ? $token->getId() : $token->getContent()) + : (\is_array($token) ? $token[0] : $token) + ; + + if (!isset($this->foundTokenKinds[$tokenKind])) { + $this->foundTokenKinds[$tokenKind] = 0; + } + + ++$this->foundTokenKinds[$tokenKind]; + } + + /** + * Register token as found. + * + * @param array|string|Token $token token prototype + */ + private function unregisterFoundToken($token) + { + // inlined extractTokenKind() call on the hot path + $tokenKind = $token instanceof Token + ? ($token->isArray() ? $token->getId() : $token->getContent()) + : (\is_array($token) ? $token[0] : $token) + ; + + if (!isset($this->foundTokenKinds[$tokenKind])) { + return; + } + + --$this->foundTokenKinds[$tokenKind]; + } + + /** + * @param array|string|Token $token token prototype + * + * @return int|string + */ + private function extractTokenKind($token) + { + return $token instanceof Token + ? ($token->isArray() ? $token->getId() : $token->getContent()) + : (\is_array($token) ? $token[0] : $token) + ; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..4b04acb380ff352f0ef691656aa31b49fce81292 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php @@ -0,0 +1,743 @@ + + * Dariusz 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 of Tokens collection. + * + * Its role is to provide the ability to analyze collection. + * + * @author Dariusz Rumiński + * @author Gregor Harlan + * @author SpacePossum + * + * @internal + */ +final class TokensAnalyzer +{ + /** + * Tokens collection instance. + * + * @var Tokens + */ + private $tokens; + + public function __construct(Tokens $tokens) + { + $this->tokens = $tokens; + } + + /** + * Get indexes of methods and properties in classy code (classes, interfaces and traits). + * + * @return array[] + */ + public function getClassyElements() + { + $this->tokens->rewind(); + $elements = []; + + for ($index = 1, $count = \count($this->tokens) - 2; $index < $count; ++$index) { + if ($this->tokens[$index]->isClassy()) { + list($index, $newElements) = $this->findClassyElements($index, $index); + $elements += $newElements; + } + } + + ksort($elements); + + return $elements; + } + + /** + * Get indexes of namespace uses. + * + * @param bool $perNamespace Return namespace uses per namespace + * + * @return int[]|int[][] + */ + public function getImportUseIndexes($perNamespace = false) + { + $tokens = $this->tokens; + + $tokens->rewind(); + + $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. + * + * @param int $index + * + * @return bool + */ + public function isArray($index) + { + 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. + * + * @param int $index + * + * @return bool + */ + public function isArrayMultiLine($index) + { + if (!$this->isArray($index)) { + throw new \InvalidArgumentException(sprintf('Not an array at given index %d.', $index)); + } + + $tokens = $this->tokens; + + // Skip only when its an array, for short arrays we need the brace for correct + // level counting + if ($tokens[$index]->isGivenKind(T_ARRAY)) { + $index = $tokens->getNextMeaningfulToken($index); + } + + $endIndex = $tokens[$index]->equals('(') + ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) + : $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index) + ; + + for (++$index; $index < $endIndex; ++$index) { + $token = $tokens[$index]; + $blockType = Tokens::detectBlockType($token); + + if ($blockType && $blockType['isStart']) { + $index = $tokens->findBlockEnd($blockType['type'], $index); + + continue; + } + + if ( + $token->isWhitespace() && + !$tokens[$index - 1]->isGivenKind(T_END_HEREDOC) && + false !== strpos($token->getContent(), "\n") + ) { + return true; + } + } + + return false; + } + + /** + * Returns the attributes of the method under the given index. + * + * The array has the following items: + * 'visibility' int|null T_PRIVATE, T_PROTECTED or T_PUBLIC + * 'static' bool + * 'abstract' bool + * 'final' bool + * + * @param int $index Token index of the method (T_FUNCTION) + * + * @return array + */ + public function getMethodAttributes($index) + { + $tokens = $this->tokens; + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_FUNCTION)) { + throw new \LogicException(sprintf('No T_FUNCTION at given index %d, got %s.', $index, $token->getName())); + } + + $attributes = [ + 'visibility' => null, + 'static' => false, + 'abstract' => false, + 'final' => false, + ]; + + for ($i = $index; $i >= 0; --$i) { + $tokenIndex = $tokens->getPrevMeaningfulToken($i); + + $i = $tokenIndex; + $token = $tokens[$tokenIndex]; + + 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. + * + * @param int $index + * + * @return bool + */ + public function isAnonymousClass($index) + { + $tokens = $this->tokens; + $token = $tokens[$index]; + + if (!$token->isClassy()) { + throw new \LogicException(sprintf('No classy token at given index %d.', $index)); + } + + if (!$token->isGivenKind(T_CLASS)) { + return false; + } + + return $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NEW); + } + + /** + * Check if the function under given index is a lambda. + * + * @param int $index + * + * @return bool + */ + public function isLambda($index) + { + if ( + !$this->tokens[$index]->isGivenKind(T_FUNCTION) + && (\PHP_VERSION_ID < 70400 || !$this->tokens[$index]->isGivenKind(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('('); + } + + /** + * Check if the T_STRING under given index is a constant invocation. + * + * @param int $index + * + * @return bool + */ + public function isConstantInvocation($index) + { + 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, 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_OBJECT_OPERATOR, T_TRAIT, CT::T_TYPE_COLON])) { + 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 goto label + if ($this->tokens[$nextIndex]->equals(':') && $this->tokens[$prevIndex]->equalsAny([';', '}', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]])) { + return false; + } + + return true; + } + + /** + * Checks if there is an unary successor operator under given index. + * + * @param int $index + * + * @return bool + */ + public function isUnarySuccessorOperator($index) + { + 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 an unary predecessor operator under given index. + * + * @param int $index + * + * @return bool + */ + public function isUnaryPredecessorOperator($index) + { + 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_FUNCTION], + [T_OPEN_TAG], + [T_OPEN_TAG_WITH_ECHO], + ]; + $prevToken = $tokens[$tokens->getPrevTokenOfKind($index, $searchTokens)]; + + return $prevToken->isGivenKind(T_FUNCTION); + } + + /** + * Checks if there is a binary operator under given index. + * + * @param int $index + * + * @return bool + */ + public function isBinaryOperator($index) + { + 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, // ^= + CT::T_TYPE_ALTERNATION => true, // | + ]; + + if (\defined('T_SPACESHIP')) { + $arrayOperators[T_SPACESHIP] = true; // <=> + } + + if (\defined('T_COALESCE')) { + $arrayOperators[T_COALESCE] = true; // ?? + } + + if (\defined('T_COALESCE_EQUAL')) { + $arrayOperators[T_COALESCE_EQUAL] = true; // ??= + } + } + + $tokens = $this->tokens; + $token = $tokens[$index]; + + if ($token->isArray()) { + return isset($arrayOperators[$token->getId()]); + } + + if (isset($nonArrayOperators[$token->getContent()])) { + return true; + } + + if (isset($potentialUnaryNonArrayOperators[$token->getContent()])) { + return !$this->isUnaryPredecessorOperator($index); + } + + return false; + } + + /** + * Check if `T_WHILE` token at given index is `do { ... } while ();` syntax + * and not `while () { ...}`. + * + * @param int $index + * + * @return bool + */ + public function isWhilePartOfDoWhile($index) + { + $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); + } + + /** + * 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 + * @param int $index + * + * @return array + */ + private function findClassyElements($classIndex, $index) + { + $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->isClassy()) { // 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) { + list($index, $newElements) = $this->findClassyElements($nestedClassIndex, $index); + $elements += $newElements; + + break; + } + + continue; + } + + if ($token->isClassy()) { // anonymous class in class + list($index, $newElements) = $this->findClassyElements($index, $index); + $elements += $newElements; + } + } + } else { + list($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] = [ + 'token' => $token, + 'type' => 'property', + 'classIndex' => $classIndex, + ]; + + continue; + } + + if ($token->isGivenKind(T_FUNCTION)) { + $elements[$index] = [ + 'token' => $token, + 'type' => 'method', + 'classIndex' => $classIndex, + ]; + } elseif ($token->isGivenKind(T_CONST)) { + $elements[$index] = [ + 'token' => $token, + 'type' => 'const', + 'classIndex' => $classIndex, + ]; + } + } + + return [$index, $elements]; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..347cf14bdb74fcd4583a51e3541983a9207c49ff --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php @@ -0,0 +1,61 @@ + + * Dariusz 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 +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + return [CT::T_ARRAY_TYPEHINT]; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 50000; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + 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()]); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..c86792bb96c09d48316f21dab330a55b8e80bccd --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.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\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 +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + return [CT::T_BRACE_CLASS_INSTANTIATION_OPEN, CT::T_BRACE_CLASS_INSTANTIATION_CLOSE]; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + // must run after CurlyBraceTransformer and SquareBraceTransformer + return -2; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 50000; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + if (!$tokens[$index]->equals('(') || !$tokens[$tokens->getNextMeaningfulToken($index)]->equals([T_NEW])) { + return; + } + + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny([ + ']', + [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], + [CT::T_ARRAY_SQUARE_BRACE_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, ')']); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..b55959d14de98d5486b93a2c026806605cf92d56 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.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\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 +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + return [CT::T_CLASS_CONSTANT]; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 50500; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + 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()]); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/CurlyBraceTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/CurlyBraceTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..7dcb368ddd02ffc1b3c0f8cdc9d71eedf335cf81 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/CurlyBraceTransformer.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\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 CurlyBraceTransformer extends AbstractTransformer +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + 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, + ]; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 50000; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + $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); + + if (\PHP_VERSION_ID >= 70000) { + $this->transformIntoGroupUseBraces($tokens, $token, $index); + } + } + + /** + * Transform closing `}` for T_CURLY_OPEN into CT::T_CURLY_CLOSE. + * + * This should be done at very beginning of curly braces transformations. + * + * @param int $index + */ + private function transformIntoCurlyCloseBrace(Tokens $tokens, Token $token, $index) + { + if (!$token->isGivenKind(T_CURLY_OPEN)) { + return; + } + + $level = 1; + $nestIndex = $index; + + while (0 < $level) { + ++$nestIndex; + + // we count all kind of { + if ($tokens[$nestIndex]->equals('{')) { + ++$level; + + continue; + } + + // we count all kind of } + if ($tokens[$nestIndex]->equals('}')) { + --$level; + } + } + + $tokens[$nestIndex] = new Token([CT::T_CURLY_CLOSE, '}']); + } + + private function transformIntoDollarCloseBrace(Tokens $tokens, Token $token, $index) + { + 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, $index) + { + if (!$token->isGivenKind(T_OBJECT_OPERATOR)) { + return; + } + + if (!$tokens[$index + 1]->equals('{')) { + return; + } + + $openIndex = $index + 1; + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $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, $index) + { + if (!$token->equals('$')) { + return; + } + + $openIndex = $tokens->getNextMeaningfulToken($index); + + if (null === $openIndex) { + return; + } + + $openToken = $tokens[$openIndex]; + + if (!$openToken->equals('{')) { + return; + } + + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $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, $index) + { + 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)]->isGivenKind(T_OBJECT_OPERATOR) + ) { + return; + } + + if ( + $tokens[$prevIndex]->equals(')') + && !$tokens[$tokens->getPrevMeaningfulToken( + $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevIndex) + )]->isGivenKind(T_ARRAY) + ) { + return; + } + + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $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, $index) + { + if (!$token->equals('{')) { + return; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + return; + } + + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + $tokens[$index] = new Token([CT::T_GROUP_IMPORT_BRACE_OPEN, '{']); + $tokens[$closeIndex] = new Token([CT::T_GROUP_IMPORT_BRACE_CLOSE, '}']); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..ea8a0cd8a2e4c6b8057fa2f4f086e12b2376297a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.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; + +/** + * 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 +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + return [CT::T_CONST_IMPORT, CT::T_FUNCTION_IMPORT]; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 50600; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + if (!$token->isGivenKind([T_CONST, T_FUNCTION])) { + return; + } + + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + + if ($prevToken->isGivenKind(T_USE)) { + $tokens[$index] = new Token([ + $token->isGivenKind(T_FUNCTION) ? CT::T_FUNCTION_IMPORT : CT::T_CONST_IMPORT, + $token->getContent(), + ]); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..e2700dc73db8b978661fdbf005fddd47448b4176 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php @@ -0,0 +1,61 @@ + + * Dariusz 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 +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + return [CT::T_NAMESPACE_OPERATOR]; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 50300; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + if (!$token->isGivenKind(T_NAMESPACE)) { + return; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + $nextToken = $tokens[$nextIndex]; + + if ($nextToken->isGivenKind(T_NS_SEPARATOR)) { + $tokens[$index] = new Token([CT::T_NAMESPACE_OPERATOR, $token->getContent()]); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..8eb1aa3a8de38497661ff32499b33fd4fbaf3673 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.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\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 +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + return [CT::T_NULLABLE_TYPE]; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + // needs to run after TypeColonTransformer + return -20; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 70100; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + if (!$token->equals('?')) { + return; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevIndex]; + + if ($prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON], [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_VAR], [T_STATIC]])) { + $tokens[$index] = new Token([CT::T_NULLABLE_TYPE, '?']); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..636a9e7af52354d050f9a200d00a5a7cb54c5128 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.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\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 +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + return [CT::T_RETURN_REF]; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 50000; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + $prevKinds = [T_FUNCTION]; + if (\PHP_VERSION_ID >= 70400) { + $prevKinds[] = T_FN; + } + + if ( + $token->equals('&') + && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind($prevKinds) + ) { + $tokens[$index] = new Token([CT::T_RETURN_REF, '&']); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..24499a52d0fd7fe8fc98fb0cd7fa95abf3646d61 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.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\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 + * @author SpacePossum + * + * @internal + */ +final class SquareBraceTransformer extends AbstractTransformer +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + 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, + ]; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + // must run after CurlyBraceTransformer + return -1; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + // 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 50000; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + if ($this->isArrayDestructing($tokens, $index)) { + $this->transformIntoDestructuringSquareBrace($tokens, $index); + + return; + } + + if ($this->isShortArray($tokens, $index)) { + $this->transformIntoArraySquareBrace($tokens, $index); + } + } + + /** + * @param int $index + */ + private function transformIntoArraySquareBrace(Tokens $tokens, $index) + { + $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, ']']); + } + + /** + * @param int $index + */ + private function transformIntoDestructuringSquareBrace(Tokens $tokens, $index) + { + $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. + * + * @param int $index + * + * @return bool + */ + private function isShortArray(Tokens $tokens, $index) + { + 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); + } + + /** + * @param int $index + * + * @return bool + */ + private function isArrayDestructing(Tokens $tokens, $index) + { + if (\PHP_VERSION_ID < 70100 || !$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; + } + + $type = Tokens::detectBlockType($tokens[$index]); + $end = $tokens->findBlockEnd($type['type'], $index); + + $nextToken = $tokens[$tokens->getNextMeaningfulToken($end)]; + + return $nextToken->equals('='); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..fefebc5fc0f3af29f84af2ad83f481f4ba410658 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.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\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform `|` operator into CT::T_TYPE_ALTERNATION in `} catch (ExceptionType1 | ExceptionType2 $e) {`. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class TypeAlternationTransformer extends AbstractTransformer +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + return [CT::T_TYPE_ALTERNATION]; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 70100; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + if (!$token->equals('|')) { + return; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevIndex]; + + if (!$prevToken->isGivenKind(T_STRING)) { + return; + } + + do { + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + if (null === $prevIndex) { + break; + } + + $prevToken = $tokens[$prevIndex]; + + if ($prevToken->isGivenKind([T_NS_SEPARATOR, T_STRING])) { + continue; + } + + if ( + $prevToken->isGivenKind(CT::T_TYPE_ALTERNATION) + || ( + $prevToken->equals('(') + && $tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isGivenKind(T_CATCH) + ) + ) { + $tokens[$index] = new Token([CT::T_TYPE_ALTERNATION, '|']); + } + + break; + } while (true); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..63dd21d23977017eb6eff99ad24d1a08bb343d82 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.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\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 +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + return [CT::T_TYPE_COLON]; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + // needs to run after ReturnRefTransformer and UseTransformer + return -10; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 70000; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + if (!$token->equals(':')) { + return; + } + + $endIndex = $tokens->getPrevMeaningfulToken($index); + + 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]; + } + + $prevKinds = [T_FUNCTION, CT::T_RETURN_REF, CT::T_USE_LAMBDA]; + if (\PHP_VERSION_ID >= 70400) { + $prevKinds[] = T_FN; + } + + if ($prevToken->isGivenKind($prevKinds)) { + $tokens[$index] = new Token([CT::T_TYPE_COLON, ':']); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..db4f906fd791a841fde303dffb5c3b89139c45ce --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.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\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 +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + return [CT::T_USE_TRAIT, CT::T_USE_LAMBDA]; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + // Should run after CurlyBraceTransformer and before TypeColonTransformer + return -5; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 50300; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + 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 + + if (!$token->isGivenKind([T_CLASS, T_TRAIT])) { + return; + } + + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_DOUBLE_COLON)) { + 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()]); + } + } + } + + /** + * Check if token under given index is `use` statement for lambda function. + * + * @param int $index + * + * @return bool + */ + private function isUseForLambda(Tokens $tokens, $index) + { + $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; + + // test `function () use ($foo) {}` case + return $nextToken->equals('('); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php new file mode 100644 index 0000000000000000000000000000000000000000..9878ff333adf4c4e83f5f4a6fb4c461f1d1270d6 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.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\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 +{ + /** + * {@inheritdoc} + */ + public function getCustomTokens() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getRequiredPhpVersionId() + { + return 50000; + } + + /** + * {@inheritdoc} + */ + public function process(Tokens $tokens, Token $token, $index) + { + 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])); + } + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..91edaca5544ea4386492bb5fbab3796f0c0b0891 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.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; + +/** + * 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 array + */ + public function getCustomTokens(); + + /** + * 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(); + + /** + * Returns the priority of the transformer. + * + * The default priority is 0 and higher priorities are executed first. + * + * @return int + */ + public function getPriority(); + + /** + * 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, eg 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 PhpCsFixer\Utils; +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 TransformerInterface[] + */ + private $items = []; + + /** + * Register built in Transformers. + */ + private function __construct() + { + $this->registerBuiltInTransformers(); + + usort($this->items, static function (TransformerInterface $a, TransformerInterface $b) { + return Utils::cmpInt($b->getPriority(), $a->getPriority()); + }); + } + + /** + * @return Transformers + */ + public static function create() + { + 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) + { + foreach ($this->items as $transformer) { + foreach ($tokens as $index => $token) { + $transformer->process($tokens, $token, $index); + } + } + } + + /** + * @param TransformerInterface $transformer Transformer + */ + private function registerTransformer(TransformerInterface $transformer) + { + if (\PHP_VERSION_ID >= $transformer->getRequiredPhpVersionId()) { + $this->items[] = $transformer; + } + } + + private function registerBuiltInTransformers() + { + static $registered = false; + + if ($registered) { + return; + } + + $registered = true; + + foreach ($this->findBuiltInTransformers() as $transformer) { + $this->registerTransformer($transformer); + } + } + + /** + * @return \Generator|TransformerInterface[] + */ + private function findBuiltInTransformers() + { + /** @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/lib/composer/friendsofphp/php-cs-fixer/src/ToolInfo.php b/lib/composer/friendsofphp/php-cs-fixer/src/ToolInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..98e3e2b114d1228c37ea63a3aa53ffe2da356326 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/ToolInfo.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; + +use PhpCsFixer\Console\Application; + +/** + * Obtain information about using version of tool. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class ToolInfo implements ToolInfoInterface +{ + const COMPOSER_PACKAGE_NAME = 'friendsofphp/php-cs-fixer'; + + const COMPOSER_LEGACY_PACKAGE_NAME = 'fabpot/php-cs-fixer'; + + /** + * @var null|array + */ + private $composerInstallationDetails; + + /** + * @var null|bool + */ + private $isInstalledByComposer; + + public function getComposerInstallationDetails() + { + 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); + + $packages = isset($composerInstalled['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() + { + $package = $this->getComposerInstallationDetails(); + + $versionSuffix = ''; + + if (isset($package['dist']['reference'])) { + $versionSuffix = '#'.$package['dist']['reference']; + } + + return $package['version'].$versionSuffix; + } + + public function getVersion() + { + if ($this->isInstalledByComposer()) { + return Application::VERSION.':'.$this->getComposerVersion(); + } + + return Application::VERSION; + } + + public function isInstalledAsPhar() + { + return 'phar://' === substr(__DIR__, 0, 7); + } + + public function isInstalledByComposer() + { + if (null === $this->isInstalledByComposer) { + $this->isInstalledByComposer = !$this->isInstalledAsPhar() && file_exists($this->getComposerInstalledFile()); + } + + return $this->isInstalledByComposer; + } + + public function getPharDownloadUri($version) + { + return sprintf( + 'https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/%s/php-cs-fixer.phar', + $version + ); + } + + private function getComposerInstalledFile() + { + return __DIR__.'/../../../composer/installed.json'; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/ToolInfoInterface.php b/lib/composer/friendsofphp/php-cs-fixer/src/ToolInfoInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..d5c370893fd1ec8ad4e91de639fe9d1f348c51cd --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/ToolInfoInterface.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; + +/** + * @internal + */ +interface ToolInfoInterface +{ + public function getComposerInstallationDetails(); + + public function getComposerVersion(); + + public function getVersion(); + + public function isInstalledAsPhar(); + + public function isInstalledByComposer(); + + public function getPharDownloadUri($version); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/Utils.php b/lib/composer/friendsofphp/php-cs-fixer/src/Utils.php new file mode 100644 index 0000000000000000000000000000000000000000..f9f900b926b023670e06b91b81d7b21b8206f06b --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/Utils.php @@ -0,0 +1,185 @@ + + * Dariusz 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 + */ +final class Utils +{ + /** + * Calculate a bitmask for given constant names. + * + * @param string[] $options constant names + * + * @return int + */ + public static function calculateBitmask(array $options) + { + $bitmask = 0; + + foreach ($options as $optionName) { + if (\defined($optionName)) { + $bitmask |= \constant($optionName); + } + } + + return $bitmask; + } + + /** + * Converts a camel cased string to a snake cased string. + * + * @param string $string + * + * @return string + */ + public static function camelCaseToUnderscore($string) + { + return 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. + * + * @param mixed[] $elements + * @param callable $getComparedValue a callable that takes a single element and returns the value to compare + * @param callable $compareValues a callable that compares two values + * + * @return mixed[] + */ + public static function stableSort(array $elements, callable $getComparedValue, callable $compareValues) + { + array_walk($elements, static function (&$element, $index) use ($getComparedValue) { + $element = [$element, $index, $getComparedValue($element)]; + }); + + usort($elements, static function ($a, $b) use ($compareValues) { + $comparison = $compareValues($a[2], $b[2]); + + if (0 !== $comparison) { + return $comparison; + } + + return self::cmpInt($a[1], $b[1]); + }); + + return array_map(static function (array $item) { + return $item[0]; + }, $elements); + } + + /** + * Sort fixers by their priorities. + * + * @param FixerInterface[] $fixers + * + * @return FixerInterface[] + */ + public static function sortFixers(array $fixers) + { + // 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 function (FixerInterface $fixer) { + return $fixer->getPriority(); + }, + static function ($a, $b) { + return self::cmpInt($b, $a); + } + ); + } + + /** + * Join names in natural language wrapped in backticks, e.g. `a`, `b` and `c`. + * + * @param string[] $names + * + * @throws \InvalidArgumentException + * + * @return string + */ + public static function naturalLanguageJoinWithBackticks(array $names) + { + if (empty($names)) { + throw new \InvalidArgumentException('Array of names cannot be empty'); + } + + $names = array_map(static function ($name) { + return sprintf('`%s`', $name); + }, $names); + + $last = array_pop($names); + + if ($names) { + return implode(', ', $names).' and '.$last; + } + + return $last; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/WhitespacesFixerConfig.php b/lib/composer/friendsofphp/php-cs-fixer/src/WhitespacesFixerConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..93ad5f433b686e4f7ae0997d60018f78e9607c1f --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/WhitespacesFixerConfig.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; + +/** + * @author Dariusz Rumiński + */ +final class WhitespacesFixerConfig +{ + private $indent; + private $lineEnding; + + /** + * @param string $indent + * @param string $lineEnding + */ + public function __construct($indent = ' ', $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; + } + + /** + * @return string + */ + public function getIndent() + { + return $this->indent; + } + + /** + * @return string + */ + public function getLineEnding() + { + return $this->lineEnding; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/src/WordMatcher.php b/lib/composer/friendsofphp/php-cs-fixer/src/WordMatcher.php new file mode 100644 index 0000000000000000000000000000000000000000..d0f529d6b0809fadfc93364b68ff28700275ea26 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/src/WordMatcher.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; + +/** + * @author Dariusz Rumiński + * @author SpacePossum + * + * @internal + */ +final class WordMatcher +{ + /** + * @var string[] + */ + private $candidates; + + /** + * @param string[] $candidates + */ + public function __construct(array $candidates) + { + $this->candidates = $candidates; + } + + /** + * @param string $needle + * + * @return null|string + */ + public function match($needle) + { + $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/lib/composer/friendsofphp/php-cs-fixer/tests/Test/AbstractFixerTestCase.php b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/AbstractFixerTestCase.php new file mode 100644 index 0000000000000000000000000000000000000000..1a0016c831f9e8eefda6381418e8022ea3750f14 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/AbstractFixerTestCase.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\Tests\Test; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Linter\CachingLinter; +use PhpCsFixer\Linter\Linter; +use PhpCsFixer\Linter\LinterInterface; +use PhpCsFixer\Tests\Test\Assert\AssertTokensTrait; +use PhpCsFixer\Tests\TestCase; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Prophecy\Argument; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +abstract class AbstractFixerTestCase extends TestCase +{ + use AssertTokensTrait; + use IsIdenticalConstraint; + + /** + * @var null|LinterInterface + */ + protected $linter; + + /** + * @var null|AbstractFixer + */ + protected $fixer; + + protected function setUp() + { + parent::setUp(); + + $this->linter = $this->getLinter(); + $this->fixer = $this->createFixer(); + + // @todo remove at 3.0 together with env var itself + if (getenv('PHP_CS_FIXER_TEST_USE_LEGACY_TOKENIZER')) { + Tokens::setLegacyMode(true); + } + } + + protected function tearDown() + { + parent::tearDown(); + + $this->linter = null; + $this->fixer = null; + + // @todo remove at 3.0 + Tokens::setLegacyMode(false); + } + + /** + * @return AbstractFixer + */ + protected function createFixer() + { + $fixerClassName = preg_replace('/^(PhpCsFixer)\\\\Tests(\\\\.+)Test$/', '$1$2', static::class); + + return new $fixerClassName(); + } + + /** + * @param string $filename + * + * @return \SplFileInfo + */ + protected function getTestFile($filename = __FILE__) + { + static $files = []; + + if (!isset($files[$filename])) { + $files[$filename] = new \SplFileInfo($filename); + } + + return $files[$filename]; + } + + /** + * Tests if a fixer fixes a given string to match the expected result. + * + * It is used both if you want to test if something is fixed or if it is not touched by the fixer. + * It also makes sure that the expected output does not change when run through the fixer. That means that you + * do not need two test cases like [$expected] and [$expected, $input] (where $expected is the same in both cases) + * as the latter covers both of them. + * This method throws an exception if $expected and $input are equal to prevent test cases that accidentally do + * not test anything. + * + * @param string $expected The expected fixer output + * @param null|string $input The fixer input, or null if it should intentionally be equal to the output + * @param null|\SplFileInfo $file The file to fix, or null if unneeded + */ + protected function doTest($expected, $input = null, \SplFileInfo $file = null) + { + if ($expected === $input) { + throw new \InvalidArgumentException('Input parameter must not be equal to expected parameter.'); + } + + $file = $file ?: $this->getTestFile(); + $fileIsSupported = $this->fixer->supports($file); + + if (null !== $input) { + static::assertNull($this->lintSource($input)); + + Tokens::clearCache(); + $tokens = Tokens::fromCode($input); + + if ($fileIsSupported) { + static::assertTrue($this->fixer->isCandidate($tokens), 'Fixer must be a candidate for input code.'); + static::assertFalse($tokens->isChanged(), 'Fixer must not touch Tokens on candidate check.'); + $fixResult = $this->fixer->fix($file, $tokens); + static::assertNull($fixResult, '->fix method must return null.'); + } + + static::assertThat( + $tokens->generateCode(), + self::createIsIdenticalStringConstraint($expected), + 'Code build on input code must match expected code.' + ); + static::assertTrue($tokens->isChanged(), 'Tokens collection built on input code must be marked as changed after fixing.'); + + $tokens->clearEmptyTokens(); + + static::assertSame( + \count($tokens), + \count(array_unique(array_map(static function (Token $token) { + return spl_object_hash($token); + }, $tokens->toArray()))), + 'Token items inside Tokens collection must be unique.' + ); + + Tokens::clearCache(); + $expectedTokens = Tokens::fromCode($expected); + static::assertTokens($expectedTokens, $tokens); + } + + static::assertNull($this->lintSource($expected)); + + Tokens::clearCache(); + $tokens = Tokens::fromCode($expected); + + if ($fileIsSupported) { + $fixResult = $this->fixer->fix($file, $tokens); + static::assertNull($fixResult, '->fix method must return null.'); + } + + static::assertThat( + $tokens->generateCode(), + self::createIsIdenticalStringConstraint($expected), + 'Code build on expected code must not change.' + ); + static::assertFalse($tokens->isChanged(), 'Tokens collection built on expected code must not be marked as changed after fixing.'); + } + + /** + * @param string $source + * + * @return null|string + */ + protected function lintSource($source) + { + try { + $this->linter->lintSource($source)->check(); + } catch (\Exception $e) { + return $e->getMessage()."\n\nSource:\n{$source}"; + } + + return null; + } + + /** + * @return LinterInterface + */ + private function getLinter() + { + static $linter = null; + + if (null === $linter) { + if (getenv('SKIP_LINT_TEST_CASES')) { + $linterProphecy = $this->prophesize(\PhpCsFixer\Linter\LinterInterface::class); + $linterProphecy + ->lintSource(Argument::type('string')) + ->willReturn($this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class)->reveal()) + ; + + $linter = $linterProphecy->reveal(); + } else { + $linter = new CachingLinter(new Linter()); + } + } + + return $linter; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/tests/Test/AbstractIntegrationCaseFactory.php b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/AbstractIntegrationCaseFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..cd3c9b52ad1da1b67734eeddc8668c67bcac1f28 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/AbstractIntegrationCaseFactory.php @@ -0,0 +1,254 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Test; + +use PhpCsFixer\RuleSet; +use Symfony\Component\Finder\SplFileInfo; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +abstract class AbstractIntegrationCaseFactory implements IntegrationCaseFactoryInterface +{ + /** + * @return IntegrationCase + */ + public function create(SplFileInfo $file) + { + try { + if (!preg_match( + '/^ + --TEST-- \r?\n(? .*?) + \s --RULESET-- \r?\n(?<ruleset> .*?) + (?:\s --CONFIG-- \r?\n(?<config> .*?))? + (?:\s --SETTINGS-- \r?\n(?<settings> .*?))? + (?:\s --REQUIREMENTS-- \r?\n(?<requirements> .*?))? + (?:\s --EXPECT-- \r?\n(?<expect> .*?\r?\n*))? + (?:\s --INPUT-- \r?\n(?<input> .*))? + $/sx', + $file->getContents(), + $match + )) { + throw new \InvalidArgumentException('File format is invalid.'); + } + + $match = array_merge( + [ + 'config' => null, + 'settings' => null, + 'requirements' => null, + 'expect' => null, + 'input' => null, + ], + $match + ); + + return new IntegrationCase( + $file->getRelativePathname(), + $this->determineTitle($file, $match['title']), + $this->determineSettings($file, $match['settings']), + $this->determineRequirements($file, $match['requirements']), + $this->determineConfig($file, $match['config']), + $this->determineRuleset($file, $match['ruleset']), + $this->determineExpectedCode($file, $match['expect']), + $this->determineInputCode($file, $match['input']) + ); + } catch (\InvalidArgumentException $e) { + throw new \InvalidArgumentException( + sprintf('%s Test file: "%s".', $e->getMessage(), $file->getRelativePathname()), + $e->getCode(), + $e + ); + } + } + + /** + * Parses the '--CONFIG--' block of a '.test' file. + * + * @param string $config + * + * @return array + */ + protected function determineConfig(SplFileInfo $file, $config) + { + $parsed = $this->parseJson($config, [ + 'indent' => ' ', + 'lineEnding' => "\n", + ]); + + if (!\is_string($parsed['indent'])) { + throw new \InvalidArgumentException(sprintf( + 'Expected string value for "indent", got "%s".', + \is_object($parsed['indent']) ? \get_class($parsed['indent']) : \gettype($parsed['indent']).'#'.$parsed['indent'] + )); + } + + if (!\is_string($parsed['lineEnding'])) { + throw new \InvalidArgumentException(sprintf( + 'Expected string value for "lineEnding", got "%s".', + \is_object($parsed['lineEnding']) ? \get_class($parsed['lineEnding']) : \gettype($parsed['lineEnding']).'#'.$parsed['lineEnding'] + )); + } + + return $parsed; + } + + /** + * Parses the '--REQUIREMENTS--' block of a '.test' file and determines requirements. + * + * @param string $config + * + * @return array + */ + protected function determineRequirements(SplFileInfo $file, $config) + { + $parsed = $this->parseJson($config, [ + 'php' => \PHP_VERSION_ID, + ]); + + if (!\is_int($parsed['php'])) { + throw new \InvalidArgumentException(sprintf( + 'Expected int value like 50509 for "php", got "%s".', + \is_object($parsed['php']) ? \get_class($parsed['php']) : \gettype($parsed['php']).'#'.$parsed['php'] + )); + } + + return $parsed; + } + + /** + * Parses the '--RULESET--' block of a '.test' file and determines what fixers should be used. + * + * @param string $config + * + * @return RuleSet + */ + protected function determineRuleset(SplFileInfo $file, $config) + { + return new RuleSet($this->parseJson($config)); + } + + /** + * Parses the '--TEST--' block of a '.test' file and determines title. + * + * @param string $config + * + * @return string + */ + protected function determineTitle(SplFileInfo $file, $config) + { + return $config; + } + + /** + * Parses the '--SETTINGS--' block of a '.test' file and determines settings. + * + * @param string $config + * + * @return array + */ + protected function determineSettings(SplFileInfo $file, $config) + { + $parsed = $this->parseJson($config, [ + 'checkPriority' => true, + ]); + + if (!\is_bool($parsed['checkPriority'])) { + throw new \InvalidArgumentException(sprintf( + 'Expected bool value for "checkPriority", got "%s".', + \is_object($parsed['checkPriority']) ? \get_class($parsed['checkPriority']) : \gettype($parsed['checkPriority']).'#'.$parsed['checkPriority'] + )); + } + + return $parsed; + } + + /** + * @param null|string $code + * + * @return string + */ + protected function determineExpectedCode(SplFileInfo $file, $code) + { + $code = $this->determineCode($file, $code, '-out.php'); + + if (null === $code) { + throw new \InvalidArgumentException('Missing expected code.'); + } + + return $code; + } + + /** + * @param null|string $code + * + * @return null|string + */ + protected function determineInputCode(SplFileInfo $file, $code) + { + return $this->determineCode($file, $code, '-in.php'); + } + + /** + * @param null|string $code + * @param string $suffix + * + * @return null|string + */ + private function determineCode(SplFileInfo $file, $code, $suffix) + { + if (null !== $code) { + return $code; + } + + $candidateFile = new SplFileInfo($file->getPathname().$suffix, '', ''); + if ($candidateFile->isFile()) { + return $candidateFile->getContents(); + } + + return null; + } + + /** + * @param null|string $encoded + * + * @return array + */ + private function parseJson($encoded, array $template = null) + { + // content is optional if template is provided + if (!$encoded && null !== $template) { + $decoded = []; + } else { + $decoded = json_decode($encoded, true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException(sprintf('Malformed JSON: "%s", error: "%s".', $encoded, json_last_error_msg())); + } + } + + if (null !== $template) { + $decoded = array_merge( + $template, + array_intersect_key( + $decoded, + array_flip(array_keys($template)) + ) + ); + } + + return $decoded; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/tests/Test/AbstractIntegrationTestCase.php b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/AbstractIntegrationTestCase.php new file mode 100644 index 0000000000000000000000000000000000000000..8d8d6863f6409fa898cbfb2c5482ead44b2b1f7a --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/AbstractIntegrationTestCase.php @@ -0,0 +1,402 @@ +<?php + +/* + * This file is part of PHP CS Fixer. + * + * (c) Fabien Potencier <fabien@symfony.com> + * Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Test; + +use PhpCsFixer\Cache\NullCacheManager; +use PhpCsFixer\Differ\SebastianBergmannDiffer; +use PhpCsFixer\Error\Error; +use PhpCsFixer\Error\ErrorsManager; +use PhpCsFixer\FileRemoval; +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\FixerFactory; +use PhpCsFixer\Linter\CachingLinter; +use PhpCsFixer\Linter\Linter; +use PhpCsFixer\Linter\LinterInterface; +use PhpCsFixer\Runner\Runner; +use PhpCsFixer\Tests\TestCase; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\WhitespacesFixerConfig; +use Prophecy\Argument; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Integration test base class. + * + * This test searches for '.test' fixture files in the given directory. + * Each fixture file will be parsed and tested against the expected result. + * + * Fixture files have the following format: + * + * --TEST-- + * Example test description. + * --RULESET-- + * {"@PSR2": true, "strict": true} + * --CONFIG--* + * {"indent": " ", "lineEnding": "\n"} + * --SETTINGS--* + * {"key": "value"} # optional extension point for custom IntegrationTestCase class + * --EXPECT-- + * Expected code after fixing + * --INPUT--* + * Code to fix + * + * * Section or any line in it may be omitted. + * ** PHP minimum version. Default to current running php version (no effect). + * + * @author SpacePossum + * + * @internal + */ +abstract class AbstractIntegrationTestCase extends TestCase +{ + use IsIdenticalConstraint; + + /** + * @var null|LinterInterface + */ + protected $linter; + + /** + * @var null|FileRemoval + */ + private static $fileRemoval; + + public static function setUpBeforeClass() + { + parent::setUpBeforeClass(); + + $tmpFile = static::getTempFile(); + self::$fileRemoval = new FileRemoval(); + self::$fileRemoval->observe($tmpFile); + + if (!is_file($tmpFile)) { + $dir = \dirname($tmpFile); + + if (!is_dir($dir)) { + $fs = new Filesystem(); + $fs->mkdir($dir, 0766); + } + } + } + + public static function tearDownAfterClass() + { + parent::tearDownAfterClass(); + + $tmpFile = static::getTempFile(); + + self::$fileRemoval->delete($tmpFile); + self::$fileRemoval = null; + } + + protected function setUp() + { + parent::setUp(); + + $this->linter = $this->getLinter(); + + // @todo remove at 3.0 together with env var itself + if (getenv('PHP_CS_FIXER_TEST_USE_LEGACY_TOKENIZER')) { + Tokens::setLegacyMode(true); + } + } + + protected function tearDown() + { + parent::tearDown(); + + $this->linter = null; + + // @todo remove at 3.0 + Tokens::setLegacyMode(false); + } + + /** + * @dataProvider provideIntegrationCases + * + * @see doTest() + */ + public function testIntegration(IntegrationCase $case) + { + $this->doTest($case); + } + + /** + * Creates test data by parsing '.test' files. + * + * @return IntegrationCase[][] + */ + public function provideIntegrationCases() + { + $fixturesDir = realpath(static::getFixturesDir()); + if (!is_dir($fixturesDir)) { + throw new \UnexpectedValueException(sprintf('Given fixture dir "%s" is not a directory.', $fixturesDir)); + } + + $factory = static::createIntegrationCaseFactory(); + $tests = []; + + /** @var SplFileInfo $file */ + foreach (Finder::create()->files()->in($fixturesDir) as $file) { + if ('test' !== $file->getExtension()) { + continue; + } + + $tests[$file->getPathname()] = [ + $factory->create($file), + ]; + } + + return $tests; + } + + /** + * @return IntegrationCaseFactoryInterface + */ + protected static function createIntegrationCaseFactory() + { + return new IntegrationCaseFactory(); + } + + /** + * Returns the full path to directory which contains the tests. + * + * @return string + */ + protected static function getFixturesDir() + { + throw new \BadMethodCallException('Method "getFixturesDir" must be overridden by the extending class.'); + } + + /** + * Returns the full path to the temporary file where the test will write to. + * + * @return string + */ + protected static function getTempFile() + { + throw new \BadMethodCallException('Method "getTempFile" must be overridden by the extending class.'); + } + + /** + * Applies the given fixers on the input and checks the result. + * + * It will write the input to a temp file. The file will be fixed by a Fixer instance + * configured with the given fixers. The result is compared with the expected output. + * It checks if no errors were reported during the fixing. + */ + protected function doTest(IntegrationCase $case) + { + if (\PHP_VERSION_ID < $case->getRequirement('php')) { + static::markTestSkipped(sprintf('PHP %d (or later) is required for "%s", current "%d".', $case->getRequirement('php'), $case->getFileName(), \PHP_VERSION_ID)); + } + + $input = $case->getInputCode(); + $expected = $case->getExpectedCode(); + + $input = $case->hasInputCode() ? $input : $expected; + + $tmpFile = static::getTempFile(); + + if (false === @file_put_contents($tmpFile, $input)) { + throw new IOException(sprintf('Failed to write to tmp. file "%s".', $tmpFile)); + } + + $errorsManager = new ErrorsManager(); + $fixers = static::createFixers($case); + $runner = new Runner( + new \ArrayIterator([new \SplFileInfo($tmpFile)]), + $fixers, + new SebastianBergmannDiffer(), + null, + $errorsManager, + $this->linter, + false, + new NullCacheManager() + ); + + Tokens::clearCache(); + $result = $runner->fix(); + $changed = array_pop($result); + + if (!$errorsManager->isEmpty()) { + $errors = $errorsManager->getExceptionErrors(); + static::assertEmpty($errors, sprintf('Errors reported during fixing of file "%s": %s', $case->getFileName(), $this->implodeErrors($errors))); + + $errors = $errorsManager->getInvalidErrors(); + static::assertEmpty($errors, sprintf('Errors reported during linting before fixing file "%s": %s.', $case->getFileName(), $this->implodeErrors($errors))); + + $errors = $errorsManager->getLintErrors(); + static::assertEmpty($errors, sprintf('Errors reported during linting after fixing file "%s": %s.', $case->getFileName(), $this->implodeErrors($errors))); + } + + if (!$case->hasInputCode()) { + static::assertEmpty( + $changed, + sprintf( + "Expected no changes made to test \"%s\" in \"%s\".\nFixers applied:\n%s.\nDiff.:\n%s.", + $case->getTitle(), + $case->getFileName(), + null === $changed ? '[None]' : implode(',', $changed['appliedFixers']), + null === $changed ? '[None]' : $changed['diff'] + ) + ); + + return; + } + + static::assertNotEmpty($changed, sprintf('Expected changes made to test "%s" in "%s".', $case->getTitle(), $case->getFileName())); + $fixedInputCode = file_get_contents($tmpFile); + static::assertThat( + $fixedInputCode, + self::createIsIdenticalStringConstraint($expected), + sprintf( + "Expected changes do not match result for \"%s\" in \"%s\".\nFixers applied:\n%s.", + $case->getTitle(), + $case->getFileName(), + null === $changed ? '[None]' : implode(',', $changed['appliedFixers']) + ) + ); + + if (1 < \count($fixers)) { + $tmpFile = static::getTempFile(); + if (false === @file_put_contents($tmpFile, $input)) { + throw new IOException(sprintf('Failed to write to tmp. file "%s".', $tmpFile)); + } + + $runner = new Runner( + new \ArrayIterator([new \SplFileInfo($tmpFile)]), + array_reverse($fixers), + new SebastianBergmannDiffer(), + null, + $errorsManager, + $this->linter, + false, + new NullCacheManager() + ); + + Tokens::clearCache(); + $runner->fix(); + $fixedInputCodeWithReversedFixers = file_get_contents($tmpFile); + + static::assertRevertedOrderFixing($case, $fixedInputCode, $fixedInputCodeWithReversedFixers); + } + + // run the test again with the `expected` part, this should always stay the same + $this->testIntegration( + new IntegrationCase( + $case->getFileName(), + $case->getTitle().' "--EXPECT-- part run"', + $case->getSettings(), + $case->getRequirements(), + $case->getConfig(), + $case->getRuleset(), + $case->getExpectedCode(), + null + ) + ); + } + + /** + * @param string $fixedInputCode + * @param string $fixedInputCodeWithReversedFixers + */ + protected static function assertRevertedOrderFixing(IntegrationCase $case, $fixedInputCode, $fixedInputCodeWithReversedFixers) + { + // If output is different depends on rules order - we need to verify that the rules are ordered by priority. + // If not, any order is valid. + if ($fixedInputCode !== $fixedInputCodeWithReversedFixers) { + static::assertGreaterThan( + 1, + \count(array_unique(array_map( + static function (FixerInterface $fixer) { + return $fixer->getPriority(); + }, + static::createFixers($case) + ))), + sprintf( + 'Rules priorities are not differential enough. If rules would be used in reverse order then final output would be different than the expected one. For that, different priorities must be set up for used rules to ensure stable order of them. In "%s".', + $case->getFileName() + ) + ); + } + } + + /** + * @return FixerInterface[] + */ + private static function createFixers(IntegrationCase $case) + { + $config = $case->getConfig(); + + return FixerFactory::create() + ->registerBuiltInFixers() + ->useRuleSet($case->getRuleset()) + ->setWhitespacesConfig( + new WhitespacesFixerConfig($config['indent'], $config['lineEnding']) + ) + ->getFixers() + ; + } + + /** + * @param Error[] $errors + * + * @return string + */ + private function implodeErrors(array $errors) + { + $errorStr = ''; + foreach ($errors as $error) { + $source = $error->getSource(); + $errorStr .= sprintf("%d: %s%s\n", $error->getType(), $error->getFilePath(), null === $source ? '' : ' '.$source->getMessage()."\n\n".$source->getTraceAsString()); + } + + return $errorStr; + } + + /** + * @return LinterInterface + */ + private function getLinter() + { + static $linter = null; + + if (null === $linter) { + if (getenv('SKIP_LINT_TEST_CASES')) { + $linterProphecy = $this->prophesize(\PhpCsFixer\Linter\LinterInterface::class); + $linterProphecy + ->lintSource(Argument::type('string')) + ->willReturn($this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class)->reveal()) + ; + $linterProphecy + ->lintFile(Argument::type('string')) + ->willReturn($this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class)->reveal()) + ; + $linterProphecy + ->isAsync() + ->willReturn(false) + ; + + $linter = $linterProphecy->reveal(); + } else { + $linter = new CachingLinter(new Linter()); + } + } + + return $linter; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/tests/Test/Assert/AssertTokensTrait.php b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/Assert/AssertTokensTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..409446ef4f8f8f818f0d5d4246f592856c1e8333 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/Assert/AssertTokensTrait.php @@ -0,0 +1,48 @@ +<?php + +/* + * This file is part of PHP CS Fixer. + * + * (c) Fabien Potencier <fabien@symfony.com> + * Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Test\Assert; + +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * @internal + */ +trait AssertTokensTrait +{ + private static function assertTokens(Tokens $expectedTokens, Tokens $inputTokens) + { + foreach ($expectedTokens as $index => $expectedToken) { + $inputToken = $inputTokens[$index]; + + static::assertTrue( + $expectedToken->equals($inputToken), + sprintf("The token at index %d must be:\n%s,\ngot:\n%s.", $index, $expectedToken->toJson(), $inputToken->toJson()) + ); + + $expectedTokenKind = $expectedToken->isArray() ? $expectedToken->getId() : $expectedToken->getContent(); + static::assertTrue( + $inputTokens->isTokenKindFound($expectedTokenKind), + sprintf( + 'The token kind %s (%s) must be found in tokens collection.', + $expectedTokenKind, + \is_string($expectedTokenKind) ? $expectedTokenKind : Token::getNameForId($expectedTokenKind) + ) + ); + } + + static::assertSame($expectedTokens->count(), $inputTokens->count(), 'Both collections must have the same length.'); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IntegrationCase.php b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IntegrationCase.php new file mode 100644 index 0000000000000000000000000000000000000000..41487990c2be8e39e34261aca1012ef9dd05827c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IntegrationCase.php @@ -0,0 +1,153 @@ +<?php + +/* + * This file is part of PHP CS Fixer. + * + * (c) Fabien Potencier <fabien@symfony.com> + * Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Test; + +use PhpCsFixer\RuleSet; + +/** + * @author Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * @internal + */ +final class IntegrationCase +{ + private $config; + + /** + * @var string + */ + private $expectedCode; + + /** + * @var string + */ + private $fileName; + + /** + * @var null|string + */ + private $inputCode; + + /** + * Env requirements (possible keys: php). + * + * @var array + */ + private $requirements; + + /** + * @var RuleSet + */ + private $ruleset; + + /** + * Settings how to perform the test (possible keys: none in base class, use as extension point for custom IntegrationTestCase). + * + * @var array + */ + private $settings; + + /** + * @var string + */ + private $title; + + /** + * @param string $fileName + * @param string $title + * @param string $expectedCode + * @param null|string $inputCode + */ + public function __construct( + $fileName, + $title, + array $settings, + array $requirements, + array $config, + RuleSet $ruleset, + $expectedCode, + $inputCode + ) { + $this->fileName = $fileName; + $this->title = $title; + $this->settings = $settings; + $this->requirements = $requirements; + $this->config = $config; + $this->ruleset = $ruleset; + $this->expectedCode = $expectedCode; + $this->inputCode = $inputCode; + } + + public function hasInputCode() + { + return null !== $this->inputCode; + } + + public function getConfig() + { + return $this->config; + } + + public function getExpectedCode() + { + return $this->expectedCode; + } + + public function getFileName() + { + return $this->fileName; + } + + public function getInputCode() + { + return $this->inputCode; + } + + /** + * @param string $name + * + * @return mixed + */ + public function getRequirement($name) + { + if (!\array_key_exists($name, $this->requirements)) { + throw new \InvalidArgumentException(sprintf( + 'Unknown requirement key "%s", expected any of "%s".', + $name, + implode('","', array_keys($this->requirements)) + )); + } + + return $this->requirements[$name]; + } + + public function getRequirements() + { + return $this->requirements; + } + + public function getRuleset() + { + return $this->ruleset; + } + + public function getSettings() + { + return $this->settings; + } + + public function getTitle() + { + return $this->title; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IntegrationCaseFactory.php b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IntegrationCaseFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..14702f2b3e4b82c51ff5236eb36cff2f07b43a1e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IntegrationCaseFactory.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of PHP CS Fixer. + * + * (c) Fabien Potencier <fabien@symfony.com> + * Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Test; + +/** + * @author Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * @internal + */ +final class IntegrationCaseFactory extends AbstractIntegrationCaseFactory +{ +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IntegrationCaseFactoryInterface.php b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IntegrationCaseFactoryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..f69a2ebf687d2775c9d87027c4b43e44d564e65e --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IntegrationCaseFactoryInterface.php @@ -0,0 +1,28 @@ +<?php + +/* + * This file is part of PHP CS Fixer. + * + * (c) Fabien Potencier <fabien@symfony.com> + * Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Test; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * @author Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * @internal + */ +interface IntegrationCaseFactoryInterface +{ + /** + * @return IntegrationCase + */ + public function create(SplFileInfo $file); +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/tests/Test/InternalIntegrationCaseFactory.php b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/InternalIntegrationCaseFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..0228b2cc537c73a1b9047ac4a40957ff81e48f60 --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/InternalIntegrationCaseFactory.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of PHP CS Fixer. + * + * (c) Fabien Potencier <fabien@symfony.com> + * Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Test; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * @author Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * @internal + */ +final class InternalIntegrationCaseFactory extends AbstractIntegrationCaseFactory +{ + /** + * {@inheritdoc} + */ + protected function determineSettings(SplFileInfo $file, $config) + { + $parsed = parent::determineSettings($file, $config); + + $parsed['isExplicitPriorityCheck'] = \in_array('priority', explode(\DIRECTORY_SEPARATOR, $file->getRelativePathname()), true); + + return $parsed; + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IsIdenticalConstraint.php b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IsIdenticalConstraint.php new file mode 100644 index 0000000000000000000000000000000000000000..114d9cb0e979d6cbee1218aeb155dc7ff5a977ce --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/tests/Test/IsIdenticalConstraint.php @@ -0,0 +1,56 @@ +<?php + +/* + * This file is part of PHP CS Fixer. + * + * (c) Fabien Potencier <fabien@symfony.com> + * Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Test; + +use PhpCsFixer\PhpunitConstraintIsIdenticalString\Constraint\IsIdenticalString; +use PHPUnit\Framework\Constraint\IsIdentical as PhpUnitIsIdentical; + +/** + * @internal + * + * @todo Remove me when usages will end up in dedicated package. + */ +trait IsIdenticalConstraint +{ + /** + * @todo Remove me when this class will end up in dedicated package. + * + * @param string $expected + * + * @return IsIdenticalString|\PHPUnit_Framework_Constraint_IsIdentical|PhpUnitIsIdentical + */ + private static function createIsIdenticalStringConstraint($expected) + { + $candidate = self::getIsIdenticalStringConstraintClassName(); + + return new $candidate($expected); + } + + /** + * @return string + */ + private static function getIsIdenticalStringConstraintClassName() + { + foreach ([ + IsIdenticalString::class, + PhpUnitIsIdentical::class, + 'PHPUnit_Framework_Constraint_IsIdentical', + ] as $className) { + if (class_exists($className)) { + return $className; + } + } + + throw new \RuntimeException('PHPUnit not installed?!'); + } +} diff --git a/lib/composer/friendsofphp/php-cs-fixer/tests/TestCase.php b/lib/composer/friendsofphp/php-cs-fixer/tests/TestCase.php new file mode 100644 index 0000000000000000000000000000000000000000..1702107b2ca0eba6d48f2f3399120b55ccf5863c --- /dev/null +++ b/lib/composer/friendsofphp/php-cs-fixer/tests/TestCase.php @@ -0,0 +1,49 @@ +<?php + +/* + * This file is part of PHP CS Fixer. + * + * (c) Fabien Potencier <fabien@symfony.com> + * Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests; + +use PHPUnit\Framework\TestCase as BaseTestCase; +use PHPUnitGoodPractices\Traits\ExpectationViaCodeOverAnnotationTrait; +use PHPUnitGoodPractices\Traits\ExpectOverSetExceptionTrait; +use PHPUnitGoodPractices\Traits\IdentityOverEqualityTrait; +use PHPUnitGoodPractices\Traits\ProphecyOverMockObjectTrait; +use PHPUnitGoodPractices\Traits\ProphesizeOnlyInterfaceTrait; + +if (trait_exists(ProphesizeOnlyInterfaceTrait::class)) { + /** + * @author Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * @internal + */ + abstract class TestCase extends BaseTestCase + { + use ExpectationViaCodeOverAnnotationTrait; + use ExpectOverSetExceptionTrait; + use IdentityOverEqualityTrait; + use ProphecyOverMockObjectTrait; + use ProphesizeOnlyInterfaceTrait; + } +} else { + /** + * Version without traits for cases when this class is used as a lib. + * + * @author Dariusz Rumiński <dariusz.ruminski@gmail.com> + * + * @internal + * + * @todo 3.0 To be removed when we clean up composer prod-autoloader from dev-packages. + */ + abstract class TestCase extends BaseTestCase + { + } +} diff --git a/lib/composer/netresearch/jsonmapper/LICENSE b/lib/composer/netresearch/jsonmapper/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2ebd4813bba3d5c1b73e3e70118bf63392972463 --- /dev/null +++ b/lib/composer/netresearch/jsonmapper/LICENSE @@ -0,0 +1,47 @@ +Open Software License v. 3.0 (OSL-3.0) + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + + Licensed under the Open Software License version 3.0 + +1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + a) to reproduce the Original Work in copies, either alone or as part of a collective work; + + b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + c) to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + d) to perform the Original Work publicly; and + + e) to display the Original Work publicly. + +2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + +3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + +4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor’s trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + +5) External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + +6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + +7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + +8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + +9) Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including “fair use” or “fair dealing”). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + +10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + +11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + +12) Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + +13) Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + +14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For 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. + +15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + +16) Modification of This License. This License is Copyright (c) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/lib/composer/netresearch/jsonmapper/composer.json b/lib/composer/netresearch/jsonmapper/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..b6d2777028e00282bde8e31bde91dcff7b79dd43 --- /dev/null +++ b/lib/composer/netresearch/jsonmapper/composer.json @@ -0,0 +1,31 @@ +{ + "name": "netresearch/jsonmapper", + "description": "Map nested JSON structures onto PHP classes", + "license": "OSL-3.0", + "autoload": { + "psr-0": {"JsonMapper": "src/"} + }, + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues" + }, + "require":{ + "php": ">=5.6", + "ext-spl": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4 || ~7.0", + "squizlabs/php_codesniffer": "~3.5" + } +} diff --git a/lib/composer/netresearch/jsonmapper/contributing.rst b/lib/composer/netresearch/jsonmapper/contributing.rst new file mode 100644 index 0000000000000000000000000000000000000000..1e40f12c12b391a3000cf68c9e91e3e284d41476 --- /dev/null +++ b/lib/composer/netresearch/jsonmapper/contributing.rst @@ -0,0 +1,13 @@ +************************************** +How to add your features to JsonMapper +************************************** + +If you want to add a new feature or a fix to this library, please consider these aspects: + +- Respect the original code style and continue using it - it uses `PEAR Coding Standards`__. +- Pull requests fixing a bug should include a test case that illustrates the wrong behaviour. +- Pull requests adding a new feature should also include a test for the new feature. + + __ http://pear.php.net/manual/en/standards.php + +Having test cases included in your pull request greatly helps reviewing it and will increase the chance of it being merged. diff --git a/lib/composer/netresearch/jsonmapper/src/JsonMapper.php b/lib/composer/netresearch/jsonmapper/src/JsonMapper.php new file mode 100644 index 0000000000000000000000000000000000000000..6977b0fe93704a4d6b5bb5bb672a506054c5a60b --- /dev/null +++ b/lib/composer/netresearch/jsonmapper/src/JsonMapper.php @@ -0,0 +1,829 @@ +<?php +/** + * Part of JsonMapper + * + * PHP version 5 + * + * @category Netresearch + * @package JsonMapper + * @author Christian Weiske <cweiske@cweiske.de> + * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 + * @link http://cweiske.de/ + */ + +/** + * Automatically map JSON structures into objects. + * + * @category Netresearch + * @package JsonMapper + * @author Christian Weiske <cweiske@cweiske.de> + * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 + * @link http://cweiske.de/ + */ +class JsonMapper +{ + /** + * PSR-3 compatible logger object + * + * @link http://www.php-fig.org/psr/psr-3/ + * @var object + * @see setLogger() + */ + protected $logger; + + /** + * Throw an exception when JSON data contain a property + * that is not defined in the PHP class + * + * @var boolean + */ + public $bExceptionOnUndefinedProperty = false; + + /** + * Throw an exception if the JSON data miss a property + * that is marked with @required in the PHP class + * + * @var boolean + */ + public $bExceptionOnMissingData = false; + + /** + * If the types of map() parameters shall be checked. + * + * You have to disable it if you're using the json_decode "assoc" parameter. + * + * json_decode($str, false) + * + * @var boolean + */ + public $bEnforceMapType = true; + + /** + * Throw an exception when an object is expected but the JSON contains + * a non-object type. + * + * @var boolean + */ + public $bStrictObjectTypeChecking = false; + + /** + * Throw an exception, if null value is found + * but the type of attribute does not allow nulls. + * + * @var bool + */ + public $bStrictNullTypes = true; + + /** + * Allow mapping of private and proteted properties. + * + * @var boolean + */ + public $bIgnoreVisibility = false; + + /** + * Remove attributes that were not passed in JSON, + * to avoid confusion between them and NULL values. + * + * @var boolean + */ + public $bRemoveUndefinedAttributes = false; + + /** + * Override class names that JsonMapper uses to create objects. + * Useful when your setter methods accept abstract classes or interfaces. + * + * @var array + */ + public $classMap = array(); + + /** + * Callback used when an undefined property is found. + * + * Works only when $bExceptionOnUndefinedProperty is disabled. + * + * Parameters to this function are: + * 1. Object that is being filled + * 2. Name of the unknown JSON property + * 3. JSON value of the property + * + * @var callable + */ + public $undefinedPropertyHandler = null; + + /** + * Runtime cache for inspected classes. This is particularly effective if + * mapArray() is called with a large number of objects + * + * @var array property inspection result cache + */ + protected $arInspectedClasses = array(); + + /** + * Method to call on each object after deserialization is done. + * + * Is only called if it exists on the object. + * + * @var string|null + */ + public $postMappingMethod = null; + + /** + * Map data all data in $json into the given $object instance. + * + * @param object $json JSON object structure from json_decode() + * @param object $object Object to map $json data into + * + * @return mixed Mapped object is returned. + * @see mapArray() + */ + public function map($json, $object) + { + if ($this->bEnforceMapType && !is_object($json)) { + throw new InvalidArgumentException( + 'JsonMapper::map() requires first argument to be an object' + . ', ' . gettype($json) . ' given.' + ); + } + if (!is_object($object)) { + throw new InvalidArgumentException( + 'JsonMapper::map() requires second argument to be an object' + . ', ' . gettype($object) . ' given.' + ); + } + + $strClassName = get_class($object); + $rc = new ReflectionClass($object); + $strNs = $rc->getNamespaceName(); + $providedProperties = array(); + foreach ($json as $key => $jvalue) { + $key = $this->getSafeName($key); + $providedProperties[$key] = true; + + // Store the property inspection results so we don't have to do it + // again for subsequent objects of the same type + if (!isset($this->arInspectedClasses[$strClassName][$key])) { + $this->arInspectedClasses[$strClassName][$key] + = $this->inspectProperty($rc, $key); + } + + list($hasProperty, $accessor, $type) + = $this->arInspectedClasses[$strClassName][$key]; + + if (!$hasProperty) { + if ($this->bExceptionOnUndefinedProperty) { + throw new JsonMapper_Exception( + 'JSON property "' . $key . '" does not exist' + . ' in object of type ' . $strClassName + ); + } else if ($this->undefinedPropertyHandler !== null) { + call_user_func( + $this->undefinedPropertyHandler, + $object, $key, $jvalue + ); + } else { + $this->log( + 'info', + 'Property {property} does not exist in {class}', + array('property' => $key, 'class' => $strClassName) + ); + } + continue; + } + + if ($accessor === null) { + if ($this->bExceptionOnUndefinedProperty) { + throw new JsonMapper_Exception( + 'JSON property "' . $key . '" has no public setter method' + . ' in object of type ' . $strClassName + ); + } + $this->log( + 'info', + 'Property {property} has no public setter method in {class}', + array('property' => $key, 'class' => $strClassName) + ); + continue; + } + + if ($this->isNullable($type) || !$this->bStrictNullTypes) { + if ($jvalue === null) { + $this->setProperty($object, $accessor, null); + continue; + } + $type = $this->removeNullable($type); + } else if ($jvalue === null) { + throw new JsonMapper_Exception( + 'JSON property "' . $key . '" in class "' + . $strClassName . '" must not be NULL' + ); + } + + $type = $this->getFullNamespace($type, $strNs); + $type = $this->getMappedType($type, $jvalue); + + if ($type === null || $type === 'mixed') { + //no given type - simply set the json data + $this->setProperty($object, $accessor, $jvalue); + continue; + } else if ($this->isObjectOfSameType($type, $jvalue)) { + $this->setProperty($object, $accessor, $jvalue); + continue; + } else if ($this->isSimpleType($type)) { + if ($type === 'string' && is_object($jvalue)) { + throw new JsonMapper_Exception( + 'JSON property "' . $key . '" in class "' + . $strClassName . '" is an object and' + . ' cannot be converted to a string' + ); + } + settype($jvalue, $type); + $this->setProperty($object, $accessor, $jvalue); + continue; + } + + //FIXME: check if type exists, give detailed error message if not + if ($type === '') { + throw new JsonMapper_Exception( + 'Empty type at property "' + . $strClassName . '::$' . $key . '"' + ); + } + + $array = null; + $subtype = null; + if ($this->isArrayOfType($type)) { + //array + $array = array(); + $subtype = substr($type, 0, -2); + } else if (substr($type, -1) == ']') { + list($proptype, $subtype) = explode('[', substr($type, 0, -1)); + if ($proptype == 'array') { + $array = array(); + } else { + $array = $this->createInstance($proptype, false, $jvalue); + } + } else { + if (is_a($type, 'ArrayObject', true)) { + $array = $this->createInstance($type, false, $jvalue); + } + } + + if ($array !== null) { + if (!is_array($jvalue) && $this->isFlatType(gettype($jvalue))) { + throw new JsonMapper_Exception( + 'JSON property "' . $key . '" must be an array, ' + . gettype($jvalue) . ' given' + ); + } + + $cleanSubtype = $this->removeNullable($subtype); + $subtype = $this->getFullNamespace($cleanSubtype, $strNs); + $child = $this->mapArray($jvalue, $array, $subtype, $key); + } else if ($this->isFlatType(gettype($jvalue))) { + //use constructor parameter if we have a class + // but only a flat type (i.e. string, int) + if ($this->bStrictObjectTypeChecking) { + throw new JsonMapper_Exception( + 'JSON property "' . $key . '" must be an object, ' + . gettype($jvalue) . ' given' + ); + } + $child = $this->createInstance($type, true, $jvalue); + } else { + $child = $this->createInstance($type, false, $jvalue); + $this->map($jvalue, $child); + } + $this->setProperty($object, $accessor, $child); + } + + if ($this->bExceptionOnMissingData) { + $this->checkMissingData($providedProperties, $rc); + } + + if ($this->bRemoveUndefinedAttributes) { + $this->removeUndefinedAttributes($object, $providedProperties); + } + + if ($this->postMappingMethod !== null + && $rc->hasMethod($this->postMappingMethod) + ) { + $refDeserializePostMethod = $rc->getMethod( + $this->postMappingMethod + ); + $refDeserializePostMethod->setAccessible(true); + $refDeserializePostMethod->invoke($object); + } + + return $object; + } + + /** + * Convert a type name to a fully namespaced type name. + * + * @param string $type Type name (simple type or class name) + * @param string $strNs Base namespace that gets prepended to the type name + * + * @return string Fully-qualified type name with namespace + */ + protected function getFullNamespace($type, $strNs) + { + if ($type === null || $type === '' || $type[0] == '\\' + || $strNs == '' + ) { + return $type; + } + list($first) = explode('[', $type, 2); + if ($this->isSimpleType($first) || $first === 'mixed') { + return $type; + } + + //create a full qualified namespace + return '\\' . $strNs . '\\' . $type; + } + + /** + * Check required properties exist in json + * + * @param array $providedProperties array with json properties + * @param object $rc Reflection class to check + * + * @throws JsonMapper_Exception + * + * @return void + */ + protected function checkMissingData($providedProperties, ReflectionClass $rc) + { + foreach ($rc->getProperties() as $property) { + $rprop = $rc->getProperty($property->name); + $docblock = $rprop->getDocComment(); + $annotations = $this->parseAnnotations($docblock); + if (isset($annotations['required']) + && !isset($providedProperties[$property->name]) + ) { + throw new JsonMapper_Exception( + 'Required property "' . $property->name . '" of class ' + . $rc->getName() + . ' is missing in JSON data' + ); + } + } + } + + /** + * Remove attributes from object that were not passed in JSON data. + * + * This is to avoid confusion between those that were actually passed + * as NULL, and those that weren't provided at all. + * + * @param object $object Object to remove properties from + * @param array $providedProperties Array with JSON properties + * + * @return void + */ + protected function removeUndefinedAttributes($object, $providedProperties) + { + foreach (get_object_vars($object) as $propertyName => $dummy) { + if (!isset($providedProperties[$propertyName])) { + unset($object->{$propertyName}); + } + } + } + + /** + * Map an array + * + * @param array $json JSON array structure from json_decode() + * @param mixed $array Array or ArrayObject that gets filled with + * data from $json + * @param string $class Class name for children objects. + * All children will get mapped onto this type. + * Supports class names and simple types + * like "string" and nullability "string|null". + * Pass "null" to not convert any values + * @param string $parent_key Defines the key this array belongs to + * in order to aid debugging. + * + * @return mixed Mapped $array is returned + */ + public function mapArray($json, $array, $class = null, $parent_key = '') + { + $originalClass = $class; + foreach ($json as $key => $jvalue) { + $class = $this->getMappedType($originalClass, $jvalue); + if ($class === null) { + $array[$key] = $jvalue; + } else if ($this->isArrayOfType($class)) { + $array[$key] = $this->mapArray( + $jvalue, + array(), + substr($class, 0, -2) + ); + } else if ($this->isFlatType(gettype($jvalue))) { + //use constructor parameter if we have a class + // but only a flat type (i.e. string, int) + if ($jvalue === null) { + $array[$key] = null; + } else { + if ($this->isSimpleType($class)) { + settype($jvalue, $class); + $array[$key] = $jvalue; + } else { + $array[$key] = $this->createInstance( + $class, true, $jvalue + ); + } + } + } else if ($this->isFlatType($class)) { + throw new JsonMapper_Exception( + 'JSON property "' . ($parent_key ? $parent_key : '?') . '"' + . ' is an array of type "' . $class . '"' + . ' but contained a value of type' + . ' "' . gettype($jvalue) . '"' + ); + } else if (is_a($class, 'ArrayObject', true)) { + $array[$key] = $this->mapArray( + $jvalue, + $this->createInstance($class) + ); + } else { + $array[$key] = $this->map( + $jvalue, $this->createInstance($class, false, $jvalue) + ); + } + } + return $array; + } + + /** + * Try to find out if a property exists in a given class. + * Checks property first, falls back to setter method. + * + * @param object $rc Reflection class to check + * @param string $name Property name + * + * @return array First value: if the property exists + * Second value: the accessor to use ( + * ReflectionMethod or ReflectionProperty, or null) + * Third value: type of the property + */ + protected function inspectProperty(ReflectionClass $rc, $name) + { + //try setter method first + $setter = 'set' . $this->getCamelCaseName($name); + + if ($rc->hasMethod($setter)) { + $rmeth = $rc->getMethod($setter); + if ($rmeth->isPublic() || $this->bIgnoreVisibility) { + $rparams = $rmeth->getParameters(); + if (count($rparams) > 0) { + $pclass = $rparams[0]->getClass(); + $nullability = ''; + if ($rparams[0]->allowsNull()) { + $nullability = '|null'; + } + if ($pclass !== null) { + return array( + true, $rmeth, + '\\' . $pclass->getName() . $nullability + ); + } + } + + $docblock = $rmeth->getDocComment(); + $annotations = $this->parseAnnotations($docblock); + + if (!isset($annotations['param'][0])) { + // If there is no annotations (higher priority) inspect + // if there's a scalar type being defined + if (PHP_MAJOR_VERSION >= 7) { + $ptype = $rparams[0]->getType(); + if ($ptype !== null) { + // ReflectionType::__toString() is deprecated + if (PHP_VERSION >= 7.1 + && $ptype instanceof ReflectionNamedType + ) { + $ptype = $ptype->getName(); + } + return array(true, $rmeth, $ptype . $nullability); + } + } + return array(true, $rmeth, null); + } + list($type) = explode(' ', trim($annotations['param'][0])); + return array(true, $rmeth, $type); + } + } + + //now try to set the property directly + //we have to look it up in the class hierarchy + $class = $rc; + $rprop = null; + do { + if ($class->hasProperty($name)) { + $rprop = $class->getProperty($name); + } + } while ($rprop === null && $class = $class->getParentClass()); + + if ($rprop === null) { + //case-insensitive property matching + foreach ($rc->getProperties() as $p) { + if ((strcasecmp($p->name, $name) === 0)) { + $rprop = $p; + break; + } + } + } + if ($rprop !== null) { + if ($rprop->isPublic() || $this->bIgnoreVisibility) { + $docblock = $rprop->getDocComment(); + $annotations = $this->parseAnnotations($docblock); + + if (!isset($annotations['var'][0])) { + return array(true, $rprop, null); + } + + //support "@var type description" + list($type) = explode(' ', $annotations['var'][0]); + + return array(true, $rprop, $type); + } else { + //no setter, private property + return array(true, null, null); + } + } + + //no setter, no property + return array(false, null, null); + } + + /** + * Removes - and _ and makes the next letter uppercase + * + * @param string $name Property name + * + * @return string CamelCasedVariableName + */ + protected function getCamelCaseName($name) + { + return str_replace( + ' ', '', ucwords(str_replace(array('_', '-'), ' ', $name)) + ); + } + + /** + * Since hyphens cannot be used in variables we have to uppercase them. + * + * Technically you may use them, but they are awkward to access. + * + * @param string $name Property name + * + * @return string Name without hyphen + */ + protected function getSafeName($name) + { + if (strpos($name, '-') !== false) { + $name = $this->getCamelCaseName($name); + } + + return $name; + } + + /** + * Set a property on a given object to a given value. + * + * Checks if the setter or the property are public are made before + * calling this method. + * + * @param object $object Object to set property on + * @param object $accessor ReflectionMethod or ReflectionProperty + * @param mixed $value Value of property + * + * @return void + */ + protected function setProperty( + $object, $accessor, $value + ) { + if (!$accessor->isPublic() && $this->bIgnoreVisibility) { + $accessor->setAccessible(true); + } + if ($accessor instanceof ReflectionProperty) { + $accessor->setValue($object, $value); + } else { + //setter method + $accessor->invoke($object, $value); + } + } + + /** + * Create a new object of the given type. + * + * This method exists to be overwritten in child classes, + * so you can do dependency injection or so. + * + * @param string $class Class name to instantiate + * @param boolean $useParameter Pass $parameter to the constructor or not + * @param mixed $jvalue Constructor parameter (the json value) + * + * @return object Freshly created object + */ + protected function createInstance( + $class, $useParameter = false, $jvalue = null + ) { + if ($useParameter) { + return new $class($jvalue); + } else { + $reflectClass = new ReflectionClass($class); + $constructor = $reflectClass->getConstructor(); + if (null === $constructor + || $constructor->getNumberOfRequiredParameters() > 0 + ) { + return $reflectClass->newInstanceWithoutConstructor(); + } + return $reflectClass->newInstance(); + } + } + + /** + * Get the mapped class/type name for this class. + * Returns the incoming classname if not mapped. + * + * @param string $type Type name to map + * @param mixed $jvalue Constructor parameter (the json value) + * + * @return string The mapped type/class name + */ + protected function getMappedType($type, $jvalue = null) + { + if (isset($this->classMap[$type])) { + $target = $this->classMap[$type]; + } else if (is_string($type) && $type !== '' && $type[0] == '\\' + && isset($this->classMap[substr($type, 1)]) + ) { + $target = $this->classMap[substr($type, 1)]; + } else { + $target = null; + } + + if ($target) { + if (is_callable($target)) { + $type = $target($type, $jvalue); + } else { + $type = $target; + } + } + return $type; + } + + /** + * Checks if the given type is a "simple type" + * + * @param string $type type name from gettype() + * + * @return boolean True if it is a simple PHP type + * + * @see isFlatType() + */ + protected function isSimpleType($type) + { + return $type == 'string' + || $type == 'boolean' || $type == 'bool' + || $type == 'integer' || $type == 'int' + || $type == 'double' || $type == 'float' + || $type == 'array' || $type == 'object'; + } + + /** + * Checks if the object is of this type or has this type as one of its parents + * + * @param string $type class name of type being required + * @param mixed $value Some PHP value to be tested + * + * @return boolean True if $object has type of $type + */ + protected function isObjectOfSameType($type, $value) + { + if (false === is_object($value)) { + return false; + } + + return is_a($value, $type); + } + + /** + * Checks if the given type is a type that is not nested + * (simple type except array and object) + * + * @param string $type type name from gettype() + * + * @return boolean True if it is a non-nested PHP type + * + * @see isSimpleType() + */ + protected function isFlatType($type) + { + return $type == 'NULL' + || $type == 'string' + || $type == 'boolean' || $type == 'bool' + || $type == 'integer' || $type == 'int' + || $type == 'double' || $type == 'float'; + } + + /** + * Returns true if type is an array of elements + * (bracket notation) + * + * @param string $strType type to be matched + * + * @return bool + */ + protected function isArrayOfType($strType) + { + return substr($strType, -2) === '[]'; + } + + /** + * Checks if the given type is nullable + * + * @param string $type type name from the phpdoc param + * + * @return boolean True if it is nullable + */ + protected function isNullable($type) + { + return stripos('|' . $type . '|', '|null|') !== false; + } + + /** + * Remove the 'null' section of a type + * + * @param string $type type name from the phpdoc param + * + * @return string The new type value + */ + protected function removeNullable($type) + { + if ($type === null) { + return null; + } + return substr( + str_ireplace('|null|', '|', '|' . $type . '|'), + 1, -1 + ); + } + + /** + * Copied from PHPUnit 3.7.29, Util/Test.php + * + * @param string $docblock Full method docblock + * + * @return array + */ + protected static function parseAnnotations($docblock) + { + $annotations = array(); + // Strip away the docblock header and footer + // to ease parsing of one line annotations + $docblock = substr($docblock, 3, -2); + + $re = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m'; + if (preg_match_all($re, $docblock, $matches)) { + $numMatches = count($matches[0]); + + for ($i = 0; $i < $numMatches; ++$i) { + $annotations[$matches['name'][$i]][] = $matches['value'][$i]; + } + } + + return $annotations; + } + + /** + * Log a message to the $logger object + * + * @param string $level Logging level + * @param string $message Text to log + * @param array $context Additional information + * + * @return null + */ + protected function log($level, $message, array $context = array()) + { + if ($this->logger) { + $this->logger->log($level, $message, $context); + } + } + + /** + * Sets a logger instance on the object + * + * @param LoggerInterface $logger PSR-3 compatible logger object + * + * @return null + */ + public function setLogger($logger) + { + $this->logger = $logger; + } +} +?> diff --git a/lib/composer/netresearch/jsonmapper/src/JsonMapper/Exception.php b/lib/composer/netresearch/jsonmapper/src/JsonMapper/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..bb8040c6057bb7f7c128504c1f603a31b9f505ee --- /dev/null +++ b/lib/composer/netresearch/jsonmapper/src/JsonMapper/Exception.php @@ -0,0 +1,26 @@ +<?php +/** + * Part of JsonMapper + * + * PHP version 5 + * + * @category Netresearch + * @package JsonMapper + * @author Christian Weiske <cweiske@cweiske.de> + * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 + * @link http://cweiske.de/ + */ + +/** + * Simple exception + * + * @category Netresearch + * @package JsonMapper + * @author Christian Weiske <cweiske@cweiske.de> + * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 + * @link http://cweiske.de/ + */ +class JsonMapper_Exception extends Exception +{ +} +?> diff --git a/lib/composer/nextcloud/coding-standard/.gitignore b/lib/composer/nextcloud/coding-standard/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ec2f613a1517e027360298926080a69110528315 --- /dev/null +++ b/lib/composer/nextcloud/coding-standard/.gitignore @@ -0,0 +1,7 @@ +composer.phar +/composer.lock +/vendor/ + +/.idea + +.php_cs.cache diff --git a/lib/composer/nextcloud/coding-standard/.php_cs.dist b/lib/composer/nextcloud/coding-standard/.php_cs.dist new file mode 100644 index 0000000000000000000000000000000000000000..ecf14263dd1248927af8fa9961afa249c2e6ade1 --- /dev/null +++ b/lib/composer/nextcloud/coding-standard/.php_cs.dist @@ -0,0 +1,13 @@ +<?php + +declare(strict_types=1); + +require_once './vendor/autoload.php'; + +use ChristophWurst\Nextcloud\CodingStandard\Config; + +$config = new Config(); +$config + ->getFinder() + ->in(__DIR__); +return $config; diff --git a/lib/composer/nextcloud/coding-standard/LICENSE b/lib/composer/nextcloud/coding-standard/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..887be18503627d19ace7f3d891df200fd1dc1964 --- /dev/null +++ b/lib/composer/nextcloud/coding-standard/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Christoph Wurst + +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/lib/composer/nextcloud/coding-standard/README.md b/lib/composer/nextcloud/coding-standard/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f2c6dd426bf3872f63ca3a7cd9956a631b17188a --- /dev/null +++ b/lib/composer/nextcloud/coding-standard/README.md @@ -0,0 +1,47 @@ +# Nextcloud Coding Standard + +Nextcloud coding standards for the [php cs fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer). + +## Installation + +Add the package to your dev dependencies + +```bash +composer require --dev nextcloud/coding-standard +``` + +and create a `.php_cs.dist` like + +```php +<?php + +declare(strict_types=1); + +require_once './vendor/autoload.php'; + +use Nextcloud\CodingStandard\Config; + +$config = new Config(); +$config + ->getFinder() + ->ignoreVCSIgnored(true) + ->notPath('build') + ->notPath('l10n') + ->notPath('src') + ->notPath('vendor') + ->in(__DIR__); +return $config; +``` + +To run the fixer you first have to [install it](https://github.com/FriendsOfPhp/PHP-CS-Fixer#installation). Then you can run `php-cs fix` to apply all automated fixes. + +For convenience you may add it to the `scripts` section of your `composer.json`: + +```json +{ + "scripts": { + "cs:check": "php-cs-fixer fix --dry-run", + "cs:fix": "php-cs-fixer fix" + } +} +``` diff --git a/lib/composer/nextcloud/coding-standard/composer.json b/lib/composer/nextcloud/coding-standard/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..23e670d5047d853b7e76312a5457380a4fe75364 --- /dev/null +++ b/lib/composer/nextcloud/coding-standard/composer.json @@ -0,0 +1,21 @@ +{ + "name": "nextcloud/coding-standard", + "description": "Nextcloud coding standards for the php cs fixer", + "type": "library", + "require": { + "php": "^7.2", + "friendsofphp/php-cs-fixer": "^2.16" + }, + "license": "MIT", + "authors": [ + { + "name": "Christoph Wurst", + "email": "christoph@winzerhof-wurst.at" + } + ], + "autoload": { + "psr-4": { + "Nextcloud\\CodingStandard\\": "src" + } + } +} diff --git a/lib/composer/nextcloud/coding-standard/src/Config.php b/lib/composer/nextcloud/coding-standard/src/Config.php new file mode 100644 index 0000000000000000000000000000000000000000..b2691972d6576c37140e0dd56aa3f594502bcca0 --- /dev/null +++ b/lib/composer/nextcloud/coding-standard/src/Config.php @@ -0,0 +1,62 @@ +<?php + +declare(strict_types=1); + +namespace Nextcloud\CodingStandard; + +use PhpCsFixer\Config as Base; + +class Config extends Base +{ + + public function __construct($name = 'default') + { + parent::__construct($name); + $this->setIndent("\t"); + } + + public function getRules() + { + return [ + '@PSR1' => true, + '@PSR2' => true, + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_syntax' => [ + 'syntax' => 'short', + ], + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'braces' => [ + 'position_after_anonymous_constructs' => 'same', + 'position_after_control_structures' => 'same', + 'position_after_functions_and_oop_constructs' => 'same', + ], + 'elseif' => true, + 'encoding' => true, + 'full_opening_tag' => true, + 'function_declaration' => [ + 'closure_function_spacing' => 'one', + ], + 'indentation_type' => true, + 'line_ending' => true, + 'lowercase_keywords' => true, + 'method_argument_space' => [], + 'no_closing_tag' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_unused_imports' => true, + 'single_blank_line_at_eof' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'switch_case_space' => true, + 'visibility_required' => [ + 'elements' => ['property', 'method', 'const'] + ], + ]; + } + +} diff --git a/lib/composer/nikic/php-parser/LICENSE b/lib/composer/nikic/php-parser/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2e56718358791b095d659b54f6c6e03f2bfb5f4f --- /dev/null +++ b/lib/composer/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/lib/composer/nikic/php-parser/README.md b/lib/composer/nikic/php-parser/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f1dd929d7ff2b782461b475ab46e746fc9788da4 --- /dev/null +++ b/lib/composer/nikic/php-parser/README.md @@ -0,0 +1,225 @@ +PHP Parser +========== + +[![Build Status](https://travis-ci.org/nikic/PHP-Parser.svg?branch=master)](https://travis-ci.org/nikic/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 5.2 to PHP 7.4 parser written in PHP. Its purpose is to simplify static code analysis and +manipulation. + +[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.4). + +[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2). + +Features +-------- + +The main features provided by this library are: + + * Parsing PHP 5 and PHP 7 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. + * Experimental: 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 +<?php +use PhpParser\Error; +use PhpParser\NodeDumper; +use PhpParser\ParserFactory; + +$code = <<<'CODE' +<?php + +function test($foo) +{ + var_dump($foo); +} +CODE; + +$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); +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( + byRef: false + name: Identifier( + name: test + ) + params: array( + 0: Param( + 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( + parts: array( + 0: var_dump + ) + ) + args: array( + 0: Arg( + 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( + byRef: false + name: Identifier( + name: test + ) + params: array( + 0: Param( + 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 +<?php + +function test($foo) +{ +} +``` + +For a more comprehensive introduction, see the documentation. + +Documentation +------------- + + 1. [Introduction](doc/0_Introduction.markdown) + 2. [Usage of basic components](doc/2_Usage_of_basic_components.markdown) + +Component documentation: + + * [Walking the AST](doc/component/Walking_the_AST.markdown) + * Node visitors + * Modifying the AST from a visitor + * Short-circuiting traversals + * Interleaved visitors + * Simple node finding API + * Parent and sibling references + * [Name resolution](doc/component/Name_resolution.markdown) + * Name resolver options + * Name resolution context + * [Pretty printing](doc/component/Pretty_printing.markdown) + * Converting AST back to PHP code + * Customizing formatting + * Formatting-preserving code transformations + * [AST builders](doc/component/AST_builders.markdown) + * Fluent builders for AST nodes + * [Lexer](doc/component/Lexer.markdown) + * Lexer options + * Token and file positions for nodes + * Custom attributes + * [Error handling](doc/component/Error_handling.markdown) + * Column information for errors + * Error recovery (parsing of syntactically incorrect code) + * [Constant expression evaluation](doc/component/Constant_expression_evaluation.markdown) + * Evaluating constant/property/etc initializers + * Handling errors and unsupported expressions + * [JSON representation](doc/component/JSON_representation.markdown) + * JSON encoding and decoding of ASTs + * [Performance](doc/component/Performance.markdown) + * Disabling XDebug + * Reusing objects + * Garbage collection impact + * [Frequently asked questions](doc/component/FAQ.markdown) + * Parent and sibling references + + [doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc + [doc_master]: https://github.com/nikic/PHP-Parser/tree/master/doc diff --git a/lib/composer/nikic/php-parser/bin/php-parse b/lib/composer/nikic/php-parser/bin/php-parse new file mode 100755 index 0000000000000000000000000000000000000000..a002f8527193b836c5ccf98c36483edcc8f37355 --- /dev/null +++ b/lib/composer/nikic/php-parser/bin/php-parse @@ -0,0 +1,205 @@ +#!/usr/bin/env php +<?php + +foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) { + if (file_exists($file)) { + require $file; + break; + } +} + +ini_set('xdebug.max_nesting_level', 3000); + +// Disable XDebug var_dump() output truncation +ini_set('xdebug.var_display_max_children', -1); +ini_set('xdebug.var_display_max_data', -1); +ini_set('xdebug.var_display_max_depth', -1); + +list($operations, $files, $attributes) = parseArgs($argv); + +/* Dump nodes by default */ +if (empty($operations)) { + $operations[] = 'dump'; +} + +if (empty($files)) { + showHelp("Must specify at least one file."); +} + +$lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => [ + 'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments' +]]); +$parser = (new PhpParser\ParserFactory)->create( + PhpParser\ParserFactory::PREFER_PHP7, + $lexer +); +$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 (strpos($file, '<?php') === 0) { + $code = $file; + fwrite(STDERR, "====> 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] "<?php code" +Turn PHP source code into an abstract syntax tree. + +Operations is a list of the following options (--dump by default): + + -d, --dump Dump nodes using NodeDumper + -p, --pretty-print Pretty print file using PrettyPrinter\Standard + -j, --json-dump Print json_encode() result + --var-dump var_dump() nodes (for exact structure) + -N, --resolve-names Resolve names using NodeVisitor\NameResolver + -c, --with-column-info Show column-numbers for errors (if available) + -P, --with-positions Show positions in node dumps + -r, --with-recovery Use parsing with error recovery + -h, --help Display this page + +Example: + php-parse -d -p -N -d file.php + + Dumps nodes, pretty prints them, then resolves names and dumps them again. + + +OUTPUT + ); + exit($error ? 1 : 0); +} + +function parseArgs($args) { + $operations = []; + $files = []; + $attributes = [ + 'with-column-info' => false, + 'with-positions' => false, + 'with-recovery' => false, + ]; + + 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 ($arg[0] === '-') { + showHelp("Invalid operation $arg."); + } else { + $files[] = $arg; + } + } + } + + return [$operations, $files, $attributes]; +} diff --git a/lib/composer/nikic/php-parser/composer.json b/lib/composer/nikic/php-parser/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..2fd064a21242490c5ac6129e9b19af4323e4b9d1 --- /dev/null +++ b/lib/composer/nikic/php-parser/composer.json @@ -0,0 +1,41 @@ +{ + "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.0", + "ext-tokenizer": "*" + }, + "require-dev": { + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0", + "ircmaxell/php-yacc": "^0.0.7" + }, + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "autoload-dev": { + "psr-4": { + "PhpParser\\": "test/PhpParser/" + } + }, + "bin": [ + "bin/php-parse" + ] +} diff --git a/lib/composer/nikic/php-parser/grammar/README.md b/lib/composer/nikic/php-parser/grammar/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4bae11d8261cb1bf6462f5be54e22f1a7f099afa --- /dev/null +++ b/lib/composer/nikic/php-parser/grammar/README.md @@ -0,0 +1,30 @@ +What do all those files mean? +============================= + + * `php5.y`: PHP 5 grammar written in a pseudo language + * `php7.y`: PHP 7 grammar written in a pseudo language + * `tokens.y`: Tokens definition shared between PHP 5 and PHP 7 grammars + * `parser.template`: A `kmyacc` parser prototype file for PHP + * `tokens.template`: A `kmyacc` prototype file for the `Tokens` class + * `rebuildParsers.php`: Preprocesses the grammar and builds the parser using `kmyacc` + +.phpy pseudo language +===================== + +The `.y` file is a normal grammar in `kmyacc` (`yacc`) style, with some transformations +applied to it: + + * Nodes are created using the syntax `Name[..., ...]`. This is transformed into + `new Name(..., ..., attributes())` + * Some function-like constructs are resolved (see `rebuildParsers.php` for a list) + +Building the parser +=================== + +Run `php grammar/rebuildParsers.php` to rebuild the parsers. Additional options: + + * The `KMYACC` environment variable can be used to specify an alternative `kmyacc` binary. + By default the `phpyacc` dev dependency will be used. To use the original `kmyacc`, you + need to compile [moriyoshi's fork](https://github.com/moriyoshi/kmyacc-forked). + * The `--debug` option enables emission of debug symbols and creates the `y.output` file. + * The `--keep-tmp-grammar` option preserves the preprocessed grammar file. diff --git a/lib/composer/nikic/php-parser/grammar/parser.template b/lib/composer/nikic/php-parser/grammar/parser.template new file mode 100644 index 0000000000000000000000000000000000000000..6166607c9e4787be64704016fe0e009a21e51583 --- /dev/null +++ b/lib/composer/nikic/php-parser/grammar/parser.template @@ -0,0 +1,106 @@ +<?php +$meta # +#semval($) $this->semValue +#semval($,%t) $this->semValue +#semval(%n) $stackPos-(%l-%n) +#semval(%n,%t) $stackPos-(%l-%n) + +namespace PhpParser\Parser; + +use PhpParser\Error; +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\Scalar; +use PhpParser\Node\Stmt; +#include; + +/* This is an automatically GENERATED file, which should not be manually edited. + * Instead edit one of the following: + * * the grammar files grammar/php5.y or grammar/php7.y + * * the skeleton file grammar/parser.template + * * the preprocessing script grammar/rebuildParsers.php + */ +class #(-p) extends \PhpParser\ParserAbstract +{ + protected $tokenToSymbolMapSize = #(YYMAXLEX); + protected $actionTableSize = #(YYLAST); + protected $gotoTableSize = #(YYGLAST); + + protected $invalidSymbol = #(YYBADCH); + protected $errorSymbol = #(YYINTERRTOK); + protected $defaultAction = #(YYDEFAULT); + protected $unexpectedTokenRule = #(YYUNEXPECTED); + + protected $YY2TBLSTATE = #(YY2TBLSTATE); + protected $numNonLeafStates = #(YYNLSTATES); + + protected $symbolToName = array( + #listvar terminals + ); + + protected $tokenToSymbol = array( + #listvar yytranslate + ); + + protected $action = array( + #listvar yyaction + ); + + protected $actionCheck = array( + #listvar yycheck + ); + + protected $actionBase = array( + #listvar yybase + ); + + protected $actionDefault = array( + #listvar yydefault + ); + + protected $goto = array( + #listvar yygoto + ); + + protected $gotoCheck = array( + #listvar yygcheck + ); + + protected $gotoBase = array( + #listvar yygbase + ); + + protected $gotoDefault = array( + #listvar yygdefault + ); + + protected $ruleToNonTerminal = array( + #listvar yylhs + ); + + protected $ruleToLength = array( + #listvar yylen + ); +#if -t + + protected $productions = array( + #production-strings; + ); +#endif + + protected function initReduceCallbacks() { + $this->reduceCallbacks = [ +#reduce + %n => function ($stackPos) { + %b + }, +#noact + %n => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, +#endreduce + ]; + } +} +#tailcode; diff --git a/lib/composer/nikic/php-parser/grammar/php5.y b/lib/composer/nikic/php-parser/grammar/php5.y new file mode 100644 index 0000000000000000000000000000000000000000..c7d245dc78f6b1c67f0e9739c376aa91953a5a84 --- /dev/null +++ b/lib/composer/nikic/php-parser/grammar/php5.y @@ -0,0 +1,1026 @@ +%pure_parser +%expect 6 + +%tokens + +%% + +start: + top_statement_list { $$ = $this->handleNamespaces($1); } +; + +top_statement_list_ex: + top_statement_list_ex top_statement { pushNormalizing($1, $2); } + | /* empty */ { init(); } +; + +top_statement_list: + top_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +reserved_non_modifiers: + 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 +; + +semi_reserved: + reserved_non_modifiers + | T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC +; + +identifier_ex: + T_STRING { $$ = Node\Identifier[$1]; } + | semi_reserved { $$ = Node\Identifier[$1]; } +; + +identifier: + T_STRING { $$ = Node\Identifier[$1]; } +; + +reserved_non_modifiers_identifier: + reserved_non_modifiers { $$ = Node\Identifier[$1]; } +; + +namespace_name: + T_STRING { $$ = Name[$1]; } + | T_NAME_QUALIFIED { $$ = Name[$1]; } +; + +legacy_namespace_name: + namespace_name { $$ = $1; } + | T_NAME_FULLY_QUALIFIED { $$ = Name[substr($1, 1)]; } +; + +plain_variable: + T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; } +; + +top_statement: + statement { $$ = $1; } + | function_declaration_statement { $$ = $1; } + | class_declaration_statement { $$ = $1; } + | T_HALT_COMPILER + { $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; } + | T_NAMESPACE namespace_name ';' + { $$ = Stmt\Namespace_[$2, null]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $this->checkNamespace($$); } + | T_NAMESPACE namespace_name '{' top_statement_list '}' + { $$ = Stmt\Namespace_[$2, $4]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($$); } + | T_NAMESPACE '{' top_statement_list '}' + { $$ = Stmt\Namespace_[null, $3]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($$); } + | T_USE use_declarations ';' { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; } + | T_USE use_type use_declarations ';' { $$ = Stmt\Use_[$3, $2]; } + | group_use_declaration ';' { $$ = $1; } + | T_CONST constant_declaration_list ';' { $$ = Stmt\Const_[$2]; } +; + +use_type: + T_FUNCTION { $$ = Stmt\Use_::TYPE_FUNCTION; } + | T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; } +; + +group_use_declaration: + T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}' + { $$ = Stmt\GroupUse[$3, $6, $2]; } + | T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}' + { $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; } +; + +unprefixed_use_declarations: + unprefixed_use_declarations ',' unprefixed_use_declaration + { push($1, $3); } + | unprefixed_use_declaration { init($1); } +; + +use_declarations: + use_declarations ',' use_declaration { push($1, $3); } + | use_declaration { init($1); } +; + +inline_use_declarations: + inline_use_declarations ',' inline_use_declaration { push($1, $3); } + | inline_use_declaration { init($1); } +; + +unprefixed_use_declaration: + namespace_name + { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); } + | namespace_name T_AS identifier + { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); } +; + +use_declaration: + legacy_namespace_name + { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); } + | legacy_namespace_name T_AS identifier + { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); } +; + +inline_use_declaration: + unprefixed_use_declaration { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; } + | use_type unprefixed_use_declaration { $$ = $2; $$->type = $1; } +; + +constant_declaration_list: + constant_declaration_list ',' constant_declaration { push($1, $3); } + | constant_declaration { init($1); } +; + +constant_declaration: + identifier '=' static_scalar { $$ = Node\Const_[$1, $3]; } +; + +class_const_list: + class_const_list ',' class_const { push($1, $3); } + | class_const { init($1); } +; + +class_const: + identifier_ex '=' static_scalar { $$ = Node\Const_[$1, $3]; } +; + +inner_statement_list_ex: + inner_statement_list_ex inner_statement { pushNormalizing($1, $2); } + | /* empty */ { init(); } +; + +inner_statement_list: + inner_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +inner_statement: + statement { $$ = $1; } + | function_declaration_statement { $$ = $1; } + | class_declaration_statement { $$ = $1; } + | T_HALT_COMPILER + { throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); } +; + +non_empty_statement: + '{' inner_statement_list '}' + { + if ($2) { + $$ = $2; prependLeadingComments($$); + } else { + makeNop($$, $this->startAttributeStack[#1], $this->endAttributes); + if (null === $$) { $$ = array(); } + } + } + | T_IF parentheses_expr statement elseif_list else_single + { $$ = Stmt\If_[$2, ['stmts' => toArray($3), 'elseifs' => $4, 'else' => $5]]; } + | T_IF parentheses_expr ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';' + { $$ = Stmt\If_[$2, ['stmts' => $4, 'elseifs' => $5, 'else' => $6]]; } + | T_WHILE parentheses_expr while_statement { $$ = Stmt\While_[$2, $3]; } + | T_DO statement T_WHILE parentheses_expr ';' { $$ = Stmt\Do_ [$4, toArray($2)]; } + | T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement + { $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; } + | T_SWITCH parentheses_expr switch_case_list { $$ = Stmt\Switch_[$2, $3]; } + | T_BREAK ';' { $$ = Stmt\Break_[null]; } + | T_BREAK expr ';' { $$ = Stmt\Break_[$2]; } + | T_CONTINUE ';' { $$ = Stmt\Continue_[null]; } + | T_CONTINUE expr ';' { $$ = Stmt\Continue_[$2]; } + | T_RETURN ';' { $$ = Stmt\Return_[null]; } + | T_RETURN expr ';' { $$ = Stmt\Return_[$2]; } + | T_GLOBAL global_var_list ';' { $$ = Stmt\Global_[$2]; } + | T_STATIC static_var_list ';' { $$ = Stmt\Static_[$2]; } + | T_ECHO expr_list ';' { $$ = Stmt\Echo_[$2]; } + | T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; } + | yield_expr ';' { $$ = Stmt\Expression[$1]; } + | expr ';' { $$ = Stmt\Expression[$1]; } + | T_UNSET '(' variables_list ')' ';' { $$ = Stmt\Unset_[$3]; } + | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement + { $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; } + | T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement + { $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; } + | T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; } + | T_TRY '{' inner_statement_list '}' catches optional_finally + { $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); } + | T_THROW expr ';' { $$ = Stmt\Throw_[$2]; } + | T_GOTO identifier ';' { $$ = Stmt\Goto_[$2]; } + | identifier ':' { $$ = Stmt\Label[$1]; } + | expr error { $$ = Stmt\Expression[$1]; } + | error { $$ = array(); /* means: no statement */ } +; + +statement: + non_empty_statement { $$ = $1; } + | ';' + { makeNop($$, $this->startAttributeStack[#1], $this->endAttributes); + if ($$ === null) $$ = array(); /* means: no statement */ } +; + +catches: + /* empty */ { init(); } + | catches catch { push($1, $2); } +; + +catch: + T_CATCH '(' name plain_variable ')' '{' inner_statement_list '}' + { $$ = Stmt\Catch_[array($3), $4, $7]; } +; + +optional_finally: + /* empty */ { $$ = null; } + | T_FINALLY '{' inner_statement_list '}' { $$ = Stmt\Finally_[$3]; } +; + +variables_list: + variable { init($1); } + | variables_list ',' variable { push($1, $3); } +; + +optional_ref: + /* empty */ { $$ = false; } + | '&' { $$ = true; } +; + +optional_ellipsis: + /* empty */ { $$ = false; } + | T_ELLIPSIS { $$ = true; } +; + +function_declaration_statement: + T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}' + { $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; } +; + +class_declaration_statement: + class_entry_type identifier extends_from implements_list '{' class_statement_list '}' + { $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]]; + $this->checkClass($$, #2); } + | T_INTERFACE identifier interface_extends_list '{' class_statement_list '}' + { $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]]; + $this->checkInterface($$, #2); } + | T_TRAIT identifier '{' class_statement_list '}' + { $$ = Stmt\Trait_[$2, ['stmts' => $4]]; } +; + +class_entry_type: + T_CLASS { $$ = 0; } + | T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } + | T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; } +; + +extends_from: + /* empty */ { $$ = null; } + | T_EXTENDS class_name { $$ = $2; } +; + +interface_extends_list: + /* empty */ { $$ = array(); } + | T_EXTENDS class_name_list { $$ = $2; } +; + +implements_list: + /* empty */ { $$ = array(); } + | T_IMPLEMENTS class_name_list { $$ = $2; } +; + +class_name_list: + class_name { init($1); } + | class_name_list ',' class_name { push($1, $3); } +; + +for_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDFOR ';' { $$ = $2; } +; + +foreach_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDFOREACH ';' { $$ = $2; } +; + +declare_statement: + non_empty_statement { $$ = toArray($1); } + | ';' { $$ = null; } + | ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; } +; + +declare_list: + declare_list_element { init($1); } + | declare_list ',' declare_list_element { push($1, $3); } +; + +declare_list_element: + identifier '=' static_scalar { $$ = Stmt\DeclareDeclare[$1, $3]; } +; + +switch_case_list: + '{' case_list '}' { $$ = $2; } + | '{' ';' case_list '}' { $$ = $3; } + | ':' case_list T_ENDSWITCH ';' { $$ = $2; } + | ':' ';' case_list T_ENDSWITCH ';' { $$ = $3; } +; + +case_list: + /* empty */ { init(); } + | case_list case { push($1, $2); } +; + +case: + T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; } + | T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; } +; + +case_separator: + ':' + | ';' +; + +while_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; } +; + +elseif_list: + /* empty */ { init(); } + | elseif_list elseif { push($1, $2); } +; + +elseif: + T_ELSEIF parentheses_expr statement { $$ = Stmt\ElseIf_[$2, toArray($3)]; } +; + +new_elseif_list: + /* empty */ { init(); } + | new_elseif_list new_elseif { push($1, $2); } +; + +new_elseif: + T_ELSEIF parentheses_expr ':' inner_statement_list { $$ = Stmt\ElseIf_[$2, $4]; } +; + +else_single: + /* empty */ { $$ = null; } + | T_ELSE statement { $$ = Stmt\Else_[toArray($2)]; } +; + +new_else_single: + /* empty */ { $$ = null; } + | T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; } +; + +foreach_variable: + variable { $$ = array($1, false); } + | '&' variable { $$ = array($2, true); } + | list_expr { $$ = array($1, false); } +; + +parameter_list: + non_empty_parameter_list { $$ = $1; } + | /* empty */ { $$ = array(); } +; + +non_empty_parameter_list: + parameter { init($1); } + | non_empty_parameter_list ',' parameter { push($1, $3); } +; + +parameter: + optional_param_type optional_ref optional_ellipsis plain_variable + { $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); } + | optional_param_type optional_ref optional_ellipsis plain_variable '=' static_scalar + { $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); } +; + +type: + name { $$ = $1; } + | T_ARRAY { $$ = Node\Identifier['array']; } + | T_CALLABLE { $$ = Node\Identifier['callable']; } +; + +optional_param_type: + /* empty */ { $$ = null; } + | type { $$ = $1; } +; + +optional_return_type: + /* empty */ { $$ = null; } + | ':' type { $$ = $2; } +; + +argument_list: + '(' ')' { $$ = array(); } + | '(' non_empty_argument_list ')' { $$ = $2; } + | '(' yield_expr ')' { $$ = array(Node\Arg[$2, false, false]); } +; + +non_empty_argument_list: + argument { init($1); } + | non_empty_argument_list ',' argument { push($1, $3); } +; + +argument: + expr { $$ = Node\Arg[$1, false, false]; } + | '&' variable { $$ = Node\Arg[$2, true, false]; } + | T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; } +; + +global_var_list: + global_var_list ',' global_var { push($1, $3); } + | global_var { init($1); } +; + +global_var: + plain_variable { $$ = $1; } + | '$' variable { $$ = Expr\Variable[$2]; } + | '$' '{' expr '}' { $$ = Expr\Variable[$3]; } +; + +static_var_list: + static_var_list ',' static_var { push($1, $3); } + | static_var { init($1); } +; + +static_var: + plain_variable { $$ = Stmt\StaticVar[$1, null]; } + | plain_variable '=' static_scalar { $$ = Stmt\StaticVar[$1, $3]; } +; + +class_statement_list_ex: + class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } } + | /* empty */ { init(); } +; + +class_statement_list: + class_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +class_statement: + variable_modifiers property_declaration_list ';' + { $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); } + | T_CONST class_const_list ';' { $$ = Stmt\ClassConst[$2, 0]; } + | method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body + { $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]]; + $this->checkClassMethod($$, #1); } + | T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; } +; + +trait_adaptations: + ';' { $$ = array(); } + | '{' trait_adaptation_list '}' { $$ = $2; } +; + +trait_adaptation_list: + /* empty */ { init(); } + | trait_adaptation_list trait_adaptation { push($1, $2); } +; + +trait_adaptation: + trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';' + { $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; } + | trait_method_reference T_AS member_modifier identifier_ex ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; } + | trait_method_reference T_AS member_modifier ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; } + | trait_method_reference T_AS identifier ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; } + | trait_method_reference T_AS reserved_non_modifiers_identifier ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; } +; + +trait_method_reference_fully_qualified: + name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); } +; +trait_method_reference: + trait_method_reference_fully_qualified { $$ = $1; } + | identifier_ex { $$ = array(null, $1); } +; + +method_body: + ';' /* abstract method */ { $$ = null; } + | '{' inner_statement_list '}' { $$ = $2; } +; + +variable_modifiers: + non_empty_member_modifiers { $$ = $1; } + | T_VAR { $$ = 0; } +; + +method_modifiers: + /* empty */ { $$ = 0; } + | non_empty_member_modifiers { $$ = $1; } +; + +non_empty_member_modifiers: + member_modifier { $$ = $1; } + | non_empty_member_modifiers member_modifier { $this->checkModifier($1, $2, #2); $$ = $1 | $2; } +; + +member_modifier: + T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; } + | T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; } + | T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; } + | T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; } + | T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } + | T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; } +; + +property_declaration_list: + property_declaration { init($1); } + | property_declaration_list ',' property_declaration { push($1, $3); } +; + +property_decl_name: + T_VARIABLE { $$ = Node\VarLikeIdentifier[parseVar($1)]; } +; + +property_declaration: + property_decl_name { $$ = Stmt\PropertyProperty[$1, null]; } + | property_decl_name '=' static_scalar { $$ = Stmt\PropertyProperty[$1, $3]; } +; + +expr_list: + expr_list ',' expr { push($1, $3); } + | expr { init($1); } +; + +for_expr: + /* empty */ { $$ = array(); } + | expr_list { $$ = $1; } +; + +expr: + variable { $$ = $1; } + | list_expr '=' expr { $$ = Expr\Assign[$1, $3]; } + | variable '=' expr { $$ = Expr\Assign[$1, $3]; } + | variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; } + | variable '=' '&' new_expr { $$ = Expr\AssignRef[$1, $4]; } + | new_expr { $$ = $1; } + | T_CLONE expr { $$ = Expr\Clone_[$2]; } + | variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; } + | variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; } + | variable T_MUL_EQUAL expr { $$ = Expr\AssignOp\Mul [$1, $3]; } + | variable T_DIV_EQUAL expr { $$ = Expr\AssignOp\Div [$1, $3]; } + | variable T_CONCAT_EQUAL expr { $$ = Expr\AssignOp\Concat [$1, $3]; } + | variable T_MOD_EQUAL expr { $$ = Expr\AssignOp\Mod [$1, $3]; } + | variable T_AND_EQUAL expr { $$ = Expr\AssignOp\BitwiseAnd[$1, $3]; } + | variable T_OR_EQUAL expr { $$ = Expr\AssignOp\BitwiseOr [$1, $3]; } + | variable T_XOR_EQUAL expr { $$ = Expr\AssignOp\BitwiseXor[$1, $3]; } + | variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; } + | variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$1, $3]; } + | variable T_POW_EQUAL expr { $$ = Expr\AssignOp\Pow [$1, $3]; } + | variable T_COALESCE_EQUAL expr { $$ = Expr\AssignOp\Coalesce [$1, $3]; } + | variable T_INC { $$ = Expr\PostInc[$1]; } + | T_INC variable { $$ = Expr\PreInc [$2]; } + | variable T_DEC { $$ = Expr\PostDec[$1]; } + | T_DEC variable { $$ = Expr\PreDec [$2]; } + | expr T_BOOLEAN_OR expr { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; } + | expr T_BOOLEAN_AND expr { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; } + | expr T_LOGICAL_OR expr { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; } + | expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; } + | expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; } + | expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; } + | expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; } + | expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; } + | expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; } + | expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; } + | expr '-' expr { $$ = Expr\BinaryOp\Minus [$1, $3]; } + | expr '*' expr { $$ = Expr\BinaryOp\Mul [$1, $3]; } + | expr '/' expr { $$ = Expr\BinaryOp\Div [$1, $3]; } + | expr '%' expr { $$ = Expr\BinaryOp\Mod [$1, $3]; } + | expr T_SL expr { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; } + | expr T_SR expr { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; } + | expr T_POW expr { $$ = Expr\BinaryOp\Pow [$1, $3]; } + | '+' expr %prec T_INC { $$ = Expr\UnaryPlus [$2]; } + | '-' expr %prec T_INC { $$ = Expr\UnaryMinus[$2]; } + | '!' expr { $$ = Expr\BooleanNot[$2]; } + | '~' expr { $$ = Expr\BitwiseNot[$2]; } + | expr T_IS_IDENTICAL expr { $$ = Expr\BinaryOp\Identical [$1, $3]; } + | expr T_IS_NOT_IDENTICAL expr { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; } + | expr T_IS_EQUAL expr { $$ = Expr\BinaryOp\Equal [$1, $3]; } + | expr T_IS_NOT_EQUAL expr { $$ = Expr\BinaryOp\NotEqual [$1, $3]; } + | expr T_SPACESHIP expr { $$ = Expr\BinaryOp\Spaceship [$1, $3]; } + | expr '<' expr { $$ = Expr\BinaryOp\Smaller [$1, $3]; } + | expr T_IS_SMALLER_OR_EQUAL expr { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; } + | expr '>' expr { $$ = Expr\BinaryOp\Greater [$1, $3]; } + | expr T_IS_GREATER_OR_EQUAL expr { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; } + | expr T_INSTANCEOF class_name_reference { $$ = Expr\Instanceof_[$1, $3]; } + | parentheses_expr { $$ = $1; } + /* we need a separate '(' new_expr ')' rule to avoid problems caused by a s/r conflict */ + | '(' new_expr ')' { $$ = $2; } + | expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; } + | expr '?' ':' expr { $$ = Expr\Ternary[$1, null, $4]; } + | expr T_COALESCE expr { $$ = Expr\BinaryOp\Coalesce[$1, $3]; } + | T_ISSET '(' variables_list ')' { $$ = Expr\Isset_[$3]; } + | T_EMPTY '(' expr ')' { $$ = Expr\Empty_[$3]; } + | T_INCLUDE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; } + | T_INCLUDE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE_ONCE]; } + | T_EVAL parentheses_expr { $$ = Expr\Eval_[$2]; } + | T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; } + | T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; } + | T_INT_CAST expr { $$ = Expr\Cast\Int_ [$2]; } + | T_DOUBLE_CAST expr + { $attrs = attributes(); + $attrs['kind'] = $this->getFloatCastKind($1); + $$ = new Expr\Cast\Double($2, $attrs); } + | T_STRING_CAST expr { $$ = Expr\Cast\String_ [$2]; } + | T_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; } + | T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; } + | T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; } + | T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; } + | T_EXIT exit_expr + { $attrs = attributes(); + $attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $$ = new Expr\Exit_($2, $attrs); } + | '@' expr { $$ = Expr\ErrorSuppress[$2]; } + | scalar { $$ = $1; } + | array_expr { $$ = $1; } + | scalar_dereference { $$ = $1; } + | '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; } + | T_PRINT expr { $$ = Expr\Print_[$2]; } + | T_YIELD { $$ = Expr\Yield_[null, null]; } + | T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; } + | T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type + '{' inner_statement_list '}' + { $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $9]]; } + | T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type + '{' inner_statement_list '}' + { $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $10]]; } +; + +parentheses_expr: + '(' expr ')' { $$ = $2; } + | '(' yield_expr ')' { $$ = $2; } +; + +yield_expr: + T_YIELD expr { $$ = Expr\Yield_[$2, null]; } + | T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; } +; + +array_expr: + T_ARRAY '(' array_pair_list ')' + { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG; + $$ = new Expr\Array_($3, $attrs); } + | '[' array_pair_list ']' + { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_SHORT; + $$ = new Expr\Array_($2, $attrs); } +; + +scalar_dereference: + array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' + { $attrs = attributes(); $attrs['kind'] = strKind($1); + $$ = Expr\ArrayDimFetch[new Scalar\String_(Scalar\String_::parse($1), $attrs), $3]; } + | constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + /* alternative array syntax missing intentionally */ +; + +anonymous_class: + T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}' + { $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2); + $this->checkClass($$[0], -1); } +; + +new_expr: + T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; } + | T_NEW anonymous_class + { list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; } +; + +lexical_vars: + /* empty */ { $$ = array(); } + | T_USE '(' lexical_var_list ')' { $$ = $3; } +; + +lexical_var_list: + lexical_var { init($1); } + | lexical_var_list ',' lexical_var { push($1, $3); } +; + +lexical_var: + optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; } +; + +function_call: + name argument_list { $$ = Expr\FuncCall[$1, $2]; } + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list + { $$ = Expr\StaticCall[$1, $3, $4]; } + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list + { $$ = Expr\StaticCall[$1, $4, $6]; } + | static_property argument_list + { $$ = $this->fixupPhp5StaticPropCall($1, $2, attributes()); } + | variable_without_objects argument_list + { $$ = Expr\FuncCall[$1, $2]; } + | function_call '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + /* alternative array syntax missing intentionally */ +; + +class_name: + T_STATIC { $$ = Name[$1]; } + | name { $$ = $1; } +; + +name: + T_STRING { $$ = Name[$1]; } + | T_NAME_QUALIFIED { $$ = Name[$1]; } + | T_NAME_FULLY_QUALIFIED { $$ = Name\FullyQualified[substr($1, 1)]; } + | T_NAME_RELATIVE { $$ = Name\Relative[substr($1, 10)]; } +; + +class_name_reference: + class_name { $$ = $1; } + | dynamic_class_name_reference { $$ = $1; } +; + +dynamic_class_name_reference: + object_access_for_dcnr { $$ = $1; } + | base_variable { $$ = $1; } +; + +class_name_or_var: + class_name { $$ = $1; } + | reference_variable { $$ = $1; } +; + +object_access_for_dcnr: + base_variable T_OBJECT_OPERATOR object_property + { $$ = Expr\PropertyFetch[$1, $3]; } + | object_access_for_dcnr T_OBJECT_OPERATOR object_property + { $$ = Expr\PropertyFetch[$1, $3]; } + | object_access_for_dcnr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | object_access_for_dcnr '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } +; + +exit_expr: + /* empty */ { $$ = null; } + | '(' ')' { $$ = null; } + | parentheses_expr { $$ = $1; } +; + +backticks_expr: + /* empty */ { $$ = array(); } + | T_ENCAPSED_AND_WHITESPACE + { $$ = array(Scalar\EncapsedStringPart[Scalar\String_::parseEscapeSequences($1, '`', false)]); } + | encaps_list { parseEncapsed($1, '`', false); $$ = $1; } +; + +ctor_arguments: + /* empty */ { $$ = array(); } + | argument_list { $$ = $1; } +; + +common_scalar: + T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); } + | T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; } + | T_CONSTANT_ENCAPSED_STRING + { $attrs = attributes(); $attrs['kind'] = strKind($1); + $$ = new Scalar\String_(Scalar\String_::parse($1, false), $attrs); } + | T_LINE { $$ = Scalar\MagicConst\Line[]; } + | T_FILE { $$ = Scalar\MagicConst\File[]; } + | T_DIR { $$ = Scalar\MagicConst\Dir[]; } + | T_CLASS_C { $$ = Scalar\MagicConst\Class_[]; } + | T_TRAIT_C { $$ = Scalar\MagicConst\Trait_[]; } + | T_METHOD_C { $$ = Scalar\MagicConst\Method[]; } + | T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; } + | T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; } + | T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC + { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), false); } + | T_START_HEREDOC T_END_HEREDOC + { $$ = $this->parseDocString($1, '', $2, attributes(), stackAttributes(#2), false); } +; + +static_scalar: + common_scalar { $$ = $1; } + | class_name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = Expr\ClassConstFetch[$1, $3]; } + | name { $$ = Expr\ConstFetch[$1]; } + | T_ARRAY '(' static_array_pair_list ')' { $$ = Expr\Array_[$3]; } + | '[' static_array_pair_list ']' { $$ = Expr\Array_[$2]; } + | static_operation { $$ = $1; } +; + +static_operation: + static_scalar T_BOOLEAN_OR static_scalar { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; } + | static_scalar T_BOOLEAN_AND static_scalar { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; } + | static_scalar T_LOGICAL_OR static_scalar { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; } + | static_scalar T_LOGICAL_AND static_scalar { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; } + | static_scalar T_LOGICAL_XOR static_scalar { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; } + | static_scalar '|' static_scalar { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; } + | static_scalar '&' static_scalar { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; } + | static_scalar '^' static_scalar { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; } + | static_scalar '.' static_scalar { $$ = Expr\BinaryOp\Concat [$1, $3]; } + | static_scalar '+' static_scalar { $$ = Expr\BinaryOp\Plus [$1, $3]; } + | static_scalar '-' static_scalar { $$ = Expr\BinaryOp\Minus [$1, $3]; } + | static_scalar '*' static_scalar { $$ = Expr\BinaryOp\Mul [$1, $3]; } + | static_scalar '/' static_scalar { $$ = Expr\BinaryOp\Div [$1, $3]; } + | static_scalar '%' static_scalar { $$ = Expr\BinaryOp\Mod [$1, $3]; } + | static_scalar T_SL static_scalar { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; } + | static_scalar T_SR static_scalar { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; } + | static_scalar T_POW static_scalar { $$ = Expr\BinaryOp\Pow [$1, $3]; } + | '+' static_scalar %prec T_INC { $$ = Expr\UnaryPlus [$2]; } + | '-' static_scalar %prec T_INC { $$ = Expr\UnaryMinus[$2]; } + | '!' static_scalar { $$ = Expr\BooleanNot[$2]; } + | '~' static_scalar { $$ = Expr\BitwiseNot[$2]; } + | static_scalar T_IS_IDENTICAL static_scalar { $$ = Expr\BinaryOp\Identical [$1, $3]; } + | static_scalar T_IS_NOT_IDENTICAL static_scalar { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; } + | static_scalar T_IS_EQUAL static_scalar { $$ = Expr\BinaryOp\Equal [$1, $3]; } + | static_scalar T_IS_NOT_EQUAL static_scalar { $$ = Expr\BinaryOp\NotEqual [$1, $3]; } + | static_scalar '<' static_scalar { $$ = Expr\BinaryOp\Smaller [$1, $3]; } + | static_scalar T_IS_SMALLER_OR_EQUAL static_scalar { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; } + | static_scalar '>' static_scalar { $$ = Expr\BinaryOp\Greater [$1, $3]; } + | static_scalar T_IS_GREATER_OR_EQUAL static_scalar { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; } + | static_scalar '?' static_scalar ':' static_scalar { $$ = Expr\Ternary[$1, $3, $5]; } + | static_scalar '?' ':' static_scalar { $$ = Expr\Ternary[$1, null, $4]; } + | static_scalar '[' static_scalar ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | '(' static_scalar ')' { $$ = $2; } +; + +constant: + name { $$ = Expr\ConstFetch[$1]; } + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex + { $$ = Expr\ClassConstFetch[$1, $3]; } +; + +scalar: + common_scalar { $$ = $1; } + | constant { $$ = $1; } + | '"' encaps_list '"' + { $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); } + | T_START_HEREDOC encaps_list T_END_HEREDOC + { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); } +; + +static_array_pair_list: + /* empty */ { $$ = array(); } + | non_empty_static_array_pair_list optional_comma { $$ = $1; } +; + +optional_comma: + /* empty */ + | ',' +; + +non_empty_static_array_pair_list: + non_empty_static_array_pair_list ',' static_array_pair { push($1, $3); } + | static_array_pair { init($1); } +; + +static_array_pair: + static_scalar T_DOUBLE_ARROW static_scalar { $$ = Expr\ArrayItem[$3, $1, false]; } + | static_scalar { $$ = Expr\ArrayItem[$1, null, false]; } +; + +variable: + object_access { $$ = $1; } + | base_variable { $$ = $1; } + | function_call { $$ = $1; } + | new_expr_array_deref { $$ = $1; } +; + +new_expr_array_deref: + '(' new_expr ')' '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$2, $5]; } + | new_expr_array_deref '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + /* alternative array syntax missing intentionally */ +; + +object_access: + variable_or_new_expr T_OBJECT_OPERATOR object_property + { $$ = Expr\PropertyFetch[$1, $3]; } + | variable_or_new_expr T_OBJECT_OPERATOR object_property argument_list + { $$ = Expr\MethodCall[$1, $3, $4]; } + | object_access argument_list { $$ = Expr\FuncCall[$1, $2]; } + | object_access '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | object_access '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } +; + +variable_or_new_expr: + variable { $$ = $1; } + | '(' new_expr ')' { $$ = $2; } +; + +variable_without_objects: + reference_variable { $$ = $1; } + | '$' variable_without_objects { $$ = Expr\Variable[$2]; } +; + +base_variable: + variable_without_objects { $$ = $1; } + | static_property { $$ = $1; } +; + +static_property: + class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' reference_variable + { $$ = Expr\StaticPropertyFetch[$1, $4]; } + | static_property_with_arrays { $$ = $1; } +; + +static_property_simple_name: + T_VARIABLE + { $var = parseVar($1); $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; } +; + +static_property_with_arrays: + class_name_or_var T_PAAMAYIM_NEKUDOTAYIM static_property_simple_name + { $$ = Expr\StaticPropertyFetch[$1, $3]; } + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' '{' expr '}' + { $$ = Expr\StaticPropertyFetch[$1, $5]; } + | static_property_with_arrays '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | static_property_with_arrays '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } +; + +reference_variable: + reference_variable '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | reference_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | plain_variable { $$ = $1; } + | '$' '{' expr '}' { $$ = Expr\Variable[$3]; } +; + +dim_offset: + /* empty */ { $$ = null; } + | expr { $$ = $1; } +; + +object_property: + identifier { $$ = $1; } + | '{' expr '}' { $$ = $2; } + | variable_without_objects { $$ = $1; } + | error { $$ = Expr\Error[]; $this->errorState = 2; } +; + +list_expr: + T_LIST '(' list_expr_elements ')' { $$ = Expr\List_[$3]; } +; + +list_expr_elements: + list_expr_elements ',' list_expr_element { push($1, $3); } + | list_expr_element { init($1); } +; + +list_expr_element: + variable { $$ = Expr\ArrayItem[$1, null, false]; } + | list_expr { $$ = Expr\ArrayItem[$1, null, false]; } + | /* empty */ { $$ = null; } +; + +array_pair_list: + /* empty */ { $$ = array(); } + | non_empty_array_pair_list optional_comma { $$ = $1; } +; + +non_empty_array_pair_list: + non_empty_array_pair_list ',' array_pair { push($1, $3); } + | array_pair { init($1); } +; + +array_pair: + expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; } + | expr { $$ = Expr\ArrayItem[$1, null, false]; } + | expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; } + | '&' variable { $$ = Expr\ArrayItem[$2, null, true]; } + | T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; } +; + +encaps_list: + encaps_list encaps_var { push($1, $2); } + | encaps_list encaps_string_part { push($1, $2); } + | encaps_var { init($1); } + | encaps_string_part encaps_var { init($1, $2); } +; + +encaps_string_part: + T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; } +; + +encaps_str_varname: + T_STRING_VARNAME { $$ = Expr\Variable[$1]; } +; + +encaps_var: + plain_variable { $$ = $1; } + | plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; } + | T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; } + | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; } + | T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}' + { $$ = Expr\ArrayDimFetch[$2, $4]; } + | T_CURLY_OPEN variable '}' { $$ = $2; } +; + +encaps_var_offset: + T_STRING { $$ = Scalar\String_[$1]; } + | T_NUM_STRING { $$ = $this->parseNumString($1, attributes()); } + | plain_variable { $$ = $1; } +; + +%% diff --git a/lib/composer/nikic/php-parser/grammar/php7.y b/lib/composer/nikic/php-parser/grammar/php7.y new file mode 100644 index 0000000000000000000000000000000000000000..8add7833874fd6682bb8942c93e977b9df5bc35e --- /dev/null +++ b/lib/composer/nikic/php-parser/grammar/php7.y @@ -0,0 +1,1141 @@ +%pure_parser +%expect 2 + +%tokens + +%% + +start: + top_statement_list { $$ = $this->handleNamespaces($1); } +; + +top_statement_list_ex: + top_statement_list_ex top_statement { pushNormalizing($1, $2); } + | /* empty */ { init(); } +; + +top_statement_list: + top_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +reserved_non_modifiers: + 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 +; + +semi_reserved: + reserved_non_modifiers + | T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC +; + +identifier_ex: + T_STRING { $$ = Node\Identifier[$1]; } + | semi_reserved { $$ = Node\Identifier[$1]; } +; + +identifier: + T_STRING { $$ = Node\Identifier[$1]; } +; + +reserved_non_modifiers_identifier: + reserved_non_modifiers { $$ = Node\Identifier[$1]; } +; + +namespace_declaration_name: + T_STRING { $$ = Name[$1]; } + | semi_reserved { $$ = Name[$1]; } + | T_NAME_QUALIFIED { $$ = Name[$1]; } +; + +namespace_name: + T_STRING { $$ = Name[$1]; } + | T_NAME_QUALIFIED { $$ = Name[$1]; } +; + +legacy_namespace_name: + namespace_name { $$ = $1; } + | T_NAME_FULLY_QUALIFIED { $$ = Name[substr($1, 1)]; } +; + +plain_variable: + T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; } +; + +semi: + ';' { /* nothing */ } + | error { /* nothing */ } +; + +no_comma: + /* empty */ { /* nothing */ } + | ',' { $this->emitError(new Error('A trailing comma is not allowed here', attributes())); } +; + +optional_comma: + /* empty */ + | ',' +; + +attribute_decl: + class_name { $$ = Node\Attribute[$1, []]; } + | class_name argument_list { $$ = Node\Attribute[$1, $2]; } +; + +attribute_group: + attribute_decl { init($1); } + | attribute_group ',' attribute_decl { push($1, $3); } +; + +attribute: + T_ATTRIBUTE attribute_group optional_comma ']' { $$ = Node\AttributeGroup[$2]; } +; + +attributes: + attribute { init($1); } + | attributes attribute { push($1, $2); } +; + +optional_attributes: + /* empty */ { $$ = []; } + | attributes { $$ = $1; } +; + +top_statement: + statement { $$ = $1; } + | function_declaration_statement { $$ = $1; } + | class_declaration_statement { $$ = $1; } + | T_HALT_COMPILER + { $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; } + | T_NAMESPACE namespace_declaration_name semi + { $$ = Stmt\Namespace_[$2, null]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $this->checkNamespace($$); } + | T_NAMESPACE namespace_declaration_name '{' top_statement_list '}' + { $$ = Stmt\Namespace_[$2, $4]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($$); } + | T_NAMESPACE '{' top_statement_list '}' + { $$ = Stmt\Namespace_[null, $3]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($$); } + | T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; } + | T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; } + | group_use_declaration semi { $$ = $1; } + | T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; } +; + +use_type: + T_FUNCTION { $$ = Stmt\Use_::TYPE_FUNCTION; } + | T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; } +; + +group_use_declaration: + T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}' + { $$ = Stmt\GroupUse[$3, $6, $2]; } + | T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}' + { $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; } +; + +unprefixed_use_declarations: + non_empty_unprefixed_use_declarations optional_comma { $$ = $1; } +; + +non_empty_unprefixed_use_declarations: + non_empty_unprefixed_use_declarations ',' unprefixed_use_declaration + { push($1, $3); } + | unprefixed_use_declaration { init($1); } +; + +use_declarations: + non_empty_use_declarations no_comma { $$ = $1; } +; + +non_empty_use_declarations: + non_empty_use_declarations ',' use_declaration { push($1, $3); } + | use_declaration { init($1); } +; + +inline_use_declarations: + non_empty_inline_use_declarations optional_comma { $$ = $1; } +; + +non_empty_inline_use_declarations: + non_empty_inline_use_declarations ',' inline_use_declaration + { push($1, $3); } + | inline_use_declaration { init($1); } +; + +unprefixed_use_declaration: + namespace_name + { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); } + | namespace_name T_AS identifier + { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); } +; + +use_declaration: + legacy_namespace_name + { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); } + | legacy_namespace_name T_AS identifier + { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); } +; + +inline_use_declaration: + unprefixed_use_declaration { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; } + | use_type unprefixed_use_declaration { $$ = $2; $$->type = $1; } +; + +constant_declaration_list: + non_empty_constant_declaration_list no_comma { $$ = $1; } +; + +non_empty_constant_declaration_list: + non_empty_constant_declaration_list ',' constant_declaration + { push($1, $3); } + | constant_declaration { init($1); } +; + +constant_declaration: + identifier '=' expr { $$ = Node\Const_[$1, $3]; } +; + +class_const_list: + non_empty_class_const_list no_comma { $$ = $1; } +; + +non_empty_class_const_list: + non_empty_class_const_list ',' class_const { push($1, $3); } + | class_const { init($1); } +; + +class_const: + identifier_ex '=' expr { $$ = Node\Const_[$1, $3]; } +; + +inner_statement_list_ex: + inner_statement_list_ex inner_statement { pushNormalizing($1, $2); } + | /* empty */ { init(); } +; + +inner_statement_list: + inner_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +inner_statement: + statement { $$ = $1; } + | function_declaration_statement { $$ = $1; } + | class_declaration_statement { $$ = $1; } + | T_HALT_COMPILER + { throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); } +; + +non_empty_statement: + '{' inner_statement_list '}' + { + if ($2) { + $$ = $2; prependLeadingComments($$); + } else { + makeNop($$, $this->startAttributeStack[#1], $this->endAttributes); + if (null === $$) { $$ = array(); } + } + } + | T_IF '(' expr ')' statement elseif_list else_single + { $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; } + | T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';' + { $$ = Stmt\If_[$3, ['stmts' => $6, 'elseifs' => $7, 'else' => $8]]; } + | T_WHILE '(' expr ')' while_statement { $$ = Stmt\While_[$3, $5]; } + | T_DO statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, toArray($2)]; } + | T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement + { $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; } + | T_SWITCH '(' expr ')' switch_case_list { $$ = Stmt\Switch_[$3, $5]; } + | T_BREAK optional_expr semi { $$ = Stmt\Break_[$2]; } + | T_CONTINUE optional_expr semi { $$ = Stmt\Continue_[$2]; } + | T_RETURN optional_expr semi { $$ = Stmt\Return_[$2]; } + | T_GLOBAL global_var_list semi { $$ = Stmt\Global_[$2]; } + | T_STATIC static_var_list semi { $$ = Stmt\Static_[$2]; } + | T_ECHO expr_list_forbid_comma semi { $$ = Stmt\Echo_[$2]; } + | T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; } + | expr semi { + $e = $1; + if ($e instanceof Expr\Throw_) { + // For backwards-compatibility reasons, convert throw in statement position into + // Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_). + $$ = Stmt\Throw_[$e->expr]; + } else { + $$ = Stmt\Expression[$e]; + } + } + | T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; } + | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement + { $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; } + | T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement + { $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; } + | T_FOREACH '(' expr error ')' foreach_statement + { $$ = Stmt\Foreach_[$3, new Expr\Error(stackAttributes(#4)), ['stmts' => $6]]; } + | T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; } + | T_TRY '{' inner_statement_list '}' catches optional_finally + { $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); } + | T_GOTO identifier semi { $$ = Stmt\Goto_[$2]; } + | identifier ':' { $$ = Stmt\Label[$1]; } + | error { $$ = array(); /* means: no statement */ } +; + +statement: + non_empty_statement { $$ = $1; } + | ';' + { makeNop($$, $this->startAttributeStack[#1], $this->endAttributes); + if ($$ === null) $$ = array(); /* means: no statement */ } +; + +catches: + /* empty */ { init(); } + | catches catch { push($1, $2); } +; + +name_union: + name { init($1); } + | name_union '|' name { push($1, $3); } +; + +catch: + T_CATCH '(' name_union optional_plain_variable ')' '{' inner_statement_list '}' + { $$ = Stmt\Catch_[$3, $4, $7]; } +; + +optional_finally: + /* empty */ { $$ = null; } + | T_FINALLY '{' inner_statement_list '}' { $$ = Stmt\Finally_[$3]; } +; + +variables_list: + non_empty_variables_list optional_comma { $$ = $1; } +; + +non_empty_variables_list: + variable { init($1); } + | non_empty_variables_list ',' variable { push($1, $3); } +; + +optional_ref: + /* empty */ { $$ = false; } + | '&' { $$ = true; } +; + +optional_ellipsis: + /* empty */ { $$ = false; } + | T_ELLIPSIS { $$ = true; } +; + +block_or_error: + '{' inner_statement_list '}' { $$ = $2; } + | error { $$ = []; } +; + +function_declaration_statement: + T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error + { $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; } + | attributes T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error + { $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; } +; + +class_declaration_statement: + class_entry_type identifier extends_from implements_list '{' class_statement_list '}' + { $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6, 'attrGroups' => []]]; + $this->checkClass($$, #2); } + | attributes class_entry_type identifier extends_from implements_list '{' class_statement_list '}' + { $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]]; + $this->checkClass($$, #3); } + | optional_attributes T_INTERFACE identifier interface_extends_list '{' class_statement_list '}' + { $$ = Stmt\Interface_[$3, ['extends' => $4, 'stmts' => $6, 'attrGroups' => $1]]; + $this->checkInterface($$, #3); } + | optional_attributes T_TRAIT identifier '{' class_statement_list '}' + { $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; } +; + +class_entry_type: + T_CLASS { $$ = 0; } + | T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } + | T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; } +; + +extends_from: + /* empty */ { $$ = null; } + | T_EXTENDS class_name { $$ = $2; } +; + +interface_extends_list: + /* empty */ { $$ = array(); } + | T_EXTENDS class_name_list { $$ = $2; } +; + +implements_list: + /* empty */ { $$ = array(); } + | T_IMPLEMENTS class_name_list { $$ = $2; } +; + +class_name_list: + non_empty_class_name_list no_comma { $$ = $1; } +; + +non_empty_class_name_list: + class_name { init($1); } + | non_empty_class_name_list ',' class_name { push($1, $3); } +; + +for_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDFOR ';' { $$ = $2; } +; + +foreach_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDFOREACH ';' { $$ = $2; } +; + +declare_statement: + non_empty_statement { $$ = toArray($1); } + | ';' { $$ = null; } + | ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; } +; + +declare_list: + non_empty_declare_list no_comma { $$ = $1; } +; + +non_empty_declare_list: + declare_list_element { init($1); } + | non_empty_declare_list ',' declare_list_element { push($1, $3); } +; + +declare_list_element: + identifier '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; } +; + +switch_case_list: + '{' case_list '}' { $$ = $2; } + | '{' ';' case_list '}' { $$ = $3; } + | ':' case_list T_ENDSWITCH ';' { $$ = $2; } + | ':' ';' case_list T_ENDSWITCH ';' { $$ = $3; } +; + +case_list: + /* empty */ { init(); } + | case_list case { push($1, $2); } +; + +case: + T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; } + | T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; } +; + +case_separator: + ':' + | ';' +; + +match: + T_MATCH '(' expr ')' '{' match_arm_list '}' { $$ = Expr\Match_[$3, $6]; } +; + +match_arm_list: + /* empty */ { $$ = []; } + | non_empty_match_arm_list optional_comma { $$ = $1; } +; + +non_empty_match_arm_list: + match_arm { init($1); } + | non_empty_match_arm_list ',' match_arm { push($1, $3); } +; + +match_arm: + expr_list_allow_comma T_DOUBLE_ARROW expr { $$ = Node\MatchArm[$1, $3]; } + | T_DEFAULT optional_comma T_DOUBLE_ARROW expr { $$ = Node\MatchArm[null, $4]; } +; + +while_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; } +; + +elseif_list: + /* empty */ { init(); } + | elseif_list elseif { push($1, $2); } +; + +elseif: + T_ELSEIF '(' expr ')' statement { $$ = Stmt\ElseIf_[$3, toArray($5)]; } +; + +new_elseif_list: + /* empty */ { init(); } + | new_elseif_list new_elseif { push($1, $2); } +; + +new_elseif: + T_ELSEIF '(' expr ')' ':' inner_statement_list { $$ = Stmt\ElseIf_[$3, $6]; } +; + +else_single: + /* empty */ { $$ = null; } + | T_ELSE statement { $$ = Stmt\Else_[toArray($2)]; } +; + +new_else_single: + /* empty */ { $$ = null; } + | T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; } +; + +foreach_variable: + variable { $$ = array($1, false); } + | '&' variable { $$ = array($2, true); } + | list_expr { $$ = array($1, false); } + | array_short_syntax { $$ = array($1, false); } +; + +parameter_list: + non_empty_parameter_list optional_comma { $$ = $1; } + | /* empty */ { $$ = array(); } +; + +non_empty_parameter_list: + parameter { init($1); } + | non_empty_parameter_list ',' parameter { push($1, $3); } +; + +optional_visibility_modifier: + /* empty */ { $$ = 0; } + | T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; } + | T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; } + | T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; } +; + +parameter: + optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable + { $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1); + $this->checkParam($$); } + | optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable '=' expr + { $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1); + $this->checkParam($$); } + | optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis error + { $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); } +; + +type_expr: + type { $$ = $1; } + | '?' type { $$ = Node\NullableType[$2]; } + | union_type { $$ = Node\UnionType[$1]; } +; + +type: + type_without_static { $$ = $1; } + | T_STATIC { $$ = Node\Name['static']; } +; + +type_without_static: + name { $$ = $this->handleBuiltinTypes($1); } + | T_ARRAY { $$ = Node\Identifier['array']; } + | T_CALLABLE { $$ = Node\Identifier['callable']; } +; + +union_type: + type '|' type { init($1, $3); } + | union_type '|' type { push($1, $3); } +; + +union_type_without_static: + type_without_static '|' type_without_static { init($1, $3); } + | union_type_without_static '|' type_without_static { push($1, $3); } +; + +type_expr_without_static: + type_without_static { $$ = $1; } + | '?' type_without_static { $$ = Node\NullableType[$2]; } + | union_type_without_static { $$ = Node\UnionType[$1]; } +; + +optional_type_without_static: + /* empty */ { $$ = null; } + | type_expr_without_static { $$ = $1; } +; + +optional_return_type: + /* empty */ { $$ = null; } + | ':' type_expr { $$ = $2; } + | ':' error { $$ = null; } +; + +argument_list: + '(' ')' { $$ = array(); } + | '(' non_empty_argument_list optional_comma ')' { $$ = $2; } +; + +non_empty_argument_list: + argument { init($1); } + | non_empty_argument_list ',' argument { push($1, $3); } +; + +argument: + expr { $$ = Node\Arg[$1, false, false]; } + | '&' variable { $$ = Node\Arg[$2, true, false]; } + | T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; } + | identifier_ex ':' expr + { $$ = new Node\Arg($3, false, false, attributes(), $1); } +; + +global_var_list: + non_empty_global_var_list no_comma { $$ = $1; } +; + +non_empty_global_var_list: + non_empty_global_var_list ',' global_var { push($1, $3); } + | global_var { init($1); } +; + +global_var: + simple_variable { $$ = Expr\Variable[$1]; } +; + +static_var_list: + non_empty_static_var_list no_comma { $$ = $1; } +; + +non_empty_static_var_list: + non_empty_static_var_list ',' static_var { push($1, $3); } + | static_var { init($1); } +; + +static_var: + plain_variable { $$ = Stmt\StaticVar[$1, null]; } + | plain_variable '=' expr { $$ = Stmt\StaticVar[$1, $3]; } +; + +class_statement_list_ex: + class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } } + | /* empty */ { init(); } +; + +class_statement_list: + class_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +class_statement: + optional_attributes variable_modifiers optional_type_without_static property_declaration_list semi + { $$ = new Stmt\Property($2, $4, attributes(), $3, $1); + $this->checkProperty($$, #2); } + | optional_attributes method_modifiers T_CONST class_const_list semi + { $$ = new Stmt\ClassConst($4, $2, attributes(), $1); + $this->checkClassConst($$, #2); } + | optional_attributes method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body + { $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]]; + $this->checkClassMethod($$, #2); } + | T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; } + | error { $$ = null; /* will be skipped */ } +; + +trait_adaptations: + ';' { $$ = array(); } + | '{' trait_adaptation_list '}' { $$ = $2; } +; + +trait_adaptation_list: + /* empty */ { init(); } + | trait_adaptation_list trait_adaptation { push($1, $2); } +; + +trait_adaptation: + trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';' + { $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; } + | trait_method_reference T_AS member_modifier identifier_ex ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; } + | trait_method_reference T_AS member_modifier ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; } + | trait_method_reference T_AS identifier ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; } + | trait_method_reference T_AS reserved_non_modifiers_identifier ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; } +; + +trait_method_reference_fully_qualified: + name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); } +; +trait_method_reference: + trait_method_reference_fully_qualified { $$ = $1; } + | identifier_ex { $$ = array(null, $1); } +; + +method_body: + ';' /* abstract method */ { $$ = null; } + | block_or_error { $$ = $1; } +; + +variable_modifiers: + non_empty_member_modifiers { $$ = $1; } + | T_VAR { $$ = 0; } +; + +method_modifiers: + /* empty */ { $$ = 0; } + | non_empty_member_modifiers { $$ = $1; } +; + +non_empty_member_modifiers: + member_modifier { $$ = $1; } + | non_empty_member_modifiers member_modifier { $this->checkModifier($1, $2, #2); $$ = $1 | $2; } +; + +member_modifier: + T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; } + | T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; } + | T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; } + | T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; } + | T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } + | T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; } +; + +property_declaration_list: + non_empty_property_declaration_list no_comma { $$ = $1; } +; + +non_empty_property_declaration_list: + property_declaration { init($1); } + | non_empty_property_declaration_list ',' property_declaration + { push($1, $3); } +; + +property_decl_name: + T_VARIABLE { $$ = Node\VarLikeIdentifier[parseVar($1)]; } +; + +property_declaration: + property_decl_name { $$ = Stmt\PropertyProperty[$1, null]; } + | property_decl_name '=' expr { $$ = Stmt\PropertyProperty[$1, $3]; } +; + +expr_list_forbid_comma: + non_empty_expr_list no_comma { $$ = $1; } +; + +expr_list_allow_comma: + non_empty_expr_list optional_comma { $$ = $1; } +; + +non_empty_expr_list: + non_empty_expr_list ',' expr { push($1, $3); } + | expr { init($1); } +; + +for_expr: + /* empty */ { $$ = array(); } + | expr_list_forbid_comma { $$ = $1; } +; + +expr: + variable { $$ = $1; } + | list_expr '=' expr { $$ = Expr\Assign[$1, $3]; } + | array_short_syntax '=' expr { $$ = Expr\Assign[$1, $3]; } + | variable '=' expr { $$ = Expr\Assign[$1, $3]; } + | variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; } + | new_expr { $$ = $1; } + | match { $$ = $1; } + | T_CLONE expr { $$ = Expr\Clone_[$2]; } + | variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; } + | variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; } + | variable T_MUL_EQUAL expr { $$ = Expr\AssignOp\Mul [$1, $3]; } + | variable T_DIV_EQUAL expr { $$ = Expr\AssignOp\Div [$1, $3]; } + | variable T_CONCAT_EQUAL expr { $$ = Expr\AssignOp\Concat [$1, $3]; } + | variable T_MOD_EQUAL expr { $$ = Expr\AssignOp\Mod [$1, $3]; } + | variable T_AND_EQUAL expr { $$ = Expr\AssignOp\BitwiseAnd[$1, $3]; } + | variable T_OR_EQUAL expr { $$ = Expr\AssignOp\BitwiseOr [$1, $3]; } + | variable T_XOR_EQUAL expr { $$ = Expr\AssignOp\BitwiseXor[$1, $3]; } + | variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; } + | variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$1, $3]; } + | variable T_POW_EQUAL expr { $$ = Expr\AssignOp\Pow [$1, $3]; } + | variable T_COALESCE_EQUAL expr { $$ = Expr\AssignOp\Coalesce [$1, $3]; } + | variable T_INC { $$ = Expr\PostInc[$1]; } + | T_INC variable { $$ = Expr\PreInc [$2]; } + | variable T_DEC { $$ = Expr\PostDec[$1]; } + | T_DEC variable { $$ = Expr\PreDec [$2]; } + | expr T_BOOLEAN_OR expr { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; } + | expr T_BOOLEAN_AND expr { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; } + | expr T_LOGICAL_OR expr { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; } + | expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; } + | expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; } + | expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; } + | expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; } + | expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; } + | expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; } + | expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; } + | expr '-' expr { $$ = Expr\BinaryOp\Minus [$1, $3]; } + | expr '*' expr { $$ = Expr\BinaryOp\Mul [$1, $3]; } + | expr '/' expr { $$ = Expr\BinaryOp\Div [$1, $3]; } + | expr '%' expr { $$ = Expr\BinaryOp\Mod [$1, $3]; } + | expr T_SL expr { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; } + | expr T_SR expr { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; } + | expr T_POW expr { $$ = Expr\BinaryOp\Pow [$1, $3]; } + | '+' expr %prec T_INC { $$ = Expr\UnaryPlus [$2]; } + | '-' expr %prec T_INC { $$ = Expr\UnaryMinus[$2]; } + | '!' expr { $$ = Expr\BooleanNot[$2]; } + | '~' expr { $$ = Expr\BitwiseNot[$2]; } + | expr T_IS_IDENTICAL expr { $$ = Expr\BinaryOp\Identical [$1, $3]; } + | expr T_IS_NOT_IDENTICAL expr { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; } + | expr T_IS_EQUAL expr { $$ = Expr\BinaryOp\Equal [$1, $3]; } + | expr T_IS_NOT_EQUAL expr { $$ = Expr\BinaryOp\NotEqual [$1, $3]; } + | expr T_SPACESHIP expr { $$ = Expr\BinaryOp\Spaceship [$1, $3]; } + | expr '<' expr { $$ = Expr\BinaryOp\Smaller [$1, $3]; } + | expr T_IS_SMALLER_OR_EQUAL expr { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; } + | expr '>' expr { $$ = Expr\BinaryOp\Greater [$1, $3]; } + | expr T_IS_GREATER_OR_EQUAL expr { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; } + | expr T_INSTANCEOF class_name_reference { $$ = Expr\Instanceof_[$1, $3]; } + | '(' expr ')' { $$ = $2; } + | expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; } + | expr '?' ':' expr { $$ = Expr\Ternary[$1, null, $4]; } + | expr T_COALESCE expr { $$ = Expr\BinaryOp\Coalesce[$1, $3]; } + | T_ISSET '(' expr_list_allow_comma ')' { $$ = Expr\Isset_[$3]; } + | T_EMPTY '(' expr ')' { $$ = Expr\Empty_[$3]; } + | T_INCLUDE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; } + | T_INCLUDE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE_ONCE]; } + | T_EVAL '(' expr ')' { $$ = Expr\Eval_[$3]; } + | T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; } + | T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; } + | T_INT_CAST expr { $$ = Expr\Cast\Int_ [$2]; } + | T_DOUBLE_CAST expr + { $attrs = attributes(); + $attrs['kind'] = $this->getFloatCastKind($1); + $$ = new Expr\Cast\Double($2, $attrs); } + | T_STRING_CAST expr { $$ = Expr\Cast\String_ [$2]; } + | T_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; } + | T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; } + | T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; } + | T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; } + | T_EXIT exit_expr + { $attrs = attributes(); + $attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $$ = new Expr\Exit_($2, $attrs); } + | '@' expr { $$ = Expr\ErrorSuppress[$2]; } + | scalar { $$ = $1; } + | '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; } + | T_PRINT expr { $$ = Expr\Print_[$2]; } + | T_YIELD { $$ = Expr\Yield_[null, null]; } + | T_YIELD expr { $$ = Expr\Yield_[$2, null]; } + | T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; } + | T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; } + | T_THROW expr { $$ = Expr\Throw_[$2]; } + + | T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr + { $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8, 'attrGroups' => []]]; } + | T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr + { $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => []]]; } + | T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error + { $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; } + | T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error + { $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => []]]; } + + | attributes T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr + { $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => $1]]; } + | attributes T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr + { $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $4, 'params' => $6, 'returnType' => $8, 'expr' => $10, 'attrGroups' => $1]]; } + | attributes T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error + { $$ = Expr\Closure[['static' => false, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; } + | attributes T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error + { $$ = Expr\Closure[['static' => true, 'byRef' => $4, 'params' => $6, 'uses' => $8, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]]; } +; + +anonymous_class: + optional_attributes T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}' + { $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3); + $this->checkClass($$[0], -1); } +; + +new_expr: + T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; } + | T_NEW anonymous_class + { list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; } +; + +lexical_vars: + /* empty */ { $$ = array(); } + | T_USE '(' lexical_var_list ')' { $$ = $3; } +; + +lexical_var_list: + non_empty_lexical_var_list optional_comma { $$ = $1; } +; + +non_empty_lexical_var_list: + lexical_var { init($1); } + | non_empty_lexical_var_list ',' lexical_var { push($1, $3); } +; + +lexical_var: + optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; } +; + +function_call: + name argument_list { $$ = Expr\FuncCall[$1, $2]; } + | callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; } + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list + { $$ = Expr\StaticCall[$1, $3, $4]; } +; + +class_name: + T_STATIC { $$ = Name[$1]; } + | name { $$ = $1; } +; + +name: + T_STRING { $$ = Name[$1]; } + | T_NAME_QUALIFIED { $$ = Name[$1]; } + | T_NAME_FULLY_QUALIFIED { $$ = Name\FullyQualified[substr($1, 1)]; } + | T_NAME_RELATIVE { $$ = Name\Relative[substr($1, 10)]; } +; + +class_name_reference: + class_name { $$ = $1; } + | new_variable { $$ = $1; } + | '(' expr ')' { $$ = $2; } + | error { $$ = Expr\Error[]; $this->errorState = 2; } +; + +class_name_or_var: + class_name { $$ = $1; } + | fully_dereferencable { $$ = $1; } +; + +exit_expr: + /* empty */ { $$ = null; } + | '(' optional_expr ')' { $$ = $2; } +; + +backticks_expr: + /* empty */ { $$ = array(); } + | T_ENCAPSED_AND_WHITESPACE + { $$ = array(Scalar\EncapsedStringPart[Scalar\String_::parseEscapeSequences($1, '`')]); } + | encaps_list { parseEncapsed($1, '`', true); $$ = $1; } +; + +ctor_arguments: + /* empty */ { $$ = array(); } + | argument_list { $$ = $1; } +; + +constant: + name { $$ = Expr\ConstFetch[$1]; } + | T_LINE { $$ = Scalar\MagicConst\Line[]; } + | T_FILE { $$ = Scalar\MagicConst\File[]; } + | T_DIR { $$ = Scalar\MagicConst\Dir[]; } + | T_CLASS_C { $$ = Scalar\MagicConst\Class_[]; } + | T_TRAIT_C { $$ = Scalar\MagicConst\Trait_[]; } + | T_METHOD_C { $$ = Scalar\MagicConst\Method[]; } + | T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; } + | T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; } +; + +class_constant: + class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex + { $$ = Expr\ClassConstFetch[$1, $3]; } + /* We interpret an isolated FOO:: as an unfinished class constant fetch. It could also be + an unfinished static property fetch or unfinished scoped call. */ + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM error + { $$ = Expr\ClassConstFetch[$1, new Expr\Error(stackAttributes(#3))]; $this->errorState = 2; } +; + +array_short_syntax: + '[' array_pair_list ']' + { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_SHORT; + $$ = new Expr\Array_($2, $attrs); } +; + +dereferencable_scalar: + T_ARRAY '(' array_pair_list ')' + { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG; + $$ = new Expr\Array_($3, $attrs); } + | array_short_syntax { $$ = $1; } + | T_CONSTANT_ENCAPSED_STRING + { $attrs = attributes(); $attrs['kind'] = strKind($1); + $$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); } + | '"' encaps_list '"' + { $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); } +; + +scalar: + T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); } + | T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; } + | dereferencable_scalar { $$ = $1; } + | constant { $$ = $1; } + | class_constant { $$ = $1; } + | T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC + { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); } + | T_START_HEREDOC T_END_HEREDOC + { $$ = $this->parseDocString($1, '', $2, attributes(), stackAttributes(#2), true); } + | T_START_HEREDOC encaps_list T_END_HEREDOC + { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); } +; + +optional_expr: + /* empty */ { $$ = null; } + | expr { $$ = $1; } +; + +fully_dereferencable: + variable { $$ = $1; } + | '(' expr ')' { $$ = $2; } + | dereferencable_scalar { $$ = $1; } + | class_constant { $$ = $1; } +; + +array_object_dereferencable: + fully_dereferencable { $$ = $1; } + | constant { $$ = $1; } +; + +callable_expr: + callable_variable { $$ = $1; } + | '(' expr ')' { $$ = $2; } + | dereferencable_scalar { $$ = $1; } +; + +callable_variable: + simple_variable { $$ = Expr\Variable[$1]; } + | array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | function_call { $$ = $1; } + | array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list + { $$ = Expr\MethodCall[$1, $3, $4]; } + | array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list + { $$ = Expr\NullsafeMethodCall[$1, $3, $4]; } +; + +optional_plain_variable: + /* empty */ { $$ = null; } + | plain_variable { $$ = $1; } +; + +variable: + callable_variable { $$ = $1; } + | static_member { $$ = $1; } + | array_object_dereferencable T_OBJECT_OPERATOR property_name + { $$ = Expr\PropertyFetch[$1, $3]; } + | array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name + { $$ = Expr\NullsafePropertyFetch[$1, $3]; } +; + +simple_variable: + T_VARIABLE { $$ = parseVar($1); } + | '$' '{' expr '}' { $$ = $3; } + | '$' simple_variable { $$ = Expr\Variable[$2]; } + | '$' error { $$ = Expr\Error[]; $this->errorState = 2; } +; + +static_member_prop_name: + simple_variable + { $var = $1; $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; } +; + +static_member: + class_name_or_var T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name + { $$ = Expr\StaticPropertyFetch[$1, $3]; } +; + +new_variable: + simple_variable { $$ = Expr\Variable[$1]; } + | new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; } + | new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; } + | class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name + { $$ = Expr\StaticPropertyFetch[$1, $3]; } + | new_variable T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name + { $$ = Expr\StaticPropertyFetch[$1, $3]; } +; + +member_name: + identifier_ex { $$ = $1; } + | '{' expr '}' { $$ = $2; } + | simple_variable { $$ = Expr\Variable[$1]; } +; + +property_name: + identifier { $$ = $1; } + | '{' expr '}' { $$ = $2; } + | simple_variable { $$ = Expr\Variable[$1]; } + | error { $$ = Expr\Error[]; $this->errorState = 2; } +; + +list_expr: + T_LIST '(' inner_array_pair_list ')' { $$ = Expr\List_[$3]; } +; + +array_pair_list: + inner_array_pair_list + { $$ = $1; $end = count($$)-1; if ($$[$end] === null) array_pop($$); } +; + +comma_or_error: + ',' + | error + { /* do nothing -- prevent default action of $$=$1. See #551. */ } +; + +inner_array_pair_list: + inner_array_pair_list comma_or_error array_pair { push($1, $3); } + | array_pair { init($1); } +; + +array_pair: + expr { $$ = Expr\ArrayItem[$1, null, false]; } + | '&' variable { $$ = Expr\ArrayItem[$2, null, true]; } + | list_expr { $$ = Expr\ArrayItem[$1, null, false]; } + | expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; } + | expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; } + | expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; } + | T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; } + | /* empty */ { $$ = null; } +; + +encaps_list: + encaps_list encaps_var { push($1, $2); } + | encaps_list encaps_string_part { push($1, $2); } + | encaps_var { init($1); } + | encaps_string_part encaps_var { init($1, $2); } +; + +encaps_string_part: + T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; } +; + +encaps_str_varname: + T_STRING_VARNAME { $$ = Expr\Variable[$1]; } +; + +encaps_var: + plain_variable { $$ = $1; } + | plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; } + | plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier { $$ = Expr\NullsafePropertyFetch[$1, $3]; } + | T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; } + | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; } + | T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}' + { $$ = Expr\ArrayDimFetch[$2, $4]; } + | T_CURLY_OPEN variable '}' { $$ = $2; } +; + +encaps_var_offset: + T_STRING { $$ = Scalar\String_[$1]; } + | T_NUM_STRING { $$ = $this->parseNumString($1, attributes()); } + | '-' T_NUM_STRING { $$ = $this->parseNumString('-' . $2, attributes()); } + | plain_variable { $$ = $1; } +; + +%% diff --git a/lib/composer/nikic/php-parser/grammar/rebuildParsers.php b/lib/composer/nikic/php-parser/grammar/rebuildParsers.php new file mode 100644 index 0000000000000000000000000000000000000000..88a53f13348ce82777dbed9632341a04fd50c504 --- /dev/null +++ b/lib/composer/nikic/php-parser/grammar/rebuildParsers.php @@ -0,0 +1,261 @@ +<?php + +$grammarFileToName = [ + __DIR__ . '/php5.y' => 'Php5', + __DIR__ . '/php7.y' => 'Php7', +]; + +$tokensFile = __DIR__ . '/tokens.y'; +$tokensTemplate = __DIR__ . '/tokens.template'; +$skeletonFile = __DIR__ . '/parser.template'; +$tmpGrammarFile = __DIR__ . '/tmp_parser.phpy'; +$tmpResultFile = __DIR__ . '/tmp_parser.php'; +$resultDir = __DIR__ . '/../lib/PhpParser/Parser'; +$tokensResultsFile = $resultDir . '/Tokens.php'; + +$kmyacc = getenv('KMYACC'); +if (!$kmyacc) { + // Use phpyacc from dev dependencies by default. + $kmyacc = __DIR__ . '/../vendor/bin/phpyacc'; +} + +$options = array_flip($argv); +$optionDebug = isset($options['--debug']); +$optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']); + +/////////////////////////////// +/// Utility regex constants /// +/////////////////////////////// + +const LIB = '(?(DEFINE) + (?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\') + (?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+") + (?<string>(?&singleQuotedString)|(?&doubleQuotedString)) + (?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/) + (?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+}) +)'; + +const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?¶ms)\][^[\]]*+)*+)\]'; +const ARGS = '\((?<args>[^()]*+(?:\((?&args)\)[^()]*+)*+)\)'; + +/////////////////// +/// Main script /// +/////////////////// + +$tokens = file_get_contents($tokensFile); + +foreach ($grammarFileToName as $grammarFile => $name) { + echo "Building temporary $name grammar file.\n"; + + $grammarCode = file_get_contents($grammarFile); + $grammarCode = str_replace('%tokens', $tokens, $grammarCode); + + $grammarCode = resolveNodes($grammarCode); + $grammarCode = resolveMacros($grammarCode); + $grammarCode = resolveStackAccess($grammarCode); + + file_put_contents($tmpGrammarFile, $grammarCode); + + $additionalArgs = $optionDebug ? '-t -v' : ''; + + echo "Building $name parser.\n"; + $output = execCmd("$kmyacc $additionalArgs -m $skeletonFile -p $name $tmpGrammarFile"); + + $resultCode = file_get_contents($tmpResultFile); + $resultCode = removeTrailingWhitespace($resultCode); + + ensureDirExists($resultDir); + file_put_contents("$resultDir/$name.php", $resultCode); + unlink($tmpResultFile); + + echo "Building token definition.\n"; + $output = execCmd("$kmyacc -m $tokensTemplate $tmpGrammarFile"); + rename($tmpResultFile, $tokensResultsFile); + + if (!$optionKeepTmpGrammar) { + unlink($tmpGrammarFile); + } +} + +/////////////////////////////// +/// Preprocessing functions /// +/////////////////////////////// + +function resolveNodes($code) { + return preg_replace_callback( + '~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~', + function($matches) { + // recurse + $matches['params'] = resolveNodes($matches['params']); + + $params = magicSplit( + '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,', + $matches['params'] + ); + + $paramCode = ''; + foreach ($params as $param) { + $paramCode .= $param . ', '; + } + + return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())'; + }, + $code + ); +} + +function resolveMacros($code) { + return preg_replace_callback( + '~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~', + function($matches) { + // recurse + $matches['args'] = resolveMacros($matches['args']); + + $name = $matches['name']; + $args = magicSplit( + '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,', + $matches['args'] + ); + + if ('attributes' === $name) { + assertArgs(0, $args, $name); + return '$this->startAttributeStack[#1] + $this->endAttributes'; + } + + if ('stackAttributes' === $name) { + assertArgs(1, $args, $name); + return '$this->startAttributeStack[' . $args[0] . ']' + . ' + $this->endAttributeStack[' . $args[0] . ']'; + } + + if ('init' === $name) { + return '$$ = array(' . implode(', ', $args) . ')'; + } + + if ('push' === $name) { + assertArgs(2, $args, $name); + + return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0]; + } + + if ('pushNormalizing' === $name) { + assertArgs(2, $args, $name); + + return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }' + . ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }'; + } + + if ('toArray' == $name) { + assertArgs(1, $args, $name); + + return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')'; + } + + if ('parseVar' === $name) { + assertArgs(1, $args, $name); + + return 'substr(' . $args[0] . ', 1)'; + } + + if ('parseEncapsed' === $name) { + assertArgs(3, $args, $name); + + return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {' + . ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }'; + } + + if ('makeNop' === $name) { + assertArgs(3, $args, $name); + + return '$startAttributes = ' . $args[1] . ';' + . ' if (isset($startAttributes[\'comments\']))' + . ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }' + . ' else { ' . $args[0] . ' = null; }'; + } + + if ('makeZeroLengthNop' == $name) { + assertArgs(2, $args, $name); + + return '$startAttributes = ' . $args[1] . ';' + . ' if (isset($startAttributes[\'comments\']))' + . ' { ' . $args[0] . ' = new Stmt\Nop($this->createCommentNopAttributes($startAttributes[\'comments\'])); }' + . ' else { ' . $args[0] . ' = null; }'; + } + + if ('strKind' === $name) { + assertArgs(1, $args, $name); + + return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && ' + . '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) ' + . '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)'; + } + + if ('prependLeadingComments' === $name) { + assertArgs(1, $args, $name); + + return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; ' + . 'if (!empty($attrs[\'comments\'])) {' + . '$stmts[0]->setAttribute(\'comments\', ' + . 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }'; + } + + return $matches[0]; + }, + $code + ); +} + +function assertArgs($num, $args, $name) { + if ($num != count($args)) { + die('Wrong argument count for ' . $name . '().'); + } +} + +function resolveStackAccess($code) { + $code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code); + $code = preg_replace('/#(\d+)/', '$$1', $code); + return $code; +} + +function removeTrailingWhitespace($code) { + $lines = explode("\n", $code); + $lines = array_map('rtrim', $lines); + return implode("\n", $lines); +} + +function ensureDirExists($dir) { + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } +} + +function execCmd($cmd) { + $output = trim(shell_exec("$cmd 2>&1")); + if ($output !== "") { + echo "> " . $cmd . "\n"; + echo $output; + } + return $output; +} + +////////////////////////////// +/// Regex helper functions /// +////////////////////////////// + +function regex($regex) { + return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~'; +} + +function magicSplit($regex, $string) { + $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string); + + foreach ($pieces as &$piece) { + $piece = trim($piece); + } + + if ($pieces === ['']) { + return []; + } + + return $pieces; +} diff --git a/lib/composer/nikic/php-parser/grammar/tokens.template b/lib/composer/nikic/php-parser/grammar/tokens.template new file mode 100644 index 0000000000000000000000000000000000000000..ba4e4901c0bfc12b49d193ab4e380e1ac4cdf4f2 --- /dev/null +++ b/lib/composer/nikic/php-parser/grammar/tokens.template @@ -0,0 +1,17 @@ +<?php +$meta # +#semval($) $this->semValue +#semval($,%t) $this->semValue +#semval(%n) $this->stackPos-(%l-%n) +#semval(%n,%t) $this->stackPos-(%l-%n) + +namespace PhpParser\Parser; +#include; + +/* GENERATED file based on grammar/tokens.y */ +final class Tokens +{ +#tokenval + const %s = %n; +#endtokenval +} diff --git a/lib/composer/nikic/php-parser/grammar/tokens.y b/lib/composer/nikic/php-parser/grammar/tokens.y new file mode 100644 index 0000000000000000000000000000000000000000..b0b0360cd26574b786343d4fe103624e18ce1c21 --- /dev/null +++ b/lib/composer/nikic/php-parser/grammar/tokens.y @@ -0,0 +1,113 @@ +/* We currently rely on the token ID mapping to be the same between PHP 5 and PHP 7 - so the same lexer can be used for + * both. This is enforced by sharing this token file. */ + +%right T_THROW +%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE +%left ',' +%left T_LOGICAL_OR +%left T_LOGICAL_XOR +%left T_LOGICAL_AND +%right T_PRINT +%right T_YIELD +%right T_DOUBLE_ARROW +%right T_YIELD_FROM +%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL T_COALESCE_EQUAL +%left '?' ':' +%right T_COALESCE +%left T_BOOLEAN_OR +%left T_BOOLEAN_AND +%left '|' +%left '^' +%left '&' +%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP +%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL +%left T_SL T_SR +%left '+' '-' '.' +%left '*' '/' '%' +%right '!' +%nonassoc T_INSTANCEOF +%right '~' 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 '@' +%right T_POW +%right '[' +%nonassoc T_NEW T_CLONE +%token T_EXIT +%token T_IF +%left T_ELSEIF +%left T_ELSE +%left T_ENDIF +%token T_LNUMBER +%token T_DNUMBER +%token T_STRING +%token T_STRING_VARNAME +%token T_VARIABLE +%token T_NUM_STRING +%token T_INLINE_HTML +%token T_ENCAPSED_AND_WHITESPACE +%token T_CONSTANT_ENCAPSED_STRING +%token T_ECHO +%token T_DO +%token T_WHILE +%token T_ENDWHILE +%token T_FOR +%token T_ENDFOR +%token T_FOREACH +%token T_ENDFOREACH +%token T_DECLARE +%token T_ENDDECLARE +%token T_AS +%token T_SWITCH +%token T_MATCH +%token T_ENDSWITCH +%token T_CASE +%token T_DEFAULT +%token T_BREAK +%token T_CONTINUE +%token T_GOTO +%token T_FUNCTION +%token T_FN +%token T_CONST +%token T_RETURN +%token T_TRY +%token T_CATCH +%token T_FINALLY +%token T_THROW +%token T_USE +%token T_INSTEADOF +%token T_GLOBAL +%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC +%token T_VAR +%token T_UNSET +%token T_ISSET +%token T_EMPTY +%token T_HALT_COMPILER +%token T_CLASS +%token T_TRAIT +%token T_INTERFACE +%token T_EXTENDS +%token T_IMPLEMENTS +%token T_OBJECT_OPERATOR +%token T_NULLSAFE_OBJECT_OPERATOR +%token T_DOUBLE_ARROW +%token T_LIST +%token T_ARRAY +%token T_CALLABLE +%token T_CLASS_C +%token T_TRAIT_C +%token T_METHOD_C +%token T_FUNC_C +%token T_LINE +%token T_FILE +%token T_START_HEREDOC +%token T_END_HEREDOC +%token T_DOLLAR_OPEN_CURLY_BRACES +%token T_CURLY_OPEN +%token T_PAAMAYIM_NEKUDOTAYIM +%token T_NAMESPACE +%token T_NS_C +%token T_DIR +%token T_NS_SEPARATOR +%token T_ELLIPSIS +%token T_NAME_FULLY_QUALIFIED +%token T_NAME_QUALIFIED +%token T_NAME_RELATIVE +%token T_ATTRIBUTE \ No newline at end of file diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Builder.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder.php new file mode 100644 index 0000000000000000000000000000000000000000..26d8921efc6ad4294a1a059e2d545498ac7621ad --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder.php @@ -0,0 +1,13 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +interface Builder +{ + /** + * Returns the built node. + * + * @return Node The built node + */ + public function getNode() : Node; +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Class_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Class_.php new file mode 100644 index 0000000000000000000000000000000000000000..c2f2468914250f3818f3afd2e836ed674dde62e6 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Class_.php @@ -0,0 +1,122 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser; +use PhpParser\BuilderHelpers; +use PhpParser\Node\Name; +use PhpParser\Node\Stmt; + +class Class_ extends Declaration +{ + protected $name; + + protected $extends = null; + protected $implements = []; + protected $flags = 0; + + protected $uses = []; + protected $constants = []; + protected $properties = []; + protected $methods = []; + + /** + * 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::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT); + + return $this; + } + + /** + * Makes the class final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); + + 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); + + $targets = [ + Stmt\TraitUse::class => &$this->uses, + Stmt\ClassConst::class => &$this->constants, + Stmt\Property::class => &$this->properties, + Stmt\ClassMethod::class => &$this->methods, + ]; + + $class = \get_class($stmt); + if (!isset($targets[$class])) { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + $targets[$class][] = $stmt; + + 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), + ], $this->attributes); + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Declaration.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Declaration.php new file mode 100644 index 0000000000000000000000000000000000000000..830949928aac0f9583e1e38363c9c8f5f12dea1e --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Declaration.php @@ -0,0 +1,43 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser; +use PhpParser\BuilderHelpers; + +abstract class Declaration implements PhpParser\Builder +{ + protected $attributes = []; + + abstract public function addStmt($stmt); + + /** + * Adds multiple statements. + * + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php new file mode 100644 index 0000000000000000000000000000000000000000..8e7db399d395a7e47dd8fa940f0f82032c123c15 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php @@ -0,0 +1,74 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser\BuilderHelpers; +use PhpParser\Node; + +abstract class FunctionLike extends Declaration +{ + protected $returnByRef = false; + protected $params = []; + + /** @var string|Node\Name|Node\NullableType|null */ + protected $returnType = null; + + /** + * Make the function return by reference. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeReturnByRef() { + $this->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 array $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\NullableType $type One of array, callable, string, int, float, + * bool, iterable, or a class/interface name. + * + * @return $this The builder instance (for fluid interface) + */ + public function setReturnType($type) { + $this->returnType = BuilderHelpers::normalizeType($type); + + return $this; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Function_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Function_.php new file mode 100644 index 0000000000000000000000000000000000000000..56eda2a8125f6645de64cd0242a2cdca56d8bfd3 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Function_.php @@ -0,0 +1,50 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser; +use PhpParser\BuilderHelpers; +use PhpParser\Node; +use PhpParser\Node\Stmt; + +class Function_ extends FunctionLike +{ + protected $name; + protected $stmts = []; + + /** + * 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; + } + + /** + * 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, + ], $this->attributes); + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Interface_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Interface_.php new file mode 100644 index 0000000000000000000000000000000000000000..87e5b93ee1d8feec142198ee51109553e76b486e --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Interface_.php @@ -0,0 +1,75 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser; +use PhpParser\BuilderHelpers; +use PhpParser\Node\Name; +use PhpParser\Node\Stmt; + +class Interface_ extends Declaration +{ + protected $name; + protected $extends = []; + protected $constants = []; + protected $methods = []; + + /** + * 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; + } + + /** + * 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), + ], $this->attributes); + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Method.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Method.php new file mode 100644 index 0000000000000000000000000000000000000000..a3e8676592d4ffda874727e96dfd16ee2ba4ce9e --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Method.php @@ -0,0 +1,129 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser; +use PhpParser\BuilderHelpers; +use PhpParser\Node; +use PhpParser\Node\Stmt; + +class Method extends FunctionLike +{ + protected $name; + protected $flags = 0; + + /** @var array|null */ + protected $stmts = []; + + /** + * 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, Stmt\Class_::MODIFIER_PUBLIC); + + return $this; + } + + /** + * Makes the method protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED); + + return $this; + } + + /** + * Makes the method private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE); + + return $this; + } + + /** + * Makes the method static. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeStatic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_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, Stmt\Class_::MODIFIER_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, Stmt\Class_::MODIFIER_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; + } + + /** + * 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, + ], $this->attributes); + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php new file mode 100644 index 0000000000000000000000000000000000000000..b9ccab3ec1c9de755134744a5ab762158d1b8637 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php @@ -0,0 +1,45 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser; +use PhpParser\BuilderHelpers; +use PhpParser\Node; +use PhpParser\Node\Stmt; + +class Namespace_ extends Declaration +{ + private $name; + private $stmts = []; + + /** + * Creates a namespace builder. + * + * @param Node\Name|string|null $name Name of the namespace + */ + public function __construct($name) { + $this->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 Node The built node + */ + public function getNode() : Node { + return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes); + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Param.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Param.php new file mode 100644 index 0000000000000000000000000000000000000000..c6491786e34bc0311e4be053ba49e0037bdc589b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Param.php @@ -0,0 +1,106 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser; +use PhpParser\BuilderHelpers; +use PhpParser\Node; + +class Param implements PhpParser\Builder +{ + protected $name; + + protected $default = null; + + /** @var Node\Identifier|Node\Name|Node\NullableType|null */ + protected $type = null; + + protected $byRef = false; + + protected $variadic = false; + + /** + * 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\NullableType|Node\UnionType $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; + } + + /** + * Sets type for the parameter. + * + * @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type + * + * @return $this The builder instance (for fluid interface) + * + * @deprecated Use setType() instead + */ + public function setTypeHint($type) { + return $this->setType($type); + } + + /** + * 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; + } + + /** + * 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 + ); + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Property.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Property.php new file mode 100644 index 0000000000000000000000000000000000000000..1f3bdb27234b971f996c026754abe06d783cb943 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Property.php @@ -0,0 +1,132 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser; +use PhpParser\BuilderHelpers; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name; +use PhpParser\Node\NullableType; +use PhpParser\Node\Stmt; + +class Property implements PhpParser\Builder +{ + protected $name; + + protected $flags = 0; + protected $default = null; + protected $attributes = []; + + /** @var null|Identifier|Name|NullableType */ + protected $type; + + /** + * 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, Stmt\Class_::MODIFIER_PUBLIC); + + return $this; + } + + /** + * Makes the property protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED); + + return $this; + } + + /** + * Makes the property private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE); + + return $this; + } + + /** + * Makes the property static. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeStatic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC); + + 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|NullableType|Identifier $type + * + * @return $this + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + + 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 : Stmt\Class_::MODIFIER_PUBLIC, + [ + new Stmt\PropertyProperty($this->name, $this->default) + ], + $this->attributes, + $this->type + ); + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php new file mode 100644 index 0000000000000000000000000000000000000000..311e8cd7b65cb9947127e8d68b54223f3bfd1203 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php @@ -0,0 +1,64 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser\Builder; +use PhpParser\BuilderHelpers; +use PhpParser\Node; +use PhpParser\Node\Stmt; + +class TraitUse implements Builder +{ + protected $traits = []; + protected $adaptations = []; + + /** + * Creates a trait use builder. + * + * @param Node\Name|string ...$traits Names of used traits + */ + public function __construct(...$traits) { + foreach ($traits as $trait) { + $this->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/lib/composer/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php new file mode 100644 index 0000000000000000000000000000000000000000..eb6c0b622dd0c822dd76374675e791f923935674 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php @@ -0,0 +1,148 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser\Builder; +use PhpParser\BuilderHelpers; +use PhpParser\Node; +use PhpParser\Node\Stmt; + +class TraitUseAdaptation implements Builder +{ + const TYPE_UNDEFINED = 0; + const TYPE_ALIAS = 1; + const TYPE_PRECEDENCE = 2; + + /** @var int Type of building adaptation */ + protected $type; + + protected $trait; + protected $method; + + protected $modifier = null; + protected $alias = null; + + protected $insteadof = []; + + /** + * Creates a trait use adaptation builder. + * + * @param Node\Name|string|null $trait Name of adaptated trait + * @param Node\Identifier|string $method Name of adaptated method + */ + public function __construct($trait, $method) { + $this->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 adaptated 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 = $alias; + return $this; + } + + /** + * Sets adaptated method public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->setModifier(Stmt\Class_::MODIFIER_PUBLIC); + return $this; + } + + /** + * Sets adaptated method protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->setModifier(Stmt\Class_::MODIFIER_PROTECTED); + return $this; + } + + /** + * Sets adaptated method private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->setModifier(Stmt\Class_::MODIFIER_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) { + 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/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Trait_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Trait_.php new file mode 100644 index 0000000000000000000000000000000000000000..a836d40c60a5ab2ad8f85a750ffc531773f58e0e --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Trait_.php @@ -0,0 +1,60 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser; +use PhpParser\BuilderHelpers; +use PhpParser\Node\Stmt; + +class Trait_ extends Declaration +{ + protected $name; + protected $uses = []; + protected $properties = []; + protected $methods = []; + + /** + * 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; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + 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->properties, $this->methods) + ], $this->attributes + ); + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Use_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Use_.php new file mode 100644 index 0000000000000000000000000000000000000000..2026a1726149a1b9c15c7f45d0884ff8a56f9933 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Builder/Use_.php @@ -0,0 +1,49 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Builder; + +use PhpParser\Builder; +use PhpParser\BuilderHelpers; +use PhpParser\Node; +use PhpParser\Node\Stmt; + +class Use_ implements Builder +{ + protected $name; + protected $type; + protected $alias = null; + + /** + * Creates a name use (alias) builder. + * + * @param Node\Name|string $name Name of the entity (namespace, class, function, constant) to alias + * @param int $type One of the Stmt\Use_::TYPE_* constants + */ + public function __construct($name, int $type) { + $this->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 Node The built node + */ + public function getNode() : Node { + return new Stmt\Use_([ + new Stmt\UseUse($this->name, $this->alias) + ], $this->type); + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/BuilderFactory.php b/lib/composer/nikic/php-parser/lib/PhpParser/BuilderFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..18bd1cd5c226830dc0b365f2bcd99dfe342bd1b2 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/BuilderFactory.php @@ -0,0 +1,348 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Expr\BinaryOp\Concat; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name; +use PhpParser\Node\Scalar\String_; +use PhpParser\Node\Stmt\Use_; + +class BuilderFactory +{ + /** + * 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 a trait use builder. + * + * @param Node\Name|string ...$traits Trait names + * + * @return Builder\TraitUse The create 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 create 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 node a for a literal value. + * + * @param Expr|bool|null|int|float|string|array $value $value + * + * @return Expr + */ + public function val($value) : Expr { + return BuilderHelpers::normalizeValue($value); + } + + /** + * Creates variable node. + * + * @param string|Expr $name Name + * + * @return Expr\Variable + */ + 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 Arg[] + */ + public function args(array $args) : array { + $normalizedArgs = []; + foreach ($args as $arg) { + if ($arg instanceof Arg) { + $normalizedArgs[] = $arg; + } else { + $normalizedArgs[] = new Arg(BuilderHelpers::normalizeValue($arg)); + } + } + return $normalizedArgs; + } + + /** + * Creates a function call node. + * + * @param string|Name|Expr $name Function name + * @param array $args Function arguments + * + * @return Expr\FuncCall + */ + 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 + * + * @return Expr\MethodCall + */ + 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 + * + * @return Expr\StaticCall + */ + 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 + * + * @return Expr\New_ + */ + 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 + * + * @return Expr\ConstFetch + */ + 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 + * + * @return Expr\PropertyFetch + */ + 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 $name Constant name + * + * @return Expr\ClassConstFetch + */ + public function classConstFetch($class, $name): Expr\ClassConstFetch { + return new Expr\ClassConstFetch( + BuilderHelpers::normalizeNameOrExpr($class), + BuilderHelpers::normalizeIdentifier($name) + ); + } + + /** + * Creates nested Concat nodes from a list of expressions. + * + * @param Expr|string ...$exprs Expressions or literal strings + * + * @return Concat + */ + 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 + * @return 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/lib/composer/nikic/php-parser/lib/PhpParser/BuilderHelpers.php b/lib/composer/nikic/php-parser/lib/PhpParser/BuilderHelpers.php new file mode 100644 index 0000000000000000000000000000000000000000..180bf35d0e7732f4f67628f2cc99adffdb2e58f5 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/BuilderHelpers.php @@ -0,0 +1,285 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +use PhpParser\Node\Expr; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name; +use PhpParser\Node\NullableType; +use PhpParser\Node\Scalar; +use PhpParser\Node\Stmt; +use PhpParser\Node\UnionType; + +/** + * This class defines helpers used in the implementation of builders. Don't use it directly. + * + * @internal + */ +final class BuilderHelpers +{ + /** + * Normalizes a node: Converts builder objects to nodes. + * + * @param Node|Builder $node The node to normalize + * + * @return Node The normalized node + */ + public static function normalizeNode($node) : Node { + if ($node instanceof Builder) { + return $node->getNode(); + } elseif ($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 { + return self::normalizeNameCommon($name, false); + } + + /** + * 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) { + return self::normalizeNameCommon($name, true); + } + + /** + * Normalizes a name: Converts string names to Name nodes, optionally allowing expressions. + * + * @param Expr|Name|string $name The name to normalize + * @param bool $allowExpr Whether to also allow expressions + * + * @return Name|Expr The normalized name, or expression (if allowed) + */ + private static function normalizeNameCommon($name, bool $allowExpr) { + if ($name instanceof Name) { + return $name; + } elseif (is_string($name)) { + if (!$name) { + throw new \LogicException('Name cannot be empty'); + } + + if ($name[0] === '\\') { + return new Name\FullyQualified(substr($name, 1)); + } elseif (0 === strpos($name, 'namespace\\')) { + return new Name\Relative(substr($name, strlen('namespace\\'))); + } else { + return new Name($name); + } + } + + if ($allowExpr) { + if ($name instanceof Expr) { + return $name; + } + throw new \LogicException( + 'Name must be a string or an instance of Node\Name or Node\Expr' + ); + } else { + throw new \LogicException('Name must be a string or an instance of Node\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|NullableType|UnionType $type The type to normalize + * + * @return Name|Identifier|NullableType|UnionType The normalized type + */ + public static function normalizeType($type) { + if (!is_string($type)) { + if ( + !$type instanceof Name && !$type instanceof Identifier && + !$type instanceof NullableType && !$type instanceof UnionType + ) { + throw new \LogicException( + 'Type must be a string, or an instance of Name, Identifier, NullableType or UnionType' + ); + } + return $type; + } + + $nullable = false; + if (strlen($type) > 0 && $type[0] === '?') { + $nullable = true; + $type = substr($type, 1); + } + + $builtinTypes = [ + 'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed' + ]; + + $lowerType = strtolower($type); + if (in_array($lowerType, $builtinTypes)) { + $type = new Identifier($lowerType); + } else { + $type = self::normalizeName($type); + } + + if ($nullable && (string) $type === 'void') { + throw new \LogicException('void type cannot be nullable'); + } + + if ($nullable && (string) $type === 'mixed') { + throw new \LogicException('mixed type cannot be nullable'); + } + + 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; + } elseif (is_null($value)) { + return new Expr\ConstFetch( + new Name('null') + ); + } elseif (is_bool($value)) { + return new Expr\ConstFetch( + new Name($value ? 'true' : 'false') + ); + } elseif (is_int($value)) { + return new Scalar\LNumber($value); + } elseif (is_float($value)) { + return new Scalar\DNumber($value); + } elseif (is_string($value)) { + return new Scalar\String_($value); + } elseif (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 Expr\ArrayItem( + self::normalizeValue($itemValue) + ); + } else { + $lastKey = null; + $items[] = new Expr\ArrayItem( + self::normalizeValue($itemValue), + self::normalizeValue($itemKey) + ); + } + } + + return new Expr\Array_($items); + } else { + 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; + } elseif (is_string($docComment)) { + return new Comment\Doc($docComment); + } else { + throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc'); + } + } + + /** + * 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 { + Stmt\Class_::verifyModifier($modifiers, $modifier); + return $modifiers | $modifier; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Comment.php b/lib/composer/nikic/php-parser/lib/PhpParser/Comment.php new file mode 100644 index 0000000000000000000000000000000000000000..61e98d3dca7e63797ab563acb8f94e8052728086 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Comment.php @@ -0,0 +1,239 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +class Comment implements \JsonSerializable +{ + protected $text; + protected $startLine; + protected $startFilePos; + protected $startTokenPos; + protected $endLine; + protected $endFilePos; + protected $endTokenPos; + + /** + * Constructs a comment node. + * + * @param string $text Comment text (including comment delimiters like /*) + * @param int $startLine Line number the comment started on + * @param int $startFilePos File offset the comment started on + * @param int $startTokenPos Token offset the comment started on + */ + public function __construct( + string $text, + int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1, + int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1 + ) { + $this->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 line number the comment started on. + * + * @deprecated Use getStartLine() instead + * + * @return int Line number + */ + public function getLine() : int { + return $this->startLine; + } + + /** + * Gets the file offset the comment started on. + * + * @deprecated Use getStartFilePos() instead + * + * @return int File offset + */ + public function getFilePos() : int { + return $this->startFilePos; + } + + /** + * Gets the token offset the comment started on. + * + * @deprecated Use getStartTokenPos() instead + * + * @return int Token offset + */ + public function getTokenPos() : int { + return $this->startTokenPos; + } + + /** + * 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 trailing whitespace on the first line, but with trailing whitespace + * on all subsequent lines. + * + * @return mixed|string + */ + public function getReformattedText() { + $text = trim($this->text); + $newlinePos = strpos($text, "\n"); + if (false === $newlinePos) { + // Single line comments don't need further processing + return $text; + } elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\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', ' *', $this->text); + } elseif (preg_match('(^/\*\*?\s*[\r\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); + } elseif (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 = \INF; + foreach ($lines as $line) { + preg_match('(^\s*)', $line, $matches); + $prefixLen = strlen($matches[0]); + if ($prefixLen < $shortestPrefixLen) { + $shortestPrefixLen = $prefixLen; + } + } + return $shortestPrefixLen; + } + + /** + * @return array + * @psalm-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/lib/composer/nikic/php-parser/lib/PhpParser/Comment/Doc.php b/lib/composer/nikic/php-parser/lib/PhpParser/Comment/Doc.php new file mode 100644 index 0000000000000000000000000000000000000000..a9db6128f4fd1c2b616a1a781bd9ddf46b39294c --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Comment/Doc.php @@ -0,0 +1,7 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Comment; + +class Doc extends \PhpParser\Comment +{ +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/ConstExprEvaluationException.php b/lib/composer/nikic/php-parser/lib/PhpParser/ConstExprEvaluationException.php new file mode 100644 index 0000000000000000000000000000000000000000..49c92d5950660dd8649f4177282352992dd2291b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/ConstExprEvaluationException.php @@ -0,0 +1,6 @@ +<?php + +namespace PhpParser; + +class ConstExprEvaluationException extends \Exception +{} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php b/lib/composer/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php new file mode 100644 index 0000000000000000000000000000000000000000..7f02e6f245cfa03b41df65eb393c567e1ccb0b5d --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php @@ -0,0 +1,226 @@ +<?php + +namespace PhpParser; + +use PhpParser\Node\Expr; +use PhpParser\Node\Scalar; + +/** + * Evaluates constant expressions. + * + * This evaluator is able to evaluate all constant expressions (as defined by PHP), which can be + * evaluated without further context. If a subexpression is not of this type, a user-provided + * fallback evaluator is invoked. To support all constant expressions that are also supported by + * PHP (and not already handled by this class), the fallback evaluator must be able to handle the + * following node types: + * + * * All Scalar\MagicConst\* nodes. + * * Expr\ConstFetch nodes. Only null/false/true are already handled by this class. + * * Expr\ClassConstFetch nodes. + * + * The fallback evaluator should throw ConstExprEvaluationException for nodes it cannot evaluate. + * + * The evaluation is dependent on runtime configuration in two respects: Firstly, floating + * point to string conversions are affected by the precision ini setting. Secondly, they are also + * affected by the LC_NUMERIC locale. + */ +class ConstExprEvaluator +{ + private $fallbackEvaluator; + + /** + * Create a constant expression evaluator. + * + * The provided fallback evaluator is invoked whenever a subexpression cannot be evaluated. See + * class doc comment for more information. + * + * @param callable|null $fallbackEvaluator To call if subexpression cannot be evaluated + */ + public function __construct(callable $fallbackEvaluator = null) { + $this->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); + } + + private function evaluate(Expr $expr) { + if ($expr instanceof Scalar\LNumber + || $expr instanceof Scalar\DNumber + || $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 = []; + foreach ($expr->items as $item) { + if (null !== $item->key) { + $array[$this->evaluate($item->key)] = $this->evaluate($item->value); + } else { + $array[] = $this->evaluate($item->value); + } + } + return $array; + } + + 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); + } + + 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'); + } + + 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/lib/composer/nikic/php-parser/lib/PhpParser/Error.php b/lib/composer/nikic/php-parser/lib/PhpParser/Error.php new file mode 100644 index 0000000000000000000000000000000000000000..d1fb959d19f1543402a24c8f5146ae8f4de4d4b3 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Error.php @@ -0,0 +1,180 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +class Error extends \RuntimeException +{ + protected $rawMessage; + protected $attributes; + + /** + * Creates an Exception signifying a parse error. + * + * @param string $message Error message + * @param array|int $attributes Attributes of node/token where error occurred + * (or start line of error -- deprecated) + */ + public function __construct(string $message, $attributes = []) { + $this->rawMessage = $message; + if (is_array($attributes)) { + $this->attributes = $attributes; + } else { + $this->attributes = ['startLine' => $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) { + $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) { + $this->rawMessage = $message; + $this->updateMessage(); + } + + /** + * Sets the line the error starts in. + * + * @param int $line Error start line + */ + public function setStartLine(int $line) { + $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. + * + * @return bool + */ + 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 + * @return int + */ + 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 + * @return int + */ + 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() { + $this->message = $this->rawMessage; + + if (-1 === $this->getStartLine()) { + $this->message .= ' on unknown line'; + } else { + $this->message .= ' on line ' . $this->getStartLine(); + } + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/ErrorHandler.php b/lib/composer/nikic/php-parser/lib/PhpParser/ErrorHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..d620e7453606fe97da8dd13abba3abfff54252e8 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/ErrorHandler.php @@ -0,0 +1,13 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +interface ErrorHandler +{ + /** + * Handle an error generated during lexing, parsing or some other operation. + * + * @param Error $error The error that needs to be handled + */ + public function handleError(Error $error); +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/ErrorHandler/Collecting.php b/lib/composer/nikic/php-parser/lib/PhpParser/ErrorHandler/Collecting.php new file mode 100644 index 0000000000000000000000000000000000000000..784b61b14368d1ba55638fe801347959b9ac39ed --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/ErrorHandler/Collecting.php @@ -0,0 +1,46 @@ +<?php declare(strict_types=1); + +namespace PhpParser\ErrorHandler; + +use PhpParser\Error; +use PhpParser\ErrorHandler; + +/** + * Error handler that collects all errors into an array. + * + * This allows graceful handling of errors. + */ +class Collecting implements ErrorHandler +{ + /** @var Error[] Collected errors */ + private $errors = []; + + public function handleError(Error $error) { + $this->errors[] = $error; + } + + /** + * Get collected errors. + * + * @return Error[] + */ + public function getErrors() : array { + return $this->errors; + } + + /** + * Check whether there are any errors. + * + * @return bool + */ + public function hasErrors() : bool { + return !empty($this->errors); + } + + /** + * Reset/clear collected errors. + */ + public function clearErrors() { + $this->errors = []; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php b/lib/composer/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php new file mode 100644 index 0000000000000000000000000000000000000000..aeee989b1a6bfc9e68ee6f05cc016d7febdb1053 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php @@ -0,0 +1,18 @@ +<?php declare(strict_types=1); + +namespace PhpParser\ErrorHandler; + +use PhpParser\Error; +use PhpParser\ErrorHandler; + +/** + * Error handler that handles all errors by throwing them. + * + * This is the default strategy used by all components. + */ +class Throwing implements ErrorHandler +{ + public function handleError(Error $error) { + throw $error; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Internal/DiffElem.php b/lib/composer/nikic/php-parser/lib/PhpParser/Internal/DiffElem.php new file mode 100644 index 0000000000000000000000000000000000000000..a38b57ba93e648023310ab8d1d4b596b85be35e7 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Internal/DiffElem.php @@ -0,0 +1,27 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Internal; + +/** + * @internal + */ +class DiffElem +{ + const TYPE_KEEP = 0; + const TYPE_REMOVE = 1; + const TYPE_ADD = 2; + const TYPE_REPLACE = 3; + + /** @var int One of the TYPE_* constants */ + public $type; + /** @var mixed Is null for add operations */ + public $old; + /** @var mixed Is null for remove operations */ + public $new; + + public function __construct(int $type, $old, $new) { + $this->type = $type; + $this->old = $old; + $this->new = $new; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Internal/Differ.php b/lib/composer/nikic/php-parser/lib/PhpParser/Internal/Differ.php new file mode 100644 index 0000000000000000000000000000000000000000..7f218c74fe9ced921f40a3e4b666bcadf996f06e --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Internal/Differ.php @@ -0,0 +1,164 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Internal; + +/** + * Implements the Myers diff algorithm. + * + * Myers, Eugene W. "An O (ND) difference algorithm and its variations." + * Algorithmica 1.1 (1986): 251-266. + * + * @internal + */ +class Differ +{ + private $isEqual; + + /** + * Create differ over the given equality relation. + * + * @param callable $isEqual Equality relation with signature function($a, $b) : bool + */ + public function __construct(callable $isEqual) { + $this->isEqual = $isEqual; + } + + /** + * Calculate diff (edit script) from $old to $new. + * + * @param array $old Original array + * @param array $new New array + * + * @return DiffElem[] Diff (edit script) + */ + public function diff(array $old, array $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 array $old Original array + * @param array $new New array + * + * @return DiffElem[] Diff (edit script), including replace operations + */ + public function diffWithReplacements(array $old, array $new) { + return $this->coalesceReplacements($this->diff($old, $new)); + } + + private function calculateTrace(array $a, array $b) { + $n = \count($a); + $m = \count($b); + $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)($a[$x], $b[$y])) { + $x++; + $y++; + } + + $v[$k] = $x; + if ($x >= $n && $y >= $m) { + return [$trace, $x, $y]; + } + } + } + throw new \Exception('Should not happen'); + } + + private function extractDiff(array $trace, int $x, int $y, array $a, array $b) { + $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, $a[$x-1], $b[$y-1]); + $x--; + $y--; + } + + if ($d === 0) { + break; + } + + while ($x > $prevX) { + $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $a[$x-1], null); + $x--; + } + + while ($y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $b[$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) { + $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/lib/composer/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php b/lib/composer/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php new file mode 100644 index 0000000000000000000000000000000000000000..3eeac04a412dc13dc34527e59b0a203866e5507c --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php @@ -0,0 +1,61 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Internal; + +use PhpParser\Node; +use PhpParser\Node\Expr; + +/** + * This node is used internally by the format-preserving pretty printer to print anonymous classes. + * + * The normal anonymous class structure violates assumptions about the order of token offsets. + * Namely, the constructor arguments are part of the Expr\New_ node and follow the class node, even + * though they are actually interleaved with them. This special node type is used temporarily to + * restore a sane token offset order. + * + * @internal + */ +class PrintableNewAnonClassNode extends Expr +{ + /** @var Node\AttributeGroup[] PHP attribute groups */ + public $attrGroups; + /** @var Node\Arg[] Arguments */ + public $args; + /** @var null|Node\Name Name of extended class */ + public $extends; + /** @var Node\Name[] Names of implemented interfaces */ + public $implements; + /** @var Node\Stmt[] Statements */ + public $stmts; + + public function __construct( + array $attrGroups, array $args, Node\Name $extends = null, array $implements, + array $stmts, array $attributes + ) { + parent::__construct($attributes); + $this->attrGroups = $attrGroups; + $this->args = $args; + $this->extends = $extends; + $this->implements = $implements; + $this->stmts = $stmts; + } + + public static function fromNewNode(Expr\New_ $newNode) { + $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, $newNode->args, $class->extends, $class->implements, + $class->stmts, $newNode->getAttributes() + ); + } + + public function getType() : string { + return 'Expr_PrintableNewAnonClass'; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'args', 'extends', 'implements', 'stmts']; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php b/lib/composer/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php new file mode 100644 index 0000000000000000000000000000000000000000..39308ae6297132c1f22711976d38181e2d573e54 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php @@ -0,0 +1,279 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Internal; + +/** + * Provides operations on token streams, for use by pretty printer. + * + * @internal + */ +class TokenStream +{ + /** @var array Tokens (in token_get_all format) */ + private $tokens; + /** @var int[] Map from position to indentation */ + private $indentMap; + + /** + * Create token stream instance. + * + * @param array $tokens Tokens in token_get_all() format + */ + public function __construct(array $tokens) { + $this->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 + * + * @return bool + */ + 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 + * + * @return bool + */ + public function haveBraces(int $startPos, int $endPos) : bool { + return $this->haveTokenImmediatelyBefore($startPos, '{') + && $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--) { + $tokenType = $tokens[$pos][0]; + if ($tokenType === $expectedTokenType) { + return true; + } + if ($tokenType !== \T_WHITESPACE + && $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) { + 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 (; $pos < \count($tokens); $pos++) { + $tokenType = $tokens[$pos][0]; + if ($tokenType === $expectedTokenType) { + return true; + } + if ($tokenType !== \T_WHITESPACE + && $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) { + break; + } + } + return false; + } + + public function skipLeft(int $pos, $skipTokenType) { + $tokens = $this->tokens; + + $pos = $this->skipLeftWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + + if ($tokens[$pos][0] !== $skipTokenType) { + // Shouldn't happen. The skip token MUST be there + throw new \Exception('Encountered unexpected token'); + } + $pos--; + + return $this->skipLeftWhitespace($pos); + } + + public function skipRight(int $pos, $skipTokenType) { + $tokens = $this->tokens; + + $pos = $this->skipRightWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + + if ($tokens[$pos][0] !== $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) { + $tokens = $this->tokens; + for (; $pos >= 0; $pos--) { + $type = $tokens[$pos][0]; + if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) { + 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) { + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + $type = $tokens[$pos][0]; + if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) { + break; + } + } + return $pos; + } + + public function findRight(int $pos, $findTokenType) { + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + $type = $tokens[$pos][0]; + if ($type === $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) { + $tokens = $this->tokens; + for ($pos = $startPos; $pos < $endPos; $pos++) { + if ($tokens[$pos][0] === $tokenType) { + return true; + } + } + return false; + } + + public function haveBracesInRange(int $startPos, int $endPos) { + return $this->haveTokenInRange($startPos, $endPos, '{') + || $this->haveTokenInRange($startPos, $endPos, '}'); + } + + /** + * 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]; + if (\is_array($token)) { + $type = $token[0]; + $content = $token[1]; + if ($type === \T_CONSTANT_ENCAPSED_STRING || $type === \T_ENCAPSED_AND_WHITESPACE) { + $result .= $content; + } else { + // TODO Handle non-space indentation + if ($indent < 0) { + $result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $content); + } elseif ($indent > 0) { + $result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $content); + } else { + $result .= $content; + } + } + } else { + $result .= $token; + } + } + return $result; + } + + /** + * Precalculate the indentation at every token position. + * + * @return int[] Token position to indentation map + */ + private function calcIndentMap() { + $indentMap = []; + $indent = 0; + foreach ($this->tokens as $token) { + $indentMap[] = $indent; + + if ($token[0] === \T_WHITESPACE) { + $content = $token[1]; + $newlinePos = \strrpos($content, "\n"); + if (false !== $newlinePos) { + $indent = \strlen($content) - $newlinePos - 1; + } + } + } + + // Add a sentinel for one past end of the file + $indentMap[] = $indent; + + return $indentMap; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/JsonDecoder.php b/lib/composer/nikic/php-parser/lib/PhpParser/JsonDecoder.php new file mode 100644 index 0000000000000000000000000000000000000000..47d2003d4bae6b54d650f3e86f8a93ab6be60db0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/JsonDecoder.php @@ -0,0 +1,103 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +class JsonDecoder +{ + /** @var \ReflectionClass[] Node type to reflection class map */ + private $reflectionClassCache; + + 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); + } + + 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); + /** @var Node $node */ + $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 + ); + } + + 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]; + } + + 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/lib/composer/nikic/php-parser/lib/PhpParser/Lexer.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer.php new file mode 100644 index 0000000000000000000000000000000000000000..b0c21ead2687a54e8e2e2b9e63809f1893cdadff --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer.php @@ -0,0 +1,534 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +use PhpParser\Parser\Tokens; + +class Lexer +{ + protected $code; + protected $tokens; + protected $pos; + protected $line; + protected $filePos; + protected $prevCloseTagHasNewline; + + protected $tokenMap; + protected $dropTokens; + protected $identifierTokens; + + private $attributeStartLineUsed; + private $attributeEndLineUsed; + private $attributeStartTokenPosUsed; + private $attributeEndTokenPosUsed; + private $attributeStartFilePosUsed; + private $attributeEndFilePosUsed; + private $attributeCommentsUsed; + + /** + * Creates a Lexer. + * + * @param array $options Options array. Currently only the 'usedAttributes' option is supported, + * which is an array of attributes to add to the AST nodes. Possible + * attributes are: 'comments', 'startLine', 'endLine', 'startTokenPos', + * 'endTokenPos', 'startFilePos', 'endFilePos'. The option defaults to the + * first three. For more info see getNextToken() docs. + */ + public function __construct(array $options = []) { + // Create Map from internal tokens to PhpParser tokens. + $this->defineCompatibilityTokens(); + $this->tokenMap = $this->createTokenMap(); + $this->identifierTokens = $this->createIdentifierTokenMap(); + + // map of tokens to drop while lexing (the map is only used for isset lookup, + // that's why the value is simply set to 1; the value is never actually used.) + $this->dropTokens = array_fill_keys( + [\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], 1 + ); + + $defaultAttributes = ['comments', 'startLine', 'endLine']; + $usedAttributes = array_fill_keys($options['usedAttributes'] ?? $defaultAttributes, true); + + // Create individual boolean properties to make these checks faster. + $this->attributeStartLineUsed = isset($usedAttributes['startLine']); + $this->attributeEndLineUsed = isset($usedAttributes['endLine']); + $this->attributeStartTokenPosUsed = isset($usedAttributes['startTokenPos']); + $this->attributeEndTokenPosUsed = isset($usedAttributes['endTokenPos']); + $this->attributeStartFilePosUsed = isset($usedAttributes['startFilePos']); + $this->attributeEndFilePosUsed = isset($usedAttributes['endFilePos']); + $this->attributeCommentsUsed = isset($usedAttributes['comments']); + } + + /** + * Initializes the lexer for lexing the provided source code. + * + * This function does not throw if lexing errors occur. Instead, errors may be retrieved using + * the getErrors() method. + * + * @param string $code The source code to lex + * @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to + * ErrorHandler\Throwing + */ + public function startLexing(string $code, ErrorHandler $errorHandler = null) { + if (null === $errorHandler) { + $errorHandler = new ErrorHandler\Throwing(); + } + + $this->code = $code; // keep the code around for __halt_compiler() handling + $this->pos = -1; + $this->line = 1; + $this->filePos = 0; + + // If inline HTML occurs without preceding code, treat it as if it had a leading newline. + // This ensures proper composability, because having a newline is the "safe" assumption. + $this->prevCloseTagHasNewline = true; + + $scream = ini_set('xdebug.scream', '0'); + + error_clear_last(); + $this->tokens = @token_get_all($code); + $this->postprocessTokens($errorHandler); + + if (false !== $scream) { + ini_set('xdebug.scream', $scream); + } + } + + private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) { + $tokens = []; + for ($i = $start; $i < $end; $i++) { + $chr = $this->code[$i]; + 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) + ); + } + + $tokens[] = [\T_BAD_CHARACTER, $chr, $line]; + $errorHandler->handleError(new Error($errorMsg, [ + 'startLine' => $line, + 'endLine' => $line, + 'startFilePos' => $i, + 'endFilePos' => $i, + ])); + } + return $tokens; + } + + /** + * Check whether comment token is unterminated. + * + * @return bool + */ + private function isUnterminatedComment($token) : bool { + return ($token[0] === \T_COMMENT || $token[0] === \T_DOC_COMMENT) + && substr($token[1], 0, 2) === '/*' + && substr($token[1], -2) !== '*/'; + } + + protected function postprocessTokens(ErrorHandler $errorHandler) { + // PHP's error handling for token_get_all() is rather bad, so if we want detailed + // error information we need to compute it ourselves. Invalid character errors are + // detected by finding "gaps" in the token array. Unterminated comments are detected + // by checking if a trailing comment has a "*/" at the end. + // + // Additionally, we canonicalize to the PHP 8 comment format here, which does not include + // the trailing whitespace anymore. + // + // We also canonicalize to the PHP 8 T_NAME_* tokens. + + $filePos = 0; + $line = 1; + $numTokens = \count($this->tokens); + for ($i = 0; $i < $numTokens; $i++) { + $token = $this->tokens[$i]; + + // Since PHP 7.4 invalid characters are represented by a T_BAD_CHARACTER token. + // In this case we only need to emit an error. + if ($token[0] === \T_BAD_CHARACTER) { + $this->handleInvalidCharacterRange($filePos, $filePos + 1, $line, $errorHandler); + } + + if ($token[0] === \T_COMMENT && substr($token[1], 0, 2) !== '/*' + && preg_match('/(\r\n|\n|\r)$/D', $token[1], $matches)) { + $trailingNewline = $matches[0]; + $token[1] = substr($token[1], 0, -strlen($trailingNewline)); + $this->tokens[$i] = $token; + if (isset($this->tokens[$i + 1]) && $this->tokens[$i + 1][0] === \T_WHITESPACE) { + // Move trailing newline into following T_WHITESPACE token, if it already exists. + $this->tokens[$i + 1][1] = $trailingNewline . $this->tokens[$i + 1][1]; + $this->tokens[$i + 1][2]--; + } else { + // Otherwise, we need to create a new T_WHITESPACE token. + array_splice($this->tokens, $i + 1, 0, [ + [\T_WHITESPACE, $trailingNewline, $line], + ]); + $numTokens++; + } + } + + // Emulate PHP 8 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and T_STRING + // into a single token. + if (\is_array($token) + && ($token[0] === \T_NS_SEPARATOR || isset($this->identifierTokens[$token[0]]))) { + $lastWasSeparator = $token[0] === \T_NS_SEPARATOR; + $text = $token[1]; + for ($j = $i + 1; isset($this->tokens[$j]); $j++) { + if ($lastWasSeparator) { + if (!isset($this->identifierTokens[$this->tokens[$j][0]])) { + break; + } + $lastWasSeparator = false; + } else { + if ($this->tokens[$j][0] !== \T_NS_SEPARATOR) { + break; + } + $lastWasSeparator = true; + } + $text .= $this->tokens[$j][1]; + } + if ($lastWasSeparator) { + // Trailing separator is not part of the name. + $j--; + $text = substr($text, 0, -1); + } + if ($j > $i + 1) { + if ($token[0] === \T_NS_SEPARATOR) { + $type = \T_NAME_FULLY_QUALIFIED; + } else if ($token[0] === \T_NAMESPACE) { + $type = \T_NAME_RELATIVE; + } else { + $type = \T_NAME_QUALIFIED; + } + $token = [$type, $text, $line]; + array_splice($this->tokens, $i, $j - $i, [$token]); + $numTokens -= $j - $i - 1; + } + } + + $tokenValue = \is_string($token) ? $token : $token[1]; + $tokenLen = \strlen($tokenValue); + + if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) { + // Something is missing, must be an invalid character + $nextFilePos = strpos($this->code, $tokenValue, $filePos); + $badCharTokens = $this->handleInvalidCharacterRange( + $filePos, $nextFilePos, $line, $errorHandler); + $filePos = (int) $nextFilePos; + + array_splice($this->tokens, $i, 0, $badCharTokens); + $numTokens += \count($badCharTokens); + $i += \count($badCharTokens); + } + + $filePos += $tokenLen; + $line += substr_count($tokenValue, "\n"); + } + + if ($filePos !== \strlen($this->code)) { + if (substr($this->code, $filePos, 2) === '/*') { + // Unlike PHP, HHVM will drop unterminated comments entirely + $comment = substr($this->code, $filePos); + $errorHandler->handleError(new Error('Unterminated comment', [ + 'startLine' => $line, + 'endLine' => $line + substr_count($comment, "\n"), + 'startFilePos' => $filePos, + 'endFilePos' => $filePos + \strlen($comment), + ])); + + // Emulate the PHP behavior + $isDocComment = isset($comment[3]) && $comment[3] === '*'; + $this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line]; + } else { + // Invalid characters at the end of the input + $badCharTokens = $this->handleInvalidCharacterRange( + $filePos, \strlen($this->code), $line, $errorHandler); + $this->tokens = array_merge($this->tokens, $badCharTokens); + } + return; + } + + if (count($this->tokens) > 0) { + // Check for unterminated comment + $lastToken = $this->tokens[count($this->tokens) - 1]; + if ($this->isUnterminatedComment($lastToken)) { + $errorHandler->handleError(new Error('Unterminated comment', [ + 'startLine' => $line - substr_count($lastToken[1], "\n"), + 'endLine' => $line, + 'startFilePos' => $filePos - \strlen($lastToken[1]), + 'endFilePos' => $filePos, + ])); + } + } + } + + /** + * Fetches the next token. + * + * The available attributes are determined by the 'usedAttributes' option, which can + * be specified in the constructor. The following attributes are supported: + * + * * 'comments' => Array of PhpParser\Comment or PhpParser\Comment\Doc instances, + * representing all comments that occurred between the previous + * non-discarded token and the current one. + * * 'startLine' => Line in which the node starts. + * * 'endLine' => Line in which the node ends. + * * 'startTokenPos' => Offset into the token array of the first token in the node. + * * 'endTokenPos' => Offset into the token array of the last token in the node. + * * 'startFilePos' => Offset into the code string of the first character that is part of the node. + * * 'endFilePos' => Offset into the code string of the last character that is part of the node. + * + * @param mixed $value Variable to store token content in + * @param mixed $startAttributes Variable to store start attributes in + * @param mixed $endAttributes Variable to store end attributes in + * + * @return int Token id + */ + public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int { + $startAttributes = []; + $endAttributes = []; + + while (1) { + if (isset($this->tokens[++$this->pos])) { + $token = $this->tokens[$this->pos]; + } else { + // EOF token with ID 0 + $token = "\0"; + } + + if ($this->attributeStartLineUsed) { + $startAttributes['startLine'] = $this->line; + } + if ($this->attributeStartTokenPosUsed) { + $startAttributes['startTokenPos'] = $this->pos; + } + if ($this->attributeStartFilePosUsed) { + $startAttributes['startFilePos'] = $this->filePos; + } + + if (\is_string($token)) { + $value = $token; + if (isset($token[1])) { + // bug in token_get_all + $this->filePos += 2; + $id = ord('"'); + } else { + $this->filePos += 1; + $id = ord($token); + } + } elseif (!isset($this->dropTokens[$token[0]])) { + $value = $token[1]; + $id = $this->tokenMap[$token[0]]; + if (\T_CLOSE_TAG === $token[0]) { + $this->prevCloseTagHasNewline = false !== strpos($token[1], "\n"); + } elseif (\T_INLINE_HTML === $token[0]) { + $startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline; + } + + $this->line += substr_count($value, "\n"); + $this->filePos += \strlen($value); + } else { + $origLine = $this->line; + $origFilePos = $this->filePos; + $this->line += substr_count($token[1], "\n"); + $this->filePos += \strlen($token[1]); + + if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) { + if ($this->attributeCommentsUsed) { + $comment = \T_DOC_COMMENT === $token[0] + ? new Comment\Doc($token[1], + $origLine, $origFilePos, $this->pos, + $this->line, $this->filePos - 1, $this->pos) + : new Comment($token[1], + $origLine, $origFilePos, $this->pos, + $this->line, $this->filePos - 1, $this->pos); + $startAttributes['comments'][] = $comment; + } + } + continue; + } + + if ($this->attributeEndLineUsed) { + $endAttributes['endLine'] = $this->line; + } + if ($this->attributeEndTokenPosUsed) { + $endAttributes['endTokenPos'] = $this->pos; + } + if ($this->attributeEndFilePosUsed) { + $endAttributes['endFilePos'] = $this->filePos - 1; + } + + return $id; + } + + throw new \RuntimeException('Reached end of lexer loop'); + } + + /** + * Returns the token array for current code. + * + * The token array is in the same format as provided by the + * token_get_all() function and does not discard tokens (i.e. + * whitespace and comments are included). The token position + * attributes are against this token array. + * + * @return array Array of tokens in token_get_all() format + */ + public function getTokens() : array { + return $this->tokens; + } + + /** + * Handles __halt_compiler() by returning the text after it. + * + * @return string Remaining text + */ + public function handleHaltCompiler() : string { + // text after T_HALT_COMPILER, still including (); + $textAfter = substr($this->code, $this->filePos); + + // ensure that it is followed by (); + // this simplifies the situation, by not allowing any comments + // in between of the tokens. + if (!preg_match('~^\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) { + throw new Error('__HALT_COMPILER must be followed by "();"'); + } + + // prevent the lexer from returning any further tokens + $this->pos = count($this->tokens); + + // return with (); removed + return substr($textAfter, strlen($matches[0])); + } + + private function defineCompatibilityTokens() { + static $compatTokensDefined = false; + if ($compatTokensDefined) { + return; + } + + $compatTokens = [ + // PHP 7.4 + 'T_BAD_CHARACTER', + 'T_FN', + 'T_COALESCE_EQUAL', + // PHP 8.0 + 'T_NAME_QUALIFIED', + 'T_NAME_FULLY_QUALIFIED', + 'T_NAME_RELATIVE', + 'T_MATCH', + 'T_NULLSAFE_OBJECT_OPERATOR', + 'T_ATTRIBUTE', + ]; + + // PHP-Parser might be used together with another library that also emulates some or all + // of these tokens. Perform a sanity-check that all already defined tokens have been + // assigned a unique ID. + $usedTokenIds = []; + foreach ($compatTokens as $token) { + if (\defined($token)) { + $tokenId = \constant($token); + $clashingToken = $usedTokenIds[$tokenId] ?? null; + if ($clashingToken !== null) { + throw new \Error(sprintf( + 'Token %s has same ID as token %s, ' . + 'you may be using a library with broken token emulation', + $token, $clashingToken + )); + } + $usedTokenIds[$tokenId] = $token; + } + } + + // Now define any tokens that have not yet been emulated. Try to assign IDs from -1 + // downwards, but skip any IDs that may already be in use. + $newTokenId = -1; + foreach ($compatTokens as $token) { + if (!\defined($token)) { + while (isset($usedTokenIds[$newTokenId])) { + $newTokenId--; + } + \define($token, $newTokenId); + $newTokenId--; + } + } + + $compatTokensDefined = true; + } + + /** + * 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 = []; + + // 256 is the minimum possible token number, as everything below + // it is an ASCII value + for ($i = 256; $i < 1000; ++$i) { + if (\T_DOUBLE_COLON === $i) { + // T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM + $tokenMap[$i] = Tokens::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] = Tokens::T_ECHO; + } elseif(\T_CLOSE_TAG === $i) { + // T_CLOSE_TAG is equivalent to ';' + $tokenMap[$i] = ord(';'); + } elseif ('UNKNOWN' !== $name = token_name($i)) { + if ('T_HASHBANG' === $name) { + // HHVM uses a special token for #! hashbang lines + $tokenMap[$i] = Tokens::T_INLINE_HTML; + } elseif (defined($name = Tokens::class . '::' . $name)) { + // Other tokens can be mapped directly + $tokenMap[$i] = constant($name); + } + } + } + + // HHVM uses a special token for numbers that overflow to double + if (defined('T_ONUMBER')) { + $tokenMap[\T_ONUMBER] = Tokens::T_DNUMBER; + } + // HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant + if (defined('T_COMPILER_HALT_OFFSET')) { + $tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING; + } + + // Assign tokens for which we define compatibility constants, as token_name() does not know them. + $tokenMap[\T_FN] = Tokens::T_FN; + $tokenMap[\T_COALESCE_EQUAL] = Tokens::T_COALESCE_EQUAL; + $tokenMap[\T_NAME_QUALIFIED] = Tokens::T_NAME_QUALIFIED; + $tokenMap[\T_NAME_FULLY_QUALIFIED] = Tokens::T_NAME_FULLY_QUALIFIED; + $tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE; + $tokenMap[\T_MATCH] = Tokens::T_MATCH; + $tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR; + $tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE; + + return $tokenMap; + } + + private function createIdentifierTokenMap(): array { + // Based on semi_reserved production. + return array_fill_keys([ + \T_STRING, + \T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, + \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/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php new file mode 100644 index 0000000000000000000000000000000000000000..cf1f8e5a954b93d370f640ea8cf682f16fa57ffb --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php @@ -0,0 +1,242 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Lexer; + +use PhpParser\Error; +use PhpParser\ErrorHandler; +use PhpParser\Lexer; +use PhpParser\Lexer\TokenEmulator\AttributeEmulator; +use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator; +use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator; +use PhpParser\Lexer\TokenEmulator\FnTokenEmulator; +use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator; +use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator; +use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator; +use PhpParser\Lexer\TokenEmulator\ReverseEmulator; +use PhpParser\Lexer\TokenEmulator\TokenEmulator; +use PhpParser\Parser\Tokens; + +class Emulative extends Lexer +{ + const PHP_7_3 = '7.3dev'; + const PHP_7_4 = '7.4dev'; + const PHP_8_0 = '8.0dev'; + + /** @var mixed[] Patches used to reverse changes introduced in the code */ + private $patches = []; + + /** @var TokenEmulator[] */ + private $emulators = []; + + /** @var string */ + private $targetPhpVersion; + + /** + * @param mixed[] $options Lexer options. In addition to the usual options, + * accepts a 'phpVersion' string that specifies the + * version to emulated. Defaults to newest supported. + */ + public function __construct(array $options = []) + { + $this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_0; + unset($options['phpVersion']); + + parent::__construct($options); + + $emulators = [ + new FlexibleDocStringEmulator(), + new FnTokenEmulator(), + new MatchTokenEmulator(), + new CoaleseEqualTokenEmulator(), + new NumericLiteralSeparatorEmulator(), + new NullsafeTokenEmulator(), + new AttributeEmulator(), + ]; + + // 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; + } else if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = new ReverseEmulator($emulator); + } + } + } + + public function startLexing(string $code, ErrorHandler $errorHandler = null) { + $emulators = array_filter($this->emulators, function($emulator) use($code) { + return $emulator->isEmulationNeeded($code); + }); + + if (empty($emulators)) { + // Nothing to emulate, yay + parent::startLexing($code, $errorHandler); + return; + } + + $this->patches = []; + foreach ($emulators as $emulator) { + $code = $emulator->preprocessCode($code, $this->patches); + } + + $collector = new ErrorHandler\Collecting(); + parent::startLexing($code, $collector); + $this->sortPatches(); + $this->fixupTokens(); + + $errors = $collector->getErrors(); + if (!empty($errors)) { + $this->fixupErrors($errors); + foreach ($errors as $error) { + $errorHandler->handleError($error); + } + } + + foreach ($emulators as $emulator) { + $this->tokens = $emulator->emulate($code, $this->tokens); + } + } + + private function isForwardEmulationNeeded(string $emulatorPhpVersion): bool { + return version_compare(\PHP_VERSION, $emulatorPhpVersion, '<') + && version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>='); + } + + private function isReverseEmulationNeeded(string $emulatorPhpVersion): bool { + return version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=') + && version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<'); + } + + private function sortPatches() + { + // 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]; + }); + } + + private function fixupTokens() + { + if (\count($this->patches) === 0) { + return; + } + + // 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 + $pos = 0; + for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) { + $token = $this->tokens[$i]; + if (\is_string($token)) { + if ($patchPos === $pos) { + // Only support replacement for string tokens. + assert($patchType === 'replace'); + $this->tokens[$i] = $patchText; + + // Fetch the next patch + $patchIdx++; + if ($patchIdx >= \count($this->patches)) { + // No more patches, we're done + return; + } + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + } + + $pos += \strlen($token); + continue; + } + + $len = \strlen($token[1]); + $posDelta = 0; + while ($patchPos >= $pos && $patchPos < $pos + $len) { + $patchTextLen = \strlen($patchText); + if ($patchType === 'remove') { + if ($patchPos === $pos && $patchTextLen === $len) { + // Remove token entirely + array_splice($this->tokens, $i, 1, []); + $i--; + $c--; + } else { + // Remove from token string + $this->tokens[$i][1] = substr_replace( + $token[1], '', $patchPos - $pos + $posDelta, $patchTextLen + ); + $posDelta -= $patchTextLen; + } + } elseif ($patchType === 'add') { + // Insert into the token string + $this->tokens[$i][1] = substr_replace( + $token[1], $patchText, $patchPos - $pos + $posDelta, 0 + ); + $posDelta += $patchTextLen; + } else if ($patchType === 'replace') { + // Replace inside the token string + $this->tokens[$i][1] = substr_replace( + $token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen + ); + } else { + assert(false); + } + + // Fetch the next patch + $patchIdx++; + if ($patchIdx >= \count($this->patches)) { + // No more patches, we're done + return; + } + + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + + // Multiple patches may apply to the same token. Reload the current one to check + // If the new patch applies + $token = $this->tokens[$i]; + } + + $pos += $len; + } + + // A patch did not apply + assert(false); + } + + /** + * Fixup line and position information in errors. + * + * @param Error[] $errors + */ + private function fixupErrors(array $errors) { + 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"); + } else if ($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/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php new file mode 100644 index 0000000000000000000000000000000000000000..6776a51975fbd202e7aa75aab92701383568ce60 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php @@ -0,0 +1,56 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Lexer\TokenEmulator; + +use PhpParser\Lexer\Emulative; + +final class AttributeEmulator extends TokenEmulator +{ + public function getPhpVersion(): string + { + return Emulative::PHP_8_0; + } + + public function isEmulationNeeded(string $code) : bool + { + return strpos($code, '#[') !== 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. + $line = 1; + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + if ($tokens[$i] === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1] === '[') { + array_splice($tokens, $i, 2, [ + [\T_ATTRIBUTE, '#[', $line] + ]); + $c--; + continue; + } + if (\is_array($tokens[$i])) { + $line += substr_count($tokens[$i][1], "\n"); + } + } + + 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/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php new file mode 100644 index 0000000000000000000000000000000000000000..d91da92143c514a8b58b9cd2cf5ff01453bc485b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php @@ -0,0 +1,47 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Lexer\TokenEmulator; + +use PhpParser\Lexer\Emulative; + +final class CoaleseEqualTokenEmulator extends TokenEmulator +{ + public function getPhpVersion(): string + { + return Emulative::PHP_7_4; + } + + public function isEmulationNeeded(string $code): bool + { + return strpos($code, '??=') !== 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 + $line = 1; + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + if (isset($tokens[$i + 1])) { + if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') { + array_splice($tokens, $i, 2, [ + [\T_COALESCE_EQUAL, '??=', $line] + ]); + $c--; + continue; + } + } + if (\is_array($tokens[$i])) { + $line += substr_count($tokens[$i][1], "\n"); + } + } + + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array + { + // ??= was not valid code previously, don't bother. + return $tokens; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php new file mode 100644 index 0000000000000000000000000000000000000000..c15d6271fcb51610988acf5851cea6a8cd67170e --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php @@ -0,0 +1,76 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Lexer\TokenEmulator; + +use PhpParser\Lexer\Emulative; + +final class FlexibleDocStringEmulator extends TokenEmulator +{ + const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX' +/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n +(?:.*\r?\n)*? +(?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x +REGEX; + + public function getPhpVersion(): string + { + return Emulative::PHP_7_3; + } + + public function isEmulationNeeded(string $code) : bool + { + return strpos($code, '<<<') !== false; + } + + public function emulate(string $code, array $tokens): array + { + // Handled by preprocessing + fixup. + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array + { + // Not supported. + return $tokens; + } + + public function preprocessCode(string $code, array &$patches): string { + if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) { + // No heredoc/nowdoc found + return $code; + } + + // Keep track of how much we need to adjust string offsets due to the modifications we + // already made + $posDelta = 0; + foreach ($matches as $match) { + $indentation = $match['indentation'][0]; + $indentationStart = $match['indentation'][1]; + + $separator = $match['separator'][0]; + $separatorStart = $match['separator'][1]; + + if ($indentation === '' && $separator !== '') { + // Ordinary heredoc/nowdoc + continue; + } + + if ($indentation !== '') { + // Remove indentation + $indentationLen = strlen($indentation); + $code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen); + $patches[] = [$indentationStart + $posDelta, 'add', $indentation]; + $posDelta -= $indentationLen; + } + + if ($separator === '') { + // Insert newline as separator + $code = substr_replace($code, "\n", $separatorStart + $posDelta, 0); + $patches[] = [$separatorStart + $posDelta, 'remove', "\n"]; + $posDelta += 1; + } + } + + return $code; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php new file mode 100644 index 0000000000000000000000000000000000000000..eb7e49634ac6221891d95874a32e03b885a23d58 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php @@ -0,0 +1,23 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Lexer\TokenEmulator; + +use PhpParser\Lexer\Emulative; + +final class FnTokenEmulator extends KeywordEmulator +{ + public function getPhpVersion(): string + { + return Emulative::PHP_7_4; + } + + public function getKeywordString(): string + { + return 'fn'; + } + + public function getKeywordToken(): int + { + return \T_FN; + } +} \ No newline at end of file diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php new file mode 100644 index 0000000000000000000000000000000000000000..e7c0512bd716273a6a425c4a836ae21e83aebf19 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php @@ -0,0 +1,60 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Lexer\TokenEmulator; + +abstract class KeywordEmulator extends TokenEmulator +{ + abstract function getKeywordString(): string; + abstract function getKeywordToken(): int; + + public function isEmulationNeeded(string $code): bool + { + return strpos(strtolower($code), $this->getKeywordString()) !== false; + } + + public function emulate(string $code, array $tokens): array + { + $keywordString = $this->getKeywordString(); + foreach ($tokens as $i => $token) { + if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString) { + $previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $i); + if ($previousNonSpaceToken !== null && $previousNonSpaceToken[0] === \T_OBJECT_OPERATOR) { + continue; + } + + $tokens[$i][0] = $this->getKeywordToken(); + } + } + + return $tokens; + } + + /** + * @param mixed[] $tokens + * @return mixed[]|null + */ + private function getPreviousNonSpaceToken(array $tokens, int $start) + { + for ($i = $start - 1; $i >= 0; --$i) { + if ($tokens[$i][0] === T_WHITESPACE) { + continue; + } + + return $tokens[$i]; + } + + return null; + } + + public function reverseEmulate(string $code, array $tokens): array + { + $keywordToken = $this->getKeywordToken(); + foreach ($tokens as $i => $token) { + if ($token[0] === $keywordToken) { + $tokens[$i][0] = \T_STRING; + } + } + + return $tokens; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php new file mode 100644 index 0000000000000000000000000000000000000000..902a46dfcb76bdf11e6c187d6d6dd5046fddce24 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php @@ -0,0 +1,23 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Lexer\TokenEmulator; + +use PhpParser\Lexer\Emulative; + +final class MatchTokenEmulator extends KeywordEmulator +{ + public function getPhpVersion(): string + { + return Emulative::PHP_8_0; + } + + public function getKeywordString(): string + { + return 'match'; + } + + public function getKeywordToken(): int + { + return \T_MATCH; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php new file mode 100644 index 0000000000000000000000000000000000000000..1a29c676e450c4eec02065295ef195c83f623878 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php @@ -0,0 +1,67 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Lexer\TokenEmulator; + +use PhpParser\Lexer\Emulative; + +final class NullsafeTokenEmulator extends TokenEmulator +{ + public function getPhpVersion(): string + { + return Emulative::PHP_8_0; + } + + public function isEmulationNeeded(string $code): bool + { + return strpos($code, '?->') !== 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 + $line = 1; + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) { + array_splice($tokens, $i, 2, [ + [\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line] + ]); + $c--; + continue; + } + + // Handle ?-> inside encapsed string. + if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1]) + && $tokens[$i - 1][0] === \T_VARIABLE + && preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $tokens[$i][1], $matches) + ) { + $replacement = [ + [\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line], + [\T_STRING, $matches[1], $line], + ]; + if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) { + $replacement[] = [ + \T_ENCAPSED_AND_WHITESPACE, + \substr($tokens[$i][1], \strlen($matches[0])), + $line + ]; + } + array_splice($tokens, $i, 1, $replacement); + $c += \count($replacement) - 1; + continue; + } + + if (\is_array($tokens[$i])) { + $line += substr_count($tokens[$i][1], "\n"); + } + } + + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array + { + // ?-> was not valid code previously, don't bother. + return $tokens; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php new file mode 100644 index 0000000000000000000000000000000000000000..cdf793e46e81e865d45317fc0ef23ee882091085 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php @@ -0,0 +1,105 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Lexer\TokenEmulator; + +use PhpParser\Lexer\Emulative; + +final class NumericLiteralSeparatorEmulator extends TokenEmulator +{ + const BIN = '(?:0b[01]+(?:_[01]+)*)'; + const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)'; + const DEC = '(?:[0-9]+(?:_[0-9]+)*)'; + const SIMPLE_FLOAT = '(?:' . self::DEC . '\.' . self::DEC . '?|\.' . self::DEC . ')'; + const EXP = '(?:e[+-]?' . self::DEC . ')'; + const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')'; + const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA'; + + public function getPhpVersion(): string + { + return Emulative::PHP_7_4; + } + + public function isEmulationNeeded(string $code) : bool + { + return preg_match('~[0-9]_[0-9]~', $code) + || preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code); + } + + 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 + $codeOffset = 0; + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + $token = $tokens[$i]; + $tokenLen = \strlen(\is_array($token) ? $token[1] : $token); + + if ($token[0] !== T_LNUMBER && $token[0] !== T_DNUMBER) { + $codeOffset += $tokenLen; + continue; + } + + $res = preg_match(self::NUMBER, $code, $matches, 0, $codeOffset); + assert($res, "No number at number token position"); + + $match = $matches[0]; + $matchLen = \strlen($match); + if ($matchLen === $tokenLen) { + // Original token already holds the full number. + $codeOffset += $tokenLen; + continue; + } + + $tokenKind = $this->resolveIntegerOrFloatToken($match); + $newTokens = [[$tokenKind, $match, $token[2]]]; + + $numTokens = 1; + $len = $tokenLen; + while ($matchLen > $len) { + $nextToken = $tokens[$i + $numTokens]; + $nextTokenText = \is_array($nextToken) ? $nextToken[1] : $nextToken; + $nextTokenLen = \strlen($nextTokenText); + + $numTokens++; + if ($matchLen < $len + $nextTokenLen) { + // Split trailing characters into a partial token. + assert(is_array($nextToken), "Partial token should be an array token"); + $partialText = substr($nextTokenText, $matchLen - $len); + $newTokens[] = [$nextToken[0], $partialText, $nextToken[2]]; + break; + } + + $len += $nextTokenLen; + } + + array_splice($tokens, $i, $numTokens, $newTokens); + $c -= $numTokens - \count($newTokens); + $codeOffset += $matchLen; + } + + return $tokens; + } + + private function resolveIntegerOrFloatToken(string $str): int + { + $str = str_replace('_', '', $str); + + if (stripos($str, '0b') === 0) { + $num = bindec($str); + } elseif (stripos($str, '0x') === 0) { + $num = hexdec($str); + } elseif (stripos($str, '0') === 0 && ctype_digit($str)) { + $num = octdec($str); + } else { + $num = +$str; + } + + return is_float($num) ? T_DNUMBER : T_LNUMBER; + } + + public function reverseEmulate(string $code, array $tokens): array + { + // Numeric separators were not legal code previously, don't bother. + return $tokens; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php new file mode 100644 index 0000000000000000000000000000000000000000..90093f66b21c4e9efbc7c10c588958b5658ffff1 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php @@ -0,0 +1,36 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Lexer\TokenEmulator; + +/** + * Reverses emulation direction of the inner emulator. + */ +final class ReverseEmulator extends TokenEmulator +{ + /** @var TokenEmulator Inner emulator */ + private $emulator; + + public function __construct(TokenEmulator $emulator) { + $this->emulator = $emulator; + } + + public function getPhpVersion(): string { + 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; + } +} \ No newline at end of file diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php new file mode 100644 index 0000000000000000000000000000000000000000..a020bc0ff49f92e160a4a53944249776e5b71174 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php @@ -0,0 +1,25 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Lexer\TokenEmulator; + +/** @internal */ +abstract class TokenEmulator +{ + abstract public function getPhpVersion(): string; + + abstract public function isEmulationNeeded(string $code): bool; + + /** + * @return array Modified Tokens + */ + abstract public function emulate(string $code, array $tokens): array; + + /** + * @return array Modified Tokens + */ + abstract public function reverseEmulate(string $code, array $tokens): array; + + public function preprocessCode(string $code, array &$patches): string { + return $code; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/NameContext.php b/lib/composer/nikic/php-parser/lib/PhpParser/NameContext.php new file mode 100644 index 0000000000000000000000000000000000000000..777a4afdeebfe1cee4d605607b49422b76cfbd01 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NameContext.php @@ -0,0 +1,285 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +use PhpParser\Node\Name; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Stmt; + +class NameContext +{ + /** @var null|Name Current namespace */ + protected $namespace; + + /** @var Name[][] Map of format [aliasType => [aliasName => originalName]] */ + protected $aliases = []; + + /** @var Name[][] Same as $aliases but preserving original case */ + protected $origAliases = []; + + /** @var ErrorHandler Error handler */ + protected $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) { + $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 int $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 = []) { + // 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() { + return $this->namespace; + } + + /** + * Get resolved name. + * + * @param Name $name Name to resolve + * @param int $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) { + // 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 int $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 int $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, $type) { + $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) { + 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) { + $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/lib/composer/nikic/php-parser/lib/PhpParser/Node.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node.php new file mode 100644 index 0000000000000000000000000000000000000000..befb256504294d7c1d2dc4263bb429e46d903d73 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node.php @@ -0,0 +1,151 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +interface Node +{ + /** + * Gets the type of the node. + * + * @return string Type of the node + */ + public function getType() : string; + + /** + * Gets the names of the sub nodes. + * + * @return array Names of sub nodes + */ + public function getSubNodeNames() : array; + + /** + * Gets line the node started in (alias of getStartLine). + * + * @return int Start line (or -1 if not available) + */ + public function getLine() : int; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * Gets all comments directly preceding this node. + * + * The comments are also available through the "comments" attribute. + * + * @return Comment[] + */ + public function getComments() : array; + + /** + * Gets the doc comment of the node. + * + * @return null|Comment\Doc Doc comment object or null + */ + public function getDocComment(); + + /** + * 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); + + /** + * Sets an attribute on a node. + * + * @param string $key + * @param mixed $value + */ + public function setAttribute(string $key, $value); + + /** + * Returns whether an attribute exists. + * + * @param string $key + * + * @return bool + */ + public function hasAttribute(string $key) : bool; + + /** + * Returns the value of an attribute. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function getAttribute(string $key, $default = null); + + /** + * Returns all the attributes of this node. + * + * @return array + */ + public function getAttributes() : array; + + /** + * Replaces all the attributes of this node. + * + * @param array $attributes + */ + public function setAttributes(array $attributes); +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Arg.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Arg.php new file mode 100644 index 0000000000000000000000000000000000000000..b25b0904a229c2c85d9ad27d43442b1345eb7113 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Arg.php @@ -0,0 +1,45 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\NodeAbstract; + +class Arg extends NodeAbstract +{ + /** @var Identifier|null Parameter name (for named parameters) */ + public $name; + /** @var Expr Value to pass */ + public $value; + /** @var bool Whether to pass by ref */ + public $byRef; + /** @var bool Whether to unpack the argument */ + public $unpack; + + /** + * Constructs a function call argument node. + * + * @param Expr $value Value to pass + * @param bool $byRef Whether to pass by ref + * @param bool $unpack Whether to unpack the argument + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Attribute.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Attribute.php new file mode 100644 index 0000000000000000000000000000000000000000..c96f66e5147ef89d7f53c4b952d909bcd4d4ed97 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Attribute.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\Node; +use PhpParser\NodeAbstract; + +class Attribute extends NodeAbstract +{ + /** @var Name Attribute name */ + public $name; + + /** @var Arg[] Attribute arguments */ + public $args; + + /** + * @param Node\Name $name Attribute name + * @param Arg[] $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php new file mode 100644 index 0000000000000000000000000000000000000000..613bfc41343cb718bfc200b05afc0d98cf457267 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php @@ -0,0 +1,29 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\Node; +use PhpParser\NodeAbstract; + +class AttributeGroup extends NodeAbstract +{ + /** @var Attribute[] Attributes */ + public $attrs; + + /** + * @param Attribute[] $attrs PHP attributes + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Const_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Const_.php new file mode 100644 index 0000000000000000000000000000000000000000..789a426552d45ba3f1714a005b82ea4c68fc562b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Const_.php @@ -0,0 +1,37 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\NodeAbstract; + +/** + * @property Name $namespacedName Namespaced name (for global constants, if using NameResolver) + */ +class Const_ extends NodeAbstract +{ + /** @var Identifier Name */ + public $name; + /** @var Expr Value */ + public $value; + + /** + * Constructs a const node for use in class const and const statements. + * + * @param string|Identifier $name Name + * @param Expr $value Value + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr.php new file mode 100644 index 0000000000000000000000000000000000000000..6cf4df2233039ffa47035cab7bc69fda7d145272 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr.php @@ -0,0 +1,9 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\NodeAbstract; + +abstract class Expr extends NodeAbstract +{ +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php new file mode 100644 index 0000000000000000000000000000000000000000..71694478e946b9fe9729574cb1c67e0d7fe208fd --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class ArrayDimFetch extends Expr +{ + /** @var Expr Variable */ + public $var; + /** @var null|Expr Array index / dim */ + public $dim; + + /** + * Constructs an array index fetch node. + * + * @param Expr $var Variable + * @param null|Expr $dim Array index / dim + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php new file mode 100644 index 0000000000000000000000000000000000000000..1b078f82183200acde9c7374ae8acad2e3d0d529 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php @@ -0,0 +1,41 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class ArrayItem extends Expr +{ + /** @var null|Expr Key */ + public $key; + /** @var Expr Value */ + public $value; + /** @var bool Whether to assign by reference */ + public $byRef; + /** @var bool Whether to unpack the argument */ + public $unpack; + + /** + * Constructs an array item node. + * + * @param Expr $value Value + * @param null|Expr $key Key + * @param bool $byRef Whether to assign by reference + * @param array $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 'Expr_ArrayItem'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php new file mode 100644 index 0000000000000000000000000000000000000000..e6eaa2834db65fee7d42cab31b19219a2d54cd41 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Array_ extends Expr +{ + // For use in "kind" attribute + const KIND_LONG = 1; // array() syntax + const KIND_SHORT = 2; // [] syntax + + /** @var (ArrayItem|null)[] Items */ + public $items; + + /** + * Constructs an array node. + * + * @param (ArrayItem|null)[] $items Items of the array + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php new file mode 100644 index 0000000000000000000000000000000000000000..d293f0ae41802abf0434de0ac06790eac39e8909 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php @@ -0,0 +1,79 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\FunctionLike; + +class ArrowFunction extends Expr implements FunctionLike +{ + /** @var bool */ + public $static; + + /** @var bool */ + public $byRef; + + /** @var Node\Param[] */ + public $params = []; + + /** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType */ + public $returnType; + + /** @var Expr */ + public $expr; + /** @var Node\AttributeGroup[] */ + public $attrGroups; + + /** + * @param array $subNodes Array of the following optional subnodes: + * 'static' => false : Whether the closure is static + * 'byRef' => false : Whether to return by reference + * 'params' => array() : Parameters + * 'returnType' => null : Return type + * 'expr' => Expr : Expression body + * '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'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $this->expr = $subNodes['expr'] ?? null; + $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php new file mode 100644 index 0000000000000000000000000000000000000000..cf9e6e82b465aea897da13819cc6a337a0fee192 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Assign extends Expr +{ + /** @var Expr Variable */ + public $var; + /** @var Expr Expression */ + public $expr; + + /** + * Constructs an assignment node. + * + * @param Expr $var Variable + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php new file mode 100644 index 0000000000000000000000000000000000000000..bce8604f14d07dbcf8a97162006961aa9feba981 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +abstract class AssignOp extends Expr +{ + /** @var Expr Variable */ + public $var; + /** @var Expr Expression */ + public $expr; + + /** + * Constructs a compound assignment operation node. + * + * @param Expr $var Variable + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php new file mode 100644 index 0000000000000000000000000000000000000000..420284cdc1ba4c70fce21df388b2a149ec448ab0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class BitwiseAnd extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_BitwiseAnd'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php new file mode 100644 index 0000000000000000000000000000000000000000..481ad3bbceb316ffc2e120f050a66e48d0d756ec --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class BitwiseOr extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_BitwiseOr'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php new file mode 100644 index 0000000000000000000000000000000000000000..f41d4c8e73f5033e762669c5532e33735bb7e3ed --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class BitwiseXor extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_BitwiseXor'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Coalesce.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Coalesce.php new file mode 100644 index 0000000000000000000000000000000000000000..c0e9b316cf08ecdf6e688891a20c3b6fd949927b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Coalesce.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class Coalesce extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_Coalesce'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.php new file mode 100644 index 0000000000000000000000000000000000000000..ac1682078517252e466982da2eb935b59151c1ba --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class Concat extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_Concat'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.php new file mode 100644 index 0000000000000000000000000000000000000000..f1fcfc09adc3360a22c8e385aee1c24f5f9d26e2 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class Div extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_Div'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.php new file mode 100644 index 0000000000000000000000000000000000000000..82ef4517b7830d50e4d4e1da6fe31dcd4587f7ac --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class Minus extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_Minus'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.php new file mode 100644 index 0000000000000000000000000000000000000000..be3b4a0adbd390d6c0b21f0539137b80782523a0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class Mod extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_Mod'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.php new file mode 100644 index 0000000000000000000000000000000000000000..5c196c3bcb5b2e507b78514cf62318885f18003a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class Mul extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_Mul'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.php new file mode 100644 index 0000000000000000000000000000000000000000..dd101c61cb95b09b176cb0484715642d41c33043 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class Plus extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_Plus'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.php new file mode 100644 index 0000000000000000000000000000000000000000..5e1307d1dae824e49652c9e063bc6ea485db4d28 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class Pow extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_Pow'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php new file mode 100644 index 0000000000000000000000000000000000000000..b8f88269b682f465abc08cb01ba15cf87b470d2f --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class ShiftLeft extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_ShiftLeft'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php new file mode 100644 index 0000000000000000000000000000000000000000..e0cc67b7ff27b4f915c4e69fdb1c17130e352928 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\AssignOp; + +use PhpParser\Node\Expr\AssignOp; + +class ShiftRight extends AssignOp +{ + public function getType() : string { + return 'Expr_AssignOp_ShiftRight'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php new file mode 100644 index 0000000000000000000000000000000000000000..de3c644c3c129b793fbc5feb86ee24e3026e7aac --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class AssignRef extends Expr +{ + /** @var Expr Variable reference is assigned to */ + public $var; + /** @var Expr Variable which is referenced */ + public $expr; + + /** + * Constructs an assignment node. + * + * @param Expr $var Variable + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php new file mode 100644 index 0000000000000000000000000000000000000000..d9c582b0d25cc0c4f0f00fdc9feeabb4255a4539 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php @@ -0,0 +1,40 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +abstract class BinaryOp extends Expr +{ + /** @var Expr The left hand side expression */ + public $left; + /** @var Expr The right hand side expression */ + public $right; + + /** + * Constructs a binary operator node. + * + * @param Expr $left The left hand side expression + * @param Expr $right The right hand side expression + * @param array $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. + * + * @return string + */ + abstract public function getOperatorSigil() : string; +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php new file mode 100644 index 0000000000000000000000000000000000000000..d907393bfa70569049e973e1535d70e65c9b690e --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class BitwiseAnd extends BinaryOp +{ + public function getOperatorSigil() : string { + return '&'; + } + + public function getType() : string { + return 'Expr_BinaryOp_BitwiseAnd'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php new file mode 100644 index 0000000000000000000000000000000000000000..d92069f321caa2f94ce8157dd4f7442f1e121a4f --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class BitwiseOr extends BinaryOp +{ + public function getOperatorSigil() : string { + return '|'; + } + + public function getType() : string { + return 'Expr_BinaryOp_BitwiseOr'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php new file mode 100644 index 0000000000000000000000000000000000000000..40fa94f88709a3198a0bbc5179b616a2c232eba0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class BitwiseXor extends BinaryOp +{ + public function getOperatorSigil() : string { + return '^'; + } + + public function getType() : string { + return 'Expr_BinaryOp_BitwiseXor'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php new file mode 100644 index 0000000000000000000000000000000000000000..4c3c9e9b1e1e97b6dc69ed1aab1ea4b23bb2fc4a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class BooleanAnd extends BinaryOp +{ + public function getOperatorSigil() : string { + return '&&'; + } + + public function getType() : string { + return 'Expr_BinaryOp_BooleanAnd'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php new file mode 100644 index 0000000000000000000000000000000000000000..5ad417279ae3a084472f20b06dd1ea4f32735812 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class BooleanOr extends BinaryOp +{ + public function getOperatorSigil() : string { + return '||'; + } + + public function getType() : string { + return 'Expr_BinaryOp_BooleanOr'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php new file mode 100644 index 0000000000000000000000000000000000000000..b8cf6f45991fb47067ee79634f09e1bf5f4721d0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Coalesce extends BinaryOp +{ + public function getOperatorSigil() : string { + return '??'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Coalesce'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.php new file mode 100644 index 0000000000000000000000000000000000000000..9a8d9873c081073f24dbef3a16662f216d7d53ad --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Concat extends BinaryOp +{ + public function getOperatorSigil() : string { + return '.'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Concat'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.php new file mode 100644 index 0000000000000000000000000000000000000000..d38df0db4de54a5231f7144d5edf9db2be23db20 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Div extends BinaryOp +{ + public function getOperatorSigil() : string { + return '/'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Div'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.php new file mode 100644 index 0000000000000000000000000000000000000000..e7b11dc824df212a4860b841d35ef2537ca3f1b9 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Equal extends BinaryOp +{ + public function getOperatorSigil() : string { + return '=='; + } + + public function getType() : string { + return 'Expr_BinaryOp_Equal'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.php new file mode 100644 index 0000000000000000000000000000000000000000..da01f7a100e8cdd964cc5fc097b83274276cee88 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Greater extends BinaryOp +{ + public function getOperatorSigil() : string { + return '>'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Greater'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php new file mode 100644 index 0000000000000000000000000000000000000000..d677502cf5bd67469ca6a06bb60bab28463fd6a7 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class GreaterOrEqual extends BinaryOp +{ + public function getOperatorSigil() : string { + return '>='; + } + + public function getType() : string { + return 'Expr_BinaryOp_GreaterOrEqual'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php new file mode 100644 index 0000000000000000000000000000000000000000..3d96285c64f4fcf98f2d68fc08592d9ce0401d54 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Identical extends BinaryOp +{ + public function getOperatorSigil() : string { + return '==='; + } + + public function getType() : string { + return 'Expr_BinaryOp_Identical'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php new file mode 100644 index 0000000000000000000000000000000000000000..2a3afd548f1b87716bd514a1d8fb76ac121ff468 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class LogicalAnd extends BinaryOp +{ + public function getOperatorSigil() : string { + return 'and'; + } + + public function getType() : string { + return 'Expr_BinaryOp_LogicalAnd'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php new file mode 100644 index 0000000000000000000000000000000000000000..21507dba6302a70f29d5f90e2fbc483f367dcd6a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class LogicalOr extends BinaryOp +{ + public function getOperatorSigil() : string { + return 'or'; + } + + public function getType() : string { + return 'Expr_BinaryOp_LogicalOr'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php new file mode 100644 index 0000000000000000000000000000000000000000..261c6a9100c729ac3844d4ee5dcee4e021f05b5a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class LogicalXor extends BinaryOp +{ + public function getOperatorSigil() : string { + return 'xor'; + } + + public function getType() : string { + return 'Expr_BinaryOp_LogicalXor'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.php new file mode 100644 index 0000000000000000000000000000000000000000..54b3c6e9059264af552d26d8c80c0d58bbfc83c8 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Minus extends BinaryOp +{ + public function getOperatorSigil() : string { + return '-'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Minus'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.php new file mode 100644 index 0000000000000000000000000000000000000000..10340404017b4948a7089caffa2c69dab5ec6570 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Mod extends BinaryOp +{ + public function getOperatorSigil() : string { + return '%'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Mod'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.php new file mode 100644 index 0000000000000000000000000000000000000000..b82d0b2fcb7843bfe9f32e6549315726ebeb9ab5 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Mul extends BinaryOp +{ + public function getOperatorSigil() : string { + return '*'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Mul'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php new file mode 100644 index 0000000000000000000000000000000000000000..51075da56310787283c33995879679ee3048d28d --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class NotEqual extends BinaryOp +{ + public function getOperatorSigil() : string { + return '!='; + } + + public function getType() : string { + return 'Expr_BinaryOp_NotEqual'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php new file mode 100644 index 0000000000000000000000000000000000000000..fa4050e058c70105e13c82ab0621e89e24e131aa --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class NotIdentical extends BinaryOp +{ + public function getOperatorSigil() : string { + return '!=='; + } + + public function getType() : string { + return 'Expr_BinaryOp_NotIdentical'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php new file mode 100644 index 0000000000000000000000000000000000000000..62f0229985dbdcd299a5e5d839fa477b31a545a7 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Plus extends BinaryOp +{ + public function getOperatorSigil() : string { + return '+'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Plus'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.php new file mode 100644 index 0000000000000000000000000000000000000000..572a1e8e4343b3d0dbfc724704251b7bf4773f7c --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Pow extends BinaryOp +{ + public function getOperatorSigil() : string { + return '**'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Pow'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php new file mode 100644 index 0000000000000000000000000000000000000000..4e70b4ef0be5f69dd42112130d8db24a1d1a3fdb --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class ShiftLeft extends BinaryOp +{ + public function getOperatorSigil() : string { + return '<<'; + } + + public function getType() : string { + return 'Expr_BinaryOp_ShiftLeft'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php new file mode 100644 index 0000000000000000000000000000000000000000..45acbd046108da712b7cc7d84f970bb29b39e351 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class ShiftRight extends BinaryOp +{ + public function getOperatorSigil() : string { + return '>>'; + } + + public function getType() : string { + return 'Expr_BinaryOp_ShiftRight'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php new file mode 100644 index 0000000000000000000000000000000000000000..3cb8e7e0d16680ed69aadf22d4c45a2749fa375e --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Smaller extends BinaryOp +{ + public function getOperatorSigil() : string { + return '<'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Smaller'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php new file mode 100644 index 0000000000000000000000000000000000000000..83e8e214d0df3e5c7181480a2d2fbf086ebda725 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class SmallerOrEqual extends BinaryOp +{ + public function getOperatorSigil() : string { + return '<='; + } + + public function getType() : string { + return 'Expr_BinaryOp_SmallerOrEqual'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php new file mode 100644 index 0000000000000000000000000000000000000000..8c6d787f6aacc6d3ba4e2d4ccb6738b3f98580e4 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\BinaryOp; + +use PhpParser\Node\Expr\BinaryOp; + +class Spaceship extends BinaryOp +{ + public function getOperatorSigil() : string { + return '<=>'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Spaceship'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php new file mode 100644 index 0000000000000000000000000000000000000000..ed44984beaa42ea3deee0b91a652d8bf772988a6 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class BitwiseNot extends Expr +{ + /** @var Expr Expression */ + public $expr; + + /** + * Constructs a bitwise not node. + * + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php new file mode 100644 index 0000000000000000000000000000000000000000..bf27e9f657b0f30750aad99c57b949806beae6e0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class BooleanNot extends Expr +{ + /** @var Expr Expression */ + public $expr; + + /** + * Constructs a boolean not node. + * + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php new file mode 100644 index 0000000000000000000000000000000000000000..36769d4fc6b56562fd2407324a99c12dada907c4 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php @@ -0,0 +1,26 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +abstract class Cast extends Expr +{ + /** @var Expr Expression */ + public $expr; + + /** + * Constructs a cast node. + * + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php new file mode 100644 index 0000000000000000000000000000000000000000..57cc473b6256ce93155cb84552a898e113f2640d --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\Cast; + +use PhpParser\Node\Expr\Cast; + +class Array_ extends Cast +{ + public function getType() : string { + return 'Expr_Cast_Array'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php new file mode 100644 index 0000000000000000000000000000000000000000..04eb4af584592fc4e1801ba4305956d212d28f45 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\Cast; + +use PhpParser\Node\Expr\Cast; + +class Bool_ extends Cast +{ + public function getType() : string { + return 'Expr_Cast_Bool'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.php new file mode 100644 index 0000000000000000000000000000000000000000..891ba5f87085921a0d986af00d592493d95e3004 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.php @@ -0,0 +1,17 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\Cast; + +use PhpParser\Node\Expr\Cast; + +class Double extends Cast +{ + // For use in "kind" attribute + const KIND_DOUBLE = 1; // "double" syntax + const KIND_FLOAT = 2; // "float" syntax + const KIND_REAL = 3; // "real" syntax + + public function getType() : string { + return 'Expr_Cast_Double'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php new file mode 100644 index 0000000000000000000000000000000000000000..01ed594bd0e5d625e02721e9b853e05fec153538 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\Cast; + +use PhpParser\Node\Expr\Cast; + +class Int_ extends Cast +{ + public function getType() : string { + return 'Expr_Cast_Int'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.php new file mode 100644 index 0000000000000000000000000000000000000000..163752be89563027cc4111f63cfe5db49569e790 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\Cast; + +use PhpParser\Node\Expr\Cast; + +class Object_ extends Cast +{ + public function getType() : string { + return 'Expr_Cast_Object'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php new file mode 100644 index 0000000000000000000000000000000000000000..b3d99270ac8dd1bdd7e4c464aebc315ca0c973c3 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\Cast; + +use PhpParser\Node\Expr\Cast; + +class String_ extends Cast +{ + public function getType() : string { + return 'Expr_Cast_String'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.php new file mode 100644 index 0000000000000000000000000000000000000000..accda3e4ff786ce4fee15f2f23151a0feb813123 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.php @@ -0,0 +1,12 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr\Cast; + +use PhpParser\Node\Expr\Cast; + +class Unset_ extends Cast +{ + public function getType() : string { + return 'Expr_Cast_Unset'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php new file mode 100644 index 0000000000000000000000000000000000000000..faf832f9380bec04fd32305b64a3e0b02feaa9d0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php @@ -0,0 +1,36 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name; + +class ClassConstFetch extends Expr +{ + /** @var Name|Expr Class name */ + public $class; + /** @var Identifier|Error Constant name */ + public $name; + + /** + * Constructs a class const fetch node. + * + * @param Name|Expr $class Class name + * @param string|Identifier|Error $name Constant name + * @param array $attributes Additional attributes + */ + public function __construct($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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php new file mode 100644 index 0000000000000000000000000000000000000000..db216b8f8416f8872f2aceb0c4d8c9349ce5220b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Clone_ extends Expr +{ + /** @var Expr Expression */ + public $expr; + + /** + * Constructs a clone node. + * + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php new file mode 100644 index 0000000000000000000000000000000000000000..56e621f252480abaccff6c150b56c754db13b81b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php @@ -0,0 +1,79 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\FunctionLike; + +class Closure extends Expr implements FunctionLike +{ + /** @var bool Whether the closure is static */ + public $static; + /** @var bool Whether to return by reference */ + public $byRef; + /** @var Node\Param[] Parameters */ + public $params; + /** @var ClosureUse[] use()s */ + public $uses; + /** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */ + public $returnType; + /** @var Node\Stmt[] Statements */ + public $stmts; + /** @var Node\AttributeGroup[] PHP attribute groups */ + public $attrGroups; + + /** + * Constructs a lambda function node. + * + * @param array $subNodes Array of the following optional subnodes: + * 'static' => 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'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php new file mode 100644 index 0000000000000000000000000000000000000000..2b8a096666388891f5bb6c15b23193b8debc8846 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class ClosureUse extends Expr +{ + /** @var Expr\Variable Variable to use */ + public $var; + /** @var bool Whether to use by reference */ + public $byRef; + + /** + * Constructs a closure use node. + * + * @param Expr\Variable $var Variable to use + * @param bool $byRef Whether to use by reference + * @param array $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 'Expr_ClosureUse'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php new file mode 100644 index 0000000000000000000000000000000000000000..14ebd16bd8400b6d3118c15cff9ea2136c6edb38 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php @@ -0,0 +1,31 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; +use PhpParser\Node\Name; + +class ConstFetch extends Expr +{ + /** @var Name Constant name */ + public $name; + + /** + * Constructs a const fetch node. + * + * @param Name $name Constant name + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php new file mode 100644 index 0000000000000000000000000000000000000000..4042ec93ca37a8f546d2a78bc362f4dfb378c9ff --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Empty_ extends Expr +{ + /** @var Expr Expression */ + public $expr; + + /** + * Constructs an empty() node. + * + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php new file mode 100644 index 0000000000000000000000000000000000000000..1637f3aeae0a52ea4bdbc442f6816ea21c42ca17 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php @@ -0,0 +1,31 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +/** + * Error node used during parsing with error recovery. + * + * An error node may be placed at a position where an expression is required, but an error occurred. + * Error nodes will not be present if the parser is run in throwOnError mode (the default). + */ +class Error extends Expr +{ + /** + * Constructs an error node. + * + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php new file mode 100644 index 0000000000000000000000000000000000000000..c44ff6f93181748a0dcf56977bde40f8ed9b6558 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class ErrorSuppress extends Expr +{ + /** @var Expr Expression */ + public $expr; + + /** + * Constructs an error suppress node. + * + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php new file mode 100644 index 0000000000000000000000000000000000000000..856854743877cfd17c0d38bd2c2d0d5891e93422 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Eval_ extends Expr +{ + /** @var Expr Expression */ + public $expr; + + /** + * Constructs an eval() node. + * + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php new file mode 100644 index 0000000000000000000000000000000000000000..b88a8f7e6f3f3912a6f9d32c18c3575da14e3406 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Exit_ extends Expr +{ + /* For use in "kind" attribute */ + const KIND_EXIT = 1; + const KIND_DIE = 2; + + /** @var null|Expr Expression */ + public $expr; + + /** + * Constructs an exit() node. + * + * @param null|Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php new file mode 100644 index 0000000000000000000000000000000000000000..1e8afa55962e994e2a699c710c890f40ff766a4b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php @@ -0,0 +1,35 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node; +use PhpParser\Node\Expr; + +class FuncCall extends Expr +{ + /** @var Node\Name|Expr Function name */ + public $name; + /** @var Node\Arg[] Arguments */ + public $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr $name Function name + * @param Node\Arg[] $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct($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'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php new file mode 100644 index 0000000000000000000000000000000000000000..07ce5968e40e4ffe72d298ca9d894a7b8a9ff8f6 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php @@ -0,0 +1,39 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Include_ extends Expr +{ + const TYPE_INCLUDE = 1; + const TYPE_INCLUDE_ONCE = 2; + const TYPE_REQUIRE = 3; + const TYPE_REQUIRE_ONCE = 4; + + /** @var Expr Expression */ + public $expr; + /** @var int Type of include */ + public $type; + + /** + * Constructs an include node. + * + * @param Expr $expr Expression + * @param int $type Type of include + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php new file mode 100644 index 0000000000000000000000000000000000000000..9000d47bb1b0e36ff70a064c9e934a688bb58ce3 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php @@ -0,0 +1,35 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; +use PhpParser\Node\Name; + +class Instanceof_ extends Expr +{ + /** @var Expr Expression */ + public $expr; + /** @var Name|Expr Class name */ + public $class; + + /** + * Constructs an instanceof check node. + * + * @param Expr $expr Expression + * @param Name|Expr $class Class name + * @param array $attributes Additional attributes + */ + public function __construct(Expr $expr, $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php new file mode 100644 index 0000000000000000000000000000000000000000..76b7387587b87e78e12f671f21db82eb1b169a70 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Isset_ extends Expr +{ + /** @var Expr[] Variables */ + public $vars; + + /** + * Constructs an array node. + * + * @param Expr[] $vars Variables + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php new file mode 100644 index 0000000000000000000000000000000000000000..c27a27b95738d2ccb8cb06684a358253391d38be --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class List_ extends Expr +{ + /** @var (ArrayItem|null)[] List of items to assign to */ + public $items; + + /** + * Constructs a list() destructuring node. + * + * @param (ArrayItem|null)[] $items List of items to assign to + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php new file mode 100644 index 0000000000000000000000000000000000000000..2455a3026410372bcbd1d987f3a81a000d1578a9 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php @@ -0,0 +1,31 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node; +use PhpParser\Node\MatchArm; + +class Match_ extends Node\Expr +{ + /** @var Node\Expr */ + public $cond; + /** @var MatchArm[] */ + public $arms; + + /** + * @param MatchArm[] $arms + */ + 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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php new file mode 100644 index 0000000000000000000000000000000000000000..bd81bb43f6d7ec53cb2d15ff11da0f5361548c5c --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php @@ -0,0 +1,40 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Identifier; + +class MethodCall extends Expr +{ + /** @var Expr Variable holding object */ + public $var; + /** @var Identifier|Expr Method name */ + public $name; + /** @var Arg[] Arguments */ + public $args; + + /** + * Constructs a function call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param Arg[] $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'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php new file mode 100644 index 0000000000000000000000000000000000000000..c86f0c60158171ad14dfa62d396efdfad78c8d77 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php @@ -0,0 +1,35 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node; +use PhpParser\Node\Expr; + +class New_ extends Expr +{ + /** @var Node\Name|Expr|Node\Stmt\Class_ Class name */ + public $class; + /** @var Node\Arg[] Arguments */ + public $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes) + * @param Node\Arg[] $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct($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'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php new file mode 100644 index 0000000000000000000000000000000000000000..361e44622758cde938c9ce6d81c719077fac9d29 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php @@ -0,0 +1,40 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Identifier; + +class NullsafeMethodCall extends Expr +{ + /** @var Expr Variable holding object */ + public $var; + /** @var Identifier|Expr Method name */ + public $name; + /** @var Arg[] Arguments */ + public $args; + + /** + * Constructs a nullsafe method call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param Arg[] $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'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php new file mode 100644 index 0000000000000000000000000000000000000000..9317eb3b919cce090ac431b248a41902481ecf02 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php @@ -0,0 +1,35 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; +use PhpParser\Node\Identifier; + +class NullsafePropertyFetch extends Expr +{ + /** @var Expr Variable holding object */ + public $var; + /** @var Identifier|Expr Property name */ + public $name; + + /** + * Constructs a nullsafe property fetch node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Property name + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php new file mode 100644 index 0000000000000000000000000000000000000000..94d6c296d89b18aae05fa8c5bde48ae68a2cc830 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class PostDec extends Expr +{ + /** @var Expr Variable */ + public $var; + + /** + * Constructs a post decrement node. + * + * @param Expr $var Variable + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php new file mode 100644 index 0000000000000000000000000000000000000000..005c443a2de79e1e02681cd04abe59a4c1eb4ad6 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class PostInc extends Expr +{ + /** @var Expr Variable */ + public $var; + + /** + * Constructs a post increment node. + * + * @param Expr $var Variable + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php new file mode 100644 index 0000000000000000000000000000000000000000..a5ca685a8a6524f2910844454ef8828fd37de8fd --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class PreDec extends Expr +{ + /** @var Expr Variable */ + public $var; + + /** + * Constructs a pre decrement node. + * + * @param Expr $var Variable + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php new file mode 100644 index 0000000000000000000000000000000000000000..0986c447483fd3b63831ec030963d6e395d6d365 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class PreInc extends Expr +{ + /** @var Expr Variable */ + public $var; + + /** + * Constructs a pre increment node. + * + * @param Expr $var Variable + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php new file mode 100644 index 0000000000000000000000000000000000000000..2d43c2ac82a3d8f58d79c1650756a9d057ca9188 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Print_ extends Expr +{ + /** @var Expr Expression */ + public $expr; + + /** + * Constructs an print() node. + * + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php new file mode 100644 index 0000000000000000000000000000000000000000..4281f31ccf5c1c348e16e2ff05959015c7940f32 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php @@ -0,0 +1,35 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; +use PhpParser\Node\Identifier; + +class PropertyFetch extends Expr +{ + /** @var Expr Variable holding object */ + public $var; + /** @var Identifier|Expr Property name */ + public $name; + + /** + * Constructs a function call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Property name + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php new file mode 100644 index 0000000000000000000000000000000000000000..537a7cc80926622020e359f381da5a8190470795 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class ShellExec extends Expr +{ + /** @var array Encapsed string array */ + public $parts; + + /** + * Constructs a shell exec (backtick) node. + * + * @param array $parts Encapsed string array + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php new file mode 100644 index 0000000000000000000000000000000000000000..9883f5af515aa93a27c9feef6502ac1f47848210 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php @@ -0,0 +1,40 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\Identifier; + +class StaticCall extends Expr +{ + /** @var Node\Name|Expr Class name */ + public $class; + /** @var Identifier|Expr Method name */ + public $name; + /** @var Node\Arg[] Arguments */ + public $args; + + /** + * Constructs a static method call node. + * + * @param Node\Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param Node\Arg[] $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct($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'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php new file mode 100644 index 0000000000000000000000000000000000000000..1ee1a25e50d66d1f44bd3f1b4ddc201a2020044b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php @@ -0,0 +1,36 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\VarLikeIdentifier; + +class StaticPropertyFetch extends Expr +{ + /** @var Name|Expr Class name */ + public $class; + /** @var VarLikeIdentifier|Expr Property name */ + public $name; + + /** + * Constructs a static property fetch node. + * + * @param Name|Expr $class Class name + * @param string|VarLikeIdentifier|Expr $name Property name + * @param array $attributes Additional attributes + */ + public function __construct($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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php new file mode 100644 index 0000000000000000000000000000000000000000..9316f47d4dc7e5782abac0092922546750792660 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php @@ -0,0 +1,38 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Ternary extends Expr +{ + /** @var Expr Condition */ + public $cond; + /** @var null|Expr Expression for true */ + public $if; + /** @var Expr Expression for false */ + public $else; + + /** + * Constructs a ternary operator node. + * + * @param Expr $cond Condition + * @param null|Expr $if Expression for true + * @param Expr $else Expression for false + * @param array $attributes Additional attributes + */ + public function __construct(Expr $cond, $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php new file mode 100644 index 0000000000000000000000000000000000000000..5c97f0e2b4d199bd83ce969fa7e92d0f18a43f09 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node; + +class Throw_ extends Node\Expr +{ + /** @var Node\Expr Expression */ + public $expr; + + /** + * Constructs a throw expression node. + * + * @param Node\Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php new file mode 100644 index 0000000000000000000000000000000000000000..ce8808bc6489791b8ba730527316a49618f437f3 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class UnaryMinus extends Expr +{ + /** @var Expr Expression */ + public $expr; + + /** + * Constructs a unary minus node. + * + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php new file mode 100644 index 0000000000000000000000000000000000000000..d23047e54efcd117dbebca52255a08cbf57169cf --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class UnaryPlus extends Expr +{ + /** @var Expr Expression */ + public $expr; + + /** + * Constructs a unary plus node. + * + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php new file mode 100644 index 0000000000000000000000000000000000000000..1f2b2314a3b636426c10bd3385d03bde769aa1fd --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Variable extends Expr +{ + /** @var string|Expr Name */ + public $name; + + /** + * Constructs a variable node. + * + * @param string|Expr $name Name + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php new file mode 100644 index 0000000000000000000000000000000000000000..a3efce618cd072c011d66738d52994b8ff158eed --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class YieldFrom extends Expr +{ + /** @var Expr Expression to yield from */ + public $expr; + + /** + * Constructs an "yield from" node. + * + * @param Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php new file mode 100644 index 0000000000000000000000000000000000000000..aef8fc333dd8ed37b10a7afa2374d863d124cd52 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Expr; + +use PhpParser\Node\Expr; + +class Yield_ extends Expr +{ + /** @var null|Expr Key expression */ + public $key; + /** @var null|Expr Value expression */ + public $value; + + /** + * Constructs a yield expression node. + * + * @param null|Expr $value Value expression + * @param null|Expr $key Key expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php new file mode 100644 index 0000000000000000000000000000000000000000..bbcf53e55fb71b371e02ea3f1da676f95c963780 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php @@ -0,0 +1,43 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\Node; + +interface FunctionLike extends Node +{ + /** + * Whether to return by reference + * + * @return bool + */ + public function returnsByRef() : bool; + + /** + * List of parameters + * + * @return Param[] + */ + public function getParams() : array; + + /** + * Get the declared return type or null + * + * @return null|Identifier|Name|NullableType|UnionType + */ + public function getReturnType(); + + /** + * The function body + * + * @return Stmt[]|null + */ + public function getStmts(); + + /** + * Get PHP attribute groups. + * + * @return AttributeGroup[] + */ + public function getAttrGroups() : array; +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Identifier.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Identifier.php new file mode 100644 index 0000000000000000000000000000000000000000..2f262db0aa569e93f00f81122bf4ade1f18c212c --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Identifier.php @@ -0,0 +1,75 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\NodeAbstract; + +/** + * Represents a non-namespaced name. Namespaced names are represented using Name nodes. + */ +class Identifier extends NodeAbstract +{ + /** @var string Identifier as string */ + public $name; + + private static $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/MatchArm.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/MatchArm.php new file mode 100644 index 0000000000000000000000000000000000000000..2ae1c86b8555302270f4dd8f3411e8c179531b06 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/MatchArm.php @@ -0,0 +1,31 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\Node; +use PhpParser\NodeAbstract; + +class MatchArm extends NodeAbstract +{ + /** @var null|Node\Expr[] */ + public $conds; + /** @var Node\Expr */ + public $body; + + /** + * @param null|Node\Expr[] $conds + */ + public function __construct($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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Name.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Name.php new file mode 100644 index 0000000000000000000000000000000000000000..6b1cc9f8ed2e6ff11cfc4eff04aadbe50e0e67d2 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Name.php @@ -0,0 +1,242 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\NodeAbstract; + +class Name extends NodeAbstract +{ + /** @var string[] Parts of the name */ + public $parts; + + private static $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 + */ + public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->parts = self::prepareName($name); + } + + public function getSubNodeNames() : array { + return ['parts']; + } + + /** + * 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 { + return $this->parts[0]; + } + + /** + * 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 { + return $this->parts[count($this->parts) - 1]; + } + + /** + * Checks whether the name is unqualified. (E.g. Name) + * + * @return bool Whether the name is unqualified + */ + public function isUnqualified() : bool { + return 1 === count($this->parts); + } + + /** + * Checks whether the name is qualified. (E.g. Name\Name) + * + * @return bool Whether the name is qualified + */ + public function isQualified() : bool { + return 1 < count($this->parts); + } + + /** + * 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 implode('\\', $this->parts); + } + + /** + * 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(implode('\\', $this->parts)); + } + + /** + * 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 count($this->parts) === 1 + && isset(self::$specialClassNames[strtolower($this->parts[0])]); + } + + /** + * 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 implode('\\', $this->parts); + } + + /** + * 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) { + $numParts = count($this->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) { + 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($this->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; + } elseif (null === $name1) { + return new static(self::prepareName($name2), $attributes); + } elseif (null === $name2) { + return new static(self::prepareName($name1), $attributes); + } else { + return new static( + array_merge(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 an array. + * + * @param string|string[]|self $name Name to prepare + * + * @return string[] Prepared name + */ + private static function prepareName($name) : array { + if (\is_string($name)) { + if ('' === $name) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + + return explode('\\', $name); + } elseif (\is_array($name)) { + if (empty($name)) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + + return $name; + } elseif ($name instanceof self) { + return $name->parts; + } + + throw new \InvalidArgumentException( + 'Expected string, array of parts or Name instance' + ); + } + + public function getType() : string { + return 'Name'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php new file mode 100644 index 0000000000000000000000000000000000000000..1df93a56b60cdc127135c58f5e1c0269cdfa07f0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php @@ -0,0 +1,50 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Name; + +class FullyQualified extends \PhpParser\Node\Name +{ + /** + * Checks whether the name is unqualified. (E.g. Name) + * + * @return bool Whether the name is unqualified + */ + public function isUnqualified() : bool { + return false; + } + + /** + * Checks whether the name is qualified. (E.g. Name\Name) + * + * @return bool Whether the name is qualified + */ + public function isQualified() : bool { + return false; + } + + /** + * Checks whether the name is fully qualified. (E.g. \Name) + * + * @return bool Whether the name is fully qualified + */ + public function isFullyQualified() : bool { + return true; + } + + /** + * 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; + } + + public function toCodeString() : string { + return '\\' . $this->toString(); + } + + public function getType() : string { + return 'Name_FullyQualified'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php new file mode 100644 index 0000000000000000000000000000000000000000..57bf7af2b2671c794fd8346d77fc5c3d5cb27e04 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php @@ -0,0 +1,50 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Name; + +class Relative extends \PhpParser\Node\Name +{ + /** + * Checks whether the name is unqualified. (E.g. Name) + * + * @return bool Whether the name is unqualified + */ + public function isUnqualified() : bool { + return false; + } + + /** + * Checks whether the name is qualified. (E.g. Name\Name) + * + * @return bool Whether the name is qualified + */ + public function isQualified() : bool { + return false; + } + + /** + * 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 true; + } + + public function toCodeString() : string { + return 'namespace\\' . $this->toString(); + } + + public function getType() : string { + return 'Name_Relative'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/NullableType.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/NullableType.php new file mode 100644 index 0000000000000000000000000000000000000000..36463657e98905df5cc4e4b30db53428b0886c90 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/NullableType.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\NodeAbstract; + +class NullableType extends NodeAbstract +{ + /** @var Identifier|Name Type */ + public $type; + + /** + * Constructs a nullable type (wrapping another type). + * + * @param string|Identifier|Name $type Type + * @param array $attributes Additional attributes + */ + public function __construct($type, array $attributes = []) { + $this->attributes = $attributes; + $this->type = \is_string($type) ? new Identifier($type) : $type; + } + + public function getSubNodeNames() : array { + return ['type']; + } + + public function getType() : string { + return 'NullableType'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Param.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Param.php new file mode 100644 index 0000000000000000000000000000000000000000..315b5f24f64750353b1d8040eedc7b5a02b215e7 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Param.php @@ -0,0 +1,60 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\NodeAbstract; + +class Param extends NodeAbstract +{ + /** @var null|Identifier|Name|NullableType|UnionType Type declaration */ + public $type; + /** @var bool Whether parameter is passed by reference */ + public $byRef; + /** @var bool Whether this is a variadic argument */ + public $variadic; + /** @var Expr\Variable|Expr\Error Parameter variable */ + public $var; + /** @var null|Expr Default value */ + public $default; + /** @var int */ + public $flags; + /** @var AttributeGroup[] PHP attribute groups */ + public $attrGroups; + + /** + * Constructs a parameter node. + * + * @param Expr\Variable|Expr\Error $var Parameter variable + * @param null|Expr $default Default value + * @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration + * @param bool $byRef Whether is passed by reference + * @param bool $variadic Whether this is a variadic argument + * @param array $attributes Additional attributes + * @param int $flags Optional visibility flags + * @param AttributeGroup[] $attrGroups PHP attribute groups + */ + public function __construct( + $var, Expr $default = null, $type = null, + bool $byRef = false, bool $variadic = false, + array $attributes = [], + int $flags = 0, + array $attrGroups = [] + ) { + $this->attributes = $attributes; + $this->type = \is_string($type) ? new Identifier($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'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar.php new file mode 100644 index 0000000000000000000000000000000000000000..8117909b65a48524f639c04b574f615a94bd8e90 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar.php @@ -0,0 +1,7 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +abstract class Scalar extends Expr +{ +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php new file mode 100644 index 0000000000000000000000000000000000000000..29ce0dd40133cd735aa040b073a9820533b42a43 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php @@ -0,0 +1,70 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar; + +use PhpParser\Node\Scalar; + +class DNumber extends Scalar +{ + /** @var float Number value */ + public $value; + + /** + * Constructs a float number scalar node. + * + * @param float $value Value of the number + * @param array $attributes Additional attributes + */ + public function __construct(float $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames() : array { + return ['value']; + } + + /** + * @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); + + // if string contains any of .eE just cast it to float + if (false !== strpbrk($str, '.eE')) { + return (float) $str; + } + + // otherwise it's an integer notation that overflowed into a float + // if it starts with 0 it's 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 + // 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_DNumber'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php new file mode 100644 index 0000000000000000000000000000000000000000..fa5d2e2681998809581b694bd746d04d9926ee68 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php @@ -0,0 +1,31 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar; + +use PhpParser\Node\Expr; +use PhpParser\Node\Scalar; + +class Encapsed extends Scalar +{ + /** @var Expr[] list of string parts */ + public $parts; + + /** + * Constructs an encapsed string node. + * + * @param Expr[] $parts Encaps list + * @param array $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_Encapsed'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php new file mode 100644 index 0000000000000000000000000000000000000000..bb3194c1d7cc1b8cac22f2850c34f837a16357a2 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar; + +use PhpParser\Node\Scalar; + +class EncapsedStringPart extends Scalar +{ + /** @var string String value */ + public $value; + + /** + * Constructs a node representing a string part of an encapsed string. + * + * @param string $value String value + * @param array $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 'Scalar_EncapsedStringPart'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php new file mode 100644 index 0000000000000000000000000000000000000000..b33943547e2aaad5d112070bfce2a524489b041e --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php @@ -0,0 +1,73 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar; + +use PhpParser\Error; +use PhpParser\Node\Scalar; + +class LNumber extends Scalar +{ + /* For use in "kind" attribute */ + const KIND_BIN = 2; + const KIND_OCT = 8; + const KIND_DEC = 10; + const KIND_HEX = 16; + + /** @var int Number value */ + public $value; + + /** + * Constructs an integer number scalar node. + * + * @param int $value Value of the number + * @param array $attributes Additional attributes + */ + public function __construct(int $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames() : array { + return ['value']; + } + + /** + * Constructs an LNumber 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 LNumber The constructed LNumber, including kind attribute + */ + public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber { + $str = str_replace('_', '', $str); + + if ('0' !== $str[0] || '0' === $str) { + $attributes['kind'] = LNumber::KIND_DEC; + return new LNumber((int) $str, $attributes); + } + + if ('x' === $str[1] || 'X' === $str[1]) { + $attributes['kind'] = LNumber::KIND_HEX; + return new LNumber(hexdec($str), $attributes); + } + + if ('b' === $str[1] || 'B' === $str[1]) { + $attributes['kind'] = LNumber::KIND_BIN; + return new LNumber(bindec($str), $attributes); + } + + if (!$allowInvalidOctal && strpbrk($str, '89')) { + throw new Error('Invalid numeric literal', $attributes); + } + + // use intval instead of octdec to get proper cutting behavior with malformed numbers + $attributes['kind'] = LNumber::KIND_OCT; + return new LNumber(intval($str, 8), $attributes); + } + + public function getType() : string { + return 'Scalar_LNumber'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php new file mode 100644 index 0000000000000000000000000000000000000000..941f0c762079533469e035d49fef5e8bf035d8fd --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php @@ -0,0 +1,28 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar; + +use PhpParser\Node\Scalar; + +abstract class MagicConst extends Scalar +{ + /** + * Constructs a magic constant node. + * + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php new file mode 100644 index 0000000000000000000000000000000000000000..244328476d6bac95a148ab5d888ad7396895671f --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar\MagicConst; + +use PhpParser\Node\Scalar\MagicConst; + +class Class_ extends MagicConst +{ + public function getName() : string { + return '__CLASS__'; + } + + public function getType() : string { + return 'Scalar_MagicConst_Class'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php new file mode 100644 index 0000000000000000000000000000000000000000..2b618473e3829a6fd643fcea13ef8d62bc5f18e4 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar\MagicConst; + +use PhpParser\Node\Scalar\MagicConst; + +class Dir extends MagicConst +{ + public function getName() : string { + return '__DIR__'; + } + + public function getType() : string { + return 'Scalar_MagicConst_Dir'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php new file mode 100644 index 0000000000000000000000000000000000000000..3422db069238299b04e987b2fa846a5505e8db70 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar\MagicConst; + +use PhpParser\Node\Scalar\MagicConst; + +class File extends MagicConst +{ + public function getName() : string { + return '__FILE__'; + } + + public function getType() : string { + return 'Scalar_MagicConst_File'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.php new file mode 100644 index 0000000000000000000000000000000000000000..1db65a15130ce0273a72d32b128a56e7cdfed25f --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar\MagicConst; + +use PhpParser\Node\Scalar\MagicConst; + +class Function_ extends MagicConst +{ + public function getName() : string { + return '__FUNCTION__'; + } + + public function getType() : string { + return 'Scalar_MagicConst_Function'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php new file mode 100644 index 0000000000000000000000000000000000000000..25d3de57c131705b47a60c67eb930e761543648b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar\MagicConst; + +use PhpParser\Node\Scalar\MagicConst; + +class Line extends MagicConst +{ + public function getName() : string { + return '__LINE__'; + } + + public function getType() : string { + return 'Scalar_MagicConst_Line'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.php new file mode 100644 index 0000000000000000000000000000000000000000..d168d56f105a979afcb5702e18f4e0fe414afe5f --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar\MagicConst; + +use PhpParser\Node\Scalar\MagicConst; + +class Method extends MagicConst +{ + public function getName() : string { + return '__METHOD__'; + } + + public function getType() : string { + return 'Scalar_MagicConst_Method'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php new file mode 100644 index 0000000000000000000000000000000000000000..4fabb751af361c4c4828987e76337c0048c21ee7 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar\MagicConst; + +use PhpParser\Node\Scalar\MagicConst; + +class Namespace_ extends MagicConst +{ + public function getName() : string { + return '__NAMESPACE__'; + } + + public function getType() : string { + return 'Scalar_MagicConst_Namespace'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.php new file mode 100644 index 0000000000000000000000000000000000000000..5ee7e40a3c744357dd1492510b05bed6ec3a3989 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.php @@ -0,0 +1,16 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar\MagicConst; + +use PhpParser\Node\Scalar\MagicConst; + +class Trait_ extends MagicConst +{ + public function getName() : string { + return '__TRAIT__'; + } + + public function getType() : string { + return 'Scalar_MagicConst_Trait'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php new file mode 100644 index 0000000000000000000000000000000000000000..8a6d93a47415a7de82b007dcf71490c526b8b19b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php @@ -0,0 +1,141 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Scalar; + +use PhpParser\Error; +use PhpParser\Node\Scalar; + +class String_ extends Scalar +{ + /* For use in "kind" attribute */ + const KIND_SINGLE_QUOTED = 1; + const KIND_DOUBLE_QUOTED = 2; + const KIND_HEREDOC = 3; + const KIND_NOWDOC = 4; + + /** @var string String value */ + public $value; + + protected static $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']; + } + + /** + * @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, $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]; + } elseif ('x' === $str[0] || 'X' === $str[0]) { + return chr(hexdec(substr($str, 1))); + } elseif ('u' === $str[0]) { + return self::codePointToUtf8(hexdec($matches[2])); + } 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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt.php new file mode 100644 index 0000000000000000000000000000000000000000..69d33e5796066e0ec235a87b6f524bcc36b406f3 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt.php @@ -0,0 +1,9 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\NodeAbstract; + +abstract class Stmt extends NodeAbstract +{ +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php new file mode 100644 index 0000000000000000000000000000000000000000..6adc5a6c6f716a0357696cbf8a9f7314c0af9d97 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Break_ extends Node\Stmt +{ + /** @var null|Node\Expr Number of loops to break */ + public $num; + + /** + * Constructs a break node. + * + * @param null|Node\Expr $num Number of loops to break + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php new file mode 100644 index 0000000000000000000000000000000000000000..2bf044c90082e4b780af11983b6cb93f6e3dc808 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Case_ extends Node\Stmt +{ + /** @var null|Node\Expr Condition (null for default) */ + public $cond; + /** @var Node\Stmt[] Statements */ + public $stmts; + + /** + * Constructs a case node. + * + * @param null|Node\Expr $cond Condition (null for default) + * @param Node\Stmt[] $stmts Statements + * @param array $attributes Additional attributes + */ + public function __construct($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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php new file mode 100644 index 0000000000000000000000000000000000000000..9b9c09478222def88a553b92d70092e166080b6a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php @@ -0,0 +1,41 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; +use PhpParser\Node\Expr; + +class Catch_ extends Node\Stmt +{ + /** @var Node\Name[] Types of exceptions to catch */ + public $types; + /** @var Expr\Variable|null Variable for exception */ + public $var; + /** @var Node\Stmt[] Statements */ + public $stmts; + + /** + * Constructs a catch node. + * + * @param Node\Name[] $types Types of exceptions to catch + * @param Expr\Variable|null $var Variable for exception + * @param Node\Stmt[] $stmts Statements + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php new file mode 100644 index 0000000000000000000000000000000000000000..c459acb1527f04eb633296db96fcc9c7c4362768 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php @@ -0,0 +1,71 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class ClassConst extends Node\Stmt +{ + /** @var int Modifiers */ + public $flags; + /** @var Node\Const_[] Constant declarations */ + public $consts; + /** @var Node\AttributeGroup[] */ + public $attrGroups; + + /** + * Constructs a class const list node. + * + * @param Node\Const_[] $consts Constant declarations + * @param int $flags Modifiers + * @param array $attributes Additional attributes + * @param Node\AttributeGroup[] $attrGroups PHP attribute groups + */ + public function __construct( + array $consts, + int $flags = 0, + array $attributes = [], + array $attrGroups = [] + ) { + $this->attributes = $attributes; + $this->flags = $flags; + $this->consts = $consts; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'flags', 'consts']; + } + + /** + * Whether constant is explicitly or implicitly public. + * + * @return bool + */ + public function isPublic() : bool { + return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 + || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; + } + + /** + * Whether constant is protected. + * + * @return bool + */ + public function isProtected() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); + } + + /** + * Whether constant is private. + * + * @return bool + */ + public function isPrivate() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); + } + + public function getType() : string { + return 'Stmt_ClassConst'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php new file mode 100644 index 0000000000000000000000000000000000000000..840c4f67ec5f4b9691de3914321921e5abc76600 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php @@ -0,0 +1,109 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +/** + * @property Node\Name $namespacedName Namespaced name (if using NameResolver) + */ +abstract class ClassLike extends Node\Stmt +{ + /** @var Node\Identifier|null Name */ + public $name; + /** @var Node\Stmt[] Statements */ + public $stmts; + /** @var Node\AttributeGroup[] PHP attribute groups */ + public $attrGroups; + + /** + * @return TraitUse[] + */ + public function getTraitUses() : array { + $traitUses = []; + foreach ($this->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) { + foreach ($this->stmts as $stmt) { + if ($stmt instanceof Property) { + foreach ($stmt->props as $prop) { + if ($prop instanceof PropertyProperty && $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) { + $lowerName = strtolower($name); + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassMethod && $lowerName === $stmt->name->toLowerString()) { + return $stmt; + } + } + return null; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php new file mode 100644 index 0000000000000000000000000000000000000000..92157fab26b4f13da38d73a9d17bec892e84a913 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php @@ -0,0 +1,159 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; +use PhpParser\Node\FunctionLike; + +class ClassMethod extends Node\Stmt implements FunctionLike +{ + /** @var int Flags */ + public $flags; + /** @var bool Whether to return by reference */ + public $byRef; + /** @var Node\Identifier Name */ + public $name; + /** @var Node\Param[] Parameters */ + public $params; + /** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */ + public $returnType; + /** @var Node\Stmt[]|null Statements */ + public $stmts; + /** @var Node\AttributeGroup[] PHP attribute groups */ + public $attrGroups; + + private static $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, + ]; + + /** + * Constructs a class method node. + * + * @param string|Node\Identifier $name Name + * @param array $subNodes Array of the following optional subnodes: + * 'flags => MODIFIER_PUBLIC: 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'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $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() { + return $this->stmts; + } + + public function getAttrGroups() : array { + return $this->attrGroups; + } + + /** + * Whether the method is explicitly or implicitly public. + * + * @return bool + */ + public function isPublic() : bool { + return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 + || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; + } + + /** + * Whether the method is protected. + * + * @return bool + */ + public function isProtected() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); + } + + /** + * Whether the method is private. + * + * @return bool + */ + public function isPrivate() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); + } + + /** + * Whether the method is abstract. + * + * @return bool + */ + public function isAbstract() : bool { + return (bool) ($this->flags & Class_::MODIFIER_ABSTRACT); + } + + /** + * Whether the method is final. + * + * @return bool + */ + public function isFinal() : bool { + return (bool) ($this->flags & Class_::MODIFIER_FINAL); + } + + /** + * Whether the method is static. + * + * @return bool + */ + public function isStatic() : bool { + return (bool) ($this->flags & Class_::MODIFIER_STATIC); + } + + /** + * Whether the method is magic. + * + * @return bool + */ + public function isMagic() : bool { + return isset(self::$magicNames[$this->name->toLowerString()]); + } + + public function getType() : string { + return 'Stmt_ClassMethod'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php new file mode 100644 index 0000000000000000000000000000000000000000..ace266f74b71cf554997b9c0c5beffd3f77690aa --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php @@ -0,0 +1,107 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Error; +use PhpParser\Node; + +class Class_ extends ClassLike +{ + const MODIFIER_PUBLIC = 1; + const MODIFIER_PROTECTED = 2; + const MODIFIER_PRIVATE = 4; + const MODIFIER_STATIC = 8; + const MODIFIER_ABSTRACT = 16; + const MODIFIER_FINAL = 32; + + const VISIBILITY_MODIFIER_MASK = 7; // 1 | 2 | 4 + + /** @var int Type */ + public $flags; + /** @var null|Node\Name Name of extended class */ + public $extends; + /** @var Node\Name[] Names of implemented interfaces */ + public $implements; + + /** + * Constructs a class node. + * + * @param string|Node\Identifier|null $name Name + * @param array $subNodes Array of the following optional subnodes: + * 'flags' => 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. + * + * @return bool + */ + public function isAbstract() : bool { + return (bool) ($this->flags & self::MODIFIER_ABSTRACT); + } + + /** + * Whether the class is final. + * + * @return bool + */ + public function isFinal() : bool { + return (bool) ($this->flags & self::MODIFIER_FINAL); + } + + /** + * Whether the class is anonymous. + * + * @return bool + */ + public function isAnonymous() : bool { + return null === $this->name; + } + + /** + * @internal + */ + public static function verifyModifier($a, $b) { + if ($a & self::VISIBILITY_MODIFIER_MASK && $b & self::VISIBILITY_MODIFIER_MASK) { + throw new Error('Multiple access type modifiers are not allowed'); + } + + if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) { + throw new Error('Multiple abstract modifiers are not allowed'); + } + + if ($a & self::MODIFIER_STATIC && $b & self::MODIFIER_STATIC) { + throw new Error('Multiple static modifiers are not allowed'); + } + + if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) { + throw new Error('Multiple final modifiers are not allowed'); + } + + if ($a & 48 && $b & 48) { + throw new Error('Cannot use the final modifier on an abstract class member'); + } + } + + public function getType() : string { + return 'Stmt_Class'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php new file mode 100644 index 0000000000000000000000000000000000000000..e6316345ee34b9fe1ceb473027b4a18395d63471 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Const_ extends Node\Stmt +{ + /** @var Node\Const_[] Constant declarations */ + public $consts; + + /** + * Constructs a const list node. + * + * @param Node\Const_[] $consts Constant declarations + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php new file mode 100644 index 0000000000000000000000000000000000000000..24882683b37c607cff0ce1c20df9fb8e344ee10a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Continue_ extends Node\Stmt +{ + /** @var null|Node\Expr Number of loops to continue */ + public $num; + + /** + * Constructs a continue node. + * + * @param null|Node\Expr $num Number of loops to continue + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php new file mode 100644 index 0000000000000000000000000000000000000000..ac07f30c78f0cc91a873862cdf73051cd965182b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class DeclareDeclare extends Node\Stmt +{ + /** @var Node\Identifier Key */ + public $key; + /** @var Node\Expr Value */ + public $value; + + /** + * Constructs a declare key=>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 'Stmt_DeclareDeclare'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php new file mode 100644 index 0000000000000000000000000000000000000000..f46ff0bafde696a6a74f25d9909f0a0044c67ab0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Declare_ extends Node\Stmt +{ + /** @var DeclareDeclare[] List of declares */ + public $declares; + /** @var Node\Stmt[]|null Statements */ + public $stmts; + + /** + * Constructs a declare node. + * + * @param DeclareDeclare[] $declares List of declares + * @param Node\Stmt[]|null $stmts Statements + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php new file mode 100644 index 0000000000000000000000000000000000000000..78e90da03a083c416b293662e1e4eee6d8466e37 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Do_ extends Node\Stmt +{ + /** @var Node\Stmt[] Statements */ + public $stmts; + /** @var Node\Expr Condition */ + public $cond; + + /** + * Constructs a do while node. + * + * @param Node\Expr $cond Condition + * @param Node\Stmt[] $stmts Statements + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php new file mode 100644 index 0000000000000000000000000000000000000000..7cc50d5d6e805aab34a2485651487eb41dcbddd0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Echo_ extends Node\Stmt +{ + /** @var Node\Expr[] Expressions */ + public $exprs; + + /** + * Constructs an echo node. + * + * @param Node\Expr[] $exprs Expressions + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php new file mode 100644 index 0000000000000000000000000000000000000000..eef1ece3242585e0d31a98a67a834a3b0f1019c7 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class ElseIf_ extends Node\Stmt +{ + /** @var Node\Expr Condition */ + public $cond; + /** @var Node\Stmt[] Statements */ + public $stmts; + + /** + * Constructs an elseif node. + * + * @param Node\Expr $cond Condition + * @param Node\Stmt[] $stmts Statements + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php new file mode 100644 index 0000000000000000000000000000000000000000..0e61778e26021b4d739ff8517b3e5f00f5adef90 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Else_ extends Node\Stmt +{ + /** @var Node\Stmt[] Statements */ + public $stmts; + + /** + * Constructs an else node. + * + * @param Node\Stmt[] $stmts Statements + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php new file mode 100644 index 0000000000000000000000000000000000000000..99d1687deddbb37a41ac19e79324f3aee7091965 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php @@ -0,0 +1,33 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +/** + * Represents statements of type "expr;" + */ +class Expression extends Node\Stmt +{ + /** @var Node\Expr Expression */ + public $expr; + + /** + * Constructs an expression statement. + * + * @param Node\Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php new file mode 100644 index 0000000000000000000000000000000000000000..d55b8b687269b3eb42dd8493dd54f928a68d070a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Finally_ extends Node\Stmt +{ + /** @var Node\Stmt[] Statements */ + public $stmts; + + /** + * Constructs a finally node. + * + * @param Node\Stmt[] $stmts Statements + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php new file mode 100644 index 0000000000000000000000000000000000000000..1323d37cf34ce5f5723c7fe16d2b0b27c6a89562 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php @@ -0,0 +1,43 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class For_ extends Node\Stmt +{ + /** @var Node\Expr[] Init expressions */ + public $init; + /** @var Node\Expr[] Loop conditions */ + public $cond; + /** @var Node\Expr[] Loop expressions */ + public $loop; + /** @var Node\Stmt[] Statements */ + public $stmts; + + /** + * Constructs a for loop node. + * + * @param array $subNodes Array of the following optional subnodes: + * 'init' => 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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php new file mode 100644 index 0000000000000000000000000000000000000000..0556a7ce5f6a8061cebd067bff90d696d58d3ea6 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php @@ -0,0 +1,47 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Foreach_ extends Node\Stmt +{ + /** @var Node\Expr Expression to iterate */ + public $expr; + /** @var null|Node\Expr Variable to assign key to */ + public $keyVar; + /** @var bool Whether to assign value by reference */ + public $byRef; + /** @var Node\Expr Variable to assign value to */ + public $valueVar; + /** @var Node\Stmt[] Statements */ + public $stmts; + + /** + * Constructs a foreach node. + * + * @param Node\Expr $expr Expression to iterate + * @param Node\Expr $valueVar Variable to assign value to + * @param array $subNodes Array of the following optional subnodes: + * 'keyVar' => 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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php new file mode 100644 index 0000000000000000000000000000000000000000..f08481fae1c40e789ef32c6650d26b1b16dd200d --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php @@ -0,0 +1,77 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; +use PhpParser\Node\FunctionLike; + +/** + * @property Node\Name $namespacedName Namespaced name (if using NameResolver) + */ +class Function_ extends Node\Stmt implements FunctionLike +{ + /** @var bool Whether function returns by reference */ + public $byRef; + /** @var Node\Identifier Name */ + public $name; + /** @var Node\Param[] Parameters */ + public $params; + /** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */ + public $returnType; + /** @var Node\Stmt[] Statements */ + public $stmts; + /** @var Node\AttributeGroup[] PHP attribute groups */ + public $attrGroups; + + /** + * Constructs a function node. + * + * @param string|Node\Identifier $name Name + * @param array $subNodes Array of the following optional subnodes: + * '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->byRef = $subNodes['byRef'] ?? false; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->params = $subNodes['params'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php new file mode 100644 index 0000000000000000000000000000000000000000..a0022ad9328f5ead35ea7b067f771c70ff388f96 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Global_ extends Node\Stmt +{ + /** @var Node\Expr[] Variables */ + public $vars; + + /** + * Constructs a global variables list node. + * + * @param Node\Expr[] $vars Variables to unset + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php new file mode 100644 index 0000000000000000000000000000000000000000..24a57f7807763aa2f6be834fd485363e1133da4a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php @@ -0,0 +1,31 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node\Identifier; +use PhpParser\Node\Stmt; + +class Goto_ extends Stmt +{ + /** @var Identifier Name of label to jump to */ + public $name; + + /** + * Constructs a goto node. + * + * @param string|Identifier $name Name of label to jump to + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php new file mode 100644 index 0000000000000000000000000000000000000000..24520d2233c77d6eb02238ed55e5e8c0c97612e0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php @@ -0,0 +1,39 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node\Name; +use PhpParser\Node\Stmt; + +class GroupUse extends Stmt +{ + /** @var int Type of group use */ + public $type; + /** @var Name Prefix for uses */ + public $prefix; + /** @var UseUse[] Uses */ + public $uses; + + /** + * Constructs a group use node. + * + * @param Name $prefix Prefix for uses + * @param UseUse[] $uses Uses + * @param int $type Type of group use + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php new file mode 100644 index 0000000000000000000000000000000000000000..8e624e0f1fb4f67b1bf18909b7ff706c0adeac75 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node\Stmt; + +class HaltCompiler extends Stmt +{ + /** @var string Remaining text after halt compiler statement. */ + public $remaining; + + /** + * Constructs a __halt_compiler node. + * + * @param string $remaining Remaining text after halt compiler statement. + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php new file mode 100644 index 0000000000000000000000000000000000000000..a1bae4bf891e1384cf4cbc02fe6b5c344425bb91 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php @@ -0,0 +1,43 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class If_ extends Node\Stmt +{ + /** @var Node\Expr Condition expression */ + public $cond; + /** @var Node\Stmt[] Statements */ + public $stmts; + /** @var ElseIf_[] Elseif clauses */ + public $elseifs; + /** @var null|Else_ Else clause */ + public $else; + + /** + * Constructs an if node. + * + * @param Node\Expr $cond Condition + * @param array $subNodes Array of the following optional subnodes: + * 'stmts' => 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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php new file mode 100644 index 0000000000000000000000000000000000000000..0711d2842c2147c206654a48d7fad8707435ae99 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node\Stmt; + +class InlineHTML extends Stmt +{ + /** @var string String */ + public $value; + + /** + * Constructs an inline HTML node. + * + * @param string $value 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']; + } + + public function getType() : string { + return 'Stmt_InlineHTML'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php new file mode 100644 index 0000000000000000000000000000000000000000..4d587dd4843e7f2bff1ff68aa2fd98f4bcf10b4d --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php @@ -0,0 +1,37 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Interface_ extends ClassLike +{ + /** @var Node\Name[] Extended interfaces */ + public $extends; + + /** + * Constructs a class node. + * + * @param string|Node\Identifier $name Name + * @param array $subNodes Array of the following optional subnodes: + * 'extends' => 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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php new file mode 100644 index 0000000000000000000000000000000000000000..3edcb3be7ead9126dbca75ce20d22f66d1a4a86a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php @@ -0,0 +1,31 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node\Identifier; +use PhpParser\Node\Stmt; + +class Label extends Stmt +{ + /** @var Identifier Name */ + public $name; + + /** + * Constructs a label node. + * + * @param string|Identifier $name Name + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php new file mode 100644 index 0000000000000000000000000000000000000000..c63204577c3fba326847291c56f0ddffae7593d9 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php @@ -0,0 +1,38 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Namespace_ extends Node\Stmt +{ + /* For use in the "kind" attribute */ + const KIND_SEMICOLON = 1; + const KIND_BRACED = 2; + + /** @var null|Node\Name Name */ + public $name; + /** @var Node\Stmt[] Statements */ + public $stmts; + + /** + * Constructs a namespace node. + * + * @param null|Node\Name $name Name + * @param null|Node\Stmt[] $stmts Statements + * @param array $attributes Additional attributes + */ + public function __construct(Node\Name $name = null, $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php new file mode 100644 index 0000000000000000000000000000000000000000..f86f8df7d324df84763fd2c70032750145c3f62d --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php @@ -0,0 +1,17 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +/** Nop/empty statement (;). */ +class Nop extends Node\Stmt +{ + public function getSubNodeNames() : array { + return []; + } + + public function getType() : string { + return 'Stmt_Nop'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php new file mode 100644 index 0000000000000000000000000000000000000000..324345b85f992c9b3f4527831682f40af99ddea6 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php @@ -0,0 +1,83 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name; +use PhpParser\Node\NullableType; +use PhpParser\Node\UnionType; + +class Property extends Node\Stmt +{ + /** @var int Modifiers */ + public $flags; + /** @var PropertyProperty[] Properties */ + public $props; + /** @var null|Identifier|Name|NullableType|UnionType Type declaration */ + public $type; + /** @var Node\AttributeGroup[] PHP attribute groups */ + public $attrGroups; + + /** + * Constructs a class property list node. + * + * @param int $flags Modifiers + * @param PropertyProperty[] $props Properties + * @param array $attributes Additional attributes + * @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration + * @param Node\AttributeGroup[] $attrGroups PHP attribute groups + */ + public function __construct(int $flags, array $props, array $attributes = [], $type = null, array $attrGroups = []) { + $this->attributes = $attributes; + $this->flags = $flags; + $this->props = $props; + $this->type = \is_string($type) ? new Identifier($type) : $type; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'flags', 'type', 'props']; + } + + /** + * Whether the property is explicitly or implicitly public. + * + * @return bool + */ + public function isPublic() : bool { + return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 + || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; + } + + /** + * Whether the property is protected. + * + * @return bool + */ + public function isProtected() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); + } + + /** + * Whether the property is private. + * + * @return bool + */ + public function isPrivate() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); + } + + /** + * Whether the property is static. + * + * @return bool + */ + public function isStatic() : bool { + return (bool) ($this->flags & Class_::MODIFIER_STATIC); + } + + public function getType() : string { + return 'Stmt_Property'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php new file mode 100644 index 0000000000000000000000000000000000000000..205731e20eb772e298b7fec1721f432cf862a4ed --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class PropertyProperty extends Node\Stmt +{ + /** @var Node\VarLikeIdentifier Name */ + public $name; + /** @var null|Node\Expr Default */ + public $default; + + /** + * Constructs a class property node. + * + * @param string|Node\VarLikeIdentifier $name Name + * @param null|Node\Expr $default Default value + * @param array $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 'Stmt_PropertyProperty'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php new file mode 100644 index 0000000000000000000000000000000000000000..efc578c58f6e89ec0cda9e8977d29812ddd99ac0 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Return_ extends Node\Stmt +{ + /** @var null|Node\Expr Expression */ + public $expr; + + /** + * Constructs a return node. + * + * @param null|Node\Expr $expr Expression + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php new file mode 100644 index 0000000000000000000000000000000000000000..29584560d3de3b8a0b17dbd6af5c2b8389cd39da --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php @@ -0,0 +1,37 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; +use PhpParser\Node\Expr; + +class StaticVar extends Node\Stmt +{ + /** @var Expr\Variable Variable */ + public $var; + /** @var null|Node\Expr Default value */ + public $default; + + /** + * Constructs a static variable node. + * + * @param Expr\Variable $var Name + * @param null|Node\Expr $default Default value + * @param array $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 'Stmt_StaticVar'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php new file mode 100644 index 0000000000000000000000000000000000000000..464898ffa66d5cd459a172f0434a2045178cd70b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node\Stmt; + +class Static_ extends Stmt +{ + /** @var StaticVar[] Variable definitions */ + public $vars; + + /** + * Constructs a static variables list node. + * + * @param StaticVar[] $vars Variable definitions + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php new file mode 100644 index 0000000000000000000000000000000000000000..2c8dae02215ed88ba7d918c5d3125df768748cca --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Switch_ extends Node\Stmt +{ + /** @var Node\Expr Condition */ + public $cond; + /** @var Case_[] Case list */ + public $cases; + + /** + * Constructs a case node. + * + * @param Node\Expr $cond Condition + * @param Case_[] $cases Case list + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php new file mode 100644 index 0000000000000000000000000000000000000000..a34e2b362437f8ffc4ec9be123ca59fab938a142 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Throw_ extends Node\Stmt +{ + /** @var Node\Expr Expression */ + public $expr; + + /** + * Constructs a legacy throw statement node. + * + * @param Node\Expr $expr Expression + * @param array $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_Throw'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php new file mode 100644 index 0000000000000000000000000000000000000000..9e97053b40fe5df46c460a03f885763241874a30 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class TraitUse extends Node\Stmt +{ + /** @var Node\Name[] Traits */ + public $traits; + /** @var TraitUseAdaptation[] Adaptations */ + public $adaptations; + + /** + * Constructs a trait use node. + * + * @param Node\Name[] $traits Traits + * @param TraitUseAdaptation[] $adaptations Adaptations + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php new file mode 100644 index 0000000000000000000000000000000000000000..8bdd2c041f582f2df42727cdc3d634d4f731b023 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php @@ -0,0 +1,13 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +abstract class TraitUseAdaptation extends Node\Stmt +{ + /** @var Node\Name|null Trait name */ + public $trait; + /** @var Node\Identifier Method name */ + public $method; +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php new file mode 100644 index 0000000000000000000000000000000000000000..a3bccbd10c5ad70c847c3d6c9c9f379731c04e93 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php @@ -0,0 +1,38 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt\TraitUseAdaptation; + +use PhpParser\Node; + +class Alias extends Node\Stmt\TraitUseAdaptation +{ + /** @var null|int New modifier */ + public $newModifier; + /** @var null|Node\Identifier New name */ + public $newName; + + /** + * Constructs a trait use precedence adaptation node. + * + * @param null|Node\Name $trait Trait name + * @param string|Node\Identifier $method Method name + * @param null|int $newModifier New modifier + * @param null|string|Node\Identifier $newName New name + * @param array $attributes Additional attributes + */ + public function __construct($trait, $method, $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php new file mode 100644 index 0000000000000000000000000000000000000000..80385f64e32b3ce98168e98736abd26749db4906 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt\TraitUseAdaptation; + +use PhpParser\Node; + +class Precedence extends Node\Stmt\TraitUseAdaptation +{ + /** @var Node\Name[] Overwritten traits */ + public $insteadof; + + /** + * Constructs a trait use precedence adaptation node. + * + * @param Node\Name $trait Trait name + * @param string|Node\Identifier $method Method name + * @param Node\Name[] $insteadof Overwritten traits + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php new file mode 100644 index 0000000000000000000000000000000000000000..0cec203ac71bf0a718e07facc0dcafed396a78c6 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php @@ -0,0 +1,32 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Trait_ extends ClassLike +{ + /** + * Constructs a trait node. + * + * @param string|Node\Identifier $name Name + * @param array $subNodes Array of the following optional subnodes: + * '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->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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php new file mode 100644 index 0000000000000000000000000000000000000000..7fc158c570b5b32c2d61933e2a9605d6709b3150 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php @@ -0,0 +1,38 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class TryCatch extends Node\Stmt +{ + /** @var Node\Stmt[] Statements */ + public $stmts; + /** @var Catch_[] Catches */ + public $catches; + /** @var null|Finally_ Optional finally node */ + public $finally; + + /** + * Constructs a try catch node. + * + * @param Node\Stmt[] $stmts Statements + * @param Catch_[] $catches Catches + * @param null|Finally_ $finally Optional finally node + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php new file mode 100644 index 0000000000000000000000000000000000000000..310e427aa201f4d283f17b6e60adf9270ea41df1 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class Unset_ extends Node\Stmt +{ + /** @var Node\Expr[] Variables to unset */ + public $vars; + + /** + * Constructs an unset node. + * + * @param Node\Expr[] $vars Variables to unset + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php new file mode 100644 index 0000000000000000000000000000000000000000..32bd7847da42a0c477003cbb28ebe6587a92a9cf --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php @@ -0,0 +1,52 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; +use PhpParser\Node\Identifier; + +class UseUse extends Node\Stmt +{ + /** @var int One of the Stmt\Use_::TYPE_* constants. Will only differ from TYPE_UNKNOWN for mixed group uses */ + public $type; + /** @var Node\Name Namespace, class, function or constant to alias */ + public $name; + /** @var Identifier|null Alias */ + public $alias; + + /** + * Constructs an alias (use) node. + * + * @param Node\Name $name Namespace/Class to alias + * @param null|string|Identifier $alias Alias + * @param int $type Type of the use element (for mixed group use only) + * @param array $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. + * + * @return Identifier + */ + public function getAlias() : Identifier { + if (null !== $this->alias) { + return $this->alias; + } + + return new Identifier($this->name->getLast()); + } + + public function getType() : string { + return 'Stmt_UseUse'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php new file mode 100644 index 0000000000000000000000000000000000000000..8753da313dd588bbe981f80c846e49dd52a14dd3 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php @@ -0,0 +1,47 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node\Stmt; + +class Use_ extends Stmt +{ + /** + * Unknown type. Both Stmt\Use_ / Stmt\GroupUse and Stmt\UseUse have a $type property, one of them will always be + * TYPE_UNKNOWN while the other has one of the three other possible types. For normal use statements the type on the + * Stmt\UseUse is unknown. It's only the other way around for mixed group use declarations. + */ + const TYPE_UNKNOWN = 0; + /** Class or namespace import */ + const TYPE_NORMAL = 1; + /** Function import */ + const TYPE_FUNCTION = 2; + /** Constant import */ + const TYPE_CONSTANT = 3; + + /** @var int Type of alias */ + public $type; + /** @var UseUse[] Aliases */ + public $uses; + + /** + * Constructs an alias (use) list node. + * + * @param UseUse[] $uses Aliases + * @param int $type Type of alias + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php new file mode 100644 index 0000000000000000000000000000000000000000..f41034f8c24b624f3a53e8af4559a5ec908e12e7 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php @@ -0,0 +1,34 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node\Stmt; + +use PhpParser\Node; + +class While_ extends Node\Stmt +{ + /** @var Node\Expr Condition */ + public $cond; + /** @var Node\Stmt[] Statements */ + public $stmts; + + /** + * Constructs a while node. + * + * @param Node\Expr $cond Condition + * @param Node\Stmt[] $stmts Statements + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/UnionType.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/UnionType.php new file mode 100644 index 0000000000000000000000000000000000000000..c8f45235d6c627d0244eabc2674c231003e43662 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/UnionType.php @@ -0,0 +1,30 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +use PhpParser\NodeAbstract; + +class UnionType extends NodeAbstract +{ + /** @var (Identifier|Name)[] Types */ + public $types; + + /** + * Constructs a union type. + * + * @param (Identifier|Name)[] $types Types + * @param array $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/lib/composer/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php b/lib/composer/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php new file mode 100644 index 0000000000000000000000000000000000000000..a30807a6d58237f802f1768810ae5fc016de4357 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php @@ -0,0 +1,17 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Node; + +/** + * Represents a name that is written in source code with a leading dollar, + * but is not a proper variable. The leading dollar is not stored as part of the name. + * + * Examples: Names in property declarations are formatted as variables. Names in static property + * lookups are also formatted as variables. + */ +class VarLikeIdentifier extends Identifier +{ + public function getType() : string { + return 'VarLikeIdentifier'; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/NodeAbstract.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeAbstract.php new file mode 100644 index 0000000000000000000000000000000000000000..04514da116ce25961fb73f86a6f66eff85ef65ad --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeAbstract.php @@ -0,0 +1,178 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +abstract class NodeAbstract implements Node, \JsonSerializable +{ + protected $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() { + $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) { + $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) { + $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) { + $this->attributes = $attributes; + } + + /** + * @return array + */ + public function jsonSerialize() : array { + return ['nodeType' => $this->getType()] + get_object_vars($this); + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/NodeDumper.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeDumper.php new file mode 100644 index 0000000000000000000000000000000000000000..197ebc144cad3fc6c3102fbc22740ee4f03814a7 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeDumper.php @@ -0,0 +1,203 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +use PhpParser\Node\Expr\Include_; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\GroupUse; +use PhpParser\Node\Stmt\Use_; +use PhpParser\Node\Stmt\UseUse; + +class NodeDumper +{ + private $dumpComments; + private $dumpPositions; + private $code; + + /** + * 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(). + * + * @param array $options Options (see description) + */ + public function __construct(array $options = []) { + $this->dumpComments = !empty($options['dumpComments']); + $this->dumpPositions = !empty($options['dumpPositions']); + } + + /** + * 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; + return $this->dumpRecursive($node); + } + + protected function dumpRecursive($node) { + if ($node instanceof Node) { + $r = $node->getType(); + if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) { + $r .= $p; + } + $r .= '('; + + foreach ($node->getSubNodeNames() as $key) { + $r .= "\n " . $key . ': '; + + $value = $node->$key; + if (null === $value) { + $r .= 'null'; + } elseif (false === $value) { + $r .= 'false'; + } elseif (true === $value) { + $r .= 'true'; + } elseif (is_scalar($value)) { + if ('flags' === $key || 'newModifier' === $key) { + $r .= $this->dumpFlags($value); + } elseif ('type' === $key && $node instanceof Include_) { + $r .= $this->dumpIncludeType($value); + } elseif ('type' === $key + && ($node instanceof Use_ || $node instanceof UseUse || $node instanceof GroupUse)) { + $r .= $this->dumpUseType($value); + } else { + $r .= $value; + } + } else { + $r .= str_replace("\n", "\n ", $this->dumpRecursive($value)); + } + } + + if ($this->dumpComments && $comments = $node->getComments()) { + $r .= "\n comments: " . str_replace("\n", "\n ", $this->dumpRecursive($comments)); + } + } elseif (is_array($node)) { + $r = 'array('; + + foreach ($node as $key => $value) { + $r .= "\n " . $key . ': '; + + if (null === $value) { + $r .= 'null'; + } elseif (false === $value) { + $r .= 'false'; + } elseif (true === $value) { + $r .= 'true'; + } elseif (is_scalar($value)) { + $r .= $value; + } else { + $r .= str_replace("\n", "\n ", $this->dumpRecursive($value)); + } + } + } elseif ($node instanceof Comment) { + return $node->getReformattedText(); + } else { + throw new \InvalidArgumentException('Can only dump nodes and arrays.'); + } + + return $r . "\n)"; + } + + protected function dumpFlags($flags) { + $strs = []; + if ($flags & Class_::MODIFIER_PUBLIC) { + $strs[] = 'MODIFIER_PUBLIC'; + } + if ($flags & Class_::MODIFIER_PROTECTED) { + $strs[] = 'MODIFIER_PROTECTED'; + } + if ($flags & Class_::MODIFIER_PRIVATE) { + $strs[] = 'MODIFIER_PRIVATE'; + } + if ($flags & Class_::MODIFIER_ABSTRACT) { + $strs[] = 'MODIFIER_ABSTRACT'; + } + if ($flags & Class_::MODIFIER_STATIC) { + $strs[] = 'MODIFIER_STATIC'; + } + if ($flags & Class_::MODIFIER_FINAL) { + $strs[] = 'MODIFIER_FINAL'; + } + + if ($strs) { + return implode(' | ', $strs) . ' (' . $flags . ')'; + } else { + return $flags; + } + } + + protected function dumpIncludeType($type) { + $map = [ + Include_::TYPE_INCLUDE => 'TYPE_INCLUDE', + Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE', + Include_::TYPE_REQUIRE => 'TYPE_REQUIRE', + Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE', + ]; + + if (!isset($map[$type])) { + return $type; + } + return $map[$type] . ' (' . $type . ')'; + } + + protected function dumpUseType($type) { + $map = [ + Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN', + Use_::TYPE_NORMAL => 'TYPE_NORMAL', + Use_::TYPE_FUNCTION => 'TYPE_FUNCTION', + Use_::TYPE_CONSTANT => 'TYPE_CONSTANT', + ]; + + if (!isset($map[$type])) { + return $type; + } + return $map[$type] . ' (' . $type . ')'; + } + + /** + * 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) { + 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($code, $pos) { + 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/lib/composer/nikic/php-parser/lib/PhpParser/NodeFinder.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeFinder.php new file mode 100644 index 0000000000000000000000000000000000000000..2e7cfdad4dc982a76cf1e622a4bff25d5cdf0b7b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeFinder.php @@ -0,0 +1,81 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +use PhpParser\NodeVisitor\FindingVisitor; +use PhpParser\NodeVisitor\FirstFindingVisitor; + +class NodeFinder +{ + /** + * Find all nodes 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 Node[] Found nodes satisfying the filter callback + */ + public function find($nodes, callable $filter) : array { + if (!is_array($nodes)) { + $nodes = [$nodes]; + } + + $visitor = new FindingVisitor($filter); + + $traverser = new NodeTraverser; + $traverser->addVisitor($visitor); + $traverser->traverse($nodes); + + return $visitor->getFoundNodes(); + } + + /** + * Find all nodes that are instances of a certain class. + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param string $class Class name + * + * @return Node[] 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) { + if (!is_array($nodes)) { + $nodes = [$nodes]; + } + + $visitor = new FirstFindingVisitor($filter); + + $traverser = new NodeTraverser; + $traverser->addVisitor($visitor); + $traverser->traverse($nodes); + + return $visitor->getFoundNode(); + } + + /** + * Find first node that is an instance of a certain class. + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param string $class Class name + * + * @return null|Node Found node, which is an instance of $class (or null if none found) + */ + public function findFirstInstanceOf($nodes, string $class) { + return $this->findFirst($nodes, function ($node) use ($class) { + return $node instanceof $class; + }); + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/NodeTraverser.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeTraverser.php new file mode 100644 index 0000000000000000000000000000000000000000..97d45bdaaa7c95ba7ecdae9580a83e3239732bd1 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeTraverser.php @@ -0,0 +1,291 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +class NodeTraverser implements NodeTraverserInterface +{ + /** + * If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes + * of the current node will not be traversed for any visitors. + * + * For subsequent visitors enterNode() will still be called on the current + * node and leaveNode() will also be invoked for the current node. + */ + const DONT_TRAVERSE_CHILDREN = 1; + + /** + * If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns + * STOP_TRAVERSAL, traversal is aborted. + * + * The afterTraverse() method will still be invoked. + */ + const STOP_TRAVERSAL = 2; + + /** + * If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs + * in an array, it will be removed from the array. + * + * For subsequent visitors leaveNode() will still be invoked for the + * removed node. + */ + const REMOVE_NODE = 3; + + /** + * If NodeVisitor::enterNode() returns DONT_TRAVERSE_CURRENT_AND_CHILDREN, child nodes + * of the current node will not be traversed for any visitors. + * + * For subsequent visitors enterNode() will not be called as well. + * leaveNode() will be invoked for visitors that has enterNode() method invoked. + */ + const DONT_TRAVERSE_CURRENT_AND_CHILDREN = 4; + + /** @var NodeVisitor[] Visitors */ + protected $visitors = []; + + /** @var bool Whether traversal should be stopped */ + protected $stopTraversal; + + public function __construct() { + // for BC + } + + /** + * Adds a visitor. + * + * @param NodeVisitor $visitor Visitor to add + */ + public function addVisitor(NodeVisitor $visitor) { + $this->visitors[] = $visitor; + } + + /** + * Removes an added visitor. + * + * @param NodeVisitor $visitor + */ + public function removeVisitor(NodeVisitor $visitor) { + foreach ($this->visitors as $index => $storedVisitor) { + if ($storedVisitor === $visitor) { + unset($this->visitors[$index]); + break; + } + } + } + + /** + * 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); + + foreach ($this->visitors as $visitor) { + if (null !== $return = $visitor->afterTraverse($nodes)) { + $nodes = $return; + } + } + + return $nodes; + } + + /** + * Recursively traverse a node. + * + * @param Node $node Node to traverse. + * + * @return Node Result of traversal (may be original node or new one) + */ + protected function traverseNode(Node $node) : Node { + foreach ($node->getSubNodeNames() as $name) { + $subNode =& $node->$name; + + if (\is_array($subNode)) { + $subNode = $this->traverseArray($subNode); + if ($this->stopTraversal) { + break; + } + } elseif ($subNode instanceof Node) { + $traverseChildren = true; + $breakVisitorIndex = null; + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($subNode); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $return; + } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = false; + $breakVisitorIndex = $visitorIndex; + break; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $subNode = $this->traverseNode($subNode); + if ($this->stopTraversal) { + break; + } + } + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->leaveNode($subNode); + + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $return; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } 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) + ); + } + } + + if ($breakVisitorIndex === $visitorIndex) { + break; + } + } + } + } + + return $node; + } + + /** + * 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; + $breakVisitorIndex = null; + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($node); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $node = $return; + } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = false; + $breakVisitorIndex = $visitorIndex; + break; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $node = $this->traverseNode($node); + if ($this->stopTraversal) { + break; + } + } + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->leaveNode($node); + + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $node = $return; + } elseif (\is_array($return)) { + $doNodes[] = [$i, $return]; + break; + } elseif (self::REMOVE_NODE === $return) { + $doNodes[] = [$i, []]; + break; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (false === $return) { + throw new \LogicException( + 'bool(false) return from leaveNode() no longer supported. ' . + 'Return NodeTraverser::REMOVE_NODE instead' + ); + } else { + throw new \LogicException( + 'leaveNode() returned invalid value of type ' . gettype($return) + ); + } + } + + if ($breakVisitorIndex === $visitorIndex) { + break; + } + } + } 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($old, $new) { + 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/lib/composer/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..77ff3d27f65c61f555f75fc9595b172fae2b2165 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php @@ -0,0 +1,29 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +interface NodeTraverserInterface +{ + /** + * Adds a visitor. + * + * @param NodeVisitor $visitor Visitor to add + */ + public function addVisitor(NodeVisitor $visitor); + + /** + * Removes an added visitor. + * + * @param NodeVisitor $visitor + */ + public function removeVisitor(NodeVisitor $visitor); + + /** + * 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; +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..f1f7f3e3e34e0ed1fb84be2da83d4ad480a9e9b4 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor.php @@ -0,0 +1,72 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +interface NodeVisitor +{ + /** + * Called once before 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 beforeTraverse(array $nodes); + + /** + * Called when entering a node. + * + * Return value semantics: + * * null + * => $node stays as-is + * * NodeTraverser::DONT_TRAVERSE_CHILDREN + * => Children of $node are not traversed. $node stays as-is + * * NodeTraverser::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 Replacement node (or special return value) + */ + public function enterNode(Node $node); + + /** + * Called when leaving a node. + * + * Return value semantics: + * * null + * => $node stays as-is + * * NodeTraverser::REMOVE_NODE + * => $node is removed from the parent array + * * NodeTraverser::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/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..a85fa493b099c89ee024a987880175dd694458f4 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php @@ -0,0 +1,20 @@ +<?php declare(strict_types=1); + +namespace PhpParser\NodeVisitor; + +use PhpParser\Node; +use PhpParser\NodeVisitorAbstract; + +/** + * Visitor cloning all nodes and linking to the original nodes using an attribute. + * + * This visitor is required to perform format-preserving pretty prints. + */ +class CloningVisitor extends NodeVisitorAbstract +{ + public function enterNode(Node $origNode) { + $node = clone $origNode; + $node->setAttribute('origNode', $origNode); + return $node; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..9531edbce7c4dcbf3e6206444a20676e24c3acab --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php @@ -0,0 +1,48 @@ +<?php declare(strict_types=1); + +namespace PhpParser\NodeVisitor; + +use PhpParser\Node; +use PhpParser\NodeVisitorAbstract; + +/** + * This visitor can be used to find and collect all nodes satisfying some criterion determined by + * a filter callback. + */ +class FindingVisitor extends NodeVisitorAbstract +{ + /** @var callable Filter callback */ + protected $filterCallback; + /** @var Node[] Found nodes */ + protected $foundNodes; + + public function __construct(callable $filterCallback) { + $this->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) { + $this->foundNodes = []; + + return null; + } + + public function enterNode(Node $node) { + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNodes[] = $node; + } + + return null; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..596a7d7fd5b33f69c338f4ed08708517bfd8dd96 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php @@ -0,0 +1,50 @@ +<?php declare(strict_types=1); + +namespace PhpParser\NodeVisitor; + +use PhpParser\Node; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; + +/** + * This visitor can be used to find the first node satisfying some criterion determined by + * a filter callback. + */ +class FirstFindingVisitor extends NodeVisitorAbstract +{ + /** @var callable Filter callback */ + protected $filterCallback; + /** @var null|Node Found node */ + protected $foundNode; + + public function __construct(callable $filterCallback) { + $this->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() { + return $this->foundNode; + } + + public function beforeTraverse(array $nodes) { + $this->foundNode = null; + + return null; + } + + public function enterNode(Node $node) { + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNode = $node; + return NodeTraverser::STOP_TRAVERSAL; + } + + return null; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..c55532a5eae24c6734360a3c437e7cbfe1599f07 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php @@ -0,0 +1,246 @@ +<?php declare(strict_types=1); + +namespace PhpParser\NodeVisitor; + +use PhpParser\ErrorHandler; +use PhpParser\NameContext; +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Stmt; +use PhpParser\NodeVisitorAbstract; + +class NameResolver extends NodeVisitorAbstract +{ + /** @var NameContext Naming context */ + protected $nameContext; + + /** @var bool Whether to preserve original names */ + protected $preserveOriginalNames; + + /** @var bool Whether to replace resolved nodes in place, or to add resolvedNode attributes */ + protected $replaceNodes; + + /** + * Constructs a name resolution visitor. + * + * Options: + * * preserveOriginalNames (default false): An "originalName" attribute will be added to + * all name nodes that underwent resolution. + * * replaceNodes (default true): Resolved names are replaced in-place. Otherwise, a + * resolvedName attribute is added. (Names that cannot be statically resolved receive a + * namespacedName attribute, as usual.) + * + * @param ErrorHandler|null $errorHandler Error handler + * @param array $options Options + */ + public function __construct(ErrorHandler $errorHandler = null, array $options = []) { + $this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing); + $this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false; + $this->replaceNodes = $options['replaceNodes'] ?? true; + } + + /** + * Get name resolution context. + * + * @return NameContext + */ + public function getNameContext() : NameContext { + return $this->nameContext; + } + + public function beforeTraverse(array $nodes) { + $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); + } + } elseif ($node instanceof Stmt\Interface_) { + foreach ($node->extends 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); + } + } else if ($node instanceof Stmt\ClassConst) { + $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; + } + + private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) { + // 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 $node */ + private function resolveSignature($node) { + foreach ($node->params as $param) { + $param->type = $this->resolveType($param->type); + $this->resolveAttrGroups($param); + } + $node->returnType = $this->resolveType($node->returnType); + } + + private function resolveType($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) { + 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 int $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) { + return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL); + } + + protected function addNamespacedName(Node $node) { + $node->namespacedName = Name::concat( + $this->nameContext->getNamespace(), (string) $node->name); + } + + protected function resolveAttrGroups(Node $node) + { + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $attr->name = $this->resolveClassName($attr->name); + } + } + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..ea372e5b991c3e601c20d2fd7ccbe40469179a2a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php @@ -0,0 +1,52 @@ +<?php declare(strict_types=1); + +namespace PhpParser\NodeVisitor; + +use PhpParser\Node; +use PhpParser\NodeVisitorAbstract; + +/** + * Visitor that connects a child node to its parent node + * as well as its sibling nodes. + * + * On the child node, the parent node can be accessed through + * <code>$node->getAttribute('parent')</code>, the previous + * node can be accessed through <code>$node->getAttribute('previous')</code>, + * and the next node can be accessed through <code>$node->getAttribute('next')</code>. + */ +final class NodeConnectingVisitor extends NodeVisitorAbstract +{ + /** + * @var Node[] + */ + private $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/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..b98d2bfa6fc3c271150e94981c7248061b6504c6 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php @@ -0,0 +1,41 @@ +<?php declare(strict_types=1); + +namespace PhpParser\NodeVisitor; + +use function array_pop; +use function count; +use PhpParser\Node; +use PhpParser\NodeVisitorAbstract; + +/** + * Visitor that connects a child node to its parent node. + * + * On the child node, the parent node can be accessed through + * <code>$node->getAttribute('parent')</code>. + */ +final class ParentConnectingVisitor extends NodeVisitorAbstract +{ + /** + * @var Node[] + */ + private $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/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php new file mode 100644 index 0000000000000000000000000000000000000000..d378d6709628e4df1e216af18423d6960ab3644c --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php @@ -0,0 +1,25 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +/** + * @codeCoverageIgnore + */ +class NodeVisitorAbstract implements NodeVisitor +{ + public function beforeTraverse(array $nodes) { + return null; + } + + public function enterNode(Node $node) { + return null; + } + + public function leaveNode(Node $node) { + return null; + } + + public function afterTraverse(array $nodes) { + return null; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Parser.php b/lib/composer/nikic/php-parser/lib/PhpParser/Parser.php new file mode 100644 index 0000000000000000000000000000000000000000..8956c767187d317be1fc4621794e5d7ba30a7d0a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Parser.php @@ -0,0 +1,18 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +interface Parser +{ + /** + * Parses PHP code into a node tree. + * + * @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); +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Multiple.php b/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Multiple.php new file mode 100644 index 0000000000000000000000000000000000000000..77fd1f3fbb63ce9fda87dc1a340a378f71209830 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Multiple.php @@ -0,0 +1,55 @@ +<?php declare(strict_types=1); + +namespace PhpParser\Parser; + +use PhpParser\Error; +use PhpParser\ErrorHandler; +use PhpParser\Parser; + +class Multiple implements Parser +{ + /** @var Parser[] List of parsers to try, in order of preference */ + private $parsers; + + /** + * Create a parser which will try multiple parsers in an order of preference. + * + * Parsers will be invoked in the order they're provided to the constructor. If one of the + * parsers runs without throwing, it's output is returned. Otherwise the exception that the + * first parser generated is thrown. + * + * @param Parser[] $parsers + */ + public function __construct(array $parsers) { + $this->parsers = $parsers; + } + + public function parse(string $code, ErrorHandler $errorHandler = null) { + if (null === $errorHandler) { + $errorHandler = new ErrorHandler\Throwing; + } + + list($firstStmts, $firstError) = $this->tryParse($this->parsers[0], $errorHandler, $code); + if ($firstError === null) { + return $firstStmts; + } + + for ($i = 1, $c = count($this->parsers); $i < $c; ++$i) { + list($stmts, $error) = $this->tryParse($this->parsers[$i], $errorHandler, $code); + if ($error === null) { + return $stmts; + } + } + + throw $firstError; + } + + private function tryParse(Parser $parser, ErrorHandler $errorHandler, $code) { + $stmts = null; + $error = null; + try { + $stmts = $parser->parse($code, $errorHandler); + } catch (Error $error) {} + return [$stmts, $error]; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Php5.php b/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Php5.php new file mode 100644 index 0000000000000000000000000000000000000000..c67a5e707a0f2001d9d09f8d99c104c6abf723fc --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Php5.php @@ -0,0 +1,2629 @@ +<?php + +namespace PhpParser\Parser; + +use PhpParser\Error; +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\Scalar; +use PhpParser\Node\Stmt; + +/* This is an automatically GENERATED file, which should not be manually edited. + * Instead edit one of the following: + * * the grammar files grammar/php5.y or grammar/php7.y + * * the skeleton file grammar/parser.template + * * the preprocessing script grammar/rebuildParsers.php + */ +class Php5 extends \PhpParser\ParserAbstract +{ + protected $tokenToSymbolMapSize = 392; + protected $actionTableSize = 1069; + protected $gotoTableSize = 580; + + protected $invalidSymbol = 165; + protected $errorSymbol = 1; + protected $defaultAction = -32766; + protected $unexpectedTokenRule = 32767; + + protected $YY2TBLSTATE = 405; + protected $numNonLeafStates = 658; + + protected $symbolToName = array( + "EOF", + "error", + "T_THROW", + "T_INCLUDE", + "T_INCLUDE_ONCE", + "T_EVAL", + "T_REQUIRE", + "T_REQUIRE_ONCE", + "','", + "T_LOGICAL_OR", + "T_LOGICAL_XOR", + "T_LOGICAL_AND", + "T_PRINT", + "T_YIELD", + "T_DOUBLE_ARROW", + "T_YIELD_FROM", + "'='", + "T_PLUS_EQUAL", + "T_MINUS_EQUAL", + "T_MUL_EQUAL", + "T_DIV_EQUAL", + "T_CONCAT_EQUAL", + "T_MOD_EQUAL", + "T_AND_EQUAL", + "T_OR_EQUAL", + "T_XOR_EQUAL", + "T_SL_EQUAL", + "T_SR_EQUAL", + "T_POW_EQUAL", + "T_COALESCE_EQUAL", + "'?'", + "':'", + "T_COALESCE", + "T_BOOLEAN_OR", + "T_BOOLEAN_AND", + "'|'", + "'^'", + "'&'", + "T_IS_EQUAL", + "T_IS_NOT_EQUAL", + "T_IS_IDENTICAL", + "T_IS_NOT_IDENTICAL", + "T_SPACESHIP", + "'<'", + "T_IS_SMALLER_OR_EQUAL", + "'>'", + "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_VAR", + "T_UNSET", + "T_ISSET", + "T_EMPTY", + "T_HALT_COMPILER", + "T_CLASS", + "T_TRAIT", + "T_INTERFACE", + "T_EXTENDS", + "T_IMPLEMENTS", + "T_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_NULLSAFE_OBJECT_OPERATOR", + "T_ATTRIBUTE" + ); + + protected $tokenToSymbol = array( + 0, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 55, 162, 165, 159, 54, 37, 165, + 157, 158, 52, 49, 8, 50, 51, 53, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 31, 154, + 43, 16, 45, 30, 67, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 69, 165, 161, 36, 165, 160, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 155, 35, 156, 57, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 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, 38, 39, 40, 41, + 42, 44, 46, 47, 48, 56, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 68, 70, 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, 163, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, + 153, 164 + ); + + protected $action = array( + 693, 663, 664, 665, 666, 667, 282, 668, 669, 670, + 706, 707, 221, 222, 223, 224, 225, 226, 227, 228, + 229, 0, 230, 231, 232, 233, 234, 235, 236, 237, + 238, 239, 240, 241,-32766,-32766,-32766,-32766,-32766,-32766, + -32766,-32766,-32767,-32767,-32767,-32767, 27, 242, 243,-32766, + -32766,-32766,-32766,-32766, 671,-32766, 333,-32766,-32766,-32766, + -32766,-32766,-32766,-32767,-32767,-32767,-32767,-32767, 672, 673, + 674, 675, 676, 677, 678, 1034, 816, 740, 941, 942, + 943, 940, 939, 938, 679, 680, 681, 682, 683, 684, + 685, 686, 687, 688, 689, 709, 732, 710, 711, 712, + 713, 701, 702, 703, 731, 704, 705, 690, 691, 692, + 694, 695, 696, 734, 735, 736, 737, 738, 739, 697, + 698, 699, 700, 730, 721, 719, 720, 716, 717, 437, + 708, 714, 715, 722, 723, 725, 724, 726, 727, 55, + 56, 417, 57, 58, 718, 729, 728, 28, 59, 60, + -220, 61,-32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766, + -32766, 36,-32767,-32767,-32767,-32767, 1034, 35, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118,-32766,-32766,-32766,-32766, 62, 63, 1034, 125, 285, + 292, 64, 748, 65, 290, 291, 66, 67, 68, 69, + 70, 71, 72, 73, 763, 25, 298, 74, 409, 973, + 975, 294, 294, 1086, 1087, 1064, 796, 748, 218, 219, + 220, 465,-32766,-32766,-32766, 742, 864, 817, 54, 807, + 9,-32766,-32766,-32766, 760, 320, 761, 410, 10, 202, + 246, 428, 209,-32766, 933,-32766,-32766,-32766,-32766,-32766, + -32766, 488,-32766, 438,-32766,-32766,-32766,-32766,-32766, 473, + 474, 941, 942, 943, 940, 939, 938,-32766, 475, 476, + 337, 1092, 1093, 1094, 1095, 1089, 1090, 315, 1214, -255, + 747, 1215, -505, 1096, 1091, 888, 889, 1066, 1065, 1067, + 218, 219, 220, 41, 414, 337, 330, 895, 332, 418, + -126, -126, -126, 75, 52, 464, -4, 817, 54, 805, + -224, 202, 40, 21, 419, -126, 466, -126, 467, -126, + 468, -126, 359, 420, 128, 128, 748, 1171, 31, 32, + 421, 422, 1034, 894, 33, 469,-32766,-32766,-32766, 1186, + 351, 352, 470, 471,-32766,-32766,-32766, 309, 472, 865, + 323, 788, 835, 423, 424,-32767,-32767,-32767,-32767, 97, + 98, 99, 100, 101, 615,-32766, 313,-32766,-32766,-32766, + -32766, 354, 1185, 1171, 218, 219, 220, 475, 748, 418, + 819, 629, -126, 297, 915, 464, 817, 54,-32766, 805, + 124, 748, 40, 21, 419, 202, 466, 48, 467, 534, + 468, 129, 429, 420, 337, 341, 888, 889, 31, 32, + 421, 422, 416, 405, 33, 469,-32766,-32766, 311, 298, + 351, 352, 470, 471,-32766,-32766,-32766, 748, 472, 412, + 748, 752, 835, 423, 424, 338, 1066, 1065, 1067, 219, + 220, 919, 1136, 296, 20,-32766, 576,-32766,-32766,-32766, + 742, 341, 342, 413, 429, 1064, 337, 512, 418, 202, + 819, 629, -4, 1034, 464, 817, 54, 49, 805, 337, + 762, 40, 21, 419, 51, 466, 1034, 467, 475, 468, + 340, 748, 420, 120, -205, -205, -205, 31, 32, 421, + 422, 1062,-32766, 33, 469,-32766,-32766,-32766, 744, 351, + 352, 470, 471, 429, 1098, 337, 429, 472, 337, 1034, + 788, 835, 423, 424, 415, 1098,-32766, 802,-32766,-32766, + 102, 103, 104, 1137, 303, 202, 130, 1066, 1065, 1067, + 337, 123, 239, 240, 241, 748, 105, 418, 1205, 819, + 629, -205, 440, 464,-32766,-32766,-32766, 805, 242, 243, + 40, 21, 419, 121, 466, 126, 467, 429, 468, 337, + 122, 420, 1052, -204, -204, -204, 31, 32, 421, 422, + 1034, 745, 33, 469, 220, 759, 817, 54, 351, 352, + 470, 471, 218, 219, 220, 119, 472, 244, 127, 788, + 835, 423, 424, 202,-32766,-32766,-32766, 30, 293, 803, + 79, 80, 81, 202, 798, 210, 632, 99, 100, 101, + 236, 237, 238, 817, 54,-32766, 211, 800, 819, 629, + -204, 34, 1034, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, + 99, 100, 101, 102, 103, 104, 286, 303, 418, 1034, + 817, 54,-32766,-32766, 464, 218, 219, 220, 805, 105, + 914, 40, 21, 419, 78, 466, 212, 467, 337, 468, + 133, 247, 420, 295, 567, 248, 202, 31, 32, 421, + 633, 242, 243, 33, 469, 418, 249, 817, 54, 351, + 352, 464, 760, -84, 761, 805, 310, 472, 40, 21, + 419,-32766, 466, 640, 467, 643, 468, 447, 22, 420, + 815, 452, 584, 132, 31, 32, 421, 637, 134, 364, + 33, 469, 418, 303, 817, 54, 351, 352, 464, 819, + 629, 828, 805, 43, 472, 40, 21, 419, 44, 466, + 45, 467, 46, 468, 591, 592, 420, 753, 635, 930, + 649, 31, 32, 421, 641, 918, 657, 33, 469, 418, + 105, 817, 54, 351, 352, 464, 819, 629, 47, 805, + 50, 472, 40, 21, 419, 53, 466, 131, 467, 298, + 468, 599, 742, 420,-32766, -274, 516, 570, 31, 32, + 421, 646, 748, 946, 33, 469, 418, 589, 436,-32766, + 351, 352, 464, 819, 629, 623, 805, 836, 472, 40, + 21, 419, 611, 466, -82, 467, 603, 468, 11, 573, + 420, 439, 456, 281, 318, 31, 32, 421, 588, 432, + 321, 33, 469, 418, -414, 458, 322, 351, 352, 464, + 851, 629, 837, 805, -505, 472, 40, 21, 419, 654, + 466, 38, 467, 24, 468, 0, 0, 420, 319, 0, + -405, 0, 31, 32, 421, 245, 312, 314, 33, 469, + -506, 0, 0, 1097, 351, 352, 1143, 819, 629, 0, + 0, 527, 472, 213, 214, 6, 7, 12, 14, 215, + 363, 216, -415, 558, 789, -221, 830, 0, 0, 747, + 0, 0, 0, 207, 39, 652, 653, 758, 806, 814, + 793, 1086, 1087, 808, 819, 629, 213, 214, 867, 1088, + 858, 859, 215, 791, 216, 852, 849, 847, 925, 926, + 923, 813, 797, 799, 801, 804, 207, 922, 756, 757, + 924, 287, 78, 331, 1086, 1087, 353, 630, 634, 636, + 638, 639, 1088, 642, 644, 645, 647, 648, 631, 1142, + 1211, 1213, 755, 834, 754, 833, 1212, 554, 832, 1092, + 1093, 1094, 1095, 1089, 1090, 388, 1048, 824, 1036, 831, + 1037, 1096, 1091, 822, 931, 856, 857, 451, 1210, 1179, + 0, 217, 1177, 1162, 1175, 1077, 906, 1183, 1173, 0, + 554, 26, 1092, 1093, 1094, 1095, 1089, 1090, 388, 29, + 37, 42, 76, 77, 1096, 1091, 208, 284, 288, 289, + 304, 305, 306, 307, 217, 335, 406, 408, 0, -220, + 16, 17, 18, 383, 448, 455, 457, 462, 548, 620, + 1039, 1042, 896, 1102, 1038, 1014, 559, 1013, 1079, 0, + 0, -424, 1032, 0, 1043, 1045, 1044, 1047, 1046, 1061, + 1176, 1161, 1157, 1174, 1076, 1208, 1103, 1156, 595 + ); + + protected $actionCheck = array( + 2, 3, 4, 5, 6, 7, 14, 9, 10, 11, + 12, 13, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 0, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 9, 10, 11, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 8, 68, 69, 33, + 34, 35, 36, 37, 56, 30, 8, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 70, 71, + 72, 73, 74, 75, 76, 13, 1, 79, 115, 116, + 117, 118, 119, 120, 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, 31, + 132, 133, 134, 135, 136, 137, 138, 139, 140, 3, + 4, 5, 6, 7, 146, 147, 148, 8, 12, 13, + 158, 15, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 14, 43, 44, 45, 46, 13, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 33, 34, 35, 36, 49, 50, 13, 8, 8, + 37, 55, 81, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 156, 69, 70, 71, 72, 58, + 59, 37, 37, 77, 78, 79, 154, 81, 9, 10, + 11, 85, 9, 10, 11, 79, 31, 1, 2, 154, + 107, 9, 10, 11, 105, 112, 107, 126, 8, 30, + 31, 105, 8, 30, 121, 32, 33, 34, 35, 36, + 37, 115, 30, 155, 32, 33, 34, 35, 36, 123, + 124, 115, 116, 117, 118, 119, 120, 115, 132, 133, + 159, 135, 136, 137, 138, 139, 140, 141, 79, 156, + 151, 82, 131, 147, 148, 133, 134, 151, 152, 153, + 9, 10, 11, 157, 8, 159, 160, 158, 162, 73, + 74, 75, 76, 150, 69, 79, 0, 1, 2, 83, + 158, 30, 86, 87, 88, 89, 90, 91, 92, 93, + 94, 95, 8, 97, 150, 150, 81, 81, 102, 103, + 104, 105, 13, 158, 108, 109, 9, 10, 11, 158, + 114, 115, 116, 117, 9, 10, 11, 8, 122, 154, + 8, 125, 126, 127, 128, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 79, 30, 131, 32, 33, 34, + 35, 8, 1, 81, 9, 10, 11, 132, 81, 73, + 154, 155, 156, 37, 154, 79, 1, 2, 115, 83, + 155, 81, 86, 87, 88, 30, 90, 69, 92, 80, + 94, 155, 157, 97, 159, 159, 133, 134, 102, 103, + 104, 105, 8, 107, 108, 109, 9, 10, 112, 70, + 114, 115, 116, 117, 9, 10, 11, 81, 122, 8, + 81, 125, 126, 127, 128, 8, 151, 152, 153, 10, + 11, 156, 161, 8, 158, 30, 84, 32, 33, 34, + 79, 159, 146, 8, 157, 79, 159, 84, 73, 30, + 154, 155, 156, 13, 79, 1, 2, 69, 83, 159, + 156, 86, 87, 88, 69, 90, 13, 92, 132, 94, + 69, 81, 97, 155, 99, 100, 101, 102, 103, 104, + 105, 115, 9, 108, 109, 9, 10, 11, 79, 114, + 115, 116, 117, 157, 142, 159, 157, 122, 159, 13, + 125, 126, 127, 128, 8, 142, 30, 154, 32, 33, + 52, 53, 54, 158, 56, 30, 155, 151, 152, 153, + 159, 14, 52, 53, 54, 81, 68, 73, 84, 154, + 155, 156, 131, 79, 33, 34, 35, 83, 68, 69, + 86, 87, 88, 155, 90, 155, 92, 157, 94, 159, + 155, 97, 158, 99, 100, 101, 102, 103, 104, 105, + 13, 152, 108, 109, 11, 154, 1, 2, 114, 115, + 116, 117, 9, 10, 11, 16, 122, 14, 31, 125, + 126, 127, 128, 30, 9, 10, 11, 143, 144, 154, + 9, 10, 11, 30, 154, 16, 31, 49, 50, 51, + 49, 50, 51, 1, 2, 30, 16, 154, 154, 155, + 156, 30, 13, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 37, 56, 73, 13, + 1, 2, 33, 34, 79, 9, 10, 11, 83, 68, + 154, 86, 87, 88, 155, 90, 16, 92, 159, 94, + 155, 16, 97, 37, 159, 16, 30, 102, 103, 104, + 31, 68, 69, 108, 109, 73, 16, 1, 2, 114, + 115, 79, 105, 31, 107, 83, 31, 122, 86, 87, + 88, 33, 90, 31, 92, 31, 94, 74, 75, 97, + 31, 74, 75, 31, 102, 103, 104, 31, 100, 101, + 108, 109, 73, 56, 1, 2, 114, 115, 79, 154, + 155, 37, 83, 69, 122, 86, 87, 88, 69, 90, + 69, 92, 69, 94, 110, 111, 97, 154, 155, 154, + 155, 102, 103, 104, 31, 154, 155, 108, 109, 73, + 68, 1, 2, 114, 115, 79, 154, 155, 69, 83, + 69, 122, 86, 87, 88, 69, 90, 69, 92, 70, + 94, 76, 79, 97, 84, 81, 84, 89, 102, 103, + 104, 31, 81, 81, 108, 109, 73, 112, 88, 115, + 114, 115, 79, 154, 155, 91, 83, 126, 122, 86, + 87, 88, 93, 90, 96, 92, 95, 94, 96, 99, + 97, 96, 96, 96, 129, 102, 103, 104, 99, 105, + 113, 108, 109, 73, 145, 105, 129, 114, 115, 79, + 154, 155, 126, 83, 131, 122, 86, 87, 88, 156, + 90, 154, 92, 157, 94, -1, -1, 97, 130, -1, + 145, -1, 102, 103, 104, 31, 131, 131, 108, 109, + 131, -1, -1, 142, 114, 115, 142, 154, 155, -1, + -1, 149, 122, 49, 50, 145, 145, 145, 145, 55, + 145, 57, 145, 149, 156, 158, 150, -1, -1, 151, + -1, -1, -1, 69, 154, 154, 154, 154, 154, 154, + 154, 77, 78, 154, 154, 155, 49, 50, 154, 85, + 154, 154, 55, 154, 57, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 69, 154, 154, 154, + 154, 159, 155, 155, 77, 78, 155, 155, 155, 155, + 155, 155, 85, 155, 155, 155, 155, 155, 155, 162, + 156, 156, 156, 156, 156, 156, 156, 133, 156, 135, + 136, 137, 138, 139, 140, 141, 156, 156, 156, 156, + 156, 147, 148, 156, 156, 156, 156, 156, 156, 156, + -1, 157, 156, 156, 156, 156, 156, 156, 156, -1, + 133, 157, 135, 136, 137, 138, 139, 140, 141, 157, + 157, 157, 157, 157, 147, 148, 157, 157, 157, 157, + 157, 157, 157, 157, 157, 157, 157, 157, -1, 158, + 158, 158, 158, 158, 158, 158, 158, 158, 158, 158, + 158, 158, 158, 158, 158, 158, 158, 158, 158, -1, + -1, 160, 160, -1, 161, 161, 161, 161, 161, 161, + 161, 161, 161, 161, 161, 161, 161, 161, 161 + ); + + protected $actionBase = array( + 0, 226, 306, 385, 464, 285, 246, 246, 786, -2, + -2, 146, -2, -2, -2, 649, 723, 760, 723, 575, + 686, 612, 612, 612, 175, 153, 153, 153, 174, 890, + 319, 62, 450, 463, 557, 609, 636, 496, 496, 496, + 496, 136, 136, 496, 496, 496, 496, 496, 496, 496, + 496, 496, 496, 496, 496, 496, 496, 496, 496, 496, + 496, 496, 496, 496, 496, 496, 496, 496, 496, 496, + 496, 496, 496, 496, 496, 496, 496, 496, 496, 496, + 496, 496, 496, 496, 496, 496, 496, 496, 496, 496, + 496, 496, 496, 496, 496, 496, 496, 496, 496, 496, + 496, 496, 496, 496, 496, 496, 496, 496, 496, 496, + 496, 496, 496, 496, 496, 496, 496, 496, 496, 496, + 496, 496, 496, 496, 496, 496, 496, 496, 496, 496, + 496, 496, 496, 496, 496, 195, 75, 777, 517, 147, + 778, 779, 780, 886, 727, 887, 832, 833, 682, 836, + 837, 838, 839, 840, 831, 841, 907, 842, 591, 591, + 591, 591, 591, 591, 591, 591, 591, 591, 591, 591, + 483, 573, 365, 209, 281, 407, 646, 646, 646, 646, + 646, 646, 646, 327, 327, 327, 327, 327, 327, 327, + 327, 327, 327, 327, 327, 327, 327, 327, 327, 327, + 327, 429, 834, 585, 585, 585, 563, 867, 867, 867, + 867, 867, 867, 867, 867, 867, 867, 867, 867, 867, + 867, 867, 867, 867, 867, 867, 867, 867, 867, 867, + 867, 867, 867, 867, 867, 867, 867, 867, 867, 867, + 867, 867, 867, 867, 867, 867, 867, 867, 867, 867, + 495, 486, -21, -21, 415, 668, 335, 619, 222, 511, + 213, 25, 25, 25, 25, 25, 148, 16, 4, 4, + 4, 4, 151, 312, 312, 312, 312, 119, 119, 119, + 119, 346, 346, 123, 245, 245, 349, 400, 297, 297, + 297, 297, 297, 297, 297, 297, 297, 297, 111, 558, + 558, 561, 561, 310, 152, 152, 152, 152, 704, 273, + 273, 129, 371, 371, 371, 373, 734, 797, 376, 376, + 376, 376, 376, 376, 468, 468, 468, 480, 480, 480, + 702, 587, 454, 587, 454, 684, 748, 509, 748, 700, + 199, 515, 803, 398, 720, 829, 729, 830, 601, 747, + 235, 782, 724, 419, 782, 633, 637, 634, 419, 419, + 715, 98, 863, 292, 195, 595, 405, 667, 781, 421, + 732, 784, 363, 445, 411, 593, 328, 286, 744, 785, + 888, 889, 181, 739, 667, 667, 667, 139, 362, 328, + -8, 613, 613, 613, 613, 48, 613, 613, 613, 613, + 314, 230, 506, 404, 783, 703, 703, 712, 694, 852, + 696, 696, 703, 711, 703, 712, 694, 854, 854, 854, + 854, 703, 694, 703, 703, 703, 696, 696, 694, 709, + 696, 38, 694, 695, 707, 707, 854, 751, 752, 703, + 703, 728, 696, 696, 696, 728, 694, 854, 685, 746, + 234, 696, 854, 665, 711, 665, 703, 685, 694, 665, + 711, 711, 665, 21, 662, 664, 853, 855, 869, 792, + 681, 716, 861, 862, 856, 860, 844, 679, 753, 754, + 569, 669, 671, 673, 699, 740, 701, 735, 724, 692, + 692, 692, 713, 741, 713, 692, 692, 692, 692, 692, + 692, 692, 692, 893, 689, 745, 736, 710, 755, 589, + 600, 793, 731, 738, 882, 875, 891, 892, 863, 880, + 713, 894, 697, 180, 650, 864, 693, 788, 713, 865, + 713, 794, 713, 883, 804, 708, 805, 806, 692, 884, + 895, 896, 897, 898, 899, 900, 901, 902, 706, 903, + 756, 698, 876, 339, 859, 715, 742, 725, 791, 759, + 807, 342, 904, 808, 713, 713, 795, 787, 713, 796, + 764, 750, 872, 766, 877, 905, 731, 726, 878, 713, + 730, 809, 906, 342, 672, 705, 737, 721, 767, 870, + 885, 868, 798, 655, 659, 810, 812, 820, 674, 769, + 873, 874, 871, 771, 799, 670, 800, 719, 821, 801, + 866, 772, 822, 823, 881, 718, 743, 717, 722, 714, + 802, 824, 879, 773, 774, 775, 827, 776, 828, 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, 136, + 136, 136, 136, -2, -2, -2, -2, 0, 0, -2, + 0, 0, 0, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 0, + 0, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 591, 591, 591, 591, 591, 591, 591, + 591, 591, 591, 591, 591, 591, 591, 591, 591, 591, + 591, 591, 591, 591, 591, 591, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 591, -21, + -21, -21, -21, 591, -21, -21, -21, -21, -21, -21, + -21, 591, 591, 591, 591, 591, 591, 591, 591, 591, + 591, 591, 591, 591, 591, 591, 591, 591, 591, -21, + 376, 591, 591, 591, -21, 376, 376, 376, 376, 376, + 376, 376, 376, 376, 376, 376, 376, 376, 376, 376, + 376, 376, 376, 376, 376, 376, 376, 376, 376, 376, + 376, 376, 376, 376, 376, 376, 376, 376, 376, 376, + 376, 376, 376, 376, 376, 376, 376, 376, -21, 591, + 0, 0, 591, -21, 591, -21, 591, -21, 591, 591, + 591, 591, 591, 591, -21, -21, -21, -21, -21, -21, + 0, 468, 468, 468, 468, -21, -21, -21, -21, 376, + 376, -37, 376, 376, 376, 376, 376, 376, 376, 376, + 376, 376, 376, 376, 376, 376, 376, 468, 468, 480, + 480, 376, 376, 376, 376, 376, -37, 376, 376, 419, + 711, 711, 711, 454, 454, 454, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 454, 419, + 0, 419, 0, 376, 419, 711, 419, 454, 711, 711, + 419, 696, 618, 618, 618, 618, 342, 328, 0, 711, + 711, 0, 711, 0, 0, 0, 0, 0, 696, 0, + 703, 0, 0, 0, 0, 692, 180, 0, 725, 427, + 0, 0, 0, 0, 0, 0, 725, 427, 435, 435, + 0, 706, 692, 692, 692, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 342 + ); + + protected $actionDefault = array( + 3,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 534, 534, 489,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 293, 293, 293, + 32767,32767,32767, 522, 522, 522, 522, 522, 522, 522, + 522, 522, 522, 522,32767,32767,32767,32767,32767,32767, + 376,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,32767,32767,32767,32767,32767, 382, 539, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 357, 358, + 360, 361, 292, 542, 523, 241, 383, 538, 291, 243, + 321, 493,32767,32767,32767, 323, 120, 252, 197, 492, + 123, 290, 228, 375, 377, 322, 297, 302, 303, 304, + 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, + 296, 449,32767, 354, 353, 352, 451, 486, 486, 489, + 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, + 450, 319, 477, 476, 320, 447, 324, 448, 326, 452, + 325, 342, 343, 340, 341, 344, 454, 453, 470, 471, + 468, 469, 295, 345, 346, 347, 348, 472, 473, 474, + 475,32767,32767, 276, 533, 533,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 333, + 334, 461, 462,32767, 232, 232, 232, 232, 277, 232, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 328, 329, 327, 456, 457, 455, + 423,32767,32767,32767, 425,32767,32767,32767,32767,32767, + 32767,32767,32767, 494,32767,32767,32767,32767,32767, 507, + 412,32767, 404,32767,32767, 216, 218, 165,32767,32767, + 480,32767,32767,32767,32767,32767, 512, 338,32767,32767, + 114,32767,32767,32767, 549,32767, 507,32767, 114,32767, + 32767,32767,32767, 351, 330, 331, 332,32767,32767, 511, + 505, 464, 465, 466, 467,32767, 458, 459, 460, 463, + 32767,32767,32767,32767,32767,32767,32767,32767, 169, 420, + 426, 426,32767,32767,32767,32767, 169,32767,32767,32767, + 32767,32767, 169,32767,32767,32767, 510, 509, 169,32767, + 405, 488, 169, 182, 180, 180,32767, 202, 202,32767, + 32767, 184, 481, 500,32767, 184, 169,32767, 393, 171, + 488,32767,32767, 234,32767, 234,32767, 393, 169, 234, + 32767,32767, 234,32767, 406, 430,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 372, 373, 483, 496,32767, 497,32767, 404, 336, + 337, 339, 316,32767, 318, 362, 363, 364, 365, 366, + 367, 368, 370,32767, 410,32767, 413,32767,32767,32767, + 251,32767, 547,32767,32767, 300, 547,32767,32767,32767, + 541,32767,32767, 294,32767,32767,32767,32767, 247,32767, + 167,32767, 531,32767, 548,32767, 505,32767, 335,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 506,32767, + 32767,32767,32767, 223,32767, 443,32767, 114,32767,32767, + 32767, 183,32767,32767, 298, 242,32767,32767, 540,32767, + 32767,32767,32767,32767,32767,32767,32767, 112,32767, 168, + 32767,32767,32767, 185,32767,32767, 505,32767,32767,32767, + 32767,32767,32767,32767, 289,32767,32767,32767,32767,32767, + 32767,32767, 505,32767,32767, 227,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 406,32767, 270,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 125, + 125, 3, 125, 125, 254, 3, 254, 125, 254, 254, + 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, + 210, 213, 202, 202, 162, 125, 125, 262 + ); + + protected $goto = array( + 165, 139, 139, 139, 165, 143, 146, 140, 141, 142, + 148, 186, 167, 162, 162, 162, 162, 143, 143, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 137, 158, 159, 160, 161, 183, 138, 184, 489, 490, + 367, 491, 495, 496, 497, 498, 499, 500, 501, 502, + 959, 163, 144, 145, 147, 170, 175, 185, 203, 251, + 254, 256, 258, 260, 261, 262, 263, 264, 265, 273, + 274, 275, 276, 299, 300, 324, 325, 326, 384, 385, + 386, 538, 187, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 149, 150, 151, 166, + 152, 168, 153, 204, 169, 154, 155, 156, 205, 157, + 135, 616, 556, 574, 578, 622, 624, 556, 556, 556, + 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, + 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, + 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, + 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, + 1099, 515, 345, 571, 600, 1099, 1099, 1099, 1099, 1099, + 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, + 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, + 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, + 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 504, 1202, + 1202, 1075, 1074, 504, 540, 541, 542, 543, 544, 545, + 546, 547, 549, 582, 3, 4, 173, 1202, 844, 844, + 844, 844, 839, 845, 176, 177, 178, 391, 392, 393, + 394, 172, 201, 206, 250, 255, 257, 259, 266, 267, + 268, 269, 270, 271, 277, 278, 279, 280, 301, 302, + 327, 328, 329, 396, 397, 398, 399, 174, 179, 252, + 253, 180, 181, 182, 493, 493, 750, 493, 493, 493, + 493, 493, 493, 493, 493, 493, 493, 493, 493, 493, + 493, 505, 929, 442, 444, 627, 505, 751, 779, 1100, + 610, 927, 880, 880, 765, 1190, 1190, 1168, 555, 775, + 764, 743, 1168, 555, 555, 555, 555, 555, 555, 555, + 555, 555, 555, 555, 555, 555, 555, 555, 555, 555, + 555, 555, 555, 555, 555, 555, 555, 555, 555, 555, + 555, 555, 555, 555, 555, 555, 555, 555, 555, 555, + 555, 555, 555, 555, 555, 555, 390, 602, 746, 532, + 532, 564, 528, 530, 530, 492, 494, 520, 536, 565, + 568, 579, 586, 810, 606, 506, 346, 347, 609, 850, + 506, 365, 537, 746, 533, 746, 563, 430, 430, 375, + 430, 430, 430, 430, 430, 430, 430, 430, 430, 430, + 430, 430, 430, 430, 1063, 581, 957, 596, 597, 1063, + 887, 887, 887, 887, 1160, 887, 887, 1182, 1182, 1182, + 376, 376, 376, 749, 1063, 1063, 1063, 1063, 1063, 1063, + 334, 1056, 317, 374, 374, 374, 866, 848, 846, 848, + 650, 461, 507, 875, 870, 376, 1194, 368, 374, 389, + 374, 898, 374, 1080, 583, 348, 404, 374, 1216, 590, + 601, 1017, 19, 15, 361, 1148, 1187, 525, 936, 904, + 510, 526, 904, 651, 551, 381, 1201, 1201, 587, 1007, + 550, 877, 607, 608, 873, 612, 613, 619, 621, 626, + 628, 23, 884, 937, 1201, 336, 598, 1059, 1060, 1204, + 378, 1056, 557, 539, 893, 768, 766, 379, 514, 902, + 509, 524, 655, 1057, 1159, 1057, 776, 509, 1167, 524, + 514, 514, 1058, 1167, 1049, 907, 508, 1054, 511, 433, + 434, 510, 1184, 1184, 1184, 854, 445, 945, 569, 1145, + 459, 362, 0, 0, 773, 1209, 0, 518, 0, 519, + 0, 529, 0, 0, 0, 0, 0, 1166, 0, 0, + 0, 771, 0, 0, 0, 449, 0, 0, 0, 0, + 0, 0, 605, 0, 0, 0, 0, 13, 1055, 614 + ); + + protected $gotoCheck = array( + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 56, 66, 59, 59, 59, 8, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 124, 99, 69, 39, 39, 124, 124, 124, 124, 124, + 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, + 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, + 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, + 124, 124, 124, 124, 124, 124, 124, 124, 66, 140, + 140, 122, 122, 66, 108, 108, 108, 108, 108, 108, + 108, 108, 108, 108, 29, 29, 26, 140, 66, 66, + 66, 66, 66, 66, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 115, 115, 14, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 7, 7, 7, 7, 115, 15, 28, 7, + 7, 7, 74, 74, 22, 74, 74, 116, 56, 22, + 22, 5, 116, 56, 56, 56, 56, 56, 56, 56, + 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, + 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, + 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, + 56, 56, 56, 56, 56, 56, 50, 50, 10, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 49, 60, 120, 69, 69, 60, 32, + 120, 60, 2, 10, 107, 10, 2, 56, 56, 10, + 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, + 56, 56, 56, 56, 56, 64, 99, 64, 64, 56, + 56, 56, 56, 56, 79, 56, 56, 8, 8, 8, + 121, 121, 121, 13, 56, 56, 56, 56, 56, 56, + 123, 79, 123, 12, 12, 12, 13, 13, 13, 13, + 13, 56, 13, 13, 13, 121, 138, 45, 12, 121, + 12, 81, 12, 33, 67, 67, 67, 12, 12, 125, + 48, 33, 33, 33, 33, 129, 136, 8, 95, 12, + 12, 31, 12, 31, 31, 47, 139, 139, 31, 100, + 33, 31, 31, 31, 31, 31, 31, 31, 31, 31, + 31, 33, 76, 95, 139, 17, 33, 79, 79, 139, + 11, 79, 11, 46, 78, 24, 23, 16, 46, 82, + 8, 8, 71, 79, 79, 79, 25, 8, 117, 8, + 46, 46, 79, 117, 111, 83, 8, 113, 8, 8, + 8, 12, 117, 117, 117, 68, 62, 97, 63, 128, + 106, 57, -1, -1, 8, 8, -1, 57, -1, 99, + -1, 57, -1, -1, -1, -1, -1, 117, -1, -1, + -1, 8, -1, -1, -1, 57, -1, -1, -1, -1, + -1, -1, 12, -1, -1, -1, -1, 57, 12, 12 + ); + + protected $gotoBase = array( + 0, 0, -249, 0, 0, 300, 0, 287, 105, 0, + 47, 164, 118, 421, 274, 295, 171, 184, 0, 0, + 0, 0, -49, 168, 172, 104, 24, 0, 288, -431, + 0, -159, 359, 44, 0, 0, 0, 0, 0, 125, + 0, 0, -24, 0, 0, 407, 479, 186, 178, 355, + 75, 0, 0, 0, 0, 0, 106, 119, 0, -192, + -81, 0, 101, 93, -231, 0, -90, 135, 121, -276, + 0, 148, 0, 0, 21, 0, 183, 0, 194, 71, + 0, 423, 155, 112, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 185, 0, 122, 0, 120, + 176, 0, 0, 0, 0, 0, 83, 358, 170, 0, + 0, 113, 0, 111, 0, -7, 9, 220, 0, 0, + 77, 108, -102, 100, -42, 251, 0, 0, 89, 256, + 0, 0, 0, 0, 0, 0, 181, 0, 419, 160, + -107, 0, 0 + ); + + protected $gotoDefault = array( + -32768, 463, 659, 2, 660, 733, 741, 593, 477, 625, + 577, 370, 1178, 785, 786, 787, 371, 358, 478, 369, + 400, 395, 774, 767, 769, 777, 171, 401, 780, 1, + 782, 513, 818, 1008, 355, 790, 356, 585, 792, 522, + 794, 795, 136, 372, 373, 523, 479, 380, 572, 809, + 272, 377, 811, 357, 812, 821, 360, 460, 454, 552, + 604, 425, 441, 566, 560, 531, 1072, 561, 853, 344, + 861, 656, 869, 872, 480, 553, 883, 446, 891, 1085, + 387, 897, 903, 908, 283, 911, 407, 402, 580, 916, + 917, 5, 921, 617, 618, 8, 308, 944, 594, 958, + 411, 1027, 1029, 481, 482, 517, 453, 503, 521, 483, + 1050, 435, 403, 1053, 484, 485, 426, 427, 1069, 350, + 1153, 349, 443, 316, 1140, 575, 1104, 450, 1193, 1149, + 343, 486, 487, 366, 1172, 382, 1188, 431, 1195, 1203, + 339, 535, 562 + ); + + protected $ruleToNonTerminal = array( + 0, 1, 3, 3, 2, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, + 6, 6, 7, 7, 8, 9, 10, 10, 11, 11, + 12, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 17, 17, 18, 18, 20, 20, 16, 16, + 21, 21, 22, 22, 23, 23, 24, 24, 19, 19, + 25, 27, 27, 28, 29, 29, 31, 30, 30, 30, + 30, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 13, 13, 53, 53, 55, 54, 54, 47, 47, 57, + 57, 58, 58, 14, 15, 15, 15, 61, 61, 61, + 62, 62, 65, 65, 63, 63, 67, 67, 40, 40, + 49, 49, 52, 52, 52, 51, 51, 68, 41, 41, + 41, 41, 69, 69, 70, 70, 71, 71, 38, 38, + 34, 34, 72, 36, 36, 73, 35, 35, 37, 37, + 48, 48, 48, 59, 59, 75, 75, 76, 76, 78, + 78, 78, 77, 77, 60, 60, 79, 79, 79, 80, + 80, 81, 81, 81, 43, 43, 82, 82, 82, 44, + 44, 83, 83, 84, 84, 64, 85, 85, 85, 85, + 90, 90, 91, 91, 92, 92, 92, 92, 92, 93, + 94, 94, 89, 89, 86, 86, 88, 88, 96, 96, + 95, 95, 95, 95, 95, 95, 87, 87, 98, 97, + 97, 45, 45, 39, 39, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 33, 33, 46, 46, 103, 103, 104, 104, 104, 104, + 110, 99, 99, 106, 106, 112, 112, 113, 114, 114, + 114, 114, 114, 114, 66, 66, 56, 56, 56, 56, + 100, 100, 118, 118, 115, 115, 119, 119, 119, 119, + 101, 101, 101, 105, 105, 105, 111, 111, 124, 124, + 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, + 124, 26, 26, 26, 26, 26, 26, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 109, 109, 102, 102, 102, 102, 125, 125, 128, 128, + 127, 127, 129, 129, 50, 50, 50, 50, 131, 131, + 130, 130, 130, 130, 130, 132, 132, 117, 117, 120, + 120, 116, 116, 134, 133, 133, 133, 133, 121, 121, + 121, 121, 108, 108, 122, 122, 122, 122, 74, 135, + 135, 136, 136, 136, 107, 107, 137, 137, 138, 138, + 138, 138, 138, 123, 123, 123, 123, 140, 141, 139, + 139, 139, 139, 139, 139, 139, 142, 142, 142 + ); + + protected $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, 3, 5, 4, 3, 4, + 2, 3, 1, 1, 7, 6, 3, 1, 3, 1, + 3, 1, 1, 3, 1, 3, 1, 2, 3, 1, + 3, 3, 1, 3, 2, 0, 1, 1, 1, 1, + 1, 3, 5, 8, 3, 5, 9, 3, 2, 3, + 2, 3, 2, 3, 3, 3, 3, 1, 2, 2, + 5, 7, 9, 5, 6, 3, 3, 2, 2, 1, + 1, 1, 0, 2, 8, 0, 4, 1, 3, 0, + 1, 0, 1, 10, 7, 6, 5, 1, 2, 2, + 0, 2, 0, 2, 0, 2, 1, 3, 1, 4, + 1, 4, 1, 1, 4, 1, 3, 3, 3, 4, + 4, 5, 0, 2, 4, 3, 1, 1, 1, 4, + 0, 2, 3, 0, 2, 4, 0, 2, 0, 3, + 1, 2, 1, 1, 0, 1, 3, 4, 6, 1, + 1, 1, 0, 1, 0, 2, 2, 3, 3, 1, + 3, 1, 2, 2, 3, 1, 1, 2, 4, 3, + 1, 1, 3, 2, 0, 1, 3, 3, 9, 3, + 1, 3, 0, 2, 4, 5, 4, 4, 4, 3, + 1, 1, 1, 3, 1, 1, 0, 1, 1, 2, + 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, + 3, 3, 1, 0, 1, 1, 3, 3, 4, 4, + 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, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 1, 3, 5, 4, 3, 4, 4, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 1, 1, 1, 3, 2, 1, 2, 10, 11, + 3, 3, 2, 4, 4, 3, 4, 4, 4, 4, + 7, 3, 2, 0, 4, 1, 3, 2, 2, 4, + 6, 2, 2, 4, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 3, 3, 4, 4, + 0, 2, 1, 0, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, + 2, 1, 3, 1, 4, 3, 1, 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, 5, 4, 4, 3, + 1, 3, 1, 1, 3, 3, 0, 2, 0, 1, + 3, 1, 3, 1, 1, 1, 1, 1, 6, 4, + 3, 4, 2, 4, 4, 1, 3, 1, 2, 1, + 1, 4, 1, 1, 3, 6, 4, 4, 4, 4, + 1, 4, 0, 1, 1, 3, 1, 1, 4, 3, + 1, 1, 1, 0, 0, 2, 3, 1, 3, 1, + 4, 2, 2, 2, 2, 1, 2, 1, 1, 1, + 4, 3, 3, 3, 6, 3, 1, 1, 1 + ); + + protected function initReduceCallbacks() { + $this->reduceCallbacks = [ + 0 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 1 => function ($stackPos) { + $this->semValue = $this->handleNamespaces($this->semStack[$stackPos-(1-1)]); + }, + 2 => function ($stackPos) { + if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }; + }, + 3 => function ($stackPos) { + $this->semValue = array(); + }, + 4 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 5 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 6 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 7 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 8 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 9 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 10 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 11 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 12 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 13 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 14 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 15 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 16 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 17 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 18 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 19 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 20 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 21 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 22 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 23 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 24 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 25 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 26 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 27 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 28 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 29 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 30 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 31 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 32 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 33 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 34 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 35 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 36 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 37 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 38 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 39 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 40 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 41 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 42 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 43 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 44 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 45 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 46 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 47 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 48 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 49 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 50 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 51 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 52 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 53 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 54 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 55 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 56 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 57 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 58 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 59 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 60 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 61 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 62 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 63 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 64 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 65 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 66 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 67 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 68 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 69 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 70 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 71 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 72 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 73 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 74 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 75 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 76 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 77 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 78 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 79 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 80 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 81 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 82 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 83 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 84 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 85 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 86 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 87 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 88 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 89 => function ($stackPos) { + $this->semValue = new Name(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 90 => function ($stackPos) { + $this->semValue = new Expr\Variable(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 91 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 92 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 93 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 94 => function ($stackPos) { + $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 95 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(3-2)], null, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $this->checkNamespace($this->semValue); + }, + 96 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(5-2)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, + 97 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_(null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, + 98 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 99 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 100 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 101 => function ($stackPos) { + $this->semValue = new Stmt\Const_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 102 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_FUNCTION; + }, + 103 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_CONSTANT; + }, + 104 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-6)], $this->semStack[$stackPos-(7-2)], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 105 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 106 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 107 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 108 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 109 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 110 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 111 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 112 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1)); + }, + 113 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3)); + }, + 114 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1)); + }, + 115 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3)); + }, + 116 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; $this->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, + 117 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; $this->semValue->type = $this->semStack[$stackPos-(2-1)]; + }, + 118 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 119 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 120 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 121 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 122 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 123 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 124 => function ($stackPos) { + if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }; + }, + 125 => function ($stackPos) { + $this->semValue = array(); + }, + 126 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 127 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 128 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 129 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 130 => function ($stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 131 => function ($stackPos) { + + if ($this->semStack[$stackPos-(3-2)]) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; $attrs = $this->startAttributeStack[$stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments'])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); }; + } else { + $startAttributes = $this->startAttributeStack[$stackPos-(3-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; }; + if (null === $this->semValue) { $this->semValue = array(); } + } + + }, + 132 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos-(5-2)], ['stmts' => is_array($this->semStack[$stackPos-(5-3)]) ? $this->semStack[$stackPos-(5-3)] : array($this->semStack[$stackPos-(5-3)]), 'elseifs' => $this->semStack[$stackPos-(5-4)], 'else' => $this->semStack[$stackPos-(5-5)]], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 133 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos-(8-2)], ['stmts' => $this->semStack[$stackPos-(8-4)], 'elseifs' => $this->semStack[$stackPos-(8-5)], 'else' => $this->semStack[$stackPos-(8-6)]], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 134 => function ($stackPos) { + $this->semValue = new Stmt\While_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 135 => function ($stackPos) { + $this->semValue = new Stmt\Do_($this->semStack[$stackPos-(5-4)], is_array($this->semStack[$stackPos-(5-2)]) ? $this->semStack[$stackPos-(5-2)] : array($this->semStack[$stackPos-(5-2)]), $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 136 => function ($stackPos) { + $this->semValue = new Stmt\For_(['init' => $this->semStack[$stackPos-(9-3)], 'cond' => $this->semStack[$stackPos-(9-5)], 'loop' => $this->semStack[$stackPos-(9-7)], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 137 => function ($stackPos) { + $this->semValue = new Stmt\Switch_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 138 => function ($stackPos) { + $this->semValue = new Stmt\Break_(null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 139 => function ($stackPos) { + $this->semValue = new Stmt\Break_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 140 => function ($stackPos) { + $this->semValue = new Stmt\Continue_(null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 141 => function ($stackPos) { + $this->semValue = new Stmt\Continue_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 142 => function ($stackPos) { + $this->semValue = new Stmt\Return_(null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 143 => function ($stackPos) { + $this->semValue = new Stmt\Return_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 144 => function ($stackPos) { + $this->semValue = new Stmt\Global_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 145 => function ($stackPos) { + $this->semValue = new Stmt\Static_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 146 => function ($stackPos) { + $this->semValue = new Stmt\Echo_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 147 => function ($stackPos) { + $this->semValue = new Stmt\InlineHTML($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 148 => function ($stackPos) { + $this->semValue = new Stmt\Expression($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 149 => function ($stackPos) { + $this->semValue = new Stmt\Expression($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 150 => function ($stackPos) { + $this->semValue = new Stmt\Unset_($this->semStack[$stackPos-(5-3)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 151 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$stackPos-(7-5)][1], 'stmts' => $this->semStack[$stackPos-(7-7)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 152 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(9-3)], $this->semStack[$stackPos-(9-7)][0], ['keyVar' => $this->semStack[$stackPos-(9-5)], 'byRef' => $this->semStack[$stackPos-(9-7)][1], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 153 => function ($stackPos) { + $this->semValue = new Stmt\Declare_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 154 => function ($stackPos) { + $this->semValue = new Stmt\TryCatch($this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-5)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); $this->checkTryCatch($this->semValue); + }, + 155 => function ($stackPos) { + $this->semValue = new Stmt\Throw_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 156 => function ($stackPos) { + $this->semValue = new Stmt\Goto_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 157 => function ($stackPos) { + $this->semValue = new Stmt\Label($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 158 => function ($stackPos) { + $this->semValue = new Stmt\Expression($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 159 => function ($stackPos) { + $this->semValue = array(); /* means: no statement */ + }, + 160 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 161 => function ($stackPos) { + $startAttributes = $this->startAttributeStack[$stackPos-(1-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; }; + if ($this->semValue === null) $this->semValue = array(); /* means: no statement */ + }, + 162 => function ($stackPos) { + $this->semValue = array(); + }, + 163 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 164 => function ($stackPos) { + $this->semValue = new Stmt\Catch_(array($this->semStack[$stackPos-(8-3)]), $this->semStack[$stackPos-(8-4)], $this->semStack[$stackPos-(8-7)], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 165 => function ($stackPos) { + $this->semValue = null; + }, + 166 => function ($stackPos) { + $this->semValue = new Stmt\Finally_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 167 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 168 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 169 => function ($stackPos) { + $this->semValue = false; + }, + 170 => function ($stackPos) { + $this->semValue = true; + }, + 171 => function ($stackPos) { + $this->semValue = false; + }, + 172 => function ($stackPos) { + $this->semValue = true; + }, + 173 => function ($stackPos) { + $this->semValue = new Stmt\Function_($this->semStack[$stackPos-(10-3)], ['byRef' => $this->semStack[$stackPos-(10-2)], 'params' => $this->semStack[$stackPos-(10-5)], 'returnType' => $this->semStack[$stackPos-(10-7)], 'stmts' => $this->semStack[$stackPos-(10-9)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + }, + 174 => function ($stackPos) { + $this->semValue = new Stmt\Class_($this->semStack[$stackPos-(7-2)], ['type' => $this->semStack[$stackPos-(7-1)], 'extends' => $this->semStack[$stackPos-(7-3)], 'implements' => $this->semStack[$stackPos-(7-4)], 'stmts' => $this->semStack[$stackPos-(7-6)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + $this->checkClass($this->semValue, $stackPos-(7-2)); + }, + 175 => function ($stackPos) { + $this->semValue = new Stmt\Interface_($this->semStack[$stackPos-(6-2)], ['extends' => $this->semStack[$stackPos-(6-3)], 'stmts' => $this->semStack[$stackPos-(6-5)]], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + $this->checkInterface($this->semValue, $stackPos-(6-2)); + }, + 176 => function ($stackPos) { + $this->semValue = new Stmt\Trait_($this->semStack[$stackPos-(5-2)], ['stmts' => $this->semStack[$stackPos-(5-4)]], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 177 => function ($stackPos) { + $this->semValue = 0; + }, + 178 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, + 179 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, + 180 => function ($stackPos) { + $this->semValue = null; + }, + 181 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 182 => function ($stackPos) { + $this->semValue = array(); + }, + 183 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 184 => function ($stackPos) { + $this->semValue = array(); + }, + 185 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 186 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 187 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 188 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 189 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 190 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 191 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 192 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 193 => function ($stackPos) { + $this->semValue = null; + }, + 194 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 195 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 196 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 197 => function ($stackPos) { + $this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 198 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 199 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-3)]; + }, + 200 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 201 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(5-3)]; + }, + 202 => function ($stackPos) { + $this->semValue = array(); + }, + 203 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 204 => function ($stackPos) { + $this->semValue = new Stmt\Case_($this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 205 => function ($stackPos) { + $this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 206 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 207 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 208 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 209 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 210 => function ($stackPos) { + $this->semValue = array(); + }, + 211 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 212 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(3-2)], is_array($this->semStack[$stackPos-(3-3)]) ? $this->semStack[$stackPos-(3-3)] : array($this->semStack[$stackPos-(3-3)]), $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 213 => function ($stackPos) { + $this->semValue = array(); + }, + 214 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 215 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 216 => function ($stackPos) { + $this->semValue = null; + }, + 217 => function ($stackPos) { + $this->semValue = new Stmt\Else_(is_array($this->semStack[$stackPos-(2-2)]) ? $this->semStack[$stackPos-(2-2)] : array($this->semStack[$stackPos-(2-2)]), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 218 => function ($stackPos) { + $this->semValue = null; + }, + 219 => function ($stackPos) { + $this->semValue = new Stmt\Else_($this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 220 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 221 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(2-2)], true); + }, + 222 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 223 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 224 => function ($stackPos) { + $this->semValue = array(); + }, + 225 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 226 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 227 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos-(4-4)], null, $this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); $this->checkParam($this->semValue); + }, + 228 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-6)], $this->semStack[$stackPos-(6-1)], $this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-3)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); $this->checkParam($this->semValue); + }, + 229 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 230 => function ($stackPos) { + $this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 231 => function ($stackPos) { + $this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 232 => function ($stackPos) { + $this->semValue = null; + }, + 233 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 234 => function ($stackPos) { + $this->semValue = null; + }, + 235 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 236 => function ($stackPos) { + $this->semValue = array(); + }, + 237 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 238 => function ($stackPos) { + $this->semValue = array(new Node\Arg($this->semStack[$stackPos-(3-2)], false, false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes)); + }, + 239 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 240 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 241 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(1-1)], false, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 242 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], true, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 243 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], false, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 244 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 245 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 246 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 247 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 248 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 249 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 250 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 251 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 252 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 253 => function ($stackPos) { + if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; } + }, + 254 => function ($stackPos) { + $this->semValue = array(); + }, + 255 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 256 => function ($stackPos) { + $this->semValue = new Stmt\Property($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkProperty($this->semValue, $stackPos-(3-1)); + }, + 257 => function ($stackPos) { + $this->semValue = new Stmt\ClassConst($this->semStack[$stackPos-(3-2)], 0, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 258 => function ($stackPos) { + $this->semValue = new Stmt\ClassMethod($this->semStack[$stackPos-(9-4)], ['type' => $this->semStack[$stackPos-(9-1)], 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-6)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + $this->checkClassMethod($this->semValue, $stackPos-(9-1)); + }, + 259 => function ($stackPos) { + $this->semValue = new Stmt\TraitUse($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 260 => function ($stackPos) { + $this->semValue = array(); + }, + 261 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 262 => function ($stackPos) { + $this->semValue = array(); + }, + 263 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 264 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 265 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(5-1)][0], $this->semStack[$stackPos-(5-1)][1], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 266 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], null, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 267 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 268 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 269 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, + 270 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 271 => function ($stackPos) { + $this->semValue = array(null, $this->semStack[$stackPos-(1-1)]); + }, + 272 => function ($stackPos) { + $this->semValue = null; + }, + 273 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 274 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 275 => function ($stackPos) { + $this->semValue = 0; + }, + 276 => function ($stackPos) { + $this->semValue = 0; + }, + 277 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 278 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 279 => function ($stackPos) { + $this->checkModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)]; + }, + 280 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + }, + 281 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + }, + 282 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + }, + 283 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_STATIC; + }, + 284 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, + 285 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, + 286 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 287 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 288 => function ($stackPos) { + $this->semValue = new Node\VarLikeIdentifier(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 289 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 290 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 291 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 292 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 293 => function ($stackPos) { + $this->semValue = array(); + }, + 294 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 295 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 296 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 297 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 298 => function ($stackPos) { + $this->semValue = new Expr\AssignRef($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 299 => function ($stackPos) { + $this->semValue = new Expr\AssignRef($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 300 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 301 => function ($stackPos) { + $this->semValue = new Expr\Clone_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 302 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 303 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 304 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 305 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 306 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 307 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 308 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 309 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 310 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 311 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 312 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 313 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 314 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 315 => function ($stackPos) { + $this->semValue = new Expr\PostInc($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 316 => function ($stackPos) { + $this->semValue = new Expr\PreInc($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 317 => function ($stackPos) { + $this->semValue = new Expr\PostDec($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 318 => function ($stackPos) { + $this->semValue = new Expr\PreDec($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 319 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 320 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 321 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 322 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 323 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 324 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 325 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 326 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 327 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 328 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 329 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 330 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 331 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 332 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 333 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 334 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 335 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 336 => function ($stackPos) { + $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 337 => function ($stackPos) { + $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 338 => function ($stackPos) { + $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 339 => function ($stackPos) { + $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 340 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 341 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 342 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 343 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 344 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 345 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 346 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 347 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 348 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 349 => function ($stackPos) { + $this->semValue = new Expr\Instanceof_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 350 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 351 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 352 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(5-1)], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 353 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(4-1)], null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 354 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 355 => function ($stackPos) { + $this->semValue = new Expr\Isset_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 356 => function ($stackPos) { + $this->semValue = new Expr\Empty_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 357 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 358 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 359 => function ($stackPos) { + $this->semValue = new Expr\Eval_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 360 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 361 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 362 => function ($stackPos) { + $this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 363 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; + $attrs['kind'] = $this->getFloatCastKind($this->semStack[$stackPos-(2-1)]); + $this->semValue = new Expr\Cast\Double($this->semStack[$stackPos-(2-2)], $attrs); + }, + 364 => function ($stackPos) { + $this->semValue = new Expr\Cast\String_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 365 => function ($stackPos) { + $this->semValue = new Expr\Cast\Array_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 366 => function ($stackPos) { + $this->semValue = new Expr\Cast\Object_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 367 => function ($stackPos) { + $this->semValue = new Expr\Cast\Bool_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 368 => function ($stackPos) { + $this->semValue = new Expr\Cast\Unset_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 369 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; + $attrs['kind'] = strtolower($this->semStack[$stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $this->semValue = new Expr\Exit_($this->semStack[$stackPos-(2-2)], $attrs); + }, + 370 => function ($stackPos) { + $this->semValue = new Expr\ErrorSuppress($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 371 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 372 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 373 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 374 => function ($stackPos) { + $this->semValue = new Expr\ShellExec($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 375 => function ($stackPos) { + $this->semValue = new Expr\Print_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 376 => function ($stackPos) { + $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 377 => function ($stackPos) { + $this->semValue = new Expr\YieldFrom($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 378 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$stackPos-(10-2)], 'params' => $this->semStack[$stackPos-(10-4)], 'uses' => $this->semStack[$stackPos-(10-6)], 'returnType' => $this->semStack[$stackPos-(10-7)], 'stmts' => $this->semStack[$stackPos-(10-9)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + }, + 379 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$stackPos-(11-3)], 'params' => $this->semStack[$stackPos-(11-5)], 'uses' => $this->semStack[$stackPos-(11-7)], 'returnType' => $this->semStack[$stackPos-(11-8)], 'stmts' => $this->semStack[$stackPos-(11-10)]], $this->startAttributeStack[$stackPos-(11-1)] + $this->endAttributes); + }, + 380 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 381 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 382 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos-(2-2)], null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 383 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 384 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_LONG; + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(4-3)], $attrs); + }, + 385 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_SHORT; + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(3-2)], $attrs); + }, + 386 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 387 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(4-1)][0] === "'" || ($this->semStack[$stackPos-(4-1)][1] === "'" && ($this->semStack[$stackPos-(4-1)][0] === 'b' || $this->semStack[$stackPos-(4-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED); + $this->semValue = new Expr\ArrayDimFetch(new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(4-1)]), $attrs), $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 388 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 389 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 390 => function ($stackPos) { + $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$stackPos-(7-3)], 'implements' => $this->semStack[$stackPos-(7-4)], 'stmts' => $this->semStack[$stackPos-(7-6)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes), $this->semStack[$stackPos-(7-2)]); + $this->checkClass($this->semValue[0], -1); + }, + 391 => function ($stackPos) { + $this->semValue = new Expr\New_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 392 => function ($stackPos) { + list($class, $ctorArgs) = $this->semStack[$stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 393 => function ($stackPos) { + $this->semValue = array(); + }, + 394 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-3)]; + }, + 395 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 396 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 397 => function ($stackPos) { + $this->semValue = new Expr\ClosureUse($this->semStack[$stackPos-(2-2)], $this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 398 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 399 => function ($stackPos) { + $this->semValue = new Expr\StaticCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 400 => function ($stackPos) { + $this->semValue = new Expr\StaticCall($this->semStack[$stackPos-(6-1)], $this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 401 => function ($stackPos) { + $this->semValue = $this->fixupPhp5StaticPropCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 402 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 403 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 404 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 405 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 406 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 407 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 408 => function ($stackPos) { + $this->semValue = new Name\FullyQualified(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 409 => function ($stackPos) { + $this->semValue = new Name\Relative(substr($this->semStack[$stackPos-(1-1)], 10), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 410 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 411 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 412 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 413 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 414 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 415 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 416 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 417 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 418 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 419 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 420 => function ($stackPos) { + $this->semValue = null; + }, + 421 => function ($stackPos) { + $this->semValue = null; + }, + 422 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 423 => function ($stackPos) { + $this->semValue = array(); + }, + 424 => function ($stackPos) { + $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos-(1-1)], '`', false), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes)); + }, + 425 => function ($stackPos) { + foreach ($this->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', false); } }; $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 426 => function ($stackPos) { + $this->semValue = array(); + }, + 427 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 428 => function ($stackPos) { + $this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes, true); + }, + 429 => function ($stackPos) { + $this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$stackPos-(1-1)]), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 430 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(1-1)][0] === "'" || ($this->semStack[$stackPos-(1-1)][1] === "'" && ($this->semStack[$stackPos-(1-1)][0] === 'b' || $this->semStack[$stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED); + $this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(1-1)], false), $attrs); + }, + 431 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 432 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 433 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 434 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 435 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 436 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 437 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 438 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 439 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], false); + }, + 440 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], false); + }, + 441 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 442 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 443 => function ($stackPos) { + $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 444 => function ($stackPos) { + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 445 => function ($stackPos) { + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 446 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 447 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 448 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 449 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 450 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 451 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 452 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 453 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 454 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 455 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 456 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 457 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 458 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 459 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 460 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 461 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 462 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 463 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 464 => function ($stackPos) { + $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 465 => function ($stackPos) { + $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 466 => function ($stackPos) { + $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 467 => function ($stackPos) { + $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 468 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 469 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 470 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 471 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 472 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 473 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 474 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 475 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 476 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(5-1)], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 477 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(4-1)], null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 478 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 479 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 480 => function ($stackPos) { + $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 481 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 482 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 483 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 484 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs); + }, + 485 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true); + }, + 486 => function ($stackPos) { + $this->semValue = array(); + }, + 487 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 488 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 489 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 490 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 491 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 492 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 493 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 494 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 495 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 496 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 497 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 498 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 499 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 500 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 501 => function ($stackPos) { + $this->semValue = new Expr\MethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 502 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 503 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 504 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 505 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 506 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 507 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 508 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 509 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 510 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 511 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 512 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 513 => function ($stackPos) { + $var = substr($this->semStack[$stackPos-(1-1)], 1); $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes) : $var; + }, + 514 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 515 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(6-1)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 516 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 517 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 518 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 519 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 520 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 521 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 522 => function ($stackPos) { + $this->semValue = null; + }, + 523 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 524 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 525 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 526 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 527 => function ($stackPos) { + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2; + }, + 528 => function ($stackPos) { + $this->semValue = new Expr\List_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 529 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 530 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 531 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 532 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 533 => function ($stackPos) { + $this->semValue = null; + }, + 534 => function ($stackPos) { + $this->semValue = array(); + }, + 535 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 536 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 537 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 538 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 539 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 540 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-1)], true, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 541 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 542 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 543 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 544 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 545 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 546 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); + }, + 547 => function ($stackPos) { + $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 548 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 549 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 550 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 551 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 552 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 553 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 554 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-4)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 555 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 556 => function ($stackPos) { + $this->semValue = new Scalar\String_($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 557 => function ($stackPos) { + $this->semValue = $this->parseNumString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 558 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + ]; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Php7.php b/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Php7.php new file mode 100644 index 0000000000000000000000000000000000000000..cbfbd34aa2dcd8dbf44b52c7b828605b894035a1 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Php7.php @@ -0,0 +1,2730 @@ +<?php + +namespace PhpParser\Parser; + +use PhpParser\Error; +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\Scalar; +use PhpParser\Node\Stmt; + +/* This is an automatically GENERATED file, which should not be manually edited. + * Instead edit one of the following: + * * the grammar files grammar/php5.y or grammar/php7.y + * * the skeleton file grammar/parser.template + * * the preprocessing script grammar/rebuildParsers.php + */ +class Php7 extends \PhpParser\ParserAbstract +{ + protected $tokenToSymbolMapSize = 392; + protected $actionTableSize = 1162; + protected $gotoTableSize = 611; + + protected $invalidSymbol = 165; + protected $errorSymbol = 1; + protected $defaultAction = -32766; + protected $unexpectedTokenRule = 32767; + + protected $YY2TBLSTATE = 397; + protected $numNonLeafStates = 694; + + protected $symbolToName = array( + "EOF", + "error", + "T_THROW", + "T_INCLUDE", + "T_INCLUDE_ONCE", + "T_EVAL", + "T_REQUIRE", + "T_REQUIRE_ONCE", + "','", + "T_LOGICAL_OR", + "T_LOGICAL_XOR", + "T_LOGICAL_AND", + "T_PRINT", + "T_YIELD", + "T_DOUBLE_ARROW", + "T_YIELD_FROM", + "'='", + "T_PLUS_EQUAL", + "T_MINUS_EQUAL", + "T_MUL_EQUAL", + "T_DIV_EQUAL", + "T_CONCAT_EQUAL", + "T_MOD_EQUAL", + "T_AND_EQUAL", + "T_OR_EQUAL", + "T_XOR_EQUAL", + "T_SL_EQUAL", + "T_SR_EQUAL", + "T_POW_EQUAL", + "T_COALESCE_EQUAL", + "'?'", + "':'", + "T_COALESCE", + "T_BOOLEAN_OR", + "T_BOOLEAN_AND", + "'|'", + "'^'", + "'&'", + "T_IS_EQUAL", + "T_IS_NOT_EQUAL", + "T_IS_IDENTICAL", + "T_IS_NOT_IDENTICAL", + "T_SPACESHIP", + "'<'", + "T_IS_SMALLER_OR_EQUAL", + "'>'", + "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_VAR", + "T_UNSET", + "T_ISSET", + "T_EMPTY", + "T_HALT_COMPILER", + "T_CLASS", + "T_TRAIT", + "T_INTERFACE", + "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 $tokenToSymbol = array( + 0, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 55, 163, 165, 164, 54, 37, 165, + 160, 161, 52, 49, 8, 50, 51, 53, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 31, 156, + 43, 16, 45, 30, 67, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 69, 165, 157, 36, 165, 162, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 158, 35, 159, 57, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 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, 38, 39, 40, 41, + 42, 44, 46, 47, 48, 56, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 68, 70, 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 + ); + + protected $action = array( + 130, 131, 132, 555, 133, 134,-32766, 704, 705, 706, + 135, 36, -543, -552, 455,-32766, -543,-32766,-32766,-32766, + -552, 1152, 778, 927, -549, 969, 970, 0,-32766,-32766, + -32766, -549,-32766, 1219,-32766, 245,-32766, 962,-32766,-32766, + -32766,-32766,-32766, 461,-32766,-32766,-32766,-32766,-32766,-32766, + -32766,-32766, 124, -331, 707, -331,-32766, 388, 1031, 1032, + 1033, 1030, 1029, 1028,-32766, 435, 430, 2, 261, 136, + 371, 711, 712, 713, 714, 391, 789, 397, 1031, 1032, + 1033, 1030, 1029, 1028, 715, 716, 717, 718, 719, 720, + 721, 722, 723, 724, 725, 745, 556, 746, 747, 748, + 749, 737, 738, 372, 373, 740, 741, 726, 727, 728, + 730, 731, 732, 332, 771, 772, 773, 774, 775, 733, + 734, 557, 558, 766, 757, 755, 756, 752, 753, -294, + -189, 559, 560, 751, 561, 562, 563, 564, 565, 566, + 1235, 456, 783, -503, 889, 754, 567, 568, 928, 137, + -32766,-32766,-32766, 130, 131, 132, 555, 133, 134, 983, + 704, 705, 706, 135, 36,-32766,-32766,-32766,-32766, -552, + -32766,-32766,-32766, -552, 1152, 547, 101, 102, 103, 583, + -549,-32766,-32766,-32766, -549,-32766,-32766,-32766, 245,-32766, + 80,-32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766, + 959, 958, 957,-32766,-32766, -503, -503, 707, 1264,-32766, + 388, 1265,-32766,-32766,-32766, 235, 784,-32766, 778, 19, + -503, 261, 136, 371, 711, 712, 713, 714,-32766,-32766, + 397, 788, -503,-32766, -509,-32766,-32766, 715, 716, 717, + 718, 719, 720, 721, 722, 723, 724, 725, 745, 556, + 746, 747, 748, 749, 737, 738, 372, 373, 740, 741, + 726, 727, 728, 730, 731, 732, 332, 771, 772, 773, + 774, 775, 733, 734, 557, 558, 766, 757, 755, 756, + 752, 753, -294, -189, 559, 560, 751, 561, 562, 563, + 564, 565, 566, 309, 81, 82, 83, 139, 754, 567, + 568, 681, 137, 729, 699, 700, 701, 702, 703, 1239, + 704, 705, 706, 742, 743, 33, 1238, 84, 85, 86, + 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 31, 263,-32766,-32766,-32766, 104, 105, 106, 577, 263, + 1216, 126, -188, 107, 142, 440, 441, 707,-32766,-32766, + -32766, 107, -254,-32766, 247,-32766,-32766,-32766,-32766,-32766, + -32766, 708, 709, 710, 711, 712, 713, 714, 293,-32766, + 776,-32766,-32766,-32766,-32766,-32766, 295, 715, 716, 717, + 718, 719, 720, 721, 722, 723, 724, 725, 745, 768, + 746, 747, 748, 749, 737, 738, 739, 767, 740, 741, + 726, 727, 728, 730, 731, 732, 770, 771, 772, 773, + 774, 775, 733, 734, 735, 736, 766, 757, 755, 756, + 752, 753, 529, 311, 744, 750, 751, 758, 759, 761, + 760, 762, 763, 234,-32766,-32766,-32766, 307, 754, 765, + 764, 48, 49, 50, 486, 51, 52, 481, 397, 18, + 321, 53, 54, 345, 55,-32766, 982,-32766,-32766,-32766, + -32766,-32766,-32766,-32767,-32767,-32767,-32767,-32767, 349,-32767, + -32767,-32767,-32767, 99, 100, 101, 102, 103, 814, 354, + 815, 1191, 356, 1152, 871, 271, 408, 871, 56, 57, + 409, 814, 410, 815, 58, -188, 59, 240, 241, 60, + 61, 62, 63, 64, 65, 66, 67,-32766, 26, 262, + 68, 412, 487, 411, 672, 967, 1185, 1186, 488, 1150, + 1216, 1154, 1153, 1155, 1183, 40, 23, 489, 1009, 490, + -82, 491, 147, 492, 969, 970, 493, 494, 786, 429, + 430, 42, 43, 413, 418, 415, 871, 44, 495, 391, + 496, 497, 248, 344, 320, 1159, 1154, 1153, 1155, 793, + 896, 498, 499, 500, 148, 1008, 861, 692, 787, 861, + 967, 1254, 501, 502, 150, 1173, 1174, 1175, 1176, 1170, + 1171, 281, 624, 24, 26, -14, 151, 1177, 1172, 969, + 970, 1154, 1153, 1155, 282, -82, 1216, -502, 152, 69, + 1183, 305, 306, 311, 34, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 154, -149, + -149, -149, 639, 640, 146, 376, 1159, 1159, 861, 615, + 616, 32, 243, 35, -149, 1216, -149, 121, -149, 873, + -149, 667, 873, 122, 667, 242, 1067, 1069, 501, 502, + 414, 1173, 1174, 1175, 1176, 1170, 1171, -501, 127, -502, + -502, 496, 497, 1177, 1172, -504, 128, 871, 424, 425, + 847, 896, -107, -107, -502, 71, 442, 443, 306, 311, + -107,-32766, 432, 433, -49, 141, -502, 1152, -508, 155, + 156, 780, 157, -84,-32766,-32766,-32766, 673,-32766, -76, + -32766, 873,-32766, 667, -149,-32766, 1216, 1216, 1179, 282, + -32766,-32766,-32766, -73, 73, -71,-32766,-32766, 311, -501, + -501, 129,-32766, 388, -70, -69,-32766, -504, -504, -68, + -32766, -67, 1152, -66, -501, -65, 871, -64, 275,-32766, + -32766,-32766, -504,-32766, -45,-32766, -501,-32766, -16, 861, + -32766, 145, -107, 264, -504,-32766,-32766,-32766, 682, 72, + 244,-32766,-32766,-32766, 685, 782, 674,-32766, 388, 1152, + 669, 871, -501, 870, 144,-32766,-32766,-32766,-32766, 272, + -32766, 282,-32766, 273,-32766, 73, 73,-32766, 1216, 311, + 311, 276,-32766,-32766,-32766, 885,-32766, 246,-32766,-32766, + 277, 677, 1152, 314,-32766, 388, -4, 871, 263,-32766, + -32766,-32766,-32766,-32766, 107,-32766, 143,-32766, 861, 778, + -32766, 871, 873,-32766, 667,-32766,-32766,-32766, 625, 647, + 871,-32766,-32766,-32766, -501, -501, 787,-32766, 388, 1152, + 1037,-32766, 969, 970, 1266,-32766,-32766,-32766,-32766, -501, + -32766, 531,-32766, 861,-32766, 660, 871,-32766, 630, 535, + 683, -501,-32766,-32766,-32766, 138,-32766, 642,-32766,-32766, + 1023, 311, 1152, 20,-32766, 388, 437, 466, 631,-32766, + -32766,-32766,-32766,-32766, 643,-32766, 286,-32766, -506, 861, + -32766, 913, 407, 667, 613,-32766,-32766,-32766,-32766, 284, + -467,-32766,-32766, 861, 46, 283, 282,-32766, 388, 686, + 897, 414, 861, 402, 898,-32766, 294, 38, 280, -232, + -232, -232, 496, 497, 1007, 414, 873, 26, 667, 1190, + 786, 806, 896, -107, -107, 1192, 496, 497, 861, 1216, + 47, -457, 8, 1183, 22, 847, 896, -107, -107, 347, + -506, -506, 541, 9, -231, -231, -231, 581, 1180, 887, + 414, 39, 873, 848, 667, -4, 289, 290, 689, 690, + 852, 496, 497, 937, 914, 1261, 873, -506, 667, -232, + 847, 896, -107, -107, 921, 873, 911, 667, 922, 850, + 909, -537, 502, 123, 1173, 1174, 1175, 1176, 1170, 1171, + 1012, 1015, 291, 292, 1016, 1013, 1177, 1172, 1014, 1020, + 1263, 873, 30, 667, -231, 304, 798, 348, 71, 1205, + 1223, 306, 311, 1257, 618, -535, 346, 668, -107, 125, + -107, 671, 675, 676, 678, 285, 679, 680, -107, -107, + -107, -107, -107, -107, -107, 684, 670, -257, 809, 808, + 817, 895, 929, 816, 1262, 894, 892, 893, 1138, 880, + 888, 878, 919, 920, 1260, 1217, 1206, 1224, 1230, 1233, + 0, -509, -508, -507, 1, 27, 28, 37, 41, 45, + 70, 74, -308, -255, 75, 76, 77, 78, 79, 140, + 149, 153, 239, 310, 333, 334, 335, 336, 337, 338, + 339, 340, 341, 342, 343, 403, 404, 0, -254, 12, + 13, 14, 15, 17, 375, 457, 458, 465, 468, 469, + 470, 471, 475, 476, 477, 484, 654, 1163, 1106, 1181, + 984, 1142, -259, -99, 11, 16, 25, 279, 374, 574, + 578, 605, 659, 1110, 1158, 1107, 1236, 0, -471, 1123, + 0, 1184 + ); + + protected $actionCheck = array( + 2, 3, 4, 5, 6, 7, 115, 9, 10, 11, + 12, 13, 157, 1, 31, 73, 161, 9, 10, 11, + 8, 79, 79, 31, 1, 134, 135, 0, 86, 87, + 88, 8, 90, 1, 92, 37, 94, 1, 30, 97, + 32, 33, 34, 101, 102, 103, 104, 9, 10, 11, + 108, 109, 14, 105, 56, 107, 114, 115, 115, 116, + 117, 118, 119, 120, 122, 105, 106, 8, 70, 71, + 72, 73, 74, 75, 76, 115, 1, 79, 115, 116, + 117, 118, 119, 120, 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, 8, + 8, 133, 134, 135, 136, 137, 138, 139, 140, 141, + 1, 158, 79, 69, 1, 147, 148, 149, 156, 151, + 9, 10, 11, 2, 3, 4, 5, 6, 7, 161, + 9, 10, 11, 12, 13, 9, 10, 11, 73, 157, + 9, 10, 11, 161, 79, 80, 49, 50, 51, 50, + 157, 86, 87, 88, 161, 90, 30, 92, 37, 94, + 158, 30, 97, 32, 33, 34, 35, 102, 103, 104, + 118, 119, 120, 108, 109, 131, 132, 56, 79, 114, + 115, 82, 9, 10, 11, 14, 153, 122, 79, 8, + 146, 70, 71, 72, 73, 74, 75, 76, 9, 10, + 79, 156, 158, 30, 160, 32, 33, 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, 161, 161, 133, 134, 135, 136, 137, 138, + 139, 140, 141, 69, 9, 10, 11, 158, 147, 148, + 149, 158, 151, 2, 3, 4, 5, 6, 7, 1, + 9, 10, 11, 12, 13, 30, 8, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + 8, 56, 9, 10, 11, 52, 53, 54, 1, 56, + 81, 8, 8, 68, 8, 131, 132, 56, 9, 10, + 11, 68, 161, 30, 8, 32, 33, 34, 35, 36, + 37, 70, 71, 72, 73, 74, 75, 76, 8, 30, + 79, 32, 33, 34, 35, 36, 8, 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, 84, 164, 133, 134, 135, 136, 137, 138, + 139, 140, 141, 96, 9, 10, 11, 8, 147, 148, + 149, 2, 3, 4, 5, 6, 7, 105, 79, 107, + 8, 12, 13, 8, 15, 30, 1, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 8, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 105, 8, + 107, 143, 8, 79, 1, 30, 8, 1, 49, 50, + 8, 105, 8, 107, 55, 161, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 9, 69, 70, + 71, 72, 73, 8, 31, 115, 77, 78, 79, 115, + 81, 152, 153, 154, 85, 86, 87, 88, 159, 90, + 31, 92, 14, 94, 134, 135, 97, 98, 152, 105, + 106, 102, 103, 104, 105, 106, 1, 108, 109, 115, + 116, 117, 37, 114, 115, 1, 152, 153, 154, 8, + 126, 122, 123, 124, 14, 156, 83, 158, 81, 83, + 115, 84, 133, 134, 14, 136, 137, 138, 139, 140, + 141, 142, 74, 75, 69, 31, 14, 148, 149, 134, + 135, 152, 153, 154, 155, 96, 81, 69, 14, 160, + 85, 162, 163, 164, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 14, 74, + 75, 76, 74, 75, 100, 101, 1, 1, 83, 110, + 111, 144, 145, 14, 89, 81, 91, 16, 93, 156, + 95, 158, 156, 16, 158, 37, 58, 59, 133, 134, + 105, 136, 137, 138, 139, 140, 141, 69, 16, 131, + 132, 116, 117, 148, 149, 69, 16, 1, 105, 106, + 125, 126, 127, 128, 146, 160, 105, 106, 163, 164, + 126, 73, 127, 128, 31, 16, 158, 79, 160, 16, + 16, 79, 16, 31, 86, 87, 88, 31, 90, 31, + 92, 156, 94, 158, 159, 97, 81, 81, 1, 155, + 102, 103, 104, 31, 160, 31, 108, 109, 164, 131, + 132, 31, 114, 115, 31, 31, 73, 131, 132, 31, + 122, 31, 79, 31, 146, 31, 1, 31, 30, 86, + 87, 88, 146, 90, 31, 92, 158, 94, 31, 83, + 97, 31, 126, 31, 158, 102, 103, 104, 31, 151, + 37, 108, 109, 73, 31, 153, 31, 114, 115, 79, + 158, 1, 69, 31, 31, 122, 86, 87, 88, 35, + 90, 155, 92, 35, 94, 160, 160, 97, 81, 164, + 164, 35, 102, 103, 104, 37, 73, 37, 108, 109, + 35, 31, 79, 35, 114, 115, 0, 1, 56, 86, + 87, 88, 122, 90, 68, 92, 69, 94, 83, 79, + 97, 1, 156, 115, 158, 102, 103, 104, 89, 76, + 1, 108, 109, 73, 131, 132, 81, 114, 115, 79, + 81, 84, 134, 135, 82, 122, 86, 87, 88, 146, + 90, 84, 92, 83, 94, 91, 1, 97, 95, 88, + 31, 158, 102, 103, 104, 158, 73, 93, 108, 109, + 121, 164, 79, 96, 114, 115, 96, 96, 99, 86, + 87, 88, 122, 90, 99, 92, 113, 94, 69, 83, + 97, 156, 126, 158, 112, 102, 103, 104, 115, 130, + 146, 108, 109, 83, 69, 129, 155, 114, 115, 159, + 126, 105, 83, 107, 126, 122, 129, 156, 112, 99, + 100, 101, 116, 117, 1, 105, 156, 69, 158, 143, + 152, 125, 126, 127, 128, 143, 116, 117, 83, 81, + 69, 146, 146, 85, 146, 125, 126, 127, 128, 146, + 131, 132, 150, 147, 99, 100, 101, 150, 157, 151, + 105, 156, 156, 159, 158, 159, 131, 132, 156, 156, + 156, 116, 117, 156, 156, 159, 156, 158, 158, 159, + 125, 126, 127, 128, 156, 156, 156, 158, 156, 156, + 156, 160, 134, 158, 136, 137, 138, 139, 140, 141, + 156, 156, 131, 132, 156, 156, 148, 149, 156, 156, + 159, 156, 158, 158, 159, 158, 157, 146, 160, 157, + 157, 163, 164, 157, 157, 160, 158, 158, 105, 158, + 107, 158, 158, 158, 158, 112, 158, 158, 115, 116, + 117, 118, 119, 120, 121, 158, 158, 161, 159, 159, + 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, + 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, + -1, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 159, 161, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, -1, 161, 161, + 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, + 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, + 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, + 161, 161, 161, 161, 161, 161, 161, -1, 162, 162, + -1, 163 + ); + + protected $actionBase = array( + 0, -2, 151, 555, 816, 830, 865, 379, 717, 622, + 862, 676, 780, 780, 839, 780, 493, 745, 301, 301, + -57, 301, 301, 496, 496, 496, 618, 618, 618, 618, + -58, -58, 95, 700, 733, 770, 663, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 803, 803, + 803, 803, 803, 803, 803, 803, 803, 803, 75, -8, + 347, 629, 986, 992, 988, 993, 984, 983, 987, 989, + 994, 915, 916, 753, 917, 918, 919, 920, 990, 877, + 985, 991, 285, 285, 285, 285, 285, 285, 285, 285, + 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, + 285, 285, 285, 508, 38, 219, 141, 141, 141, 141, + 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, + 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, + 156, 156, 156, 203, 525, 525, 8, 598, 161, 868, + 868, 868, 868, 868, 868, 868, 868, 868, 868, 349, + 333, 435, 435, 435, 435, 435, 436, 436, 436, 436, + 933, 564, 636, 635, 465, -52, 127, 127, 718, 718, + 759, 410, 410, 410, 444, -109, -109, -109, 74, 538, + 396, 348, 414, 414, 414, 414, 414, 802, 998, 139, + 139, 139, 139, 414, 414, 414, 606, 713, 713, 881, + 293, 293, 293, 713, 383, 777, 497, 383, 497, 129, + 793, 32, -40, -145, 793, 829, 845, 23, 12, 788, + 573, 788, 767, 863, 898, 995, 82, 789, 913, 795, + 914, 224, 678, 981, 981, 981, 981, 981, 981, 981, + 981, 981, 981, 981, 269, 982, 63, 269, 269, 269, + 529, 63, 518, 558, 63, 778, 982, 75, 805, 75, + 75, 75, 75, 944, 75, 75, 75, 75, 75, 75, + 949, 727, 723, 692, -17, 75, -8, 143, 143, 419, + 36, 143, 143, 143, 143, 75, 75, 565, 573, 762, + 812, 581, 817, 344, 762, 762, 762, 509, 121, 201, + 122, 352, 750, 750, 768, 769, 924, 924, 750, 765, + 750, 769, 929, 750, 768, 768, 750, 924, 768, 761, + 343, 488, 452, 470, 768, 768, 492, 924, 370, 768, + 768, 750, 750, 750, 797, 768, 494, 750, 356, 346, + 750, 750, 768, 768, 797, 786, 59, 779, 924, 924, + 924, 797, 455, 779, 779, 822, 823, 792, 732, 439, + 378, 561, 332, 768, 732, 732, 750, 481, 792, 732, + 792, 732, 818, 732, 732, 732, 792, 732, 765, 484, + 732, 768, 515, 211, 732, 27, 930, 931, 672, 934, + 927, 935, 955, 936, 937, 879, 794, 798, 942, 928, + 938, 926, 925, 752, 631, 637, 806, 764, 923, 756, + 756, 756, 921, 756, 756, 756, 756, 756, 756, 756, + 756, 631, 811, 813, 776, 781, 945, 652, 660, 796, + 814, 996, 997, 944, 976, 939, 771, 679, 962, 946, + 760, 867, 947, 948, 963, 977, 978, 826, 757, 861, + 899, 869, 950, 883, 756, 930, 937, 928, 938, 926, + 925, 716, 714, 710, 712, 708, 704, 694, 703, 730, + 875, 841, 872, 949, 922, 631, 873, 958, 864, 964, + 965, 878, 790, 772, 876, 900, 951, 952, 953, 884, + 979, 885, 815, 959, 896, 966, 791, 901, 967, 968, + 969, 970, 886, 902, 888, 824, 749, 932, 773, 903, + 528, 766, 775, 956, 560, 943, 889, 904, 905, 971, + 972, 973, 906, 907, 940, 827, 960, 784, 961, 957, + 828, 838, 570, 754, 758, 582, 594, 908, 909, 941, + 737, 763, 840, 842, 980, 910, 614, 843, 683, 911, + 975, 684, 686, 774, 897, 808, 783, 787, 954, 743, + 844, 912, 854, 855, 858, 974, 859, 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, 449, 449, 449, 449, 449, + 449, 301, 301, 301, 301, 449, 449, 449, 449, 449, + 449, 449, 0, 0, 301, 0, 0, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 285, 285, 285, 285, 285, 285, 285, 285, + 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, + 285, 285, 285, 285, 285, 285, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 285, 285, 285, + 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, + 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, + 285, 285, 285, 285, 285, 285, 285, 285, 414, 414, + 285, 0, 285, 414, 414, 414, 414, 414, 414, 414, + 414, 414, 414, 285, 285, 285, 285, 285, 285, 285, + 293, 293, 293, 293, 761, 414, 414, 414, 414, -37, + 293, 293, 414, 414, -37, 414, 414, 414, 761, 414, + 414, 414, 0, 0, 63, 497, 0, 0, 0, 0, + 0, 497, 497, 269, 269, 269, 269, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 63, 497, + 0, 63, 0, 765, 414, 269, 761, 308, 414, 0, + 0, 0, 0, 63, 765, 63, 497, 143, 75, 308, + 0, 534, 534, 534, 534, 0, 573, 761, 761, 761, + 761, 761, 761, 761, 761, 761, 761, 761, 0, 761, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 765, + 768, 0, 924, 0, 0, 0, 0, 750, 0, 0, + 0, 0, 0, 0, 750, 929, 768, 768, 0, 0, + 0, 0, 0, 0, 765, 0, 0, 0, 0, 0, + 0, 0, 0, 756, 790, 0, 790, 0, 756, 756, + 756 + ); + + protected $actionDefault = array( + 3,32767, 99,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 97, + 32767,32767,32767,32767,32767,32767, 555, 555, 555, 555, + 236, 99,32767,32767,32767,32767, 431, 350, 350, 350, + 32767,32767, 499, 499, 499, 499, 499, 499,32767,32767, + 32767,32767,32767,32767, 431,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, 97,32767,32767,32767, + 35, 5, 6, 8, 9, 48, 15,32767,32767,32767, + 32767,32767, 99,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 548,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 435, 414, 415, 417, 418, 349, 500, 554, + 293, 551, 348, 142, 305, 295, 224, 296, 240, 241, + 267, 345, 146, 379, 432, 381, 430, 434, 380, 355, + 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, + 370, 371, 372, 353, 354, 433, 436, 437, 440, 441, + 411, 410, 409, 377,32767,32767, 378, 352, 382,32767, + 32767,32767,32767,32767,32767,32767,32767, 99,32767, 384, + 383, 400, 401, 398, 399, 402, 403, 404, 405, 406, + 32767,32767,32767,32767,32767, 328, 391, 392, 284, 284, + 330,32767,32767,32767, 108,32767,32767,32767, 493, 408, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 99,32767, 97, 495, 374, 376, 463, + 386, 387, 385, 356,32767, 470,32767, 99, 472,32767, + 32767,32767,32767,32767,32767, 494,32767, 501, 501,32767, + 456, 97,32767,32767,32767,32767, 262,32767,32767,32767, + 32767, 562, 456, 107, 107, 107, 107, 107, 107, 107, + 107, 107, 107, 107,32767, 107,32767,32767,32767, 97, + 185,32767, 250, 252, 99, 516, 190,32767, 475,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 468, 190, 190,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 456, 396, + 135,32767, 135, 501, 388, 389, 390, 458, 501, 501, + 501,32767,32767,32767, 190,32767, 473, 473, 97, 97, + 97, 97, 468,32767, 190, 190,32767,32767, 190, 108, + 96, 96, 96, 96, 190, 190, 96, 100, 98, 190, + 190,32767,32767,32767, 205, 190, 96,32767, 98, 98, + 32767,32767, 190, 190, 205, 207, 98, 209,32767, 520, + 521, 205, 98, 209, 209, 229, 229, 447, 286, 98, + 96, 98, 98, 190, 286, 286,32767, 98, 447, 286, + 447, 286, 192, 286, 286, 286, 447, 286,32767, 98, + 286, 190, 96, 96, 286,32767,32767,32767, 458,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 488,32767, 505, 518, 394, + 395, 397, 503, 419, 420, 421, 422, 423, 424, 425, + 427, 550,32767, 462,32767,32767,32767,32767, 304, 560, + 32767, 560,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 561,32767, 501, + 32767,32767,32767,32767, 393, 7, 74, 41, 42, 50, + 56, 479, 480, 481, 482, 476, 477, 483, 478,32767, + 484, 526,32767,32767, 502, 553,32767,32767,32767,32767, + 32767,32767, 135,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 488,32767, 133,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 501,32767,32767, + 32767, 281, 283,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 501, + 32767,32767,32767, 269, 271,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 266,32767, + 32767, 344,32767,32767,32767,32767, 324,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 148, 148, 3, + 3, 307, 148, 148, 148, 307, 307, 148, 307, 307, + 148, 148, 148, 148, 148, 148, 180, 244, 247, 229, + 229, 148, 316, 148 + ); + + protected $goto = array( + 190, 190, 655, 781, 663, 399, 629, 964, 971, 972, + 393, 297, 298, 317, 549, 303, 398, 318, 400, 607, + 361, 365, 534, 572, 576, 161, 161, 161, 161, 187, + 187, 171, 173, 209, 191, 204, 187, 187, 187, 187, + 187, 188, 188, 188, 188, 188, 188, 182, 183, 184, + 185, 186, 206, 204, 207, 509, 510, 389, 511, 513, + 514, 515, 516, 517, 518, 519, 520, 1053, 162, 163, + 164, 189, 165, 166, 167, 160, 168, 169, 170, 172, + 203, 205, 208, 230, 233, 236, 238, 249, 250, 251, + 252, 253, 254, 255, 256, 257, 258, 259, 266, 267, + 300, 301, 302, 394, 395, 396, 554, 210, 211, 212, + 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, + 223, 224, 174, 225, 175, 192, 193, 194, 231, 182, + 183, 184, 185, 186, 206, 1053, 195, 176, 177, 178, + 196, 192, 179, 232, 197, 159, 198, 226, 180, 199, + 227, 228, 181, 229, 200, 201, 202, 807, 611, 611, + 804, 274, 274, 274, 274, 976, 973, 974, 592, 626, + 1147, 594, 594, 571, 533, 526, 1182, 1182, 1182, 1182, + 1182, 1182, 1182, 1182, 1182, 1182, 1250, 1250, 986, 328, + 812, 1027, 860, 855, 856, 869, 1026, 813, 857, 810, + 858, 859, 811, 803, 1251, 1251, 805, 1250, 863, 912, + 936, 910, 910, 908, 910, 687, 363, 526, 1003, 1004, + 533, 1253, 525, 945, 940, 1251, 542, 543, 820, 1148, + 838, 370, 552, 832, 308, 288, 819, 573, 864, 795, + 865, 1200, 1200, 785, 587, 588, 1200, 1200, 1200, 1200, + 1200, 1200, 1200, 1200, 1200, 1200, 1151, 1151, 1151, 968, + 1149, 1208, 1209, 968, 968, 472, 968, 968, 968, 779, + 968, 968, 968, 1232, 1232, 1232, 1232, 1151, 1151, 1151, + 1151, 1151, 785, 21, 785, 546, 1198, 1198, 1151, 1151, + 1151, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, + 1198, 523, 523, 523, 325, 876, 512, 512, 392, 877, + 582, 512, 512, 512, 512, 512, 512, 512, 512, 512, + 512, 1240, 427, 906, 906, 906, 906, 387, 387, 387, + 387, 602, 604, 427, 900, 907, 540, 904, 379, 662, + 688, 606, 608, 933, 5, 627, 6, 539, 645, 649, + 947, 653, 661, 943, 586, 377, 378, 800, 1019, 570, + 635, 666, 636, 359, 381, 382, 383, 453, 646, 652, + 652, 384, 658, 1017, 454, 323, 580, 595, 598, 599, + 600, 601, 619, 620, 621, 665, 527, 537, 1267, 450, + 1225, 1226, 527, 545, 537, 800, 628, 362, 1101, 528, + 434, 521, 521, 521, 521, 1227, 1228, 1132, 890, 405, + 575, 1133, 1136, 891, 1137, 444, 553, 445, 419, 419, + 419, 830, 329, 330, 1258, 1259, 1222, 1222, 1222, 622, + 623, 431, 637, 638, 1211, 439, 439, 550, 585, 881, + 1041, 797, 319, 833, 821, 991, 439, 590, 995, 825, + 828, 369, 822, 952, 1234, 1234, 1234, 1234, 915, 648, + 824, 955, 632, 931, 473, 691, 474, 992, 818, 1144, + 451, 996, 480, 0, 834, 1036, 1218, 917, 0, 0, + 1143, 0, 905, 0, 0, 0, 0, 0, 528, 0, + 0, 419, 419, 419, 419, 419, 419, 419, 419, 419, + 419, 419, 800, 419, 1034, 837, 0, 0, 0, 994, + 0, 0, 1220, 1220, 994, 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, 1146, 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, 270, 524, 524, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 950, + 950 + ); + + protected $gotoCheck = array( + 41, 41, 71, 6, 8, 64, 64, 105, 105, 105, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 57, 57, 57, 57, 57, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 14, 107, 107, + 25, 22, 22, 22, 22, 107, 107, 107, 54, 54, + 19, 99, 99, 114, 74, 74, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 164, 164, 111, 88, + 14, 128, 14, 14, 14, 14, 128, 14, 14, 14, + 14, 14, 14, 24, 165, 165, 26, 164, 14, 48, + 24, 24, 24, 24, 24, 24, 74, 74, 14, 14, + 74, 164, 24, 24, 24, 165, 74, 74, 34, 19, + 44, 74, 74, 34, 151, 151, 34, 74, 63, 19, + 63, 152, 152, 11, 74, 74, 152, 152, 152, 152, + 152, 152, 152, 152, 152, 152, 71, 71, 71, 71, + 19, 19, 19, 71, 71, 74, 71, 71, 71, 5, + 71, 71, 71, 8, 8, 8, 8, 71, 71, 71, + 71, 71, 11, 74, 11, 154, 153, 153, 71, 71, + 71, 153, 153, 153, 153, 153, 153, 153, 153, 153, + 153, 18, 18, 18, 161, 71, 155, 155, 12, 71, + 12, 155, 155, 155, 155, 155, 155, 155, 155, 155, + 155, 163, 18, 18, 18, 18, 18, 23, 23, 23, + 23, 81, 81, 18, 18, 18, 47, 84, 84, 84, + 47, 47, 47, 94, 45, 47, 45, 8, 47, 47, + 47, 47, 47, 47, 8, 78, 78, 21, 7, 7, + 78, 7, 78, 60, 78, 78, 78, 80, 78, 7, + 7, 78, 7, 7, 80, 78, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 8, 8, 13, 158, + 158, 158, 8, 95, 8, 21, 62, 8, 135, 13, + 8, 98, 98, 98, 98, 160, 160, 76, 76, 103, + 98, 76, 76, 76, 76, 8, 8, 8, 22, 22, + 22, 8, 88, 88, 8, 8, 114, 114, 114, 82, + 82, 79, 82, 82, 13, 133, 133, 2, 2, 16, + 16, 17, 28, 15, 15, 15, 133, 16, 15, 38, + 8, 27, 36, 16, 114, 114, 114, 114, 15, 13, + 16, 101, 16, 16, 139, 90, 139, 113, 16, 144, + 141, 116, 139, -1, 40, 131, 114, 87, -1, -1, + 16, -1, 15, -1, -1, -1, -1, -1, 13, -1, + -1, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 21, 22, 15, 15, -1, -1, -1, 114, + -1, -1, 114, 114, 114, -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, 13, -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, 23, 23, 23, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 98, + 98 + ); + + protected $gotoBase = array( + 0, 0, -232, 0, 0, 249, -6, 351, -16, 0, + 0, -37, -11, 79, -167, 22, 1, 148, 40, -188, + 0, 83, 158, 324, 199, 156, 202, 132, 162, 0, + 0, 0, 0, 0, -118, 0, 131, 0, 142, 0, + 66, -1, 0, 0, 211, -347, 0, -332, 192, 0, + 0, 0, 0, 0, 130, 0, 0, -23, 0, 0, + 323, 0, 161, 225, -229, 0, 0, 0, 0, 0, + 0, -5, 0, 0, -198, 0, 30, 42, -109, 157, + -77, -122, -246, 0, 53, 0, 0, 67, -267, 0, + 89, 0, 0, 0, 312, 352, 0, 0, 375, -63, + 0, 116, 0, 140, 0, -264, 0, -110, 0, 0, + 0, 186, 0, 118, 165, 0, 62, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, -74, 0, + 0, 64, 0, 405, 0, 135, 0, 0, 0, -4, + 0, 63, 0, 0, 65, 0, 0, 0, 0, 0, + 0, -71, 7, 52, 258, 72, 0, 0, 99, 0, + 58, 276, 0, 292, -101, -83, 0, 0 + ); + + protected $gotoDefault = array( + -32768, 485, 695, 4, 696, 769, 777, 569, 503, 664, + 324, 596, 390, 331, 862, 1040, 551, 796, 1160, 1168, + 428, 799, 312, 326, 844, 845, 846, 366, 351, 357, + 364, 617, 597, 467, 831, 422, 823, 459, 826, 421, + 835, 158, 386, 483, 839, 3, 841, 530, 872, 352, + 849, 353, 641, 851, 536, 853, 854, 360, 367, 368, + 1045, 544, 593, 866, 237, 538, 867, 350, 868, 875, + 355, 358, 650, 438, 478, 380, 1021, 579, 614, 416, + 447, 591, 603, 589, 902, 460, 436, 916, 327, 924, + 693, 1052, 609, 462, 932, 610, 939, 942, 504, 505, + 452, 954, 268, 463, 981, 633, 634, 966, 612, 979, + 446, 985, 423, 993, 1204, 426, 997, 260, 1000, 269, + 385, 401, 1005, 1006, 7, 1011, 656, 657, 10, 265, + 482, 1035, 651, 420, 1051, 406, 1120, 1122, 532, 464, + 1140, 1139, 644, 479, 1145, 1207, 417, 506, 448, 299, + 507, 287, 315, 296, 522, 278, 316, 508, 449, 1213, + 1221, 313, 29, 1241, 1252, 322, 548, 584 + ); + + protected $ruleToNonTerminal = array( + 0, 1, 3, 3, 2, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, + 6, 6, 7, 7, 8, 9, 10, 10, 10, 11, + 11, 12, 12, 13, 14, 14, 15, 15, 16, 16, + 17, 17, 20, 20, 21, 22, 22, 23, 23, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 28, 28, 29, 29, 31, 33, 33, 27, 35, 35, + 32, 37, 37, 34, 34, 36, 36, 38, 38, 30, + 39, 39, 40, 42, 43, 43, 44, 45, 45, 47, + 46, 46, 46, 46, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 24, 24, + 67, 67, 70, 70, 69, 68, 68, 61, 73, 73, + 74, 74, 75, 75, 76, 76, 25, 25, 26, 26, + 26, 26, 79, 79, 79, 80, 80, 83, 83, 81, + 81, 84, 85, 85, 55, 55, 63, 63, 66, 66, + 66, 65, 86, 86, 87, 56, 56, 56, 56, 88, + 88, 89, 89, 90, 90, 91, 92, 92, 93, 93, + 94, 94, 53, 53, 49, 49, 96, 51, 51, 97, + 50, 50, 52, 52, 62, 62, 62, 62, 77, 77, + 100, 100, 102, 102, 102, 102, 101, 101, 101, 104, + 104, 104, 105, 105, 107, 107, 107, 106, 106, 108, + 108, 109, 109, 109, 103, 103, 78, 78, 78, 19, + 19, 110, 110, 111, 111, 111, 111, 58, 112, 112, + 113, 59, 115, 115, 116, 116, 117, 117, 82, 118, + 118, 118, 118, 118, 123, 123, 124, 124, 125, 125, + 125, 125, 125, 126, 127, 127, 122, 122, 119, 119, + 121, 121, 129, 129, 128, 128, 128, 128, 128, 128, + 120, 130, 130, 132, 131, 131, 60, 95, 133, 133, + 54, 54, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 140, 134, 134, 139, 139, 142, + 143, 143, 144, 145, 145, 145, 18, 18, 71, 71, + 71, 71, 135, 135, 135, 135, 147, 147, 136, 136, + 138, 138, 138, 141, 141, 152, 152, 152, 152, 152, + 152, 152, 152, 152, 153, 153, 99, 155, 155, 155, + 155, 137, 137, 137, 137, 137, 137, 137, 137, 57, + 57, 150, 150, 150, 150, 156, 156, 146, 146, 146, + 157, 157, 157, 157, 157, 157, 72, 72, 64, 64, + 64, 64, 114, 114, 114, 114, 160, 159, 149, 149, + 149, 149, 149, 149, 149, 148, 148, 148, 158, 158, + 158, 158, 98, 154, 162, 162, 161, 161, 163, 163, + 163, 163, 163, 163, 163, 163, 151, 151, 151, 151, + 165, 166, 164, 164, 164, 164, 164, 164, 164, 164, + 167, 167, 167, 167 + ); + + protected $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, 0, 1, 0, 1, + 1, 2, 1, 3, 4, 1, 2, 0, 1, 1, + 1, 1, 1, 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, 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, + 0, 2, 1, 3, 8, 0, 4, 2, 1, 3, + 0, 1, 0, 1, 3, 1, 8, 9, 7, 8, + 7, 6, 1, 2, 2, 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, 1, 1, 1, 6, 8, 6, 1, + 2, 1, 1, 1, 1, 1, 1, 3, 3, 3, + 3, 1, 2, 1, 0, 1, 0, 2, 2, 2, + 4, 1, 3, 1, 2, 2, 3, 2, 3, 1, + 1, 2, 3, 1, 1, 3, 2, 0, 1, 5, + 5, 10, 3, 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, + 2, 1, 3, 1, 1, 3, 2, 2, 3, 1, + 0, 1, 1, 3, 3, 3, 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, 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, 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, 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() { + $this->reduceCallbacks = [ + 0 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 1 => function ($stackPos) { + $this->semValue = $this->handleNamespaces($this->semStack[$stackPos-(1-1)]); + }, + 2 => function ($stackPos) { + if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }; + }, + 3 => function ($stackPos) { + $this->semValue = array(); + }, + 4 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 5 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 6 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 7 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 8 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 9 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 10 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 11 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 12 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 13 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 14 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 15 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 16 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 17 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 18 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 19 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 20 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 21 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 22 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 23 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 24 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 25 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 26 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 27 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 28 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 29 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 30 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 31 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 32 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 33 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 34 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 35 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 36 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 37 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 38 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 39 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 40 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 41 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 42 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 43 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 44 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 45 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 46 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 47 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 48 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 49 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 50 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 51 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 52 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 53 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 54 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 55 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 56 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 57 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 58 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 59 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 60 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 61 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 62 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 63 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 64 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 65 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 66 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 67 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 68 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 69 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 70 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 71 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 72 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 73 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 74 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 75 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 76 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 77 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 78 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 79 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 80 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 81 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 82 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 83 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 84 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 85 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 86 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 87 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 88 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 89 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 90 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 91 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 92 => function ($stackPos) { + $this->semValue = new Name(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 93 => function ($stackPos) { + $this->semValue = new Expr\Variable(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 94 => function ($stackPos) { + /* nothing */ + }, + 95 => function ($stackPos) { + /* nothing */ + }, + 96 => function ($stackPos) { + /* nothing */ + }, + 97 => function ($stackPos) { + $this->emitError(new Error('A trailing comma is not allowed here', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes)); + }, + 98 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 99 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 100 => function ($stackPos) { + $this->semValue = new Node\Attribute($this->semStack[$stackPos-(1-1)], [], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 101 => function ($stackPos) { + $this->semValue = new Node\Attribute($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 102 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 103 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 104 => function ($stackPos) { + $this->semValue = new Node\AttributeGroup($this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 105 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 106 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 107 => function ($stackPos) { + $this->semValue = []; + }, + 108 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 109 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 110 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 111 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 112 => function ($stackPos) { + $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 113 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(3-2)], null, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $this->checkNamespace($this->semValue); + }, + 114 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(5-2)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, + 115 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_(null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, + 116 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 117 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 118 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 119 => function ($stackPos) { + $this->semValue = new Stmt\Const_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 120 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_FUNCTION; + }, + 121 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_CONSTANT; + }, + 122 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-6)], $this->semStack[$stackPos-(7-2)], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 123 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 124 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 125 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 126 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 127 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 128 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 129 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 130 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 131 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 132 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 133 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1)); + }, + 134 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3)); + }, + 135 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1)); + }, + 136 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3)); + }, + 137 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; $this->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, + 138 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; $this->semValue->type = $this->semStack[$stackPos-(2-1)]; + }, + 139 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 140 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 141 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 142 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 143 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 144 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 145 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 146 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 147 => function ($stackPos) { + if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }; + }, + 148 => function ($stackPos) { + $this->semValue = array(); + }, + 149 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 150 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 151 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 152 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 153 => function ($stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 154 => function ($stackPos) { + + if ($this->semStack[$stackPos-(3-2)]) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; $attrs = $this->startAttributeStack[$stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments'])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); }; + } else { + $startAttributes = $this->startAttributeStack[$stackPos-(3-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; }; + if (null === $this->semValue) { $this->semValue = array(); } + } + + }, + 155 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos-(7-3)], ['stmts' => is_array($this->semStack[$stackPos-(7-5)]) ? $this->semStack[$stackPos-(7-5)] : array($this->semStack[$stackPos-(7-5)]), 'elseifs' => $this->semStack[$stackPos-(7-6)], 'else' => $this->semStack[$stackPos-(7-7)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 156 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos-(10-3)], ['stmts' => $this->semStack[$stackPos-(10-6)], 'elseifs' => $this->semStack[$stackPos-(10-7)], 'else' => $this->semStack[$stackPos-(10-8)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + }, + 157 => function ($stackPos) { + $this->semValue = new Stmt\While_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 158 => function ($stackPos) { + $this->semValue = new Stmt\Do_($this->semStack[$stackPos-(7-5)], is_array($this->semStack[$stackPos-(7-2)]) ? $this->semStack[$stackPos-(7-2)] : array($this->semStack[$stackPos-(7-2)]), $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 159 => function ($stackPos) { + $this->semValue = new Stmt\For_(['init' => $this->semStack[$stackPos-(9-3)], 'cond' => $this->semStack[$stackPos-(9-5)], 'loop' => $this->semStack[$stackPos-(9-7)], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 160 => function ($stackPos) { + $this->semValue = new Stmt\Switch_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 161 => function ($stackPos) { + $this->semValue = new Stmt\Break_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 162 => function ($stackPos) { + $this->semValue = new Stmt\Continue_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 163 => function ($stackPos) { + $this->semValue = new Stmt\Return_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 164 => function ($stackPos) { + $this->semValue = new Stmt\Global_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 165 => function ($stackPos) { + $this->semValue = new Stmt\Static_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 166 => function ($stackPos) { + $this->semValue = new Stmt\Echo_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 167 => function ($stackPos) { + $this->semValue = new Stmt\InlineHTML($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 168 => function ($stackPos) { + + $e = $this->semStack[$stackPos-(2-1)]; + if ($e instanceof Expr\Throw_) { + // For backwards-compatibility reasons, convert throw in statement position into + // Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_). + $this->semValue = new Stmt\Throw_($e->expr, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + } else { + $this->semValue = new Stmt\Expression($e, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + } + + }, + 169 => function ($stackPos) { + $this->semValue = new Stmt\Unset_($this->semStack[$stackPos-(5-3)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 170 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$stackPos-(7-5)][1], 'stmts' => $this->semStack[$stackPos-(7-7)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 171 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(9-3)], $this->semStack[$stackPos-(9-7)][0], ['keyVar' => $this->semStack[$stackPos-(9-5)], 'byRef' => $this->semStack[$stackPos-(9-7)][1], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 172 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(6-3)], new Expr\Error($this->startAttributeStack[$stackPos-(6-4)] + $this->endAttributeStack[$stackPos-(6-4)]), ['stmts' => $this->semStack[$stackPos-(6-6)]], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 173 => function ($stackPos) { + $this->semValue = new Stmt\Declare_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 174 => function ($stackPos) { + $this->semValue = new Stmt\TryCatch($this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-5)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); $this->checkTryCatch($this->semValue); + }, + 175 => function ($stackPos) { + $this->semValue = new Stmt\Goto_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 176 => function ($stackPos) { + $this->semValue = new Stmt\Label($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 177 => function ($stackPos) { + $this->semValue = array(); /* means: no statement */ + }, + 178 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 179 => function ($stackPos) { + $startAttributes = $this->startAttributeStack[$stackPos-(1-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; }; + if ($this->semValue === null) $this->semValue = array(); /* means: no statement */ + }, + 180 => function ($stackPos) { + $this->semValue = array(); + }, + 181 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 182 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 183 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 184 => function ($stackPos) { + $this->semValue = new Stmt\Catch_($this->semStack[$stackPos-(8-3)], $this->semStack[$stackPos-(8-4)], $this->semStack[$stackPos-(8-7)], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 185 => function ($stackPos) { + $this->semValue = null; + }, + 186 => function ($stackPos) { + $this->semValue = new Stmt\Finally_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 187 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 188 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 189 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 190 => function ($stackPos) { + $this->semValue = false; + }, + 191 => function ($stackPos) { + $this->semValue = true; + }, + 192 => function ($stackPos) { + $this->semValue = false; + }, + 193 => function ($stackPos) { + $this->semValue = true; + }, + 194 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 195 => function ($stackPos) { + $this->semValue = []; + }, + 196 => function ($stackPos) { + $this->semValue = new Stmt\Function_($this->semStack[$stackPos-(8-3)], ['byRef' => $this->semStack[$stackPos-(8-2)], 'params' => $this->semStack[$stackPos-(8-5)], 'returnType' => $this->semStack[$stackPos-(8-7)], 'stmts' => $this->semStack[$stackPos-(8-8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 197 => function ($stackPos) { + $this->semValue = new Stmt\Function_($this->semStack[$stackPos-(9-4)], ['byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-6)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => $this->semStack[$stackPos-(9-1)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 198 => function ($stackPos) { + $this->semValue = new Stmt\Class_($this->semStack[$stackPos-(7-2)], ['type' => $this->semStack[$stackPos-(7-1)], 'extends' => $this->semStack[$stackPos-(7-3)], 'implements' => $this->semStack[$stackPos-(7-4)], 'stmts' => $this->semStack[$stackPos-(7-6)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + $this->checkClass($this->semValue, $stackPos-(7-2)); + }, + 199 => function ($stackPos) { + $this->semValue = new Stmt\Class_($this->semStack[$stackPos-(8-3)], ['type' => $this->semStack[$stackPos-(8-2)], 'extends' => $this->semStack[$stackPos-(8-4)], 'implements' => $this->semStack[$stackPos-(8-5)], 'stmts' => $this->semStack[$stackPos-(8-7)], 'attrGroups' => $this->semStack[$stackPos-(8-1)]], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + $this->checkClass($this->semValue, $stackPos-(8-3)); + }, + 200 => function ($stackPos) { + $this->semValue = new Stmt\Interface_($this->semStack[$stackPos-(7-3)], ['extends' => $this->semStack[$stackPos-(7-4)], 'stmts' => $this->semStack[$stackPos-(7-6)], 'attrGroups' => $this->semStack[$stackPos-(7-1)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + $this->checkInterface($this->semValue, $stackPos-(7-3)); + }, + 201 => function ($stackPos) { + $this->semValue = new Stmt\Trait_($this->semStack[$stackPos-(6-3)], ['stmts' => $this->semStack[$stackPos-(6-5)], 'attrGroups' => $this->semStack[$stackPos-(6-1)]], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 202 => function ($stackPos) { + $this->semValue = 0; + }, + 203 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, + 204 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, + 205 => function ($stackPos) { + $this->semValue = null; + }, + 206 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 207 => function ($stackPos) { + $this->semValue = array(); + }, + 208 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 209 => function ($stackPos) { + $this->semValue = array(); + }, + 210 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 211 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 212 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 213 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 214 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 215 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 216 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 217 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 218 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 219 => function ($stackPos) { + $this->semValue = null; + }, + 220 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 221 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 222 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 223 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 224 => function ($stackPos) { + $this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 225 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 226 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-3)]; + }, + 227 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 228 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(5-3)]; + }, + 229 => function ($stackPos) { + $this->semValue = array(); + }, + 230 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 231 => function ($stackPos) { + $this->semValue = new Stmt\Case_($this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 232 => function ($stackPos) { + $this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 233 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 234 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 235 => function ($stackPos) { + $this->semValue = new Expr\Match_($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-6)], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 236 => function ($stackPos) { + $this->semValue = []; + }, + 237 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 238 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 239 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 240 => function ($stackPos) { + $this->semValue = new Node\MatchArm($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 241 => function ($stackPos) { + $this->semValue = new Node\MatchArm(null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 242 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 243 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 244 => function ($stackPos) { + $this->semValue = array(); + }, + 245 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 246 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(5-3)], is_array($this->semStack[$stackPos-(5-5)]) ? $this->semStack[$stackPos-(5-5)] : array($this->semStack[$stackPos-(5-5)]), $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 247 => function ($stackPos) { + $this->semValue = array(); + }, + 248 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 249 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 250 => function ($stackPos) { + $this->semValue = null; + }, + 251 => function ($stackPos) { + $this->semValue = new Stmt\Else_(is_array($this->semStack[$stackPos-(2-2)]) ? $this->semStack[$stackPos-(2-2)] : array($this->semStack[$stackPos-(2-2)]), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 252 => function ($stackPos) { + $this->semValue = null; + }, + 253 => function ($stackPos) { + $this->semValue = new Stmt\Else_($this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 254 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 255 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(2-2)], true); + }, + 256 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 257 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 258 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 259 => function ($stackPos) { + $this->semValue = array(); + }, + 260 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 261 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 262 => function ($stackPos) { + $this->semValue = 0; + }, + 263 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + }, + 264 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + }, + 265 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + }, + 266 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos-(6-6)], null, $this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes, $this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-1)]); + $this->checkParam($this->semValue); + }, + 267 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos-(8-6)], $this->semStack[$stackPos-(8-8)], $this->semStack[$stackPos-(8-3)], $this->semStack[$stackPos-(8-4)], $this->semStack[$stackPos-(8-5)], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes, $this->semStack[$stackPos-(8-2)], $this->semStack[$stackPos-(8-1)]); + $this->checkParam($this->semValue); + }, + 268 => function ($stackPos) { + $this->semValue = new Node\Param(new Expr\Error($this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes), null, $this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes, $this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-1)]); + }, + 269 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 270 => function ($stackPos) { + $this->semValue = new Node\NullableType($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 271 => function ($stackPos) { + $this->semValue = new Node\UnionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 272 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 273 => function ($stackPos) { + $this->semValue = new Node\Name('static', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 274 => function ($stackPos) { + $this->semValue = $this->handleBuiltinTypes($this->semStack[$stackPos-(1-1)]); + }, + 275 => function ($stackPos) { + $this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 276 => function ($stackPos) { + $this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 277 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, + 278 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 279 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, + 280 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 281 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 282 => function ($stackPos) { + $this->semValue = new Node\NullableType($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 283 => function ($stackPos) { + $this->semValue = new Node\UnionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 284 => function ($stackPos) { + $this->semValue = null; + }, + 285 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 286 => function ($stackPos) { + $this->semValue = null; + }, + 287 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 288 => function ($stackPos) { + $this->semValue = null; + }, + 289 => function ($stackPos) { + $this->semValue = array(); + }, + 290 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 291 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 292 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 293 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(1-1)], false, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 294 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], true, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 295 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], false, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 296 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(3-3)], false, false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->semStack[$stackPos-(3-1)]); + }, + 297 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 298 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 299 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 300 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 301 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 302 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 303 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 304 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 305 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 306 => function ($stackPos) { + if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; } + }, + 307 => function ($stackPos) { + $this->semValue = array(); + }, + 308 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 309 => function ($stackPos) { + $this->semValue = new Stmt\Property($this->semStack[$stackPos-(5-2)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes, $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-1)]); + $this->checkProperty($this->semValue, $stackPos-(5-2)); + }, + 310 => function ($stackPos) { + $this->semValue = new Stmt\ClassConst($this->semStack[$stackPos-(5-4)], $this->semStack[$stackPos-(5-2)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes, $this->semStack[$stackPos-(5-1)]); + $this->checkClassConst($this->semValue, $stackPos-(5-2)); + }, + 311 => function ($stackPos) { + $this->semValue = new Stmt\ClassMethod($this->semStack[$stackPos-(10-5)], ['type' => $this->semStack[$stackPos-(10-2)], 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-7)], 'returnType' => $this->semStack[$stackPos-(10-9)], 'stmts' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + $this->checkClassMethod($this->semValue, $stackPos-(10-2)); + }, + 312 => function ($stackPos) { + $this->semValue = new Stmt\TraitUse($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 313 => function ($stackPos) { + $this->semValue = null; /* will be skipped */ + }, + 314 => function ($stackPos) { + $this->semValue = array(); + }, + 315 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 316 => function ($stackPos) { + $this->semValue = array(); + }, + 317 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 318 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 319 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(5-1)][0], $this->semStack[$stackPos-(5-1)][1], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 320 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], null, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 321 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 322 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 323 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, + 324 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 325 => function ($stackPos) { + $this->semValue = array(null, $this->semStack[$stackPos-(1-1)]); + }, + 326 => function ($stackPos) { + $this->semValue = null; + }, + 327 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 328 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 329 => function ($stackPos) { + $this->semValue = 0; + }, + 330 => function ($stackPos) { + $this->semValue = 0; + }, + 331 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 332 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 333 => function ($stackPos) { + $this->checkModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)]; + }, + 334 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + }, + 335 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + }, + 336 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + }, + 337 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_STATIC; + }, + 338 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, + 339 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, + 340 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 341 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 342 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 343 => function ($stackPos) { + $this->semValue = new Node\VarLikeIdentifier(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 344 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 345 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 346 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 347 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 348 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 349 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 350 => function ($stackPos) { + $this->semValue = array(); + }, + 351 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 352 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 353 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 354 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 355 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 356 => function ($stackPos) { + $this->semValue = new Expr\AssignRef($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 357 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 358 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 359 => function ($stackPos) { + $this->semValue = new Expr\Clone_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 360 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 361 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 362 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 363 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 364 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 365 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 366 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 367 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 368 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 369 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 370 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 371 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 372 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 373 => function ($stackPos) { + $this->semValue = new Expr\PostInc($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 374 => function ($stackPos) { + $this->semValue = new Expr\PreInc($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 375 => function ($stackPos) { + $this->semValue = new Expr\PostDec($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 376 => function ($stackPos) { + $this->semValue = new Expr\PreDec($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 377 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 378 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 379 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 380 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 381 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 382 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 383 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 384 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 385 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 386 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 387 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 388 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 389 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 390 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 391 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 392 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 393 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 394 => function ($stackPos) { + $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 395 => function ($stackPos) { + $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 396 => function ($stackPos) { + $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 397 => function ($stackPos) { + $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 398 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 399 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 400 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 401 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 402 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 403 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 404 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 405 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 406 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 407 => function ($stackPos) { + $this->semValue = new Expr\Instanceof_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 408 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 409 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(5-1)], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 410 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(4-1)], null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 411 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 412 => function ($stackPos) { + $this->semValue = new Expr\Isset_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 413 => function ($stackPos) { + $this->semValue = new Expr\Empty_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 414 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 415 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 416 => function ($stackPos) { + $this->semValue = new Expr\Eval_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 417 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 418 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 419 => function ($stackPos) { + $this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 420 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; + $attrs['kind'] = $this->getFloatCastKind($this->semStack[$stackPos-(2-1)]); + $this->semValue = new Expr\Cast\Double($this->semStack[$stackPos-(2-2)], $attrs); + }, + 421 => function ($stackPos) { + $this->semValue = new Expr\Cast\String_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 422 => function ($stackPos) { + $this->semValue = new Expr\Cast\Array_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 423 => function ($stackPos) { + $this->semValue = new Expr\Cast\Object_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 424 => function ($stackPos) { + $this->semValue = new Expr\Cast\Bool_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 425 => function ($stackPos) { + $this->semValue = new Expr\Cast\Unset_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 426 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; + $attrs['kind'] = strtolower($this->semStack[$stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $this->semValue = new Expr\Exit_($this->semStack[$stackPos-(2-2)], $attrs); + }, + 427 => function ($stackPos) { + $this->semValue = new Expr\ErrorSuppress($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 428 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 429 => function ($stackPos) { + $this->semValue = new Expr\ShellExec($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 430 => function ($stackPos) { + $this->semValue = new Expr\Print_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 431 => function ($stackPos) { + $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 432 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos-(2-2)], null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 433 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 434 => function ($stackPos) { + $this->semValue = new Expr\YieldFrom($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 435 => function ($stackPos) { + $this->semValue = new Expr\Throw_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 436 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $this->semStack[$stackPos-(8-2)], 'params' => $this->semStack[$stackPos-(8-4)], 'returnType' => $this->semStack[$stackPos-(8-6)], 'expr' => $this->semStack[$stackPos-(8-8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 437 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'returnType' => $this->semStack[$stackPos-(9-7)], 'expr' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 438 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$stackPos-(8-2)], 'params' => $this->semStack[$stackPos-(8-4)], 'uses' => $this->semStack[$stackPos-(8-6)], 'returnType' => $this->semStack[$stackPos-(8-7)], 'stmts' => $this->semStack[$stackPos-(8-8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 439 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'uses' => $this->semStack[$stackPos-(9-7)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 440 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'returnType' => $this->semStack[$stackPos-(9-7)], 'expr' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => $this->semStack[$stackPos-(9-1)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 441 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-6)], 'returnType' => $this->semStack[$stackPos-(10-8)], 'expr' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + }, + 442 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'uses' => $this->semStack[$stackPos-(9-7)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => $this->semStack[$stackPos-(9-1)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 443 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-6)], 'uses' => $this->semStack[$stackPos-(10-8)], 'returnType' => $this->semStack[$stackPos-(10-9)], 'stmts' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + }, + 444 => function ($stackPos) { + $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$stackPos-(8-4)], 'implements' => $this->semStack[$stackPos-(8-5)], 'stmts' => $this->semStack[$stackPos-(8-7)], 'attrGroups' => $this->semStack[$stackPos-(8-1)]], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes), $this->semStack[$stackPos-(8-3)]); + $this->checkClass($this->semValue[0], -1); + }, + 445 => function ($stackPos) { + $this->semValue = new Expr\New_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 446 => function ($stackPos) { + list($class, $ctorArgs) = $this->semStack[$stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 447 => function ($stackPos) { + $this->semValue = array(); + }, + 448 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-3)]; + }, + 449 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 450 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 451 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 452 => function ($stackPos) { + $this->semValue = new Expr\ClosureUse($this->semStack[$stackPos-(2-2)], $this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 453 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 454 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 455 => function ($stackPos) { + $this->semValue = new Expr\StaticCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 456 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 457 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 458 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 459 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 460 => function ($stackPos) { + $this->semValue = new Name\FullyQualified(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 461 => function ($stackPos) { + $this->semValue = new Name\Relative(substr($this->semStack[$stackPos-(1-1)], 10), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 462 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 463 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 464 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 465 => function ($stackPos) { + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2; + }, + 466 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 467 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 468 => function ($stackPos) { + $this->semValue = null; + }, + 469 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 470 => function ($stackPos) { + $this->semValue = array(); + }, + 471 => function ($stackPos) { + $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos-(1-1)], '`'), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes)); + }, + 472 => function ($stackPos) { + foreach ($this->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', true); } }; $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 473 => function ($stackPos) { + $this->semValue = array(); + }, + 474 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 475 => function ($stackPos) { + $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 476 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 477 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 478 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 479 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 480 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 481 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 482 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 483 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 484 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 485 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], new Expr\Error($this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)]), $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->errorState = 2; + }, + 486 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_SHORT; + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(3-2)], $attrs); + }, + 487 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_LONG; + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(4-3)], $attrs); + }, + 488 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 489 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(1-1)][0] === "'" || ($this->semStack[$stackPos-(1-1)][1] === "'" && ($this->semStack[$stackPos-(1-1)][0] === 'b' || $this->semStack[$stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED); + $this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(1-1)]), $attrs); + }, + 490 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs); + }, + 491 => function ($stackPos) { + $this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 492 => function ($stackPos) { + $this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$stackPos-(1-1)]), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 493 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 494 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 495 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 496 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true); + }, + 497 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], true); + }, + 498 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true); + }, + 499 => function ($stackPos) { + $this->semValue = null; + }, + 500 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 501 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 502 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 503 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 504 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 505 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 506 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 507 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 508 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 509 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 510 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 511 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 512 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 513 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 514 => function ($stackPos) { + $this->semValue = new Expr\MethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 515 => function ($stackPos) { + $this->semValue = new Expr\NullsafeMethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 516 => function ($stackPos) { + $this->semValue = null; + }, + 517 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 518 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 519 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 520 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 521 => function ($stackPos) { + $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 522 => function ($stackPos) { + $this->semValue = substr($this->semStack[$stackPos-(1-1)], 1); + }, + 523 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-3)]; + }, + 524 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 525 => function ($stackPos) { + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); $this->errorState = 2; + }, + 526 => function ($stackPos) { + $var = $this->semStack[$stackPos-(1-1)]; $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes) : $var; + }, + 527 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 528 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 529 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 530 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 531 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 532 => function ($stackPos) { + $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 533 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 534 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 535 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 536 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 537 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 538 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 539 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 540 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 541 => function ($stackPos) { + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2; + }, + 542 => function ($stackPos) { + $this->semValue = new Expr\List_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 543 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; $end = count($this->semValue)-1; if ($this->semValue[$end] === null) array_pop($this->semValue); + }, + 544 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 545 => function ($stackPos) { + /* do nothing -- prevent default action of $$=$this->semStack[$1]. See $551. */ + }, + 546 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 547 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 548 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 549 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 550 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 551 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 552 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-1)], true, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 553 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 554 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 555 => function ($stackPos) { + $this->semValue = null; + }, + 556 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 557 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 558 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 559 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); + }, + 560 => function ($stackPos) { + $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 561 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 562 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 563 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 564 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 565 => function ($stackPos) { + $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 566 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 567 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 568 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-4)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 569 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 570 => function ($stackPos) { + $this->semValue = new Scalar\String_($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 571 => function ($stackPos) { + $this->semValue = $this->parseNumString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 572 => function ($stackPos) { + $this->semValue = $this->parseNumString('-' . $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 573 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + ]; + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Tokens.php b/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Tokens.php new file mode 100644 index 0000000000000000000000000000000000000000..ed2062b46d0207d99cda31bb23184c56bbad2d0a --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/Parser/Tokens.php @@ -0,0 +1,144 @@ +<?php + +namespace PhpParser\Parser; + +/* GENERATED file based on grammar/tokens.y */ +final class Tokens +{ + const YYERRTOK = 256; + const T_THROW = 257; + const T_INCLUDE = 258; + const T_INCLUDE_ONCE = 259; + const T_EVAL = 260; + const T_REQUIRE = 261; + const T_REQUIRE_ONCE = 262; + const T_LOGICAL_OR = 263; + const T_LOGICAL_XOR = 264; + const T_LOGICAL_AND = 265; + const T_PRINT = 266; + const T_YIELD = 267; + const T_DOUBLE_ARROW = 268; + const T_YIELD_FROM = 269; + const T_PLUS_EQUAL = 270; + const T_MINUS_EQUAL = 271; + const T_MUL_EQUAL = 272; + const T_DIV_EQUAL = 273; + const T_CONCAT_EQUAL = 274; + const T_MOD_EQUAL = 275; + const T_AND_EQUAL = 276; + const T_OR_EQUAL = 277; + const T_XOR_EQUAL = 278; + const T_SL_EQUAL = 279; + const T_SR_EQUAL = 280; + const T_POW_EQUAL = 281; + const T_COALESCE_EQUAL = 282; + const T_COALESCE = 283; + const T_BOOLEAN_OR = 284; + const T_BOOLEAN_AND = 285; + const T_IS_EQUAL = 286; + const T_IS_NOT_EQUAL = 287; + const T_IS_IDENTICAL = 288; + const T_IS_NOT_IDENTICAL = 289; + const T_SPACESHIP = 290; + const T_IS_SMALLER_OR_EQUAL = 291; + const T_IS_GREATER_OR_EQUAL = 292; + const T_SL = 293; + const T_SR = 294; + const T_INSTANCEOF = 295; + const T_INC = 296; + const T_DEC = 297; + const T_INT_CAST = 298; + const T_DOUBLE_CAST = 299; + const T_STRING_CAST = 300; + const T_ARRAY_CAST = 301; + const T_OBJECT_CAST = 302; + const T_BOOL_CAST = 303; + const T_UNSET_CAST = 304; + const T_POW = 305; + const T_NEW = 306; + const T_CLONE = 307; + const T_EXIT = 308; + const T_IF = 309; + const T_ELSEIF = 310; + const T_ELSE = 311; + const T_ENDIF = 312; + const T_LNUMBER = 313; + const T_DNUMBER = 314; + const T_STRING = 315; + const T_STRING_VARNAME = 316; + const T_VARIABLE = 317; + const T_NUM_STRING = 318; + const T_INLINE_HTML = 319; + const T_ENCAPSED_AND_WHITESPACE = 320; + const T_CONSTANT_ENCAPSED_STRING = 321; + const T_ECHO = 322; + const T_DO = 323; + const T_WHILE = 324; + const T_ENDWHILE = 325; + const T_FOR = 326; + const T_ENDFOR = 327; + const T_FOREACH = 328; + const T_ENDFOREACH = 329; + const T_DECLARE = 330; + const T_ENDDECLARE = 331; + const T_AS = 332; + const T_SWITCH = 333; + const T_MATCH = 334; + const T_ENDSWITCH = 335; + const T_CASE = 336; + const T_DEFAULT = 337; + const T_BREAK = 338; + const T_CONTINUE = 339; + const T_GOTO = 340; + const T_FUNCTION = 341; + const T_FN = 342; + const T_CONST = 343; + const T_RETURN = 344; + const T_TRY = 345; + const T_CATCH = 346; + const T_FINALLY = 347; + const T_USE = 348; + const T_INSTEADOF = 349; + const T_GLOBAL = 350; + const T_STATIC = 351; + const T_ABSTRACT = 352; + const T_FINAL = 353; + const T_PRIVATE = 354; + const T_PROTECTED = 355; + const T_PUBLIC = 356; + const T_VAR = 357; + const T_UNSET = 358; + const T_ISSET = 359; + const T_EMPTY = 360; + const T_HALT_COMPILER = 361; + const T_CLASS = 362; + const T_TRAIT = 363; + const T_INTERFACE = 364; + const T_EXTENDS = 365; + const T_IMPLEMENTS = 366; + const T_OBJECT_OPERATOR = 367; + const T_NULLSAFE_OBJECT_OPERATOR = 368; + const T_LIST = 369; + const T_ARRAY = 370; + const T_CALLABLE = 371; + const T_CLASS_C = 372; + const T_TRAIT_C = 373; + const T_METHOD_C = 374; + const T_FUNC_C = 375; + const T_LINE = 376; + const T_FILE = 377; + const T_START_HEREDOC = 378; + const T_END_HEREDOC = 379; + const T_DOLLAR_OPEN_CURLY_BRACES = 380; + const T_CURLY_OPEN = 381; + const T_PAAMAYIM_NEKUDOTAYIM = 382; + const T_NAMESPACE = 383; + const T_NS_C = 384; + const T_DIR = 385; + const T_NS_SEPARATOR = 386; + const T_ELLIPSIS = 387; + const T_NAME_FULLY_QUALIFIED = 388; + const T_NAME_QUALIFIED = 389; + const T_NAME_RELATIVE = 390; + const T_ATTRIBUTE = 391; +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/ParserAbstract.php b/lib/composer/nikic/php-parser/lib/PhpParser/ParserAbstract.php new file mode 100644 index 0000000000000000000000000000000000000000..11c8568db921083bb362a8ddc19594a55224ee03 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/ParserAbstract.php @@ -0,0 +1,1018 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +/* + * This parser is based on a skeleton written by Moriyoshi Koizumi, which in + * turn is based on work by Masato Bito. + */ +use PhpParser\Node\Expr; +use PhpParser\Node\Expr\Cast\Double; +use PhpParser\Node\Name; +use PhpParser\Node\Param; +use PhpParser\Node\Scalar\Encapsed; +use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Scalar\String_; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\ClassConst; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Interface_; +use PhpParser\Node\Stmt\Namespace_; +use PhpParser\Node\Stmt\Property; +use PhpParser\Node\Stmt\TryCatch; +use PhpParser\Node\Stmt\UseUse; +use PhpParser\Node\VarLikeIdentifier; + +abstract class ParserAbstract implements Parser +{ + const SYMBOL_NONE = -1; + + /* + * The following members will be filled with generated parsing data: + */ + + /** @var int Size of $tokenToSymbol map */ + protected $tokenToSymbolMapSize; + /** @var int Size of $action table */ + protected $actionTableSize; + /** @var int Size of $goto table */ + protected $gotoTableSize; + + /** @var int Symbol number signifying an invalid token */ + protected $invalidSymbol; + /** @var int Symbol number of error recovery token */ + protected $errorSymbol; + /** @var int Action number signifying default action */ + protected $defaultAction; + /** @var int Rule number signifying that an unexpected token was encountered */ + protected $unexpectedTokenRule; + + protected $YY2TBLSTATE; + /** @var int Number of non-leaf states */ + protected $numNonLeafStates; + + /** @var int[] Map of lexer tokens to internal symbols */ + protected $tokenToSymbol; + /** @var string[] Map of symbols to their names */ + protected $symbolToName; + /** @var array Names of the production rules (only necessary for debugging) */ + protected $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 $actionBase; + /** @var int[] Table of actions. Indexed according to $actionBase comment. */ + protected $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 $actionCheck; + /** @var int[] Map of states to their default action */ + protected $actionDefault; + /** @var callable[] Semantic action callbacks */ + protected $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 $gotoBase; + /** @var int[] Table of states to goto after reduction. Indexed according to $gotoBase comment. */ + protected $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 $gotoCheck; + /** @var int[] Map of non-terminals to the default state to goto after their reduction */ + protected $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 $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 $ruleToLength; + + /* + * The following members are part of the parser state: + */ + + /** @var Lexer Lexer that is used when parsing */ + protected $lexer; + /** @var mixed Temporary value containing the result of last semantic action (reduction) */ + protected $semValue; + /** @var array Semantic value stack (contains values of tokens and semantic action results) */ + protected $semStack; + /** @var array[] Start attribute stack */ + protected $startAttributeStack; + /** @var array[] End attribute stack */ + protected $endAttributeStack; + /** @var array End attributes of last *shifted* token */ + protected $endAttributes; + /** @var array Start attributes of last *read* token */ + protected $lookaheadStartAttributes; + + /** @var ErrorHandler Error handler */ + protected $errorHandler; + /** @var int Error state, used to avoid error floods */ + protected $errorState; + + /** + * Initialize $reduceCallbacks map. + */ + abstract protected function initReduceCallbacks(); + + /** + * Creates a parser instance. + * + * Options: Currently none. + * + * @param Lexer $lexer A lexer + * @param array $options Options array. + */ + public function __construct(Lexer $lexer, array $options = []) { + $this->lexer = $lexer; + + if (isset($options['throwOnError'])) { + throw new \LogicException( + '"throwOnError" is no longer supported, use "errorHandler" instead'); + } + + $this->initReduceCallbacks(); + } + + /** + * 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) { + $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing; + + $this->lexer->startLexing($code, $this->errorHandler); + $result = $this->doParse(); + + // Clear out some of the interior state, so we don't hold onto unnecessary + // memory between uses of the parser + $this->startAttributeStack = []; + $this->endAttributeStack = []; + $this->semStack = []; + $this->semValue = null; + + return $result; + } + + protected function doParse() { + // We start off with no lookahead-token + $symbol = self::SYMBOL_NONE; + + // The attributes for a node are taken from the first and last token of the node. + // From the first token only the startAttributes are taken and from the last only + // the endAttributes. Both are merged using the array union operator (+). + $startAttributes = []; + $endAttributes = []; + $this->endAttributes = $endAttributes; + + // Keep stack of start and end attributes + $this->startAttributeStack = []; + $this->endAttributeStack = [$endAttributes]; + + // 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) { + // Fetch the next token id from the lexer and fetch additional info by-ref. + // The end attributes are fetched into a temporary variable and only set once the token is really + // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is + // reduced after a token was read but not yet shifted. + $tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes); + + // map the lexer token id to the internally used symbols + $symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize + ? $this->tokenToSymbol[$tokenId] + : $this->invalidSymbol; + + if ($symbol === $this->invalidSymbol) { + throw new \RangeException(sprintf( + 'The lexer returned an invalid token (id=%d, value=%s)', + $tokenId, $tokenValue + )); + } + + // This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get + // the attributes of the next token, even though they don't contain it themselves. + $this->startAttributeStack[$stackPos+1] = $startAttributes; + $this->endAttributeStack[$stackPos+1] = $endAttributes; + $this->lookaheadStartAttributes = $startAttributes; + + //$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->startAttributeStack[$stackPos] = $startAttributes; + $this->endAttributeStack[$stackPos] = $endAttributes; + $this->endAttributes = $endAttributes; + $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; + } elseif ($rule !== $this->unexpectedTokenRule) { + /* reduce */ + //$this->traceReduce($rule); + + try { + $this->reduceCallbacks[$rule]($stackPos); + } catch (Error $e) { + if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) { + $e->setStartLine($startAttributes['startLine']); + } + + $this->emitError($e); + // Can't recover from this type of error + return null; + } + + /* Goto - shift nonterminal */ + $lastEndAttributes = $this->endAttributeStack[$stackPos]; + $stackPos -= $this->ruleToLength[$rule]; + $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->endAttributeStack[$stackPos] = $lastEndAttributes; + } else { + /* error */ + switch ($this->errorState) { + case 0: + $msg = $this->getErrorMessage($symbol, $state); + $this->emitError(new Error($msg, $startAttributes + $endAttributes)); + // Break missing intentionally + 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->endAttributeStack[$stackPos] = $this->endAttributeStack[$stackPos - 1]; + $this->endAttributes = $this->endAttributeStack[$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) { + $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; + } + + /* + * Tracing functions used for debugging the parser. + */ + + /* + protected function traceNewState($state, $symbol) { + echo '% State ' . $state + . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n"; + } + + protected function traceRead($symbol) { + echo '% Reading ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceShift($symbol) { + echo '% Shift ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceAccept() { + echo "% Accepted.\n"; + } + + protected function traceReduce($n) { + echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n"; + } + + protected function tracePop($state) { + echo '% Recovering, uncovered state ' . $state . "\n"; + } + + protected function traceDiscard($symbol) { + 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; + } elseif ('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) { + // 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)); + } + } + } + + /** + * 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) { + $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', + $stmt->getLine() // Avoid marking the entire namespace as an error + )); + } + } elseif ($style !== $currentStyle) { + $this->emitError(new Error( + 'Cannot mix bracketed namespace declarations with unbracketed namespace declarations', + $stmt->getLine() // Avoid marking the entire namespace as an error + )); + // 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; + } + + /** + * Fix up parsing of static property calls in PHP 5. + * + * In PHP 5 A::$b[c][d] and A::$b[c][d]() have very different interpretation. The former is + * interpreted as (A::$b)[c][d], while the latter is the same as A::{$b[c][d]}(). We parse the + * latter as the former initially and this method fixes the AST into the correct form when we + * encounter the "()". + * + * @param Node\Expr\StaticPropertyFetch|Node\Expr\ArrayDimFetch $prop + * @param Node\Arg[] $args + * @param array $attributes + * + * @return Expr\StaticCall + */ + protected function fixupPhp5StaticPropCall($prop, array $args, array $attributes) : Expr\StaticCall { + if ($prop instanceof Node\Expr\StaticPropertyFetch) { + $name = $prop->name instanceof VarLikeIdentifier + ? $prop->name->toString() : $prop->name; + $var = new Expr\Variable($name, $prop->name->getAttributes()); + return new Expr\StaticCall($prop->class, $var, $args, $attributes); + } elseif ($prop instanceof Node\Expr\ArrayDimFetch) { + $tmp = $prop; + while ($tmp->var instanceof Node\Expr\ArrayDimFetch) { + $tmp = $tmp->var; + } + + /** @var Expr\StaticPropertyFetch $staticProp */ + $staticProp = $tmp->var; + + // Set start attributes to attributes of innermost node + $tmp = $prop; + $this->fixupStartAttributes($tmp, $staticProp->name); + while ($tmp->var instanceof Node\Expr\ArrayDimFetch) { + $tmp = $tmp->var; + $this->fixupStartAttributes($tmp, $staticProp->name); + } + + $name = $staticProp->name instanceof VarLikeIdentifier + ? $staticProp->name->toString() : $staticProp->name; + $tmp->var = new Expr\Variable($name, $staticProp->name->getAttributes()); + return new Expr\StaticCall($staticProp->class, $prop, $args, $attributes); + } else { + throw new \Exception; + } + } + + protected function fixupStartAttributes(Node $to, Node $from) { + $startAttributes = ['startLine', 'startFilePos', 'startTokenPos']; + foreach ($startAttributes as $startAttribute) { + if ($from->hasAttribute($startAttribute)) { + $to->setAttribute($startAttribute, $from->getAttribute($startAttribute)); + } + } + } + + protected function handleBuiltinTypes(Name $name) { + $builtinTypes = [ + 'bool' => true, + 'int' => true, + 'float' => true, + 'string' => true, + 'iterable' => true, + 'void' => true, + 'object' => true, + 'null' => true, + 'false' => true, + 'mixed' => true, + ]; + + if (!$name->isUnqualified()) { + return $name; + } + + $lowerName = $name->toLowerString(); + if (!isset($builtinTypes[$lowerName])) { + return $name; + } + + return new Node\Identifier($lowerName, $name->getAttributes()); + } + + /** + * Get combined start and end attributes at a stack location + * + * @param int $pos Stack location + * + * @return array Combined start and end attributes + */ + protected function getAttributesAt(int $pos) : array { + return $this->startAttributeStack[$pos] + $this->endAttributeStack[$pos]; + } + + 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; + } + + protected function parseLNumber($str, $attributes, $allowInvalidOctal = false) { + try { + return LNumber::fromString($str, $attributes, $allowInvalidOctal); + } catch (Error $error) { + $this->emitError($error); + // Use dummy value + return new LNumber(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 LNumber|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 LNumber($num, $attributes); + } + + protected function stripIndentation( + string $string, int $indentLen, string $indentChar, + bool $newlineAtStart, bool $newlineAtEnd, array $attributes + ) { + 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 + ); + } + + protected function parseDocString( + string $startToken, $contents, string $endToken, + array $attributes, array $endTokenAttributes, bool $parseUnicodeEscape + ) { + $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 === '') { + return new String_('', $attributes); + } + + $contents = $this->stripIndentation( + $contents, $indentLen, $indentChar, true, true, $attributes + ); + $contents = preg_replace('~(\r\n|\n|\r)\z~', '', $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\Scalar\EncapsedStringPart) { + // 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\Scalar\EncapsedStringPart) { + $isLast = $i === \count($contents) - 1; + $part->value = $this->stripIndentation( + $part->value, $indentLen, $indentChar, + $i === 0, $isLast, $part->getAttributes() + ); + $part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape); + if ($isLast) { + $part->value = preg_replace('~(\r\n|\n|\r)\z~', '', $part->value); + } + if ('' === $part->value) { + continue; + } + } + $newContents[] = $part; + } + return new Encapsed($newContents, $attributes); + } + } + + /** + * Create attributes for a zero-length common-capturing nop. + * + * @param Comment[] $comments + * @return array + */ + protected function createCommentNopAttributes(array $comments) { + $comment = $comments[count($comments) - 1]; + $commentEndLine = $comment->getEndLine(); + $commentEndFilePos = $comment->getEndFilePos(); + $commentEndTokenPos = $comment->getEndTokenPos(); + + $attributes = ['comments' => $comments]; + if (-1 !== $commentEndLine) { + $attributes['startLine'] = $commentEndLine; + $attributes['endLine'] = $commentEndLine; + } + if (-1 !== $commentEndFilePos) { + $attributes['startFilePos'] = $commentEndFilePos + 1; + $attributes['endFilePos'] = $commentEndFilePos; + } + if (-1 !== $commentEndTokenPos) { + $attributes['startTokenPos'] = $commentEndTokenPos + 1; + $attributes['endTokenPos'] = $commentEndTokenPos; + } + return $attributes; + } + + protected function checkModifier($a, $b, $modifierPos) { + // Jumping through some hoops here because verifyModifier() is also used elsewhere + try { + Class_::verifyModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + + protected function checkParam(Param $node) { + 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) { + 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) { + if (null !== $node->stmts) { + foreach ($node->stmts as $stmt) { + if ($stmt instanceof Namespace_) { + $this->emitError(new Error( + 'Namespace declarations cannot be nested', $stmt->getAttributes() + )); + } + } + } + } + + protected function checkClass(Class_ $node, $namePos) { + if (null !== $node->name && $node->name->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name), + $this->getAttributesAt($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() + )); + } + + foreach ($node->implements 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 checkInterface(Interface_ $node, $namePos) { + if (null !== $node->name && $node->name->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name), + $this->getAttributesAt($namePos) + )); + } + + foreach ($node->extends 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 checkClassMethod(ClassMethod $node, $modifierPos) { + if ($node->flags & Class_::MODIFIER_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; + } + } + } + + protected function checkClassConst(ClassConst $node, $modifierPos) { + if ($node->flags & Class_::MODIFIER_STATIC) { + $this->emitError(new Error( + "Cannot use 'static' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + if ($node->flags & Class_::MODIFIER_ABSTRACT) { + $this->emitError(new Error( + "Cannot use 'abstract' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + if ($node->flags & Class_::MODIFIER_FINAL) { + $this->emitError(new Error( + "Cannot use 'final' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkProperty(Property $node, $modifierPos) { + if ($node->flags & Class_::MODIFIER_ABSTRACT) { + $this->emitError(new Error('Properties cannot be declared abstract', + $this->getAttributesAt($modifierPos))); + } + + if ($node->flags & Class_::MODIFIER_FINAL) { + $this->emitError(new Error('Properties cannot be declared final', + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkUseUse(UseUse $node, $namePos) { + 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) + )); + } + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/ParserFactory.php b/lib/composer/nikic/php-parser/lib/PhpParser/ParserFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..f041e7ffe3adcc4181c6900b55061ed596f2786b --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/ParserFactory.php @@ -0,0 +1,44 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +class ParserFactory +{ + const PREFER_PHP7 = 1; + const PREFER_PHP5 = 2; + const ONLY_PHP7 = 3; + const ONLY_PHP5 = 4; + + /** + * Creates a Parser instance, according to the provided kind. + * + * @param int $kind One of ::PREFER_PHP7, ::PREFER_PHP5, ::ONLY_PHP7 or ::ONLY_PHP5 + * @param Lexer|null $lexer Lexer to use. Defaults to emulative lexer when not specified + * @param array $parserOptions Parser options. See ParserAbstract::__construct() argument + * + * @return Parser The parser instance + */ + public function create(int $kind, Lexer $lexer = null, array $parserOptions = []) : Parser { + if (null === $lexer) { + $lexer = new Lexer\Emulative(); + } + switch ($kind) { + case self::PREFER_PHP7: + return new Parser\Multiple([ + new Parser\Php7($lexer, $parserOptions), new Parser\Php5($lexer, $parserOptions) + ]); + case self::PREFER_PHP5: + return new Parser\Multiple([ + new Parser\Php5($lexer, $parserOptions), new Parser\Php7($lexer, $parserOptions) + ]); + case self::ONLY_PHP7: + return new Parser\Php7($lexer, $parserOptions); + case self::ONLY_PHP5: + return new Parser\Php5($lexer, $parserOptions); + default: + throw new \LogicException( + 'Kind must be one of ::PREFER_PHP7, ::PREFER_PHP5, ::ONLY_PHP7 or ::ONLY_PHP5' + ); + } + } +} diff --git a/lib/composer/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php b/lib/composer/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php new file mode 100644 index 0000000000000000000000000000000000000000..c44bda983341aba9ad3c8706f2ea78bafe55d781 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php @@ -0,0 +1,1069 @@ +<?php declare(strict_types=1); + +namespace PhpParser\PrettyPrinter; + +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\Expr\AssignOp; +use PhpParser\Node\Expr\BinaryOp; +use PhpParser\Node\Expr\Cast; +use PhpParser\Node\Name; +use PhpParser\Node\Scalar; +use PhpParser\Node\Scalar\MagicConst; +use PhpParser\Node\Stmt; +use PhpParser\PrettyPrinterAbstract; + +class Standard extends PrettyPrinterAbstract +{ + // Special nodes + + protected function pParam(Node\Param $node) { + return $this->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) { + return ($node->name ? $node->name->toString() . ': ' : '') + . ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') + . $this->p($node->value); + } + + protected function pConst(Node\Const_ $node) { + return $node->name . ' = ' . $this->p($node->value); + } + + protected function pNullableType(Node\NullableType $node) { + return '?' . $this->p($node->type); + } + + protected function pUnionType(Node\UnionType $node) { + return $this->pImplode($node->types, '|'); + } + + protected function pIdentifier(Node\Identifier $node) { + return $node->name; + } + + protected function pVarLikeIdentifier(Node\VarLikeIdentifier $node) { + return '$' . $node->name; + } + + protected function pAttribute(Node\Attribute $node) { + return $this->p($node->name) + . ($node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : ''); + } + + protected function pAttributeGroup(Node\AttributeGroup $node) { + return '#[' . $this->pCommaSeparated($node->attrs) . ']'; + } + + // Names + + protected function pName(Name $node) { + return implode('\\', $node->parts); + } + + protected function pName_FullyQualified(Name\FullyQualified $node) { + return '\\' . implode('\\', $node->parts); + } + + protected function pName_Relative(Name\Relative $node) { + return 'namespace\\' . implode('\\', $node->parts); + } + + // Magic Constants + + protected function pScalar_MagicConst_Class(MagicConst\Class_ $node) { + return '__CLASS__'; + } + + protected function pScalar_MagicConst_Dir(MagicConst\Dir $node) { + return '__DIR__'; + } + + protected function pScalar_MagicConst_File(MagicConst\File $node) { + return '__FILE__'; + } + + protected function pScalar_MagicConst_Function(MagicConst\Function_ $node) { + return '__FUNCTION__'; + } + + protected function pScalar_MagicConst_Line(MagicConst\Line $node) { + return '__LINE__'; + } + + protected function pScalar_MagicConst_Method(MagicConst\Method $node) { + return '__METHOD__'; + } + + protected function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node) { + return '__NAMESPACE__'; + } + + protected function pScalar_MagicConst_Trait(MagicConst\Trait_ $node) { + return '__TRAIT__'; + } + + // Scalars + + protected function pScalar_String(Scalar\String_ $node) { + $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)) { + if ($node->value === '') { + return "<<<'$label'\n$label" . $this->docStringEndToken; + } + + return "<<<'$label'\n$node->value\n$label" + . $this->docStringEndToken; + } + /* break missing intentionally */ + case Scalar\String_::KIND_SINGLE_QUOTED: + return $this->pSingleQuotedString($node->value); + case Scalar\String_::KIND_HEREDOC: + $label = $node->getAttribute('docLabel'); + if ($label && !$this->containsEndLabel($node->value, $label)) { + if ($node->value === '') { + return "<<<$label\n$label" . $this->docStringEndToken; + } + + $escaped = $this->escapeString($node->value, null); + return "<<<$label\n" . $escaped . "\n$label" + . $this->docStringEndToken; + } + /* break missing intentionally */ + case Scalar\String_::KIND_DOUBLE_QUOTED: + return '"' . $this->escapeString($node->value, '"') . '"'; + } + throw new \Exception('Invalid string kind'); + } + + protected function pScalar_Encapsed(Scalar\Encapsed $node) { + if ($node->getAttribute('kind') === Scalar\String_::KIND_HEREDOC) { + $label = $node->getAttribute('docLabel'); + if ($label && !$this->encapsedContainsEndLabel($node->parts, $label)) { + if (count($node->parts) === 1 + && $node->parts[0] instanceof Scalar\EncapsedStringPart + && $node->parts[0]->value === '' + ) { + return "<<<$label\n$label" . $this->docStringEndToken; + } + + return "<<<$label\n" . $this->pEncapsList($node->parts, null) . "\n$label" + . $this->docStringEndToken; + } + } + return '"' . $this->pEncapsList($node->parts, '"') . '"'; + } + + protected function pScalar_LNumber(Scalar\LNumber $node) { + 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\LNumber::KIND_DEC); + if (Scalar\LNumber::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\LNumber::KIND_BIN: + return $sign . '0b' . base_convert($str, 10, 2); + case Scalar\LNumber::KIND_OCT: + return $sign . '0' . base_convert($str, 10, 8); + case Scalar\LNumber::KIND_HEX: + return $sign . '0x' . base_convert($str, 10, 16); + } + throw new \Exception('Invalid number kind'); + } + + protected function pScalar_DNumber(Scalar\DNumber $node) { + if (!is_finite($node->value)) { + if ($node->value === \INF) { + return '\INF'; + } elseif ($node->value === -\INF) { + return '-\INF'; + } else { + return '\NAN'; + } + } + + // Try to find a short full-precision representation + $stringValue = sprintf('%.16G', $node->value); + if ($node->value !== (double) $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; + } + + protected function pScalar_EncapsedStringPart(Scalar\EncapsedStringPart $node) { + throw new \LogicException('Cannot directly print EncapsedStringPart'); + } + + // Assignments + + protected function pExpr_Assign(Expr\Assign $node) { + return $this->pInfixOp(Expr\Assign::class, $node->var, ' = ', $node->expr); + } + + protected function pExpr_AssignRef(Expr\AssignRef $node) { + return $this->pInfixOp(Expr\AssignRef::class, $node->var, ' =& ', $node->expr); + } + + protected function pExpr_AssignOp_Plus(AssignOp\Plus $node) { + return $this->pInfixOp(AssignOp\Plus::class, $node->var, ' += ', $node->expr); + } + + protected function pExpr_AssignOp_Minus(AssignOp\Minus $node) { + return $this->pInfixOp(AssignOp\Minus::class, $node->var, ' -= ', $node->expr); + } + + protected function pExpr_AssignOp_Mul(AssignOp\Mul $node) { + return $this->pInfixOp(AssignOp\Mul::class, $node->var, ' *= ', $node->expr); + } + + protected function pExpr_AssignOp_Div(AssignOp\Div $node) { + return $this->pInfixOp(AssignOp\Div::class, $node->var, ' /= ', $node->expr); + } + + protected function pExpr_AssignOp_Concat(AssignOp\Concat $node) { + return $this->pInfixOp(AssignOp\Concat::class, $node->var, ' .= ', $node->expr); + } + + protected function pExpr_AssignOp_Mod(AssignOp\Mod $node) { + return $this->pInfixOp(AssignOp\Mod::class, $node->var, ' %= ', $node->expr); + } + + protected function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node) { + return $this->pInfixOp(AssignOp\BitwiseAnd::class, $node->var, ' &= ', $node->expr); + } + + protected function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node) { + return $this->pInfixOp(AssignOp\BitwiseOr::class, $node->var, ' |= ', $node->expr); + } + + protected function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node) { + return $this->pInfixOp(AssignOp\BitwiseXor::class, $node->var, ' ^= ', $node->expr); + } + + protected function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node) { + return $this->pInfixOp(AssignOp\ShiftLeft::class, $node->var, ' <<= ', $node->expr); + } + + protected function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node) { + return $this->pInfixOp(AssignOp\ShiftRight::class, $node->var, ' >>= ', $node->expr); + } + + protected function pExpr_AssignOp_Pow(AssignOp\Pow $node) { + return $this->pInfixOp(AssignOp\Pow::class, $node->var, ' **= ', $node->expr); + } + + protected function pExpr_AssignOp_Coalesce(AssignOp\Coalesce $node) { + return $this->pInfixOp(AssignOp\Coalesce::class, $node->var, ' ??= ', $node->expr); + } + + // Binary expressions + + protected function pExpr_BinaryOp_Plus(BinaryOp\Plus $node) { + return $this->pInfixOp(BinaryOp\Plus::class, $node->left, ' + ', $node->right); + } + + protected function pExpr_BinaryOp_Minus(BinaryOp\Minus $node) { + return $this->pInfixOp(BinaryOp\Minus::class, $node->left, ' - ', $node->right); + } + + protected function pExpr_BinaryOp_Mul(BinaryOp\Mul $node) { + return $this->pInfixOp(BinaryOp\Mul::class, $node->left, ' * ', $node->right); + } + + protected function pExpr_BinaryOp_Div(BinaryOp\Div $node) { + return $this->pInfixOp(BinaryOp\Div::class, $node->left, ' / ', $node->right); + } + + protected function pExpr_BinaryOp_Concat(BinaryOp\Concat $node) { + return $this->pInfixOp(BinaryOp\Concat::class, $node->left, ' . ', $node->right); + } + + protected function pExpr_BinaryOp_Mod(BinaryOp\Mod $node) { + return $this->pInfixOp(BinaryOp\Mod::class, $node->left, ' % ', $node->right); + } + + protected function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node) { + return $this->pInfixOp(BinaryOp\BooleanAnd::class, $node->left, ' && ', $node->right); + } + + protected function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node) { + return $this->pInfixOp(BinaryOp\BooleanOr::class, $node->left, ' || ', $node->right); + } + + protected function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node) { + return $this->pInfixOp(BinaryOp\BitwiseAnd::class, $node->left, ' & ', $node->right); + } + + protected function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node) { + return $this->pInfixOp(BinaryOp\BitwiseOr::class, $node->left, ' | ', $node->right); + } + + protected function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node) { + return $this->pInfixOp(BinaryOp\BitwiseXor::class, $node->left, ' ^ ', $node->right); + } + + protected function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node) { + return $this->pInfixOp(BinaryOp\ShiftLeft::class, $node->left, ' << ', $node->right); + } + + protected function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node) { + return $this->pInfixOp(BinaryOp\ShiftRight::class, $node->left, ' >> ', $node->right); + } + + protected function pExpr_BinaryOp_Pow(BinaryOp\Pow $node) { + return $this->pInfixOp(BinaryOp\Pow::class, $node->left, ' ** ', $node->right); + } + + protected function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node) { + return $this->pInfixOp(BinaryOp\LogicalAnd::class, $node->left, ' and ', $node->right); + } + + protected function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node) { + return $this->pInfixOp(BinaryOp\LogicalOr::class, $node->left, ' or ', $node->right); + } + + protected function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node) { + return $this->pInfixOp(BinaryOp\LogicalXor::class, $node->left, ' xor ', $node->right); + } + + protected function pExpr_BinaryOp_Equal(BinaryOp\Equal $node) { + return $this->pInfixOp(BinaryOp\Equal::class, $node->left, ' == ', $node->right); + } + + protected function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node) { + return $this->pInfixOp(BinaryOp\NotEqual::class, $node->left, ' != ', $node->right); + } + + protected function pExpr_BinaryOp_Identical(BinaryOp\Identical $node) { + return $this->pInfixOp(BinaryOp\Identical::class, $node->left, ' === ', $node->right); + } + + protected function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node) { + return $this->pInfixOp(BinaryOp\NotIdentical::class, $node->left, ' !== ', $node->right); + } + + protected function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node) { + return $this->pInfixOp(BinaryOp\Spaceship::class, $node->left, ' <=> ', $node->right); + } + + protected function pExpr_BinaryOp_Greater(BinaryOp\Greater $node) { + return $this->pInfixOp(BinaryOp\Greater::class, $node->left, ' > ', $node->right); + } + + protected function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node) { + return $this->pInfixOp(BinaryOp\GreaterOrEqual::class, $node->left, ' >= ', $node->right); + } + + protected function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node) { + return $this->pInfixOp(BinaryOp\Smaller::class, $node->left, ' < ', $node->right); + } + + protected function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node) { + return $this->pInfixOp(BinaryOp\SmallerOrEqual::class, $node->left, ' <= ', $node->right); + } + + protected function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node) { + return $this->pInfixOp(BinaryOp\Coalesce::class, $node->left, ' ?? ', $node->right); + } + + protected function pExpr_Instanceof(Expr\Instanceof_ $node) { + list($precedence, $associativity) = $this->precedenceMap[Expr\Instanceof_::class]; + return $this->pPrec($node->expr, $precedence, $associativity, -1) + . ' instanceof ' + . $this->pNewVariable($node->class); + } + + // Unary expressions + + protected function pExpr_BooleanNot(Expr\BooleanNot $node) { + return $this->pPrefixOp(Expr\BooleanNot::class, '!', $node->expr); + } + + protected function pExpr_BitwiseNot(Expr\BitwiseNot $node) { + return $this->pPrefixOp(Expr\BitwiseNot::class, '~', $node->expr); + } + + protected function pExpr_UnaryMinus(Expr\UnaryMinus $node) { + if ($node->expr instanceof Expr\UnaryMinus || $node->expr instanceof Expr\PreDec) { + // Enforce -(-$expr) instead of --$expr + return '-(' . $this->p($node->expr) . ')'; + } + return $this->pPrefixOp(Expr\UnaryMinus::class, '-', $node->expr); + } + + protected function pExpr_UnaryPlus(Expr\UnaryPlus $node) { + if ($node->expr instanceof Expr\UnaryPlus || $node->expr instanceof Expr\PreInc) { + // Enforce +(+$expr) instead of ++$expr + return '+(' . $this->p($node->expr) . ')'; + } + return $this->pPrefixOp(Expr\UnaryPlus::class, '+', $node->expr); + } + + protected function pExpr_PreInc(Expr\PreInc $node) { + return $this->pPrefixOp(Expr\PreInc::class, '++', $node->var); + } + + protected function pExpr_PreDec(Expr\PreDec $node) { + return $this->pPrefixOp(Expr\PreDec::class, '--', $node->var); + } + + protected function pExpr_PostInc(Expr\PostInc $node) { + return $this->pPostfixOp(Expr\PostInc::class, $node->var, '++'); + } + + protected function pExpr_PostDec(Expr\PostDec $node) { + return $this->pPostfixOp(Expr\PostDec::class, $node->var, '--'); + } + + protected function pExpr_ErrorSuppress(Expr\ErrorSuppress $node) { + return $this->pPrefixOp(Expr\ErrorSuppress::class, '@', $node->expr); + } + + protected function pExpr_YieldFrom(Expr\YieldFrom $node) { + return $this->pPrefixOp(Expr\YieldFrom::class, 'yield from ', $node->expr); + } + + protected function pExpr_Print(Expr\Print_ $node) { + return $this->pPrefixOp(Expr\Print_::class, 'print ', $node->expr); + } + + // Casts + + protected function pExpr_Cast_Int(Cast\Int_ $node) { + return $this->pPrefixOp(Cast\Int_::class, '(int) ', $node->expr); + } + + protected function pExpr_Cast_Double(Cast\Double $node) { + $kind = $node->getAttribute('kind', Cast\Double::KIND_DOUBLE); + if ($kind === Cast\Double::KIND_DOUBLE) { + $cast = '(double)'; + } elseif ($kind === Cast\Double::KIND_FLOAT) { + $cast = '(float)'; + } elseif ($kind === Cast\Double::KIND_REAL) { + $cast = '(real)'; + } + return $this->pPrefixOp(Cast\Double::class, $cast . ' ', $node->expr); + } + + protected function pExpr_Cast_String(Cast\String_ $node) { + return $this->pPrefixOp(Cast\String_::class, '(string) ', $node->expr); + } + + protected function pExpr_Cast_Array(Cast\Array_ $node) { + return $this->pPrefixOp(Cast\Array_::class, '(array) ', $node->expr); + } + + protected function pExpr_Cast_Object(Cast\Object_ $node) { + return $this->pPrefixOp(Cast\Object_::class, '(object) ', $node->expr); + } + + protected function pExpr_Cast_Bool(Cast\Bool_ $node) { + return $this->pPrefixOp(Cast\Bool_::class, '(bool) ', $node->expr); + } + + protected function pExpr_Cast_Unset(Cast\Unset_ $node) { + return $this->pPrefixOp(Cast\Unset_::class, '(unset) ', $node->expr); + } + + // Function calls and similar constructs + + protected function pExpr_FuncCall(Expr\FuncCall $node) { + return $this->pCallLhs($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_MethodCall(Expr\MethodCall $node) { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_NullsafeMethodCall(Expr\NullsafeMethodCall $node) { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_StaticCall(Expr\StaticCall $node) { + return $this->pDereferenceLhs($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) { + return 'empty(' . $this->p($node->expr) . ')'; + } + + protected function pExpr_Isset(Expr\Isset_ $node) { + return 'isset(' . $this->pCommaSeparated($node->vars) . ')'; + } + + protected function pExpr_Eval(Expr\Eval_ $node) { + return 'eval(' . $this->p($node->expr) . ')'; + } + + protected function pExpr_Include(Expr\Include_ $node) { + 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 $map[$node->type] . ' ' . $this->p($node->expr); + } + + protected function pExpr_List(Expr\List_ $node) { + return 'list(' . $this->pCommaSeparated($node->items) . ')'; + } + + // Other + + protected function pExpr_Error(Expr\Error $node) { + throw new \LogicException('Cannot pretty-print AST with Error nodes'); + } + + protected function pExpr_Variable(Expr\Variable $node) { + if ($node->name instanceof Expr) { + return '${' . $this->p($node->name) . '}'; + } else { + return '$' . $node->name; + } + } + + protected function pExpr_Array(Expr\Array_ $node) { + $syntax = $node->getAttribute('kind', + $this->options['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 pExpr_ArrayItem(Expr\ArrayItem $node) { + return (null !== $node->key ? $this->p($node->key) . ' => ' : '') + . ($node->byRef ? '&' : '') + . ($node->unpack ? '...' : '') + . $this->p($node->value); + } + + protected function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node) { + return $this->pDereferenceLhs($node->var) + . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']'; + } + + protected function pExpr_ConstFetch(Expr\ConstFetch $node) { + return $this->p($node->name); + } + + protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node) { + return $this->pDereferenceLhs($node->class) . '::' . $this->p($node->name); + } + + protected function pExpr_PropertyFetch(Expr\PropertyFetch $node) { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name); + } + + protected function pExpr_NullsafePropertyFetch(Expr\NullsafePropertyFetch $node) { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name); + } + + protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node) { + return $this->pDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name); + } + + protected function pExpr_ShellExec(Expr\ShellExec $node) { + return '`' . $this->pEncapsList($node->parts, '`') . '`'; + } + + protected function pExpr_Closure(Expr\Closure $node) { + return $this->pAttrGroups($node->attrGroups, true) + . ($node->static ? 'static ' : '') + . 'function ' . ($node->byRef ? '&' : '') + . '(' . $this->pCommaSeparated($node->params) . ')' + . (!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) { + return 'match (' . $this->p($node->cond) . ') {' + . $this->pCommaSeparatedMultiline($node->arms, true) + . $this->nl + . '}'; + } + + protected function pMatchArm(Node\MatchArm $node) { + return ($node->conds ? $this->pCommaSeparated($node->conds) : 'default') + . ' => ' . $this->p($node->body); + } + + protected function pExpr_ArrowFunction(Expr\ArrowFunction $node) { + return $this->pAttrGroups($node->attrGroups, true) + . ($node->static ? 'static ' : '') + . 'fn' . ($node->byRef ? '&' : '') + . '(' . $this->pCommaSeparated($node->params) . ')' + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . ' => ' + . $this->p($node->expr); + } + + protected function pExpr_ClosureUse(Expr\ClosureUse $node) { + return ($node->byRef ? '&' : '') . $this->p($node->var); + } + + protected function pExpr_New(Expr\New_ $node) { + if ($node->class instanceof Stmt\Class_) { + $args = $node->args ? '(' . $this->pMaybeMultiline($node->args) . ')' : ''; + return 'new ' . $this->pClassCommon($node->class, $args); + } + return 'new ' . $this->pNewVariable($node->class) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_Clone(Expr\Clone_ $node) { + return 'clone ' . $this->p($node->expr); + } + + protected function pExpr_Ternary(Expr\Ternary $node) { + // 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 + ); + } + + protected function pExpr_Exit(Expr\Exit_ $node) { + $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) { + return 'throw ' . $this->p($node->expr); + } + + protected function pExpr_Yield(Expr\Yield_ $node) { + if ($node->value === null) { + return 'yield'; + } else { + // this is a bit ugly, but currently there is no way to detect whether the parentheses are necessary + return '(yield ' + . ($node->key !== null ? $this->p($node->key) . ' => ' : '') + . $this->p($node->value) + . ')'; + } + } + + // Declarations + + protected function pStmt_Namespace(Stmt\Namespace_ $node) { + 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) { + return 'use ' . $this->pUseType($node->type) + . $this->pCommaSeparated($node->uses) . ';'; + } + + protected function pStmt_GroupUse(Stmt\GroupUse $node) { + return 'use ' . $this->pUseType($node->type) . $this->pName($node->prefix) + . '\{' . $this->pCommaSeparated($node->uses) . '};'; + } + + protected function pStmt_UseUse(Stmt\UseUse $node) { + return $this->pUseType($node->type) . $this->p($node->name) + . (null !== $node->alias ? ' as ' . $node->alias : ''); + } + + protected function pUseType($type) { + return $type === Stmt\Use_::TYPE_FUNCTION ? 'function ' + : ($type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : ''); + } + + protected function pStmt_Interface(Stmt\Interface_ $node) { + 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_Class(Stmt\Class_ $node) { + return $this->pClassCommon($node, ' ' . $node->name); + } + + protected function pStmt_Trait(Stmt\Trait_ $node) { + return $this->pAttrGroups($node->attrGroups) + . 'trait ' . $node->name + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_TraitUse(Stmt\TraitUse $node) { + return 'use ' . $this->pCommaSeparated($node->traits) + . (empty($node->adaptations) + ? ';' + : ' {' . $this->pStmts($node->adaptations) . $this->nl . '}'); + } + + protected function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node) { + return $this->p($node->trait) . '::' . $node->method + . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';'; + } + + protected function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node) { + 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) { + 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 pStmt_PropertyProperty(Stmt\PropertyProperty $node) { + return '$' . $node->name + . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pStmt_ClassMethod(Stmt\ClassMethod $node) { + return $this->pAttrGroups($node->attrGroups) + . $this->pModifiers($node->flags) + . 'function ' . ($node->byRef ? '&' : '') . $node->name + . '(' . $this->pMaybeMultiline($node->params) . ')' + . (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) { + return $this->pAttrGroups($node->attrGroups) + . $this->pModifiers($node->flags) + . 'const ' . $this->pCommaSeparated($node->consts) . ';'; + } + + protected function pStmt_Function(Stmt\Function_ $node) { + return $this->pAttrGroups($node->attrGroups) + . 'function ' . ($node->byRef ? '&' : '') . $node->name + . '(' . $this->pCommaSeparated($node->params) . ')' + . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Const(Stmt\Const_ $node) { + return 'const ' . $this->pCommaSeparated($node->consts) . ';'; + } + + protected function pStmt_Declare(Stmt\Declare_ $node) { + return 'declare (' . $this->pCommaSeparated($node->declares) . ')' + . (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . $this->nl . '}' : ';'); + } + + protected function pStmt_DeclareDeclare(Stmt\DeclareDeclare $node) { + return $node->key . '=' . $this->p($node->value); + } + + // Control flow + + protected function pStmt_If(Stmt\If_ $node) { + 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) { + return 'elseif (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Else(Stmt\Else_ $node) { + return 'else {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_For(Stmt\For_ $node) { + 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) { + 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) { + return 'while (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Do(Stmt\Do_ $node) { + return 'do {' . $this->pStmts($node->stmts) . $this->nl + . '} while (' . $this->p($node->cond) . ');'; + } + + protected function pStmt_Switch(Stmt\Switch_ $node) { + return 'switch (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->cases) . $this->nl . '}'; + } + + protected function pStmt_Case(Stmt\Case_ $node) { + return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':' + . $this->pStmts($node->stmts); + } + + protected function pStmt_TryCatch(Stmt\TryCatch $node) { + 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) { + 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) { + return 'finally {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Break(Stmt\Break_ $node) { + return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + + protected function pStmt_Continue(Stmt\Continue_ $node) { + return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + + protected function pStmt_Return(Stmt\Return_ $node) { + return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';'; + } + + protected function pStmt_Throw(Stmt\Throw_ $node) { + return 'throw ' . $this->p($node->expr) . ';'; + } + + protected function pStmt_Label(Stmt\Label $node) { + return $node->name . ':'; + } + + protected function pStmt_Goto(Stmt\Goto_ $node) { + return 'goto ' . $node->name . ';'; + } + + // Other + + protected function pStmt_Expression(Stmt\Expression $node) { + return $this->p($node->expr) . ';'; + } + + protected function pStmt_Echo(Stmt\Echo_ $node) { + return 'echo ' . $this->pCommaSeparated($node->exprs) . ';'; + } + + protected function pStmt_Static(Stmt\Static_ $node) { + return 'static ' . $this->pCommaSeparated($node->vars) . ';'; + } + + protected function pStmt_Global(Stmt\Global_ $node) { + return 'global ' . $this->pCommaSeparated($node->vars) . ';'; + } + + protected function pStmt_StaticVar(Stmt\StaticVar $node) { + return $this->p($node->var) + . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pStmt_Unset(Stmt\Unset_ $node) { + return 'unset(' . $this->pCommaSeparated($node->vars) . ');'; + } + + protected function pStmt_InlineHTML(Stmt\InlineHTML $node) { + $newline = $node->getAttribute('hasLeadingNewline', true) ? "\n" : ''; + return '?>' . $newline . $node->value . '<?php '; + } + + protected function pStmt_HaltCompiler(Stmt\HaltCompiler $node) { + return '__halt_compiler();' . $node->remaining; + } + + protected function pStmt_Nop(Stmt\Nop $node) { + return ''; + } + + // Helpers + + protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) { + 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) { + if ($node instanceof Expr) { + return '{' . $this->p($node) . '}'; + } else { + return $node; + } + } + + protected function pEncapsList(array $encapsList, $quote) { + $return = ''; + foreach ($encapsList as $element) { + if ($element instanceof Scalar\EncapsedStringPart) { + $return .= $this->escapeString($element->value, $quote); + } else { + $return .= '{' . $this->p($element) . '}'; + } + } + + return $return; + } + + protected function pSingleQuotedString(string $string) { + return '\'' . addcslashes($string, '\'\\') . '\''; + } + + protected function escapeString($string, $quote) { + if (null === $quote) { + // For doc strings, don't escape newlines + $escaped = addcslashes($string, "\t\f\v$\\"); + } else { + $escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\"); + } + + // Escape other control characters + return preg_replace_callback('/([\0-\10\16-\37])(?=([0-7]?))/', function ($matches) { + $oct = decoct(ord($matches[1])); + if ($matches[2] !== '') { + // If there is a trailing digit, use the full three character form + return '\\' . str_pad($oct, 3, '0', \STR_PAD_LEFT); + } + return '\\' . $oct; + }, $escaped); + } + + protected function containsEndLabel($string, $label, $atStart = true, $atEnd = true) { + $start = $atStart ? '(?:^|[\r\n])' : '[\r\n]'; + $end = $atEnd ? '(?:$|[;\r\n])' : '[;\r\n]'; + return false !== strpos($string, $label) + && preg_match('/' . $start . $label . $end . '/', $string); + } + + protected function encapsedContainsEndLabel(array $parts, $label) { + foreach ($parts as $i => $part) { + $atStart = $i === 0; + $atEnd = $i === count($parts) - 1; + if ($part instanceof Scalar\EncapsedStringPart + && $this->containsEndLabel($part->value, $label, $atStart, $atEnd) + ) { + return true; + } + } + return false; + } + + protected function pDereferenceLhs(Node $node) { + if (!$this->dereferenceLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pCallLhs(Node $node) { + if (!$this->callLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pNewVariable(Node $node) { + // TODO: This is not fully accurate. + return $this->pDereferenceLhs($node); + } + + /** + * @param Node[] $nodes + * @return bool + */ + private function hasNodeWithComments(array $nodes) { + foreach ($nodes as $node) { + if ($node && $node->getComments()) { + return true; + } + } + return false; + } + + private function pMaybeMultiline(array $nodes, bool $trailingComma = false) { + if (!$this->hasNodeWithComments($nodes)) { + return $this->pCommaSeparated($nodes); + } else { + return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl; + } + } + + private 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/lib/composer/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php b/lib/composer/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php new file mode 100644 index 0000000000000000000000000000000000000000..bc85f76b45d33b5de052f3f60553ea4de5d8bf86 --- /dev/null +++ b/lib/composer/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php @@ -0,0 +1,1490 @@ +<?php declare(strict_types=1); + +namespace PhpParser; + +use PhpParser\Internal\DiffElem; +use PhpParser\Internal\PrintableNewAnonClassNode; +use PhpParser\Internal\TokenStream; +use PhpParser\Node\Expr; +use PhpParser\Node\Expr\AssignOp; +use PhpParser\Node\Expr\BinaryOp; +use PhpParser\Node\Expr\Cast; +use PhpParser\Node\Scalar; +use PhpParser\Node\Stmt; + +abstract class PrettyPrinterAbstract +{ + const FIXUP_PREC_LEFT = 0; // LHS operand affected by precedence + const FIXUP_PREC_RIGHT = 1; // RHS operand affected by precedence + const FIXUP_CALL_LHS = 2; // LHS of call + const FIXUP_DEREF_LHS = 3; // LHS of dereferencing operation + const FIXUP_BRACED_NAME = 4; // Name operand that may require bracing + const FIXUP_VAR_BRACED_NAME = 5; // Name operand that may require ${} bracing + const FIXUP_ENCAPSED = 6; // Encapsed string part + + protected $precedenceMap = [ + // [precedence, associativity] + // where for precedence -1 is %left, 0 is %nonassoc and 1 is %right + BinaryOp\Pow::class => [ 0, 1], + Expr\BitwiseNot::class => [ 10, 1], + Expr\PreInc::class => [ 10, 1], + Expr\PreDec::class => [ 10, 1], + Expr\PostInc::class => [ 10, -1], + Expr\PostDec::class => [ 10, -1], + Expr\UnaryPlus::class => [ 10, 1], + Expr\UnaryMinus::class => [ 10, 1], + Cast\Int_::class => [ 10, 1], + Cast\Double::class => [ 10, 1], + Cast\String_::class => [ 10, 1], + Cast\Array_::class => [ 10, 1], + Cast\Object_::class => [ 10, 1], + Cast\Bool_::class => [ 10, 1], + Cast\Unset_::class => [ 10, 1], + Expr\ErrorSuppress::class => [ 10, 1], + Expr\Instanceof_::class => [ 20, 0], + Expr\BooleanNot::class => [ 30, 1], + BinaryOp\Mul::class => [ 40, -1], + BinaryOp\Div::class => [ 40, -1], + BinaryOp\Mod::class => [ 40, -1], + BinaryOp\Plus::class => [ 50, -1], + BinaryOp\Minus::class => [ 50, -1], + BinaryOp\Concat::class => [ 50, -1], + BinaryOp\ShiftLeft::class => [ 60, -1], + BinaryOp\ShiftRight::class => [ 60, -1], + BinaryOp\Smaller::class => [ 70, 0], + BinaryOp\SmallerOrEqual::class => [ 70, 0], + BinaryOp\Greater::class => [ 70, 0], + BinaryOp\GreaterOrEqual::class => [ 70, 0], + BinaryOp\Equal::class => [ 80, 0], + BinaryOp\NotEqual::class => [ 80, 0], + BinaryOp\Identical::class => [ 80, 0], + BinaryOp\NotIdentical::class => [ 80, 0], + BinaryOp\Spaceship::class => [ 80, 0], + BinaryOp\BitwiseAnd::class => [ 90, -1], + BinaryOp\BitwiseXor::class => [100, -1], + BinaryOp\BitwiseOr::class => [110, -1], + BinaryOp\BooleanAnd::class => [120, -1], + BinaryOp\BooleanOr::class => [130, -1], + BinaryOp\Coalesce::class => [140, 1], + Expr\Ternary::class => [150, -1], + // parser uses %left for assignments, but they really behave as %right + Expr\Assign::class => [160, 1], + Expr\AssignRef::class => [160, 1], + AssignOp\Plus::class => [160, 1], + AssignOp\Minus::class => [160, 1], + AssignOp\Mul::class => [160, 1], + AssignOp\Div::class => [160, 1], + AssignOp\Concat::class => [160, 1], + AssignOp\Mod::class => [160, 1], + AssignOp\BitwiseAnd::class => [160, 1], + AssignOp\BitwiseOr::class => [160, 1], + AssignOp\BitwiseXor::class => [160, 1], + AssignOp\ShiftLeft::class => [160, 1], + AssignOp\ShiftRight::class => [160, 1], + AssignOp\Pow::class => [160, 1], + AssignOp\Coalesce::class => [160, 1], + Expr\YieldFrom::class => [165, 1], + Expr\Print_::class => [168, 1], + BinaryOp\LogicalAnd::class => [170, -1], + BinaryOp\LogicalXor::class => [180, -1], + BinaryOp\LogicalOr::class => [190, -1], + Expr\Include_::class => [200, -1], + ]; + + /** @var int Current indentation level. */ + protected $indentLevel; + /** @var string Newline including current indentation. */ + protected $nl; + /** @var string Token placed at end of doc string to ensure it is followed by a newline. */ + protected $docStringEndToken; + /** @var bool Whether semicolon namespaces can be used (i.e. no global namespace is used) */ + protected $canUseSemicolonNamespaces; + /** @var array Pretty printer options */ + protected $options; + + /** @var TokenStream Original tokens for use in format-preserving pretty print */ + protected $origTokens; + /** @var Internal\Differ Differ for node lists */ + protected $nodeListDiffer; + /** @var bool[] Map determining whether a certain character is a label character */ + protected $labelCharMap; + /** + * @var int[][] 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 $fixupMap; + /** + * @var int[][] 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 $removalMap; + /** + * @var mixed[] 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 $insertionMap; + /** + * @var string[] Map From "{$node->getType()}->{$subNode}" to string that should be inserted + * between elements of this list subnode. + */ + protected $listInsertionMap; + protected $emptyListInsertionMap; + /** @var int[] Map from "{$node->getType()}->{$subNode}" to token before which the modifiers + * should be reprinted. */ + protected $modifierChangeMap; + + /** + * Creates a pretty printer instance using the given options. + * + * Supported options: + * * bool $shortArraySyntax = false: Whether to use [] instead of array() as the default array + * syntax, if the node does not specify a format. + * + * @param array $options Dictionary of formatting options + */ + public function __construct(array $options = []) { + $this->docStringEndToken = '_DOC_STRING_END_' . mt_rand(); + + $defaultOptions = ['shortArraySyntax' => false]; + $this->options = $options + $defaultOptions; + } + + /** + * Reset pretty printing state. + */ + protected function resetState() { + $this->indentLevel = 0; + $this->nl = "\n"; + $this->origTokens = null; + } + + /** + * Set indentation level + * + * @param int $level Level in number of spaces + */ + protected function setIndentLevel(int $level) { + $this->indentLevel = $level; + $this->nl = "\n" . \str_repeat(' ', $level); + } + + /** + * Increase indentation level. + */ + protected function indent() { + $this->indentLevel += 4; + $this->nl .= ' '; + } + + /** + * Decrease indentation level. + */ + protected function outdent() { + assert($this->indentLevel >= 4); + $this->indentLevel -= 4; + $this->nl = "\n" . 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 <?php tag if it is required). + * + * @param Node[] $stmts Array of statements + * + * @return string Pretty printed statements + */ + public function prettyPrintFile(array $stmts) : string { + if (!$stmts) { + return "<?php\n\n"; + } + + $p = "<?php\n\n" . $this->prettyPrint($stmts); + + if ($stmts[0] instanceof Stmt\InlineHTML) { + $p = preg_replace('/^<\?php\s+\?>\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) { + /* 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) no-indent and doc-string-end tokens. + * + * @param string $str + * @return string + */ + protected function handleMagicTokens(string $str) : string { + // Replace doc-string-end tokens with nothing or a newline + $str = str_replace($this->docStringEndToken . ";\n", ";\n", $str); + $str = str_replace($this->docStringEndToken, "\n", $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 + * + * @return string Pretty printed infix operation + */ + protected function pInfixOp(string $class, Node $leftNode, string $operatorString, Node $rightNode) : string { + list($precedence, $associativity) = $this->precedenceMap[$class]; + + return $this->pPrec($leftNode, $precedence, $associativity, -1) + . $operatorString + . $this->pPrec($rightNode, $precedence, $associativity, 1); + } + + /** + * 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 + * + * @return string Pretty printed prefix operation + */ + protected function pPrefixOp(string $class, string $operatorString, Node $node) : string { + list($precedence, $associativity) = $this->precedenceMap[$class]; + return $operatorString . $this->pPrec($node, $precedence, $associativity, 1); + } + + /** + * 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 + * + * @return string Pretty printed postfix operation + */ + protected function pPostfixOp(string $class, Node $node, string $operatorString) : string { + list($precedence, $associativity) = $this->precedenceMap[$class]; + return $this->pPrec($node, $precedence, $associativity, -1) . $operatorString; + } + + /** + * Prints an expression node with the least amount of parentheses necessary to preserve the meaning. + * + * @param Node $node Node to pretty print + * @param int $parentPrecedence Precedence of the parent operator + * @param int $parentAssociativity Associativity of parent operator + * (-1 is left, 0 is nonassoc, 1 is right) + * @param int $childPosition Position of the node relative to the operator + * (-1 is left, 1 is right) + * + * @return string The pretty printed node + */ + protected function pPrec(Node $node, int $parentPrecedence, int $parentAssociativity, int $childPosition) : string { + $class = \get_class($node); + if (isset($this->precedenceMap[$class])) { + $childPrecedence = $this->precedenceMap[$class][0]; + if ($childPrecedence > $parentPrecedence + || ($parentPrecedence === $childPrecedence && $parentAssociativity !== $childPosition) + ) { + return '(' . $this->p($node) . ')'; + } + } + + return $this->p($node); + } + + /** + * 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 + */ + 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 array $origTokens Tokens of the original code + * + * @return string + */ + 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), 0); + } else { + // Fallback + // TODO Add <?php properly + $result = "<?php\n" . $this->pStmts($stmts, false); + } + + return ltrim($this->handleMagicTokens($result)); + } + + protected function pFallback(Node $node) { + return $this->{'p' . $node->getType()}($node); + } + + /** + * Pretty prints a node. + * + * This method also handles formatting preservation for nodes. + * + * @param Node $node Node to be pretty printed + * @param bool $parentFormatPreserved Whether parent node has preserved formatting + * + * @return string Pretty printed node + */ + protected function p(Node $node, $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); + } + + /** @var Node $origNode */ + $origNode = $node->getAttribute('origNode'); + if (null === $origNode) { + return $this->pFallback($node); + } + + $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 + $node = PrintableNewAnonClassNode::fromNewNode($node); + $origNode = PrintableNewAnonClassNode::fromNewNode($origNode); + } + + // 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); + } + + $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, $type, $subNodeName, + $fixupInfo[$subNodeName] ?? null + ); + if (null === $listResult) { + return $this->pFallback($fallbackNode); + } + + $result .= $listResult; + continue; + } + + if (is_int($subNode) && is_int($origSubNode)) { + // Check if this is a modifier change + $key = $type . '->' . $subNodeName; + if (!isset($this->modifierChangeMap[$key])) { + return $this->pFallback($fallbackNode); + } + + $findToken = $this->modifierChangeMap[$key]; + $result .= $this->pModifiers($subNode); + $pos = $this->origTokens->findRight($pos, $findToken); + continue; + } + + // If a non-node, non-array subnode changed, we don't be able to do a partial + // reconstructions, as we don't have enough offset information. Pretty print the + // whole node instead. + return $this->pFallback($fallbackNode); + } + + $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); + } + + 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); + } + + // 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, 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 array $nodes New nodes + * @param array $origNodes Original nodes + * @param int $pos Current token position (updated by reference) + * @param int $indentAdjustment Adjustment for indentation + * @param string $parentNodeType Type 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 $parentNodeType, string $subNodeName, $fixup + ) { + $diff = $this->nodeListDiffer->diffWithReplacements($origNodes, $nodes); + + $mapKey = $parentNodeType . '->' . $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|null $arrItem */ + $arrItem = $diffElem->new; + /** @var Node|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->haveBracesInRange($pos, $itemStartPos)) { + // We'd remove the brace of a code block. + // 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, 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 ($insertStr === ', ' && $this->isMultiline($origNodes)) { + $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) { + $comments = $arrItem->getComments(); + if ($comments) { + $result .= $this->nl . $this->pComments($comments); + } + $result .= $insertStr . $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->haveBracesInRange($pos, $itemStartPos)) { + // We'd remove the brace of a code block. + // 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, 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; + } + $result .= $this->p($delayedAddNode, true); + $first = false; + } + $result .= $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, $parentClass, int $subStartPos, int $subEndPos) : string { + switch ($fixup) { + case self::FIXUP_PREC_LEFT: + case self::FIXUP_PREC_RIGHT: + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + list($precedence, $associativity) = $this->precedenceMap[$parentClass]; + return $this->pPrec($subNode, $precedence, $associativity, + $fixup === self::FIXUP_PREC_LEFT ? -1 : 1); + } + 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_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 Scalar\EncapsedStringPart + && !$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. + * + * @param string $str + * @param string $append + */ + protected function safeAppend(string &$str, string $append) { + 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 a dereferencing operation must be wrapped in parenthesis. + * + * @param Node $node LHS of dereferencing operation + * + * @return bool Whether parentheses are required + */ + protected function dereferenceLhsRequiresParens(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\ConstFetch + || $node instanceof Expr\ClassConstFetch); + } + + /** + * Print modifiers, including trailing whitespace. + * + * @param int $modifiers Modifier mask to print + * + * @return string Printed modifiers + */ + protected function pModifiers(int $modifiers) { + return ($modifiers & Stmt\Class_::MODIFIER_PUBLIC ? 'public ' : '') + . ($modifiers & Stmt\Class_::MODIFIER_PROTECTED ? 'protected ' : '') + . ($modifiers & Stmt\Class_::MODIFIER_PRIVATE ? 'private ' : '') + . ($modifiers & Stmt\Class_::MODIFIER_STATIC ? 'static ' : '') + . ($modifiers & Stmt\Class_::MODIFIER_ABSTRACT ? 'abstract ' : '') + . ($modifiers & Stmt\Class_::MODIFIER_FINAL ? 'final ' : ''); + } + + /** + * 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() { + if ($this->labelCharMap) return; + + $this->labelCharMap = []; + for ($i = 0; $i < 256; $i++) { + // Since PHP 7.1 The lower range is 0x80. However, we also want to support code for + // older versions. + $this->labelCharMap[chr($i)] = $i >= 0x7f || ctype_alnum($i); + } + } + + /** + * Lazily initializes node list differ. + * + * The node list differ is used to determine differences between two array subnodes. + */ + protected function initializeNodeListDiffer() { + if ($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() { + if ($this->fixupMap) return; + + $this->fixupMap = [ + Expr\PreInc::class => ['var' => self::FIXUP_PREC_RIGHT], + Expr\PreDec::class => ['var' => self::FIXUP_PREC_RIGHT], + Expr\PostInc::class => ['var' => self::FIXUP_PREC_LEFT], + Expr\PostDec::class => ['var' => self::FIXUP_PREC_LEFT], + Expr\Instanceof_::class => [ + 'expr' => self::FIXUP_PREC_LEFT, + 'class' => self::FIXUP_PREC_RIGHT, // TODO: FIXUP_NEW_VARIABLE + ], + Expr\Ternary::class => [ + 'cond' => self::FIXUP_PREC_LEFT, + 'else' => self::FIXUP_PREC_RIGHT, + ], + + Expr\FuncCall::class => ['name' => self::FIXUP_CALL_LHS], + Expr\StaticCall::class => ['class' => self::FIXUP_DEREF_LHS], + Expr\ArrayDimFetch::class => ['var' => self::FIXUP_DEREF_LHS], + Expr\ClassConstFetch::class => ['var' => self::FIXUP_DEREF_LHS], + Expr\New_::class => ['class' => self::FIXUP_DEREF_LHS], // TODO: FIXUP_NEW_VARIABLE + 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_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\Encapsed::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 + ]; + } + + $assignOps = [ + 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 + ]; + foreach ($assignOps as $assignOp) { + $this->fixupMap[$assignOp] = [ + 'var' => self::FIXUP_PREC_LEFT, + 'expr' => self::FIXUP_PREC_RIGHT, + ]; + } + + $prefixOps = [ + 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, + ]; + foreach ($prefixOps as $prefixOp) { + $this->fixupMap[$prefixOp] = ['expr' => self::FIXUP_PREC_RIGHT]; + } + } + + /** + * Lazily initializes the removal map. + * + * The removal map is used to determine which additional tokens should be returned when a + * certain node is replaced by null. + */ + protected function initializeRemovalMap() { + if ($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, + 'Expr_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_ClassMethod->returnType' => $stripColon, + 'Stmt_Class->extends' => ['left' => \T_EXTENDS], + '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, + 'Stmt_PropertyProperty->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() { + if ($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], + 'Expr_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_Class->extends' => [null, false, ' extends ', null], + 'Expr_PrintableNewAnonClass->extends' => [null, ' 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, ' '], + 'Stmt_PropertyProperty->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() { + if ($this->listInsertionMap) return; + + $this->listInsertionMap = [ + // special + //'Expr_ShellExec->parts' => '', // TODO These need to be treated more carefully + //'Scalar_Encapsed->parts' => '', + 'Stmt_Catch->types' => '|', + 'UnionType->types' => '|', + 'Stmt_If->elseifs' => ' ', + 'Stmt_TryCatch->catches' => ' ', + + // comma-separated lists + 'Expr_Array->items' => ', ', + 'Expr_ArrowFunction->params' => ', ', + 'Expr_Closure->params' => ', ', + 'Expr_Closure->uses' => ', ', + 'Expr_FuncCall->args' => ', ', + 'Expr_Isset->vars' => ', ', + 'Expr_List->items' => ', ', + 'Expr_MethodCall->args' => ', ', + 'Expr_NullsafeMethodCall->args' => ', ', + 'Expr_New->args' => ', ', + 'Expr_PrintableNewAnonClass->args' => ', ', + 'Expr_StaticCall->args' => ', ', + 'Stmt_ClassConst->consts' => ', ', + 'Stmt_ClassMethod->params' => ', ', + 'Stmt_Class->implements' => ', ', + 'Expr_PrintableNewAnonClass->implements' => ', ', + 'Stmt_Const->consts' => ', ', + 'Stmt_Declare->declares' => ', ', + 'Stmt_Echo->exprs' => ', ', + 'Stmt_For->init' => ', ', + 'Stmt_For->cond' => ', ', + 'Stmt_For->loop' => ', ', + 'Stmt_Function->params' => ', ', + 'Stmt_Global->vars' => ', ', + 'Stmt_GroupUse->uses' => ', ', + 'Stmt_Interface->extends' => ', ', + 'Stmt_Match->arms' => ', ', + 'Stmt_Property->props' => ', ', + 'Stmt_StaticVar->vars' => ', ', + 'Stmt_TraitUse->traits' => ', ', + 'Stmt_TraitUseAdaptation_Precedence->insteadof' => ', ', + 'Stmt_Unset->vars' => ', ', + 'Stmt_Use->uses' => ', ', + 'MatchArm->conds' => ', ', + 'AttributeGroup->attrs' => ', ', + + // statement lists + 'Expr_Closure->stmts' => "\n", + 'Stmt_Case->stmts' => "\n", + 'Stmt_Catch->stmts' => "\n", + 'Stmt_Class->stmts' => "\n", + 'Expr_PrintableNewAnonClass->stmts' => "\n", + 'Stmt_Interface->stmts' => "\n", + 'Stmt_Trait->stmts' => "\n", + 'Stmt_ClassMethod->stmts' => "\n", + 'Stmt_Declare->stmts' => "\n", + 'Stmt_Do->stmts' => "\n", + 'Stmt_ElseIf->stmts' => "\n", + 'Stmt_Else->stmts' => "\n", + 'Stmt_Finally->stmts' => "\n", + 'Stmt_Foreach->stmts' => "\n", + 'Stmt_For->stmts' => "\n", + 'Stmt_Function->stmts' => "\n", + 'Stmt_If->stmts' => "\n", + 'Stmt_Namespace->stmts' => "\n", + 'Stmt_Class->attrGroups' => "\n", + 'Stmt_Interface->attrGroups' => "\n", + 'Stmt_Trait->attrGroups' => "\n", + 'Stmt_Function->attrGroups' => "\n", + 'Stmt_ClassMethod->attrGroups' => "\n", + 'Stmt_ClassConst->attrGroups' => "\n", + 'Stmt_Property->attrGroups' => "\n", + 'Expr_PrintableNewAnonClass->attrGroups' => ' ', + 'Expr_Closure->attrGroups' => ' ', + 'Expr_ArrowFunction->attrGroups' => ' ', + 'Param->attrGroups' => ' ', + 'Stmt_Switch->cases' => "\n", + 'Stmt_TraitUse->adaptations' => "\n", + 'Stmt_TryCatch->stmts' => "\n", + 'Stmt_While->stmts' => "\n", + + // dummy for top-level context + 'File->stmts' => "\n", + ]; + } + + protected function initializeEmptyListInsertionMap() { + if ($this->emptyListInsertionMap) return; + + // TODO Insertion into empty statement lists. + + // [$find, $extraLeft, $extraRight] + $this->emptyListInsertionMap = [ + 'Expr_ArrowFunction->params' => ['(', '', ''], + 'Expr_Closure->uses' => [')', ' use(', ')'], + 'Expr_Closure->params' => ['(', '', ''], + 'Expr_FuncCall->args' => ['(', '', ''], + 'Expr_MethodCall->args' => ['(', '', ''], + 'Expr_NullsafeMethodCall->args' => ['(', '', ''], + 'Expr_New->args' => ['(', '', ''], + 'Expr_PrintableNewAnonClass->args' => ['(', '', ''], + 'Expr_PrintableNewAnonClass->implements' => [null, ' implements ', ''], + 'Expr_StaticCall->args' => ['(', '', ''], + 'Stmt_Class->implements' => [null, ' implements ', ''], + 'Stmt_ClassMethod->params' => ['(', '', ''], + 'Stmt_Interface->extends' => [null, ' extends ', ''], + 'Stmt_Function->params' => ['(', '', ''], + + /* 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() { + if ($this->modifierChangeMap) return; + + $this->modifierChangeMap = [ + 'Stmt_ClassConst->flags' => \T_CONST, + 'Stmt_ClassMethod->flags' => \T_FUNCTION, + 'Stmt_Class->flags' => \T_CLASS, + 'Stmt_Property->flags' => \T_VARIABLE, + 'Param->flags' => \T_VARIABLE, + //'Stmt_TraitUseAdaptation_Alias->newModifier' => 0, // TODO + ]; + + // List of integer subnodes that are not modifiers: + // Expr_Include->type + // Stmt_GroupUse->type + // Stmt_Use->type + // Stmt_UseUse->type + } +} diff --git a/lib/composer/openlss/lib-array2xml/.gitignore b/lib/composer/openlss/lib-array2xml/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..1ee4bf6f13c2dfdf777808d0e525e2a0927c4f6a --- /dev/null +++ b/lib/composer/openlss/lib-array2xml/.gitignore @@ -0,0 +1,3 @@ +.idea +/vendor +/composer.lock diff --git a/lib/composer/openlss/lib-array2xml/COPYING b/lib/composer/openlss/lib-array2xml/COPYING new file mode 100644 index 0000000000000000000000000000000000000000..20d40b6bceca3a6c0237d7455ebf1820aeff3680 --- /dev/null +++ b/lib/composer/openlss/lib-array2xml/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. \ No newline at end of file diff --git a/lib/composer/openlss/lib-array2xml/COPYING LESSER b/lib/composer/openlss/lib-array2xml/COPYING LESSER new file mode 100644 index 0000000000000000000000000000000000000000..02bbb60bc49afc2d6a1bedf96288eab236d80fbd --- /dev/null +++ b/lib/composer/openlss/lib-array2xml/COPYING LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/lib/composer/openlss/lib-array2xml/LSS/Array2XML.php b/lib/composer/openlss/lib-array2xml/LSS/Array2XML.php new file mode 100644 index 0000000000000000000000000000000000000000..99e0659ab0b5564e1be00c3c59176c3fa3270ef5 --- /dev/null +++ b/lib/composer/openlss/lib-array2xml/LSS/Array2XML.php @@ -0,0 +1,209 @@ +<?php +/** + * OpenLSS - Lighter Smarter Simpler + * + * This file is part of OpenLSS. + * + * OpenLSS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenLSS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the + * GNU Lesser General Public License along with OpenLSS. + * If not, see <http://www.gnu.org/licenses/>. + */ +namespace LSS; + +use \DomDocument; +use \Exception; + +/** + * Array2XML: A class to convert array in PHP to XML + * It also takes into account attributes names unlike SimpleXML in PHP + * It returns the XML in form of DOMDocument class for further manipulation. + * It throws exception if the tag name or attribute name has illegal chars. + * + * Author : Lalit Patel + * Website: http://www.lalit.org/lab/convert-php-array-to-xml-with-attributes + * License: Apache License 2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * Version: 0.1 (10 July 2011) + * Version: 0.2 (16 August 2011) + * - replaced htmlentities() with htmlspecialchars() (Thanks to Liel Dulev) + * - fixed a edge case where root node has a false/null/0 value. (Thanks to Liel Dulev) + * Version: 0.3 (22 August 2011) + * - fixed tag sanitize regex which didn't allow tagnames with single character. + * Version: 0.4 (18 September 2011) + * - Added support for CDATA section using @cdata instead of @value. + * Version: 0.5 (07 December 2011) + * - Changed logic to check numeric array indices not starting from 0. + * Version: 0.6 (04 March 2012) + * - Code now doesn't @cdata to be placed in an empty array + * Version: 0.7 (24 March 2012) + * - Reverted to version 0.5 + * Version: 0.8 (02 May 2012) + * - Removed htmlspecialchars() before adding to text node or attributes. + * Version: 0.11 (28 October 2015) + * - Fixed typos; Added support for plain insertion of XML trough @xml. + * + * Usage: + * $xml = Array2XML::createXML('root_node_name', $php_array); + * echo $xml->saveXML(); + */ +class Array2XML { + + /** + * @var DOMDocument + */ + private static $xml = null; + private static $encoding = 'UTF-8'; + + /** + * Initialize the root XML node [optional] + * @param $version + * @param $encoding + * @param $format_output + */ + public static function init($version = '1.0', $encoding = 'UTF-8', $format_output = true) { + self::$xml = new DomDocument($version, $encoding); + self::$xml->formatOutput = $format_output; + self::$encoding = $encoding; + } + + /** + * Convert an Array to XML + * @param string $node_name - name of the root node to be converted + * @param array $arr - aray to be converterd + * @return DomDocument + */ + public static function &createXML($node_name, $arr = array()) { + $xml = self::getXMLRoot(); + $xml->appendChild(self::convert($node_name, $arr)); + + self::$xml = null; // clear the xml node in the class for 2nd time use. + return $xml; + } + + /** + * Convert an Array to XML. + * + * @param string $node_name + * Name of the root node to be converted. + * @param array $arr + * Array to be converted. + * + * @throws \Exception + * + * @return \DOMNode + */ + private static function &convert($node_name, $arr = array()) { + + //print_arr($node_name); + $xml = self::getXMLRoot(); + $node = $xml->createElement($node_name); + + if (is_array($arr)) { + // get the attributes first.; + if (isset($arr['@attributes'])) { + foreach ($arr['@attributes'] as $key => $value) { + if (!self::isValidTagName($key)) { + throw new Exception('[Array2XML] Illegal character in attribute name. attribute: ' . $key . ' in node: ' . $node_name); + } + $node->setAttribute($key, self::bool2str($value)); + } + unset($arr['@attributes']); //remove the key from the array once done. + } + + // check if it has a value stored in @value, if yes store the value and return + // else check if its directly stored as string + if (isset($arr['@value'])) { + $node->appendChild($xml->createTextNode(self::bool2str($arr['@value']))); + unset($arr['@value']); //remove the key from the array once done. + //return from recursion, as a note with value cannot have child nodes. + return $node; + } else if (isset($arr['@cdata'])) { + $node->appendChild($xml->createCDATASection(self::bool2str($arr['@cdata']))); + unset($arr['@cdata']); //remove the key from the array once done. + //return from recursion, as a note with cdata cannot have child nodes. + return $node; + } + else if (isset($arr['@comment']) && is_string($arr['@comment'])) { + $node->appendChild($xml->createComment(self::bool2str($arr['@comment']))); + unset($arr['@comment']); + } + else if (isset($arr['@xml'])) { + $fragment = $xml->createDocumentFragment(); + $fragment->appendXML($arr['@xml']); + $node->appendChild($fragment); + unset($arr['@xml']); + return $node; + } + } + + //create subnodes using recursion + if (is_array($arr)) { + // recurse to get the node for that key + foreach ($arr as $key => $value) { + if (!self::isValidTagName($key)) { + throw new Exception('[Array2XML] Illegal character in tag name. tag: ' . $key . ' in node: ' . $node_name); + } + if (is_array($value) && is_numeric(key($value))) { + // MORE THAN ONE NODE OF ITS KIND; + // if the new array is numeric index, means it is array of nodes of the same kind + // it should follow the parent key name + foreach ($value as $k => $v) { + $node->appendChild(self::convert($key, $v)); + } + } else { + // ONLY ONE NODE OF ITS KIND + $node->appendChild(self::convert($key, $value)); + } + unset($arr[$key]); //remove the key from the array once done. + } + } + + // after we are done with all the keys in the array (if it is one) + // we check if it has any text value, if yes, append it. + if (!is_array($arr)) { + $node->appendChild($xml->createTextNode(self::bool2str($arr))); + } + + return $node; + } + + /* + * Get the root XML node, if there isn't one, create it. + */ + private static function getXMLRoot() { + if (empty(self::$xml)) { + self::init(); + } + return self::$xml; + } + + /* + * Get string representation of boolean value + */ + private static function bool2str($v) { + //convert boolean to text value. + $v = $v === true ? 'true' : $v; + $v = $v === false ? 'false' : $v; + return $v; + } + + /* + * Check if the tag name or attribute name contains illegal characters + * Ref: http://www.w3.org/TR/xml/#sec-common-syn + */ + private static function isValidTagName($tag) { + $pattern = '/^[a-z_]+[a-z0-9\:\-\.\_]*[^:]*$/i'; + return preg_match($pattern, $tag, $matches) && $matches[0] == $tag; + } +} + diff --git a/lib/composer/openlss/lib-array2xml/LSS/XML2Array.php b/lib/composer/openlss/lib-array2xml/LSS/XML2Array.php new file mode 100644 index 0000000000000000000000000000000000000000..ad5dafaad8ac5b301ed9b55228a5cd9070fa98e3 --- /dev/null +++ b/lib/composer/openlss/lib-array2xml/LSS/XML2Array.php @@ -0,0 +1,173 @@ +<?php +/** + * OpenLSS - Lighter Smarter Simpler + * + * This file is part of OpenLSS. + * + * OpenLSS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenLSS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the + * GNU Lesser General Public License along with OpenLSS. + * If not, see <http://www.gnu.org/licenses/>. + */ +namespace LSS; +use \DOMDocument; +use \Exception; + +/** + * XML2Array: A class to convert XML to array in PHP + * It returns the array which can be converted back to XML using the Array2XML script + * It takes an XML string or a DOMDocument object as an input. + * + * See Array2XML: http://www.lalit.org/lab/convert-php-array-to-xml-with-attributes + * + * Author : Lalit Patel + * Website: http://www.lalit.org/lab/convert-xml-to-array-in-php-xml2array + * License: Apache License 2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * Version: 0.1 (07 Dec 2011) + * Version: 0.2 (04 Mar 2012) + * Fixed typo 'DomDocument' to 'DOMDocument' + * + * Usage: + * $array = XML2Array::createArray($xml); + */ + +class XML2Array { + + protected static $xml = null; + protected static $encoding = 'UTF-8'; + protected static $prefix_attributes = '@'; + + /** + * Initialize the root XML node [optional] + * @param $version + * @param $encoding + * @param $format_output + */ + public static function init($version = '1.0', $encoding = 'UTF-8', $format_output = true) { + self::$xml = new DOMDocument($version, $encoding); + self::$xml->formatOutput = $format_output; + self::$encoding = $encoding; + } + + /** + * Convert an XML to Array + * @param string $node_name - name of the root node to be converted + * @param int - Bitwise OR of the libxml option constants see @link http://php.net/manual/libxml.constants.php + * @param array $arr - aray to be converterd + * @param mixed $callback - callback function + * @return array + */ + public static function &createArray($input_xml, $options = 0, $callback = null) { + $xml = self::getXMLRoot(); + if(is_string($input_xml)) { + $parsed = $xml->loadXML($input_xml, $options); + if(!$parsed) { + throw new Exception('[XML2Array] Error parsing the XML string.'); + } + } else { + if(get_class($input_xml) != 'DOMDocument') { + throw new Exception('[XML2Array] The input XML object should be of type: DOMDocument.'); + } + $xml = self::$xml = $input_xml; + } + $array[$xml->documentElement->tagName] = self::convert($xml->documentElement, $callback); + self::$xml = null; // clear the xml node in the class for 2nd time use. + return $array; + } + + /** + * Convert an Array to XML + * @param mixed $node - XML as a string or as an object of DOMDocument + * @param mixed $callback - callback function + * @return mixed + */ + protected static function &convert($node, $callback = null) { + $output = array(); + + switch ($node->nodeType) { + case XML_CDATA_SECTION_NODE: + $output[static::$prefix_attributes.'cdata'] = trim($node->textContent); + break; + + case XML_TEXT_NODE: + $output = trim($node->textContent); + break; + + case XML_ELEMENT_NODE: + // for each child node, call the covert function recursively + for ($i=0, $m=$node->childNodes->length; $i<$m; $i++) { + if ($callback!==null) { + $callback($m=$node->childNodes->length, $i); + } + $child = $node->childNodes->item($i); + $v = self::convert($child); + if(isset($child->tagName)) { + $t = $child->tagName; + + // avoid fatal error if the content looks like '<html><body>You are being <a href="https://some.url">redirected</a>.</body></html>' + if(isset($output) && !is_array($output)) { + continue; + } + // assume more nodes of same kind are coming + if(!isset($output[$t])) { + $output[$t] = array(); + } + $output[$t][] = $v; + } else { + //check if it is not an empty text node + if($v !== '') { + $output = $v; + } + } + } + + if(is_array($output)) { + // if only one node of its kind, assign it directly instead if array($value); + foreach ($output as $t => $v) { + if(is_array($v) && count($v)==1) { + $output[$t] = $v[0]; + } + } + if(empty($output)) { + //for empty nodes + $output = ''; + } + } + + // loop through the attributes and collect them + if($node->attributes->length) { + $a = array(); + foreach($node->attributes as $attrName => $attrNode) { + $a[$attrName] = (string) $attrNode->value; + } + // if its an leaf node, store the value in @value instead of directly storing it. + if(!is_array($output)) { + $output = array(static::$prefix_attributes.'value' => $output); + } + $output[static::$prefix_attributes.'attributes'] = $a; + } + break; + } + return $output; + } + + /* + * Get the root XML node, if there isn't one, create it. + */ + protected static function getXMLRoot(){ + if(empty(self::$xml)) { + self::init(); + } + return self::$xml; + } +} diff --git a/lib/composer/openlss/lib-array2xml/README.md b/lib/composer/openlss/lib-array2xml/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d3207ce20724aab18491b5330d4116b364c33d35 --- /dev/null +++ b/lib/composer/openlss/lib-array2xml/README.md @@ -0,0 +1,72 @@ +lib-array2xml +============= + +Array2XML conversion library credit to lalit.org + +Usage +---- +```php +//create XML +$xml = Array2XML::createXML('root_node_name', $php_array); +echo $xml->saveXML(); + +//create Array +$array = XML2Array::createArray($xml); +print_r($array); +``` + +Array2XML +---- + +@xml example: +```php +// Build the array that should be transformed into a XML object. +$array = [ + 'title' => 'A title', + 'body' => [ + '@xml' => '<html><body><p>The content for the news item</p></body></html>', + ], +]; + +// Use the Array2XML object to transform it. +$xml = Array2XML::createXML('news', $array); +echo $xml->saveXML(); +``` +This will result in the following. +```xml +<?xml version="1.0" encoding="UTF-8"?> +<news> + <title>A title + + + +

The content for the news item

+ + + + +``` + +Reference +---- +More complete references can be found here + http://www.lalit.org/lab/convert-xml-to-array-in-php-xml2array/ + http://www.lalit.org/lab/convert-php-array-to-xml-with-attributes/ + +## Changelog + +### 1.0.0 +* Add ability for callbacks during processing to check status. + +### 0.5.1 +* Fix fata error when the array passed is empty fixed by pull request #6 + +### 0.5.0 +* add second parameter to XML2Array::createArray for DOMDocument::load, e.g: LIBXML_NOCDATA +* change method visibility from private to protected for overloading +* Merge pull request #5 to add child xml +* Merge pull request #4 to change method visibility and add second parameter for load. + + +### 0.1.0 +* Initial Release diff --git a/lib/composer/openlss/lib-array2xml/composer.json b/lib/composer/openlss/lib-array2xml/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..7471231fb2c94db8d15f8136d544c6b011ab97b5 --- /dev/null +++ b/lib/composer/openlss/lib-array2xml/composer.json @@ -0,0 +1,33 @@ +{ + "name": "openlss/lib-array2xml" + ,"homepage": "https://www.nullivex.com" + ,"description": "Array2XML conversion library credit to lalit.org" + ,"license": "Apache-2.0" + ,"type": "library" + ,"keywords": [ + "array" + ,"xml" + ,"xml conversion" + ,"array conversion" + ] + ,"authors": [ + { + "name": "Bryan Tong" + ,"email": "bryan@nullivex.com" + ,"homepage": "https://www.nullivex.com" + } + ,{ + "name": "Tony Butler" + ,"email": "spudz76@gmail.com" + ,"homepage": "https://www.nullivex.com" + } + ] + ,"require": { + "php": ">=5.3.2" + } + ,"autoload": { + "psr-0": { + "LSS": "" + } + } +} diff --git a/lib/composer/paragonie/random_compat/LICENSE b/lib/composer/paragonie/random_compat/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..45c7017dfb33dd003ee038cc9232d2430ffbf9a2 --- /dev/null +++ b/lib/composer/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/lib/composer/paragonie/random_compat/build-phar.sh b/lib/composer/paragonie/random_compat/build-phar.sh new file mode 100755 index 0000000000000000000000000000000000000000..b4a5ba31cc7bbd042027b4c111e224c4c99f4fc5 --- /dev/null +++ b/lib/composer/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/lib/composer/paragonie/random_compat/composer.json b/lib/composer/paragonie/random_compat/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..1fa8de9f1bc98e22e9fd47d12ae4c9582bdf87ba --- /dev/null +++ b/lib/composer/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/lib/composer/paragonie/random_compat/dist/random_compat.phar.pubkey b/lib/composer/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 0000000000000000000000000000000000000000..eb50ebfcd6d53169b6ddcf0013977f3ffa1542e8 --- /dev/null +++ b/lib/composer/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/lib/composer/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/lib/composer/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 0000000000000000000000000000000000000000..6a1d7f300675f3b42775d0b5aae49fc40768bc56 --- /dev/null +++ b/lib/composer/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/lib/composer/paragonie/random_compat/lib/random.php b/lib/composer/paragonie/random_compat/lib/random.php new file mode 100644 index 0000000000000000000000000000000000000000..c7731a56ff02161ee3d3c7d68fc466364490c2b2 --- /dev/null +++ b/lib/composer/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/lib/composer/paragonie/random_compat/psalm-autoload.php b/lib/composer/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 0000000000000000000000000000000000000000..d71d1b818c311072207e3ee4abff9f82a10d2fa9 --- /dev/null +++ b/lib/composer/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + diff --git a/lib/composer/php-cs-fixer/diff/ChangeLog.md b/lib/composer/php-cs-fixer/diff/ChangeLog.md new file mode 100644 index 0000000000000000000000000000000000000000..b8767088e96e9df79aec7046caf7300e5c5949b8 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/ChangeLog.md @@ -0,0 +1,7 @@ +# ChangeLog + +Changelog for v1.0 + +First stable release, based on: +https://github.com/sebastianbergmann/diff/releases +1.4.3 and 2.0.1 diff --git a/lib/composer/php-cs-fixer/diff/LICENSE b/lib/composer/php-cs-fixer/diff/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..19a2d69c56dec4ea07efb7f23bb0b4c6a46da50b --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/LICENSE @@ -0,0 +1,5 @@ +Code from `sebastian/diff` has been forked and republished by permission of Sebastian Bergmann. +Licenced with BSD-3-Clause @ see LICENSE_DIFF, copyright (c) Sebastian Bergmann + +Code from `GeckoPackages/GeckoDiffOutputBuilder` has been copied and republished by permission of GeckoPackages. +Licenced with MIT @ see LICENSE_GECKO, copyright (c) GeckoPackages https://github.com/GeckoPackages diff --git a/lib/composer/php-cs-fixer/diff/LICENSE_DIFF b/lib/composer/php-cs-fixer/diff/LICENSE_DIFF new file mode 100644 index 0000000000000000000000000000000000000000..93cf008811246863c3db70e2dd877c76c2a8b7fa --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/LICENSE_DIFF @@ -0,0 +1,31 @@ +Copyright (c) 2002-2017, 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/lib/composer/php-cs-fixer/diff/LICENSE_GECKO b/lib/composer/php-cs-fixer/diff/LICENSE_GECKO new file mode 100644 index 0000000000000000000000000000000000000000..066294d54d247ab49dc6ca2b4f6f3cffc745539f --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/LICENSE_GECKO @@ -0,0 +1,19 @@ +Copyright (c) https://github.com/GeckoPackages + +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. \ No newline at end of file diff --git a/lib/composer/php-cs-fixer/diff/README.md b/lib/composer/php-cs-fixer/diff/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d4106257c73bdbfa811e5ef51155b1edf1f5908e --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/README.md @@ -0,0 +1,13 @@ +# PHP-CS-Fixer/diff + +This version is for PHP CS Fixer only! Do not use it! + +Code from `sebastian/diff` has been forked a republished by permission of Sebastian Bergmann. +Licenced with BSD-3-Clause @ see LICENSE_DIFF, copyright (c) Sebastian Bergmann +https://github.com/sebastianbergmann/diff + +Code from `GeckoPackages/GeckoDiffOutputBuilder` has been copied and republished by permission of GeckoPackages. +Licenced with MIT @ see LICENSE_GECKO, copyright (c) GeckoPackages https://github.com/GeckoPackages +https://github.com/GeckoPackages/GeckoDiffOutputBuilder/ + +For questions visit us @ https://gitter.im/PHP-CS-Fixer/Lobby diff --git a/lib/composer/php-cs-fixer/diff/composer.json b/lib/composer/php-cs-fixer/diff/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..e08be75b358a4038ed7da1e6a84217c74a3c3b36 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/composer.json @@ -0,0 +1,41 @@ +{ + "name": "php-cs-fixer/diff", + "description": "sebastian/diff v2 backport support for PHP5.6", + "keywords": ["diff"], + "homepage": "https://github.com/PHP-CS-Fixer", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "SpacePossum" + } + ], + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "symfony/process": "^3.3" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "autoload-dev": { + "psr-4": { + "PhpCsFixer\\Diff\\v1_4\\Tests\\": "tests/v1_4", + "PhpCsFixer\\Diff\\v2_0\\Tests\\": "tests/v2_0", + "PhpCsFixer\\Diff\\v3_0\\": "tests/v3_0", + "PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\Tests\\": "tests/GeckoPackages/DiffOutputBuilder/Tests", + "PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\Utils\\": "tests/GeckoPackages/DiffOutputBuilder/Utils" + } + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/ConfigurationException.php b/lib/composer/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/ConfigurationException.php new file mode 100644 index 0000000000000000000000000000000000000000..315ac0725f555dbce662cd8c12835edf2145b9cf --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/ConfigurationException.php @@ -0,0 +1,36 @@ +' : \gettype($value).'#'.$value) + ), + $code, + $previous + ); + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/UnifiedDiffOutputBuilder.php b/lib/composer/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/UnifiedDiffOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..8a703725c5b209d9116033b163a1dcc8a600923a --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/UnifiedDiffOutputBuilder.php @@ -0,0 +1,295 @@ += 0 + */ + private $commonLineThreshold; + + /** + * @var string + */ + private $header; + + /** + * @var int >= 0 + */ + private $contextLines; + + private static $default = [ + 'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 + 'collapseRanges' => true, // ranges of length one are rendered with the trailing `,1` + 'fromFile' => null, + 'fromFileDate' => null, + 'toFile' => null, + 'toFileDate' => null, + 'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed) + ]; + + 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'] < 1) { + throw new ConfigurationException('commonLineThreshold', 'an int > 0', $options['commonLineThreshold']); + } + + foreach (['fromFile', 'toFile'] as $option) { + if (!\is_string($options[$option])) { + throw new ConfigurationException($option, 'a string', $options[$option]); + } + } + + foreach (['fromFileDate', 'toFileDate'] as $option) { + if (null !== $options[$option] && !\is_string($options[$option])) { + throw new ConfigurationException($option, 'a string or ', $options[$option]); + } + } + + $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) + { + if (0 === \count($diff)) { + return ''; + } + + $this->changed = false; + + $buffer = \fopen('php://memory', 'r+b'); + \fwrite($buffer, $this->header); + + $this->writeDiffHunks($buffer, $diff); + + $diff = \stream_get_contents($buffer, -1, 0); + + \fclose($buffer); + + if (!$this->changed) { + return ''; + } + + return $diff; + } + + private function writeDiffHunks($output, array $diff) + { + // detect "No newline at end of file" and insert into `$diff` if needed + + $upperLimit = \count($diff); + + // append "\ No newline at end of file" if needed + 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", self::$noNewlineAtOEFid]]); + } + } 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", self::$noNewlineAtOEFid]]); + } + + if (!\count($toFind)) { + break; + } + } + } + } + + // write hunks to output buffer + + $cutOff = \max($this->commonLineThreshold, $this->contextLines); + $hunkCapture = false; + $sameCount = $toRange = $fromRange = 0; + $toStart = $fromStart = 1; + + 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 + ; + + $contextEndOffset = $i + $this->contextLines >= \count($diff) + ? \count($diff) - $i + : $this->contextLines + ; + + $this->writeHunk( + $diff, + $hunkCapture - $contextStartOffset, + $i - $cutOff + $contextEndOffset + 1, + $fromStart - $contextStartOffset, + $fromRange - $cutOff + $contextStartOffset + $contextEndOffset, + $toStart - $contextStartOffset, + $toRange - $cutOff + $contextStartOffset + $contextEndOffset, + $output + ); + + $fromStart += $fromRange; + $toStart += $toRange; + + $hunkCapture = false; + $sameCount = $toRange = $fromRange = 0; + } + + continue; + } + + $sameCount = 0; + + if ($entry[1] === self::$noNewlineAtOEFid) { + continue; + } + + $this->changed = true; + + if (false === $hunkCapture) { + $hunkCapture = $i; + } + + if (1 === $entry[1]) { // added + ++$toRange; + } + + if (2 === $entry[1]) { // removed + ++$fromRange; + } + } + + if (false !== $hunkCapture) { + $contextStartOffset = $hunkCapture - $this->contextLines < 0 + ? $hunkCapture + : $this->contextLines + ; + + $this->writeHunk( + $diff, + $hunkCapture - $contextStartOffset, + \count($diff), + $fromStart - $contextStartOffset, + $fromRange + $contextStartOffset, + $toStart - $contextStartOffset, + $toRange + $contextStartOffset, + $output + ); + } + } + + private function writeHunk( + array $diff, + $diffStartIndex, + $diffEndIndex, + $fromStart, + $fromRange, + $toStart, + $toRange, + $output + ) { + \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] === 1) { // added + $this->changed = true; + \fwrite($output, '+'.$diff[$i][0]); + } elseif ($diff[$i][1] === 2) { // removed + $this->changed = true; + \fwrite($output, '-'.$diff[$i][0]); + } elseif ($diff[$i][1] === 0) { // same + \fwrite($output, ' '.$diff[$i][0]); + } elseif ($diff[$i][1] === self::$noNewlineAtOEFid) { + $this->changed = true; + \fwrite($output, $diff[$i][0]); + } + } + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v1_4/Chunk.php b/lib/composer/php-cs-fixer/diff/src/v1_4/Chunk.php new file mode 100644 index 0000000000000000000000000000000000000000..b247e030244825591f207f9b6713ced8d93a4ddd --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v1_4/Chunk.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 PhpCsFixer\Diff\v1_4; + +class Chunk +{ + /** + * @var int + */ + private $start; + + /** + * @var int + */ + private $startRange; + + /** + * @var int + */ + private $end; + + /** + * @var int + */ + private $endRange; + + /** + * @var array + */ + private $lines; + + /** + * @param int $start + * @param int $startRange + * @param int $end + * @param int $endRange + * @param array $lines + */ + public function __construct($start = 0, $startRange = 1, $end = 0, $endRange = 1, array $lines = array()) + { + $this->start = (int) $start; + $this->startRange = (int) $startRange; + $this->end = (int) $end; + $this->endRange = (int) $endRange; + $this->lines = $lines; + } + + /** + * @return int + */ + public function getStart() + { + return $this->start; + } + + /** + * @return int + */ + public function getStartRange() + { + return $this->startRange; + } + + /** + * @return int + */ + public function getEnd() + { + return $this->end; + } + + /** + * @return int + */ + public function getEndRange() + { + return $this->endRange; + } + + /** + * @return array + */ + public function getLines() + { + return $this->lines; + } + + /** + * @param array $lines + */ + public function setLines(array $lines) + { + $this->lines = $lines; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v1_4/Diff.php b/lib/composer/php-cs-fixer/diff/src/v1_4/Diff.php new file mode 100644 index 0000000000000000000000000000000000000000..695a519b68ac5d7ab46cf23e9563b34bb381c003 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v1_4/Diff.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 PhpCsFixer\Diff\v1_4; + +class Diff +{ + /** + * @var string + */ + private $from; + + /** + * @var string + */ + private $to; + + /** + * @var Chunk[] + */ + private $chunks; + + /** + * @param string $from + * @param string $to + * @param Chunk[] $chunks + */ + public function __construct($from, $to, array $chunks = array()) + { + $this->from = $from; + $this->to = $to; + $this->chunks = $chunks; + } + + /** + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * @return string + */ + public function getTo() + { + return $this->to; + } + + /** + * @return Chunk[] + */ + public function getChunks() + { + return $this->chunks; + } + + /** + * @param Chunk[] $chunks + */ + public function setChunks(array $chunks) + { + $this->chunks = $chunks; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v1_4/Differ.php b/lib/composer/php-cs-fixer/diff/src/v1_4/Differ.php new file mode 100644 index 0000000000000000000000000000000000000000..b2015abf44ddfd79c33b911ff847143dc2b4f1c1 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v1_4/Differ.php @@ -0,0 +1,399 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PhpCsFixer\Diff\v1_4; + +use PhpCsFixer\Diff\v1_4\LCS\LongestCommonSubsequence; +use PhpCsFixer\Diff\v1_4\LCS\TimeEfficientImplementation; +use PhpCsFixer\Diff\v1_4\LCS\MemoryEfficientImplementation; + +/** + * Diff implementation. + */ +class Differ +{ + /** + * @var string + */ + private $header; + + /** + * @var bool + */ + private $showNonDiffLines; + + /** + * @param string $header + * @param bool $showNonDiffLines + */ + public function __construct($header = "--- Original\n+++ New\n", $showNonDiffLines = true) + { + $this->header = $header; + $this->showNonDiffLines = $showNonDiffLines; + } + + /** + * Returns the diff between two arrays or strings as string. + * + * @param array|string $from + * @param array|string $to + * @param LongestCommonSubsequence $lcs + * + * @return string + */ + public function diff($from, $to, LongestCommonSubsequence $lcs = null) + { + $from = $this->validateDiffInput($from); + $to = $this->validateDiffInput($to); + $diff = $this->diffToArray($from, $to, $lcs); + $old = $this->checkIfDiffInOld($diff); + $start = isset($old[0]) ? $old[0] : 0; + $end = \count($diff); + + if ($tmp = \array_search($end, $old)) { + $end = $tmp; + } + + return $this->getBuffer($diff, $old, $start, $end); + } + + /** + * Casts variable to string if it is not a string or array. + * + * @param mixed $input + * + * @return string + */ + private function validateDiffInput($input) + { + if (!\is_array($input) && !\is_string($input)) { + return (string) $input; + } + + return $input; + } + + /** + * Takes input of the diff array and returns the old array. + * Iterates through diff line by line, + * + * @param array $diff + * + * @return array + */ + private function checkIfDiffInOld(array $diff) + { + $inOld = false; + $i = 0; + $old = array(); + + foreach ($diff as $line) { + if ($line[1] === 0 /* OLD */) { + if ($inOld === false) { + $inOld = $i; + } + } elseif ($inOld !== false) { + if (($i - $inOld) > 5) { + $old[$inOld] = $i - 1; + } + + $inOld = false; + } + + ++$i; + } + + return $old; + } + + /** + * Generates buffer in string format, returning the patch. + * + * @param array $diff + * @param array $old + * @param int $start + * @param int $end + * + * @return string + */ + private function getBuffer(array $diff, array $old, $start, $end) + { + $buffer = $this->header; + + if (!isset($old[$start])) { + $buffer = $this->getDiffBufferElementNew($diff, $buffer, $start); + ++$start; + } + + for ($i = $start; $i < $end; $i++) { + if (isset($old[$i])) { + $i = $old[$i]; + $buffer = $this->getDiffBufferElementNew($diff, $buffer, $i); + } else { + $buffer = $this->getDiffBufferElement($diff, $buffer, $i); + } + } + + return $buffer; + } + + /** + * Gets individual buffer element. + * + * @param array $diff + * @param string $buffer + * @param int $diffIndex + * + * @return string + */ + private function getDiffBufferElement(array $diff, $buffer, $diffIndex) + { + if ($diff[$diffIndex][1] === 1 /* ADDED */) { + $buffer .= '+' . $diff[$diffIndex][0] . "\n"; + } elseif ($diff[$diffIndex][1] === 2 /* REMOVED */) { + $buffer .= '-' . $diff[$diffIndex][0] . "\n"; + } elseif ($this->showNonDiffLines === true) { + $buffer .= ' ' . $diff[$diffIndex][0] . "\n"; + } + + return $buffer; + } + + /** + * Gets individual buffer element with opening. + * + * @param array $diff + * @param string $buffer + * @param int $diffIndex + * + * @return string + */ + private function getDiffBufferElementNew(array $diff, $buffer, $diffIndex) + { + if ($this->showNonDiffLines === true) { + $buffer .= "@@ @@\n"; + } + + return $this->getDiffBufferElement($diff, $buffer, $diffIndex); + } + + /** + * 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 LongestCommonSubsequence $lcs + * + * @return array + */ + public function diffToArray($from, $to, LongestCommonSubsequence $lcs = null) + { + if (\is_string($from)) { + $fromMatches = $this->getNewLineMatches($from); + $from = $this->splitStringByLines($from); + } elseif (\is_array($from)) { + $fromMatches = array(); + } else { + throw new \InvalidArgumentException('"from" must be an array or string.'); + } + + if (\is_string($to)) { + $toMatches = $this->getNewLineMatches($to); + $to = $this->splitStringByLines($to); + } elseif (\is_array($to)) { + $toMatches = array(); + } else { + throw new \InvalidArgumentException('"to" must be an array or string.'); + } + + list($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 = array(); + + if ($this->detectUnmatchedLineEndings($fromMatches, $toMatches)) { + $diff[] = array( + '#Warnings contain different line endings!', + 0 + ); + } + + foreach ($start as $token) { + $diff[] = array($token, 0 /* OLD */); + } + + \reset($from); + \reset($to); + + foreach ($common as $token) { + while (($fromToken = \reset($from)) !== $token) { + $diff[] = array(\array_shift($from), 2 /* REMOVED */); + } + + while (($toToken = \reset($to)) !== $token) { + $diff[] = array(\array_shift($to), 1 /* ADDED */); + } + + $diff[] = array($token, 0 /* OLD */); + + \array_shift($from); + \array_shift($to); + } + + while (($token = \array_shift($from)) !== null) { + $diff[] = array($token, 2 /* REMOVED */); + } + + while (($token = \array_shift($to)) !== null) { + $diff[] = array($token, 1 /* ADDED */); + } + + foreach ($end as $token) { + $diff[] = array($token, 0 /* OLD */); + } + + return $diff; + } + + /** + * Get new strings denoting new lines from a given string. + * + * @param string $string + * + * @return array + */ + private function getNewLineMatches($string) + { + \preg_match_all('(\r\n|\r|\n)', $string, $stringMatches); + + return $stringMatches; + } + + /** + * Checks if input is string, if so it will split it line-by-line. + * + * @param string $input + * + * @return array + */ + private function splitStringByLines($input) + { + return \preg_split('(\r\n|\r|\n)', $input); + } + + /** + * @param array $from + * @param array $to + * + * @return LongestCommonSubsequence + */ + private function selectLcsImplementation(array $from, array $to) + { + // 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 MemoryEfficientImplementation; + } + + return new TimeEfficientImplementation; + } + + /** + * Calculates the estimated memory footprint for the DP-based method. + * + * @param array $from + * @param array $to + * + * @return int|float + */ + private function calculateEstimatedFootprint(array $from, array $to) + { + $itemSize = PHP_INT_SIZE === 4 ? 76 : 144; + + return $itemSize * \pow(\min(\count($from), \count($to)), 2); + } + + /** + * Returns true if line ends don't match on fromMatches and toMatches. + * + * @param array $fromMatches + * @param array $toMatches + * + * @return bool + */ + private function detectUnmatchedLineEndings(array $fromMatches, array $toMatches) + { + return isset($fromMatches[0], $toMatches[0]) && + \count($fromMatches[0]) === \count($toMatches[0]) && + $fromMatches[0] !== $toMatches[0]; + } + + /** + * @param array $from + * @param array $to + * + * @return array + */ + private static function getArrayDiffParted(array &$from, array &$to) + { + $start = array(); + $end = array(); + + \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 = array($fromK => $from[$fromK]) + $end; + unset($from[$fromK], $to[$toK]); + } while (true); + + return array($from, $to, $start, $end); + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v1_4/LCS/LongestCommonSubsequence.php b/lib/composer/php-cs-fixer/diff/src/v1_4/LCS/LongestCommonSubsequence.php new file mode 100644 index 0000000000000000000000000000000000000000..dad5a0cccbe23f96bcfb3da38621e041327622bd --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v1_4/LCS/LongestCommonSubsequence.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 PhpCsFixer\Diff\v1_4\LCS; + +/** + * Interface for implementations of longest common subsequence calculation. + */ +interface LongestCommonSubsequence +{ + /** + * Calculates the longest common subsequence of two arrays. + * + * @param array $from + * @param array $to + * + * @return array + */ + public function calculate(array $from, array $to); +} diff --git a/lib/composer/php-cs-fixer/diff/src/v1_4/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php b/lib/composer/php-cs-fixer/diff/src/v1_4/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php new file mode 100644 index 0000000000000000000000000000000000000000..668525252931920c8964c9b1860bc0a835099b7a --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v1_4/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.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 PhpCsFixer\Diff\v1_4\LCS; + +/** + * Memory-efficient implementation of longest common subsequence calculation. + */ +class MemoryEfficientImplementation implements LongestCommonSubsequence +{ + /** + * Calculates the longest common subsequence of two arrays. + * + * @param array $from + * @param array $to + * + * @return array + */ + public function calculate(array $from, array $to) + { + $cFrom = \count($from); + $cTo = \count($to); + + if ($cFrom === 0) { + return array(); + } + + if ($cFrom === 1) { + if (\in_array($from[0], $to, true)) { + return array($from[0]); + } + + return array(); + } + + $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) + ); + } + + /** + * @param array $from + * @param array $to + * + * @return array + */ + private function length(array $from, array $to) + { + $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 { + $current[$j + 1] = \max($current[$j], $prev[$j + 1]); + } + } + } + + return $current; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v1_4/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php b/lib/composer/php-cs-fixer/diff/src/v1_4/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php new file mode 100644 index 0000000000000000000000000000000000000000..4acd68eff35d57fdac092318fc328856546e98ec --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v1_4/LCS/TimeEfficientLongestCommonSubsequenceImplementation.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 PhpCsFixer\Diff\v1_4\LCS; + +/** + * Time-efficient implementation of longest common subsequence calculation. + */ +class TimeEfficientImplementation implements LongestCommonSubsequence +{ + /** + * Calculates the longest common subsequence of two arrays. + * + * @param array $from + * @param array $to + * + * @return array + */ + public function calculate(array $from, array $to) + { + $common = array(); + $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; + $matrix[$o] = \max( + $matrix[$o - 1], + $matrix[$o - $width], + $from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0 + ); + } + } + + $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/lib/composer/php-cs-fixer/diff/src/v1_4/Line.php b/lib/composer/php-cs-fixer/diff/src/v1_4/Line.php new file mode 100644 index 0000000000000000000000000000000000000000..1187bcde4972f6422860c12adb28bc77994dbef8 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v1_4/Line.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 PhpCsFixer\Diff\v1_4; + +class Line +{ + const ADDED = 1; + const REMOVED = 2; + const UNCHANGED = 3; + + /** + * @var int + */ + private $type; + + /** + * @var string + */ + private $content; + + /** + * @param int $type + * @param string $content + */ + public function __construct($type = self::UNCHANGED, $content = '') + { + $this->type = $type; + $this->content = $content; + } + + /** + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v1_4/Parser.php b/lib/composer/php-cs-fixer/diff/src/v1_4/Parser.php new file mode 100644 index 0000000000000000000000000000000000000000..6f8e75b5d74359cff93ad1b10397ab5e05947c9a --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v1_4/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 PhpCsFixer\Diff\v1_4; + +/** + * Unified diff parser. + */ +class Parser +{ + /** + * @param string $string + * + * @return Diff[] + */ + public function parse($string) + { + $lines = \preg_split('(\r\n|\r|\n)', $string); + + if (!empty($lines) && $lines[\count($lines) - 1] == '') { + \array_pop($lines); + } + + $lineCount = \count($lines); + $diffs = array(); + $diff = null; + $collected = array(); + + for ($i = 0; $i < $lineCount; ++$i) { + if (\preg_match('(^---\\s+(?P\\S+))', $lines[$i], $fromMatch) && + \preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $lines[$i + 1], $toMatch)) { + if ($diff !== null) { + $this->parseFileDiff($diff, $collected); + + $diffs[] = $diff; + $collected = array(); + } + + $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; + } + + /** + * @param Diff $diff + * @param array $lines + */ + private function parseFileDiff(Diff $diff, array $lines) + { + $chunks = array(); + $chunk = null; + + 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( + $match['start'], + isset($match['startrange']) ? \max(1, $match['startrange']) : 1, + $match['end'], + isset($match['endrange']) ? \max(1, $match['endrange']) : 1 + ); + + $chunks[] = $chunk; + $diffLines = array(); + + 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/lib/composer/php-cs-fixer/diff/src/v2_0/Chunk.php b/lib/composer/php-cs-fixer/diff/src/v2_0/Chunk.php new file mode 100644 index 0000000000000000000000000000000000000000..b80ddfcf24ec8490f1f4b3ab6cec0a73e0ef00cc --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/Chunk.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 PhpCsFixer\Diff\v2_0; + +final class Chunk +{ + /** + * @var int + */ + private $start; + + /** + * @var int + */ + private $startRange; + + /** + * @var int + */ + private $end; + + /** + * @var int + */ + private $endRange; + + /** + * @var array + */ + private $lines; + + public function __construct($start = 0, $startRange = 1, $end = 0, $endRange = 1, array $lines = []) + { + $this->start = $start; + $this->startRange = $startRange; + $this->end = $end; + $this->endRange = $endRange; + $this->lines = $lines; + } + + public function getStart() + { + return $this->start; + } + + public function getStartRange() + { + return $this->startRange; + } + + public function getEnd() + { + return $this->end; + } + + public function getEndRange() + { + return $this->endRange; + } + + public function getLines() + { + return $this->lines; + } + + public function setLines(array $lines) + { + $this->lines = $lines; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v2_0/Diff.php b/lib/composer/php-cs-fixer/diff/src/v2_0/Diff.php new file mode 100644 index 0000000000000000000000000000000000000000..e89b8e9d5db81c46794301899dc7c0853984d80a --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/Diff.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 PhpCsFixer\Diff\v2_0; + +final class Diff +{ + /** + * @var string + */ + private $from; + + /** + * @var string + */ + private $to; + + /** + * @var Chunk[] + */ + private $chunks; + + /** + * @param string $from + * @param string $to + * @param Chunk[] $chunks + */ + public function __construct($from, $to, array $chunks = []) + { + $this->from = $from; + $this->to = $to; + $this->chunks = $chunks; + } + + public function getFrom() + { + return $this->from; + } + + public function getTo() + { + return $this->to; + } + + /** + * @return Chunk[] + */ + public function getChunks() + { + return $this->chunks; + } + + /** + * @param Chunk[] $chunks + */ + public function setChunks(array $chunks) + { + $this->chunks = $chunks; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v2_0/Differ.php b/lib/composer/php-cs-fixer/diff/src/v2_0/Differ.php new file mode 100644 index 0000000000000000000000000000000000000000..a8f7dba73af4df52a36d8b289b0855c5cea72aa9 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/Differ.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 PhpCsFixer\Diff\v2_0; + +use PhpCsFixer\Diff\v2_0\Output\DiffOutputBuilderInterface; +use PhpCsFixer\Diff\v2_0\Output\UnifiedDiffOutputBuilder; + +/** + * Diff implementation. + */ +final class Differ +{ + /** + * @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 + * @param LongestCommonSubsequenceCalculator|null $lcs + * + * @return string + */ + public function diff($from, $to, LongestCommonSubsequenceCalculator $lcs = null) + { + $from = $this->validateDiffInput($from); + $to = $this->validateDiffInput($to); + $diff = $this->diffToArray($from, $to, $lcs); + + return $this->outputBuilder->getDiff($diff); + } + + /** + * Casts variable to string if it is not a string or array. + * + * @param mixed $input + * + * @return string + */ + private function validateDiffInput($input) + { + if (!\is_array($input) && !\is_string($input)) { + return (string) $input; + } + + return $input; + } + + /** + * 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 + * + * @return array + */ + public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs = null) + { + 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.'); + } + + list($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, 0 /* OLD */]; + } + + \reset($from); + \reset($to); + + foreach ($common as $token) { + while (($fromToken = \reset($from)) !== $token) { + $diff[] = [\array_shift($from), 2 /* REMOVED */]; + } + + while (($toToken = \reset($to)) !== $token) { + $diff[] = [\array_shift($to), 1 /* ADDED */]; + } + + $diff[] = [$token, 0 /* OLD */]; + + \array_shift($from); + \array_shift($to); + } + + while (($token = \array_shift($from)) !== null) { + $diff[] = [$token, 2 /* REMOVED */]; + } + + while (($token = \array_shift($to)) !== null) { + $diff[] = [$token, 1 /* ADDED */]; + } + + foreach ($end as $token) { + $diff[] = [$token, 0 /* OLD */]; + } + + if ($this->detectUnmatchedLineEndings($diff)) { + \array_unshift($diff, ["#Warnings contain different line endings!\n", 3]); + } + + return $diff; + } + + /** + * Checks if input is string, if so it will split it line-by-line. + * + * @param string $input + * + * @return array + */ + private function splitStringByLines($input) + { + return \preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + } + + /** + * @param array $from + * @param array $to + * + * @return LongestCommonSubsequenceCalculator + */ + private function selectLcsImplementation(array $from, array $to) + { + // 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. + * + * @param array $from + * @param array $to + * + * @return int|float + */ + 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. + * + * @param array $diff + * + * @return bool + */ + private function detectUnmatchedLineEndings(array $diff) + { + $newLineBreaks = ['' => true]; + $oldLineBreaks = ['' => true]; + + foreach ($diff as $entry) { + if (0 === $entry[1]) { /* OLD */ + $ln = $this->getLinebreak($entry[0]); + $oldLineBreaks[$ln] = true; + $newLineBreaks[$ln] = true; + } elseif (1 === $entry[1]) { /* ADDED */ + $newLineBreaks[$this->getLinebreak($entry[0])] = true; + } elseif (2 === $entry[1]) { /* REMOVED */ + $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) + { + 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) + { + $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/lib/composer/php-cs-fixer/diff/src/v2_0/Exception/Exception.php b/lib/composer/php-cs-fixer/diff/src/v2_0/Exception/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..b7e9e926f3ae457243086825631fa7ffe58d1978 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/Exception/Exception.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PhpCsFixer\Diff\v2_0; + +interface Exception +{ +} diff --git a/lib/composer/php-cs-fixer/diff/src/v2_0/Exception/InvalidArgumentException.php b/lib/composer/php-cs-fixer/diff/src/v2_0/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..e1a0c0b53f4676c3ad06494ccc7b309fecc4ac75 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/Exception/InvalidArgumentException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PhpCsFixer\Diff\v2_0; + +class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/lib/composer/php-cs-fixer/diff/src/v2_0/Line.php b/lib/composer/php-cs-fixer/diff/src/v2_0/Line.php new file mode 100644 index 0000000000000000000000000000000000000000..75d5ec85a35db57997ccc9fbabd6480257b150c0 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/Line.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 PhpCsFixer\Diff\v2_0; + +final class Line +{ + const ADDED = 1; + const REMOVED = 2; + const UNCHANGED = 3; + + /** + * @var int + */ + private $type; + + /** + * @var string + */ + private $content; + + public function __construct($type = self::UNCHANGED, $content = '') + { + $this->type = $type; + $this->content = $content; + } + + public function getContent() + { + return $this->content; + } + + public function getType() + { + return $this->type; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v2_0/LongestCommonSubsequenceCalculator.php b/lib/composer/php-cs-fixer/diff/src/v2_0/LongestCommonSubsequenceCalculator.php new file mode 100644 index 0000000000000000000000000000000000000000..25eda02a1fbe230b400289d5d6adb79ce3d652a3 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/LongestCommonSubsequenceCalculator.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 PhpCsFixer\Diff\v2_0; + +interface LongestCommonSubsequenceCalculator +{ + /** + * Calculates the longest common subsequence of two arrays. + * + * @param array $from + * @param array $to + * + * @return array + */ + public function calculate(array $from, array $to); +} diff --git a/lib/composer/php-cs-fixer/diff/src/v2_0/MemoryEfficientLongestCommonSubsequenceCalculator.php b/lib/composer/php-cs-fixer/diff/src/v2_0/MemoryEfficientLongestCommonSubsequenceCalculator.php new file mode 100644 index 0000000000000000000000000000000000000000..e6ce284fe4927102a4ea87b6fbb041629ce9ca0c --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/MemoryEfficientLongestCommonSubsequenceCalculator.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 PhpCsFixer\Diff\v2_0; + +final class MemoryEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator +{ + /** + * {@inheritdoc} + */ + public function calculate(array $from, array $to) + { + $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) + { + $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 { + $current[$j + 1] = \max($current[$j], $prev[$j + 1]); + } + } + } + + return $current; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v2_0/Output/AbstractChunkOutputBuilder.php b/lib/composer/php-cs-fixer/diff/src/v2_0/Output/AbstractChunkOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..d7d78d7cbc8b51ab92b52ab7281b36d8b6d8e7ef --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/Output/AbstractChunkOutputBuilder.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 PhpCsFixer\Diff\v2_0\Output; + +abstract class AbstractChunkOutputBuilder implements DiffOutputBuilderInterface +{ + /** + * Takes input of the diff array and returns the common parts. + * Iterates through diff line by line. + * + * @param array $diff + * @param int $lineThreshold + * + * @return array + */ + protected function getCommonChunks(array $diff, $lineThreshold = 5) + { + $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/lib/composer/php-cs-fixer/diff/src/v2_0/Output/DiffOnlyOutputBuilder.php b/lib/composer/php-cs-fixer/diff/src/v2_0/Output/DiffOnlyOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..51cb2deccb15e45328901869ded2626ca94ef13a --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/Output/DiffOnlyOutputBuilder.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 PhpCsFixer\Diff\v2_0\Output; + +/** + * 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($header = "--- Original\n+++ New\n") + { + $this->header = $header; + } + + public function getDiff(array $diff) + { + $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] === 1 /* ADDED */) { + \fwrite($buffer, '+' . $diffEntry[0]); + } elseif ($diffEntry[1] === 2 /* REMOVED */) { + \fwrite($buffer, '-' . $diffEntry[0]); + } elseif ($diffEntry[1] === 3 /* 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/lib/composer/php-cs-fixer/diff/src/v2_0/Output/DiffOutputBuilderInterface.php b/lib/composer/php-cs-fixer/diff/src/v2_0/Output/DiffOutputBuilderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b52b8ac8da2d4c2ced23fc3aec35891a1674311f --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/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 PhpCsFixer\Diff\v2_0\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); +} diff --git a/lib/composer/php-cs-fixer/diff/src/v2_0/Output/UnifiedDiffOutputBuilder.php b/lib/composer/php-cs-fixer/diff/src/v2_0/Output/UnifiedDiffOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..e76186515fc3cafc8540b50cb688a6f45bf07e00 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/Output/UnifiedDiffOutputBuilder.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PhpCsFixer\Diff\v2_0\Output; + +/** + * Builds a diff string representation in unified diff format in chunks. + */ +final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder +{ + /** + * @var string + */ + private $header; + + /** + * @var bool + */ + private $addLineNumbers; + + public function __construct($header = "--- Original\n+++ New\n", $addLineNumbers = false) + { + $this->header = $header; + $this->addLineNumbers = $addLineNumbers; + } + + public function getDiff(array $diff) + { + $buffer = \fopen('php://memory', 'r+b'); + + if ('' !== $this->header) { + \fwrite($buffer, $this->header); + if ("\n" !== \substr($this->header, -1, 1)) { + \fwrite($buffer, "\n"); + } + } + + $this->writeDiffChunked($buffer, $diff, $this->getCommonChunks($diff)); + + $diff = \stream_get_contents($buffer, -1, 0); + + \fclose($buffer); + + return $diff; + } + + // `old` is an array with key => value pairs . Each pair represents a start and end index of `diff` + // of a list of elements all containing `same` (0) entries. + private function writeDiffChunked($output, array $diff, array $old) + { + $upperLimit = \count($diff); + $start = 0; + $fromStart = 0; + $toStart = 0; + + if (\count($old)) { // no common parts, list all diff entries + \reset($old); + + // iterate the diff, go from chunk to chunk skipping common chunk of lines between those + do { + $commonStart = \key($old); + $commonEnd = \current($old); + + if ($commonStart !== $start) { + list($fromRange, $toRange) = $this->getChunkRange($diff, $start, $commonStart); + $this->writeChunk($output, $diff, $start, $commonStart, $fromStart, $fromRange, $toStart, $toRange); + + $fromStart += $fromRange; + $toStart += $toRange; + } + + $start = $commonEnd + 1; + $commonLength = $commonEnd - $commonStart + 1; // calculate number of non-change lines in the common part + $fromStart += $commonLength; + $toStart += $commonLength; + } while (false !== \next($old)); + + \end($old); // short cut for finding possible last `change entry` + $tmp = \key($old); + \reset($old); + if ($old[$tmp] === $upperLimit - 1) { + $upperLimit = $tmp; + } + } + + if ($start < $upperLimit - 1) { // check for trailing (non) diff entries + do { + --$upperLimit; + } while (isset($diff[$upperLimit][1]) && $diff[$upperLimit][1] === 0); + ++$upperLimit; + + list($fromRange, $toRange) = $this->getChunkRange($diff, $start, $upperLimit); + $this->writeChunk($output, $diff, $start, $upperLimit, $fromStart, $fromRange, $toStart, $toRange); + } + } + + private function writeChunk( + $output, + array $diff, + $diffStartIndex, + $diffEndIndex, + $fromStart, + $fromRange, + $toStart, + $toRange + ) { + if ($this->addLineNumbers) { + \fwrite($output, '@@ -' . (1 + $fromStart)); + + if ($fromRange !== 1) { + \fwrite($output, ',' . $fromRange); + } + + \fwrite($output, ' +' . (1 + $toStart)); + if ($toRange !== 1) { + \fwrite($output, ',' . $toRange); + } + + \fwrite($output, " @@\n"); + } else { + \fwrite($output, "@@ @@\n"); + } + + for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { + if ($diff[$i][1] === 1 /* ADDED */) { + \fwrite($output, '+' . $diff[$i][0]); + } elseif ($diff[$i][1] === 2 /* REMOVED */) { + \fwrite($output, '-' . $diff[$i][0]); + } else { /* Not changed (old) 0 or Warning 3 */ + \fwrite($output, ' ' . $diff[$i][0]); + } + + $lc = \substr($diff[$i][0], -1); + if ($lc !== "\n" && $lc !== "\r") { + \fwrite($output, "\n"); // \No newline at end of file + } + } + } + + private function getChunkRange(array $diff, $diffStartIndex, $diffEndIndex) + { + $toRange = 0; + $fromRange = 0; + + for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { + if ($diff[$i][1] === 1) { // added + ++$toRange; + } elseif ($diff[$i][1] === 2) { // removed + ++$fromRange; + } elseif ($diff[$i][1] === 0) { // same + ++$fromRange; + ++$toRange; + } + } + + return [$fromRange, $toRange]; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v2_0/Parser.php b/lib/composer/php-cs-fixer/diff/src/v2_0/Parser.php new file mode 100644 index 0000000000000000000000000000000000000000..9c0ee839cdb6281fec5a1d63b1c51c9a1f3ed10c --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/Parser.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 PhpCsFixer\Diff\v2_0; + +/** + * Unified diff parser. + */ +final class Parser +{ + /** + * @param string $string + * + * @return Diff[] + */ + public function parse($string) + { + $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('(^---\\s+(?P\\S+))', $lines[$i], $fromMatch) && + \preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $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) + { + $chunks = []; + $chunk = null; + + 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/lib/composer/php-cs-fixer/diff/src/v2_0/TimeEfficientLongestCommonSubsequenceCalculator.php b/lib/composer/php-cs-fixer/diff/src/v2_0/TimeEfficientLongestCommonSubsequenceCalculator.php new file mode 100644 index 0000000000000000000000000000000000000000..51489afa11cad379766d8043e95f5d056d58a047 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v2_0/TimeEfficientLongestCommonSubsequenceCalculator.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 PhpCsFixer\Diff\v2_0; + +final class TimeEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator +{ + /** + * {@inheritdoc} + */ + public function calculate(array $from, array $to) + { + $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; + $matrix[$o] = \max( + $matrix[$o - 1], + $matrix[$o - $width], + $from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0 + ); + } + } + + $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/lib/composer/php-cs-fixer/diff/src/v3_0/Chunk.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Chunk.php new file mode 100644 index 0000000000000000000000000000000000000000..6b633c176998bb99c248da5c725c4ce55b5d2732 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Chunk.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 PhpCsFixer\Diff\v3_0; + +final class Chunk +{ + /** + * @var int + */ + private $start; + + /** + * @var int + */ + private $startRange; + + /** + * @var int + */ + private $end; + + /** + * @var int + */ + private $endRange; + + /** + * @var array + */ + private $lines; + + public function __construct($start = 0, $startRange = 1, $end = 0, $endRange = 1, array $lines = []) + { + $this->start = $start; + $this->startRange = $startRange; + $this->end = $end; + $this->endRange = $endRange; + $this->lines = $lines; + } + + public function getStart() + { + return $this->start; + } + + public function getStartRange() + { + return $this->startRange; + } + + public function getEnd() + { + return $this->end; + } + + public function getEndRange() + { + return $this->endRange; + } + + public function getLines() + { + return $this->lines; + } + + public function setLines(array $lines) + { + $this->lines = $lines; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v3_0/Diff.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Diff.php new file mode 100644 index 0000000000000000000000000000000000000000..9f537ec9286b402dd405a32a6bb63337141975b8 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Diff.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 PhpCsFixer\Diff\v3_0; + +final class Diff +{ + /** + * @var string + */ + private $from; + + /** + * @var string + */ + private $to; + + /** + * @var Chunk[] + */ + private $chunks; + + /** + * @param string $from + * @param string $to + * @param Chunk[] $chunks + */ + public function __construct($from, $to, array $chunks = []) + { + $this->from = $from; + $this->to = $to; + $this->chunks = $chunks; + } + + public function getFrom() + { + return $this->from; + } + + public function getTo() + { + return $this->to; + } + + /** + * @return Chunk[] + */ + public function getChunks() + { + return $this->chunks; + } + + /** + * @param Chunk[] $chunks + */ + public function setChunks(array $chunks) + { + $this->chunks = $chunks; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v3_0/Differ.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Differ.php new file mode 100644 index 0000000000000000000000000000000000000000..42315f5a0d934d1f1d0ae70a1e0c7e74bff83742 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Differ.php @@ -0,0 +1,329 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PhpCsFixer\Diff\v3_0; + +use PhpCsFixer\Diff\v3_0\Output\DiffOutputBuilderInterface; +use PhpCsFixer\Diff\v3_0\Output\UnifiedDiffOutputBuilder; + +/** + * Diff implementation. + */ +final class Differ +{ + const OLD = 0; + const ADDED = 1; + const REMOVED = 2; + const DIFF_LINE_END_WARNING = 3; + 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 + * @param null|LongestCommonSubsequenceCalculator $lcs + * + * @return string + */ + public function diff($from, $to, LongestCommonSubsequenceCalculator $lcs = null) + { + $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 + * + * @return array + */ + public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs = null) + { + 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.'); + } + + list($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, ["#Warnings contain different line endings!\n", self::DIFF_LINE_END_WARNING]); + } + + return $diff; + } + + /** + * Casts variable to string if it is not a string or array. + * + * @param mixed $input + * + * @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. + * + * @param string $input + * + * @return array + */ + private function splitStringByLines($input) + { + return \preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + } + + /** + * @param array $from + * @param array $to + * + * @return LongestCommonSubsequenceCalculator + */ + private function selectLcsImplementation(array $from, array $to) + { + // 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. + * + * @param array $from + * @param array $to + * + * @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. + * + * @param array $diff + * + * @return bool + */ + private function detectUnmatchedLineEndings(array $diff) + { + $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) + { + 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) + { + $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/lib/composer/php-cs-fixer/diff/src/v3_0/Exception/ConfigurationException.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Exception/ConfigurationException.php new file mode 100644 index 0000000000000000000000000000000000000000..8892d8f9a963552103f1b149f143163fcaf6b337 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Exception/ConfigurationException.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 PhpCsFixer\Diff\v3_0; + +final class ConfigurationException extends InvalidArgumentException +{ + /** + * @param string $option + * @param string $expected + * @param mixed $value + * @param int $code + * @param null|\Exception $previous + */ + public function __construct( + $option, + $expected, + $value, + $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/lib/composer/php-cs-fixer/diff/src/v3_0/Exception/Exception.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Exception/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..7a391db78d35222c395b2075ee9e7f6030a574e2 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Exception/Exception.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PhpCsFixer\Diff\v3_0; + +interface Exception +{ +} diff --git a/lib/composer/php-cs-fixer/diff/src/v3_0/Exception/InvalidArgumentException.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..5085482478bb2976f44f5378de2bfc9d35728fe7 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Exception/InvalidArgumentException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PhpCsFixer\Diff\v3_0; + +class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/lib/composer/php-cs-fixer/diff/src/v3_0/Line.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Line.php new file mode 100644 index 0000000000000000000000000000000000000000..07be8805c14f27fa50dce77d6cfac27c5e127b30 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Line.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 PhpCsFixer\Diff\v3_0; + +final class Line +{ + const ADDED = 1; + const REMOVED = 2; + const UNCHANGED = 3; + + /** + * @var int + */ + private $type; + + /** + * @var string + */ + private $content; + + public function __construct($type = self::UNCHANGED, $content = '') + { + $this->type = $type; + $this->content = $content; + } + + public function getContent() + { + return $this->content; + } + + public function getType() + { + return $this->type; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v3_0/LongestCommonSubsequenceCalculator.php b/lib/composer/php-cs-fixer/diff/src/v3_0/LongestCommonSubsequenceCalculator.php new file mode 100644 index 0000000000000000000000000000000000000000..be5ed5a1e4aa1272bbc208f8f2795a7807672d3c --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/LongestCommonSubsequenceCalculator.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 PhpCsFixer\Diff\v3_0; + +interface LongestCommonSubsequenceCalculator +{ + /** + * Calculates the longest common subsequence of two arrays. + * + * @param array $from + * @param array $to + * + * @return array + */ + public function calculate(array $from, array $to); +} diff --git a/lib/composer/php-cs-fixer/diff/src/v3_0/MemoryEfficientLongestCommonSubsequenceCalculator.php b/lib/composer/php-cs-fixer/diff/src/v3_0/MemoryEfficientLongestCommonSubsequenceCalculator.php new file mode 100644 index 0000000000000000000000000000000000000000..85a1c4e6d20856a408d68844098882a46808ecc5 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/MemoryEfficientLongestCommonSubsequenceCalculator.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 PhpCsFixer\Diff\v3_0; + +final class MemoryEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator +{ + /** + * {@inheritdoc} + */ + public function calculate(array $from, array $to) + { + $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) + { + $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 { + $current[$j + 1] = \max($current[$j], $prev[$j + 1]); + } + } + } + + return $current; + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v3_0/Output/AbstractChunkOutputBuilder.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Output/AbstractChunkOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..ecf7d321fac0c6dbc4d93e11891486ad024d837e --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Output/AbstractChunkOutputBuilder.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 PhpCsFixer\Diff\v3_0\Output; + +abstract class AbstractChunkOutputBuilder implements DiffOutputBuilderInterface +{ + /** + * Takes input of the diff array and returns the common parts. + * Iterates through diff line by line. + * + * @param array $diff + * @param int $lineThreshold + * + * @return array + */ + protected function getCommonChunks(array $diff, $lineThreshold = 5) + { + $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/lib/composer/php-cs-fixer/diff/src/v3_0/Output/DiffOnlyOutputBuilder.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Output/DiffOnlyOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..0f3b81f52413c5029ad68c84ec14f13ae1c67c9e --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Output/DiffOnlyOutputBuilder.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 PhpCsFixer\Diff\v3_0\Output; + +use PhpCsFixer\Diff\v3_0\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($header = "--- Original\n+++ New\n") + { + $this->header = $header; + } + + public function getDiff(array $diff) + { + $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/lib/composer/php-cs-fixer/diff/src/v3_0/Output/DiffOutputBuilderInterface.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Output/DiffOutputBuilderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..ae690e6ad6441d60aca60ade0bfcae93785dd93e --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Output/DiffOutputBuilderInterface.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 PhpCsFixer\Diff\v3_0\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); +} diff --git a/lib/composer/php-cs-fixer/diff/src/v3_0/Output/StrictUnifiedDiffOutputBuilder.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Output/StrictUnifiedDiffOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..49faa8ad27f2dbdd0fc74c0899c689c5ab451e27 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Output/StrictUnifiedDiffOutputBuilder.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 PhpCsFixer\Diff\v3_0\Output; + +use PhpCsFixer\Diff\v3_0\ConfigurationException; +use PhpCsFixer\Diff\v3_0\Differ; + +/** + * Strict Unified diff output builder. + * + * Generates (strict) Unified diff's (unidiffs) with hunks. + */ +final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface +{ + /** + * @var bool + */ + private $changed; + + /** + * @var bool + */ + private $collapseRanges; + + /** + * @var int >= 0 + */ + private $commonLineThreshold; + + /** + * @var string + */ + private $header; + + /** + * @var int >= 0 + */ + private $contextLines; + + 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, + ]; + + 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']); + } + + foreach (['fromFile', 'toFile'] as $option) { + if (!\is_string($options[$option])) { + throw new ConfigurationException($option, 'a string', $options[$option]); + } + } + + foreach (['fromFileDate', 'toFileDate'] as $option) { + if (null !== $options[$option] && !\is_string($options[$option])) { + throw new ConfigurationException($option, 'a string or ', $options[$option]); + } + } + + $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) + { + 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) + { + // 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; + + 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, + $diffStartIndex, + $diffEndIndex, + $fromStart, + $fromRange, + $toStart, + $toRange, + $output + ) { + \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 + //} + } + } +} diff --git a/lib/composer/php-cs-fixer/diff/src/v3_0/Output/UnifiedDiffOutputBuilder.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Output/UnifiedDiffOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..5e7276ab477ee7b26280ca8243f483223ea6590c --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Output/UnifiedDiffOutputBuilder.php @@ -0,0 +1,259 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PhpCsFixer\Diff\v3_0\Output; + +use PhpCsFixer\Diff\v3_0\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($header = "--- Original\n+++ New\n", $addLineNumbers = false) + { + $this->header = $header; + $this->addLineNumbers = $addLineNumbers; + } + + public function getDiff(array $diff) + { + $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 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) + { + // 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; + + 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, + $diffStartIndex, + $diffEndIndex, + $fromStart, + $fromRange, + $toStart, + $toRange, + $output + ) { + 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/lib/composer/php-cs-fixer/diff/src/v3_0/Parser.php b/lib/composer/php-cs-fixer/diff/src/v3_0/Parser.php new file mode 100644 index 0000000000000000000000000000000000000000..aa7175a393a0e175e5c22e65e80a06b94f54ce38 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/Parser.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 PhpCsFixer\Diff\v3_0; + +/** + * Unified diff parser. + */ +final class Parser +{ + /** + * @param string $string + * + * @return Diff[] + */ + public function parse($string) + { + $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('(^---\\s+(?P\\S+))', $lines[$i], $fromMatch) && + \preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $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) + { + $chunks = []; + $chunk = null; + + 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/lib/composer/php-cs-fixer/diff/src/v3_0/TimeEfficientLongestCommonSubsequenceCalculator.php b/lib/composer/php-cs-fixer/diff/src/v3_0/TimeEfficientLongestCommonSubsequenceCalculator.php new file mode 100644 index 0000000000000000000000000000000000000000..644968ac354a9a9161d9e24d229cd65f12c240a5 --- /dev/null +++ b/lib/composer/php-cs-fixer/diff/src/v3_0/TimeEfficientLongestCommonSubsequenceCalculator.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 PhpCsFixer\Diff\v3_0; + +final class TimeEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator +{ + /** + * {@inheritdoc} + */ + public function calculate(array $from, array $to) + { + $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; + $matrix[$o] = \max( + $matrix[$o - 1], + $matrix[$o - $width], + $from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0 + ); + } + } + + $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/lib/composer/phpdocumentor/reflection-common/.github/dependabot.yml b/lib/composer/phpdocumentor/reflection-common/.github/dependabot.yml new file mode 100644 index 0000000000000000000000000000000000000000..c630ffa6b3f0586c39cd467330f7578bfbba94ef --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-common/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: composer + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/lib/composer/phpdocumentor/reflection-common/.github/workflows/push.yml b/lib/composer/phpdocumentor/reflection-common/.github/workflows/push.yml new file mode 100644 index 0000000000000000000000000000000000000000..484410e9ac21815947bd23631a336927efd14746 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-common/.github/workflows/push.yml @@ -0,0 +1,223 @@ +on: + push: + branches: + - 2.x + pull_request: +name: Qa workflow +jobs: + setup: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Restore/cache vendor folder + uses: actions/cache@v1 + with: + path: vendor + key: all-build-${{ hashFiles('**/composer.lock') }} + restore-keys: | + all-build-${{ hashFiles('**/composer.lock') }} + all-build- + + - name: Restore/cache tools folder + uses: actions/cache@v1 + with: + path: tools + key: all-tools-${{ github.sha }} + restore-keys: | + all-tools-${{ github.sha }}- + all-tools- + + - name: composer + uses: docker://composer + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: install --no-interaction --prefer-dist --optimize-autoloader + + - name: Install phive + run: make install-phive + + - name: Install PHAR dependencies + run: tools/phive.phar --no-progress install --copy --trust-gpg-keys 4AA394086372C20A,8A03EA3B385DBAA1 --force-accept-unsigned + + phpunit-with-coverage: + runs-on: ubuntu-latest + name: Unit tests + needs: setup + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.2 + ini-values: memory_limit=2G, display_errors=On, error_reporting=-1 + coverage: pcov + + - name: Restore/cache tools folder + uses: actions/cache@v1 + with: + path: tools + key: all-tools-${{ github.sha }} + restore-keys: | + all-tools-${{ github.sha }}- + all-tools- + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ubuntu-latest-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ubuntu-latest-composer- + + - name: Install Composer dependencies + run: | + composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader + + - name: Run PHPUnit + run: php tools/phpunit + + phpunit: + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: + - ubuntu-latest + - windows-latest + - macOS-latest + php-versions: ['7.2', '7.3', '7.4', '8.0'] + name: Unit tests for PHP version ${{ matrix.php-versions }} on ${{ matrix.operating-system }} + needs: + - setup + - phpunit-with-coverage + steps: + - uses: actions/checkout@v2 + + - name: Restore/cache tools folder + uses: actions/cache@v1 + with: + path: tools + key: all-tools-${{ github.sha }} + restore-keys: | + all-tools-${{ github.sha }}- + all-tools- + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + ini-values: memory_limit=2G, display_errors=On, error_reporting=-1 + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: | + composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader + + - name: Run PHPUnit + continue-on-error: true + run: php tools/phpunit + + codestyle: + runs-on: ubuntu-latest + needs: [setup, phpunit] + steps: + - uses: actions/checkout@v2 + - name: Restore/cache vendor folder + uses: actions/cache@v1 + with: + path: vendor + key: all-build-${{ hashFiles('**/composer.lock') }} + restore-keys: | + all-build-${{ hashFiles('**/composer.lock') }} + all-build- + - name: Code style check + uses: phpDocumentor/coding-standard@latest + with: + args: -s + + phpstan: + runs-on: ubuntu-latest + needs: [setup, phpunit] + steps: + - uses: actions/checkout@v2 + - name: Restore/cache vendor folder + uses: actions/cache@v1 + with: + path: vendor + key: all-build-${{ hashFiles('**/composer.lock') }} + restore-keys: | + all-build-${{ hashFiles('**/composer.lock') }} + all-build- + - name: PHPStan + uses: phpDocumentor/phpstan-ga@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: analyse src --configuration phpstan.neon + + psalm: + runs-on: ubuntu-latest + needs: [setup, phpunit] + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.2 + ini-values: memory_limit=2G, display_errors=On, error_reporting=-1 + tools: psalm + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: | + composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader + + - name: Psalm + run: psalm --output-format=github + + bc_check: + name: BC Check + runs-on: ubuntu-latest + needs: [setup, phpunit] + steps: + - uses: actions/checkout@v2 + - name: fetch tags + run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - name: Restore/cache vendor folder + uses: actions/cache@v1 + with: + path: vendor + key: all-build-${{ hashFiles('**/composer.lock') }} + restore-keys: | + all-build-${{ hashFiles('**/composer.lock') }} + all-build- + - name: Roave BC Check + uses: docker://nyholm/roave-bc-check-ga diff --git a/lib/composer/phpdocumentor/reflection-common/LICENSE b/lib/composer/phpdocumentor/reflection-common/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..ed6926c1ee795441d5e57d3985e644e5f6028d89 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-common/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 phpDocumentor + +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/lib/composer/phpdocumentor/reflection-common/README.md b/lib/composer/phpdocumentor/reflection-common/README.md new file mode 100644 index 0000000000000000000000000000000000000000..70f830dc71dff125335730d2c85163f14b6f2278 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-common/README.md @@ -0,0 +1,11 @@ +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +![Qa workflow](https://github.com/phpDocumentor/ReflectionCommon/workflows/Qa%20workflow/badge.svg) +[![Coveralls Coverage](https://img.shields.io/coveralls/github/phpDocumentor/ReflectionCommon.svg)](https://coveralls.io/github/phpDocumentor/ReflectionCommon?branch=master) +[![Scrutinizer Code Coverage](https://img.shields.io/scrutinizer/coverage/g/phpDocumentor/ReflectionCommon.svg)](https://scrutinizer-ci.com/g/phpDocumentor/ReflectionCommon/?branch=master) +[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/phpDocumentor/ReflectionCommon.svg)](https://scrutinizer-ci.com/g/phpDocumentor/ReflectionCommon/?branch=master) +[![Stable Version](https://img.shields.io/packagist/v/phpDocumentor/Reflection-Common.svg)](https://packagist.org/packages/phpDocumentor/Reflection-Common) +[![Unstable Version](https://img.shields.io/packagist/vpre/phpDocumentor/Reflection-Common.svg)](https://packagist.org/packages/phpDocumentor/Reflection-Common) + + +ReflectionCommon +================ diff --git a/lib/composer/phpdocumentor/reflection-common/composer.json b/lib/composer/phpdocumentor/reflection-common/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..4d128b49a9e26af95feb9fe3d229582c270fa61d --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-common/composer.json @@ -0,0 +1,28 @@ +{ + "name": "phpdocumentor/reflection-common", + "keywords": ["phpdoc", "phpDocumentor", "reflection", "static analysis", "FQSEN"], + "homepage": "http://www.phpdoc.org", + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "license": "MIT", + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "require": { + "php": "^7.2 || ^8.0" + }, + "autoload" : { + "psr-4" : { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "require-dev": { + }, + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + } +} diff --git a/lib/composer/phpdocumentor/reflection-common/src/Element.php b/lib/composer/phpdocumentor/reflection-common/src/Element.php new file mode 100644 index 0000000000000000000000000000000000000000..8923e4fb01d315aeb336fe32f3d08bba9644d36e --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-common/src/Element.php @@ -0,0 +1,30 @@ +fqsen = $fqsen; + + if (isset($matches[2])) { + $this->name = $matches[2]; + } else { + $matches = explode('\\', $fqsen); + $name = end($matches); + assert(is_string($name)); + $this->name = trim($name, '()'); + } + } + + /** + * converts this class to string. + */ + public function __toString() : string + { + return $this->fqsen; + } + + /** + * Returns the name of the element without path. + */ + public function getName() : string + { + return $this->name; + } +} diff --git a/lib/composer/phpdocumentor/reflection-common/src/Location.php b/lib/composer/phpdocumentor/reflection-common/src/Location.php new file mode 100644 index 0000000000000000000000000000000000000000..177deede693c3108a467ba18b98daaf348ef82e7 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-common/src/Location.php @@ -0,0 +1,53 @@ +lineNumber = $lineNumber; + $this->columnNumber = $columnNumber; + } + + /** + * Returns the line number that is covered by this location. + */ + public function getLineNumber() : int + { + return $this->lineNumber; + } + + /** + * Returns the column number (character position on a line) for this location object. + */ + public function getColumnNumber() : int + { + return $this->columnNumber; + } +} diff --git a/lib/composer/phpdocumentor/reflection-common/src/Project.php b/lib/composer/phpdocumentor/reflection-common/src/Project.php new file mode 100644 index 0000000000000000000000000000000000000000..57839fd1467adc5e0c2818d8257c65926b7f4e31 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-common/src/Project.php @@ -0,0 +1,25 @@ +create($docComment); +``` + +The `create` method will yield an object of type `\phpDocumentor\Reflection\DocBlock` +whose methods can be queried: + +```php +// Contains the summary for this DocBlock +$summary = $docblock->getSummary(); + +// Contains \phpDocumentor\Reflection\DocBlock\Description object +$description = $docblock->getDescription(); + +// You can either cast it to string +$description = (string) $docblock->getDescription(); + +// Or use the render method to get a string representation of the Description. +$description = $docblock->getDescription()->render(); +``` + +> For more examples it would be best to review the scripts in the [`/examples` folder](/examples). diff --git a/lib/composer/phpdocumentor/reflection-docblock/composer.json b/lib/composer/phpdocumentor/reflection-docblock/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..7038f48a660936d36b7548c81f4fef74534e4499 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/composer.json @@ -0,0 +1,41 @@ +{ + "name": "phpdocumentor/reflection-docblock", + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1", + "phpdocumentor/reflection-common": "^2.2", + "ext-filter": "*" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "phpDocumentor\\Reflection\\": "tests/unit" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock.php new file mode 100644 index 0000000000000000000000000000000000000000..f3403d6e8cafbf4b1400733ed03cf7685af1f9ae --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock.php @@ -0,0 +1,204 @@ +summary = $summary; + $this->description = $description ?: new DocBlock\Description(''); + foreach ($tags as $tag) { + $this->addTag($tag); + } + + $this->context = $context; + $this->location = $location; + + $this->isTemplateEnd = $isTemplateEnd; + $this->isTemplateStart = $isTemplateStart; + } + + public function getSummary() : string + { + return $this->summary; + } + + public function getDescription() : DocBlock\Description + { + return $this->description; + } + + /** + * Returns the current context. + */ + public function getContext() : ?Types\Context + { + return $this->context; + } + + /** + * Returns the current location. + */ + public function getLocation() : ?Location + { + return $this->location; + } + + /** + * Returns whether this DocBlock is the start of a Template section. + * + * A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker + * (`#@+`) that is appended directly after the opening `/**` of a DocBlock. + * + * An example of such an opening is: + * + * ``` + * /**#@+ + * * My DocBlock + * * / + * ``` + * + * The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all + * elements that follow until another DocBlock is found that contains the closing marker (`#@-`). + * + * @see self::isTemplateEnd() for the check whether a closing marker was provided. + */ + public function isTemplateStart() : bool + { + return $this->isTemplateStart; + } + + /** + * Returns whether this DocBlock is the end of a Template section. + * + * @see self::isTemplateStart() for a more complete description of the Docblock Template functionality. + */ + public function isTemplateEnd() : bool + { + return $this->isTemplateEnd; + } + + /** + * Returns the tags for this DocBlock. + * + * @return Tag[] + */ + public function getTags() : array + { + return $this->tags; + } + + /** + * Returns an array of tags matching the given name. If no tags are found + * an empty array is returned. + * + * @param string $name String to search by. + * + * @return Tag[] + */ + public function getTagsByName(string $name) : array + { + $result = []; + + foreach ($this->getTags() as $tag) { + if ($tag->getName() !== $name) { + continue; + } + + $result[] = $tag; + } + + return $result; + } + + /** + * Checks if a tag of a certain type is present in this DocBlock. + * + * @param string $name Tag name to check for. + */ + public function hasTag(string $name) : bool + { + foreach ($this->getTags() as $tag) { + if ($tag->getName() === $name) { + return true; + } + } + + return false; + } + + /** + * Remove a tag from this DocBlock. + * + * @param Tag $tagToRemove The tag to remove. + */ + public function removeTag(Tag $tagToRemove) : void + { + foreach ($this->tags as $key => $tag) { + if ($tag === $tagToRemove) { + unset($this->tags[$key]); + break; + } + } + } + + /** + * Adds a tag to this DocBlock. + * + * @param Tag $tag The tag to add. + */ + private function addTag(Tag $tag) : void + { + $this->tags[] = $tag; + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Description.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Description.php new file mode 100644 index 0000000000000000000000000000000000000000..7b11b808e788b5da3d154729d528221c8a112286 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Description.php @@ -0,0 +1,114 @@ +create('This is a {@see Description}', $context); + * + * The description factory will interpret the given body and create a body template and list of tags from them, and pass + * that onto the constructor if this class. + * + * > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace + * > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial + * > type names and FQSENs. + * + * If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this: + * + * $description = new Description( + * 'This is a %1$s', + * [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ] + * ); + * + * It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object + * is mainly responsible for rendering. + * + * @see DescriptionFactory to create a new Description. + * @see Description\Formatter for the formatting of the body and tags. + */ +class Description +{ + /** @var string */ + private $bodyTemplate; + + /** @var Tag[] */ + private $tags; + + /** + * Initializes a Description with its body (template) and a listing of the tags used in the body template. + * + * @param Tag[] $tags + */ + public function __construct(string $bodyTemplate, array $tags = []) + { + $this->bodyTemplate = $bodyTemplate; + $this->tags = $tags; + } + + /** + * Returns the body template. + */ + public function getBodyTemplate() : string + { + return $this->bodyTemplate; + } + + /** + * Returns the tags for this DocBlock. + * + * @return Tag[] + */ + public function getTags() : array + { + return $this->tags; + } + + /** + * Renders this description as a string where the provided formatter will format the tags in the expected string + * format. + */ + public function render(?Formatter $formatter = null) : string + { + if ($formatter === null) { + $formatter = new PassthroughFormatter(); + } + + $tags = []; + foreach ($this->tags as $tag) { + $tags[] = '{' . $formatter->format($tag) . '}'; + } + + return vsprintf($this->bodyTemplate, $tags); + } + + /** + * Returns a plain string representation of this description. + */ + public function __toString() : string + { + return $this->render(); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..c27d2a0121abc058a779d49df5e59cb533015133 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php @@ -0,0 +1,177 @@ +tagFactory = $tagFactory; + } + + /** + * Returns the parsed text of this description. + */ + public function create(string $contents, ?TypeContext $context = null) : Description + { + $tokens = $this->lex($contents); + $count = count($tokens); + $tagCount = 0; + $tags = []; + + for ($i = 1; $i < $count; $i += 2) { + $tags[] = $this->tagFactory->create($tokens[$i], $context); + $tokens[$i] = '%' . ++$tagCount . '$s'; + } + + //In order to allow "literal" inline tags, the otherwise invalid + //sequence "{@}" is changed to "@", and "{}" is changed to "}". + //"%" is escaped to "%%" because of vsprintf. + //See unit tests for examples. + for ($i = 0; $i < $count; $i += 2) { + $tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]); + } + + return new Description(implode('', $tokens), $tags); + } + + /** + * Strips the contents from superfluous whitespace and splits the description into a series of tokens. + * + * @return string[] A series of tokens of which the description text is composed. + */ + private function lex(string $contents) : array + { + $contents = $this->removeSuperfluousStartingWhitespace($contents); + + // performance optimalization; if there is no inline tag, don't bother splitting it up. + if (strpos($contents, '{@') === false) { + return [$contents]; + } + + return Utils::pregSplit( + '/\{ + # "{@}" is not a valid inline tag. This ensures that we do not treat it as one, but treat it literally. + (?!@\}) + # We want to capture the whole tag line, but without the inline tag delimiters. + (\@ + # Match everything up to the next delimiter. + [^{}]* + # Nested inline tag content should not be captured, or it will appear in the result separately. + (?: + # Match nested inline tags. + (?: + # Because we did not catch the tag delimiters earlier, we must be explicit with them here. + # Notice that this also matches "{}", as a way to later introduce it as an escape sequence. + \{(?1)?\} + | + # Make sure we match hanging "{". + \{ + ) + # Match content after the nested inline tag. + [^{}]* + )* # If there are more inline tags, match them as well. We use "*" since there may not be any + # nested inline tags. + ) + \}/Sux', + $contents, + 0, + PREG_SPLIT_DELIM_CAPTURE + ); + } + + /** + * Removes the superfluous from a multi-line description. + * + * When a description has more than one line then it can happen that the second and subsequent lines have an + * additional indentation. This is commonly in use with tags like this: + * + * {@}since 1.1.0 This is an example + * description where we have an + * indentation in the second and + * subsequent lines. + * + * If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent + * lines and this may cause rendering issues when, for example, using a Markdown converter. + */ + private function removeSuperfluousStartingWhitespace(string $contents) : string + { + $lines = explode("\n", $contents); + + // if there is only one line then we don't have lines with superfluous whitespace and + // can use the contents as-is + if (count($lines) <= 1) { + return $contents; + } + + // determine how many whitespace characters need to be stripped + $startingSpaceCount = 9999999; + for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) { + // lines with a no length do not count as they are not indented at all + if (trim($lines[$i]) === '') { + continue; + } + + // determine the number of prefixing spaces by checking the difference in line length before and after + // an ltrim + $startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i]))); + } + + // strip the number of spaces from each line + if ($startingSpaceCount > 0) { + for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) { + $lines[$i] = substr($lines[$i], $startingSpaceCount); + } + } + + return implode("\n", $lines); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php new file mode 100644 index 0000000000000000000000000000000000000000..7249efb0499f20bc4aac47009674f374f0365e47 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php @@ -0,0 +1,157 @@ +getFilePath(); + + $file = $this->getExampleFileContents($filename); + if (!$file) { + return sprintf('** File not found : %s **', $filename); + } + + return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount())); + } + + /** + * Registers the project's root directory where an 'examples' folder can be expected. + */ + public function setSourceDirectory(string $directory = '') : void + { + $this->sourceDirectory = $directory; + } + + /** + * Returns the project's root directory where an 'examples' folder can be expected. + */ + public function getSourceDirectory() : string + { + return $this->sourceDirectory; + } + + /** + * Registers a series of directories that may contain examples. + * + * @param string[] $directories + */ + public function setExampleDirectories(array $directories) : void + { + $this->exampleDirectories = $directories; + } + + /** + * Returns a series of directories that may contain examples. + * + * @return string[] + */ + public function getExampleDirectories() : array + { + return $this->exampleDirectories; + } + + /** + * Attempts to find the requested example file and returns its contents or null if no file was found. + * + * This method will try several methods in search of the given example file, the first one it encounters is + * returned: + * + * 1. Iterates through all examples folders for the given filename + * 2. Checks the source folder for the given filename + * 3. Checks the 'examples' folder in the current working directory for examples + * 4. Checks the path relative to the current working directory for the given filename + * + * @return string[] all lines of the example file + */ + private function getExampleFileContents(string $filename) : ?array + { + $normalizedPath = null; + + foreach ($this->exampleDirectories as $directory) { + $exampleFileFromConfig = $this->constructExamplePath($directory, $filename); + if (is_readable($exampleFileFromConfig)) { + $normalizedPath = $exampleFileFromConfig; + break; + } + } + + if (!$normalizedPath) { + if (is_readable($this->getExamplePathFromSource($filename))) { + $normalizedPath = $this->getExamplePathFromSource($filename); + } elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) { + $normalizedPath = $this->getExamplePathFromExampleDirectory($filename); + } elseif (is_readable($filename)) { + $normalizedPath = $filename; + } + } + + $lines = $normalizedPath && is_readable($normalizedPath) ? file($normalizedPath) : false; + + return $lines !== false ? $lines : null; + } + + /** + * Get example filepath based on the example directory inside your project. + */ + private function getExamplePathFromExampleDirectory(string $file) : string + { + return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file; + } + + /** + * Returns a path to the example file in the given directory.. + */ + private function constructExamplePath(string $directory, string $file) : string + { + return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file; + } + + /** + * Get example filepath based on sourcecode. + */ + private function getExamplePathFromSource(string $file) : string + { + return sprintf( + '%s%s%s', + trim($this->getSourceDirectory(), '\\/'), + DIRECTORY_SEPARATOR, + trim($file, '"') + ); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php new file mode 100644 index 0000000000000000000000000000000000000000..531970b92f08731b4340a277a7d72d9aa6895be8 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php @@ -0,0 +1,151 @@ +indent = $indent; + $this->indentString = $indentString; + $this->isFirstLineIndented = $indentFirstLine; + $this->lineLength = $lineLength; + $this->tagFormatter = $tagFormatter ?: new PassthroughFormatter(); + } + + /** + * Generate a DocBlock comment. + * + * @param DocBlock $docblock The DocBlock to serialize. + * + * @return string The serialized doc block. + */ + public function getDocComment(DocBlock $docblock) : string + { + $indent = str_repeat($this->indentString, $this->indent); + $firstIndent = $this->isFirstLineIndented ? $indent : ''; + // 3 === strlen(' * ') + $wrapLength = $this->lineLength ? $this->lineLength - strlen($indent) - 3 : null; + + $text = $this->removeTrailingSpaces( + $indent, + $this->addAsterisksForEachLine( + $indent, + $this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength) + ) + ); + + $comment = $firstIndent . "/**\n"; + if ($text) { + $comment .= $indent . ' * ' . $text . "\n"; + $comment .= $indent . " *\n"; + } + + $comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment); + + return $comment . $indent . ' */'; + } + + private function removeTrailingSpaces(string $indent, string $text) : string + { + return str_replace( + sprintf("\n%s * \n", $indent), + sprintf("\n%s *\n", $indent), + $text + ); + } + + private function addAsterisksForEachLine(string $indent, string $text) : string + { + return str_replace( + "\n", + sprintf("\n%s * ", $indent), + $text + ); + } + + private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, ?int $wrapLength) : string + { + $text = $docblock->getSummary() . ((string) $docblock->getDescription() ? "\n\n" . $docblock->getDescription() + : ''); + if ($wrapLength !== null) { + $text = wordwrap($text, $wrapLength); + + return $text; + } + + return $text; + } + + private function addTagBlock(DocBlock $docblock, ?int $wrapLength, string $indent, string $comment) : string + { + foreach ($docblock->getTags() as $tag) { + $tagText = $this->tagFormatter->format($tag); + if ($wrapLength !== null) { + $tagText = wordwrap($tagText, $wrapLength); + } + + $tagText = str_replace( + "\n", + sprintf("\n%s * ", $indent), + $tagText + ); + + $comment .= sprintf("%s * %s\n", $indent, $tagText); + } + + return $comment; + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..e64b587ef406ef22fcb913f0d40defb8d25df38c --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php @@ -0,0 +1,347 @@ + Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise + * > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to + * > verify that a dependency is actually passed. + * + * This Factory also features a Service Locator component that is used to pass the right dependencies to the + * `create` method of a tag; each dependency should be registered as a service or as a parameter. + * + * When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass + * the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface. + */ +final class StandardTagFactory implements TagFactory +{ + /** PCRE regular expression matching a tag name. */ + public const REGEX_TAGNAME = '[\w\-\_\\\\:]+'; + + /** + * @var array> An array with a tag as a key, and an + * FQCN to a class that handles it as an array value. + */ + private $tagHandlerMappings = [ + 'author' => Author::class, + 'covers' => Covers::class, + 'deprecated' => Deprecated::class, + // 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example', + 'link' => LinkTag::class, + 'method' => Method::class, + 'param' => Param::class, + 'property-read' => PropertyRead::class, + 'property' => Property::class, + 'property-write' => PropertyWrite::class, + 'return' => Return_::class, + 'see' => SeeTag::class, + 'since' => Since::class, + 'source' => Source::class, + 'throw' => Throws::class, + 'throws' => Throws::class, + 'uses' => Uses::class, + 'var' => Var_::class, + 'version' => Version::class, + ]; + + /** + * @var array> An array with a anotation s a key, and an + * FQCN to a class that handles it as an array value. + */ + private $annotationMappings = []; + + /** + * @var ReflectionParameter[][] a lazy-loading cache containing parameters + * for each tagHandler that has been used. + */ + private $tagHandlerParameterCache = []; + + /** @var FqsenResolver */ + private $fqsenResolver; + + /** + * @var mixed[] an array representing a simple Service Locator where we can store parameters and + * services that can be inserted into the Factory Methods of Tag Handlers. + */ + private $serviceLocator = []; + + /** + * Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers. + * + * If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property + * is used. + * + * @see self::registerTagHandler() to add a new tag handler to the existing default list. + * + * @param array> $tagHandlers + */ + public function __construct(FqsenResolver $fqsenResolver, ?array $tagHandlers = null) + { + $this->fqsenResolver = $fqsenResolver; + if ($tagHandlers !== null) { + $this->tagHandlerMappings = $tagHandlers; + } + + $this->addService($fqsenResolver, FqsenResolver::class); + } + + public function create(string $tagLine, ?TypeContext $context = null) : Tag + { + if (!$context) { + $context = new TypeContext(''); + } + + [$tagName, $tagBody] = $this->extractTagParts($tagLine); + + return $this->createTag(trim($tagBody), $tagName, $context); + } + + /** + * @param mixed $value + */ + public function addParameter(string $name, $value) : void + { + $this->serviceLocator[$name] = $value; + } + + public function addService(object $service, ?string $alias = null) : void + { + $this->serviceLocator[$alias ?: get_class($service)] = $service; + } + + public function registerTagHandler(string $tagName, string $handler) : void + { + Assert::stringNotEmpty($tagName); + Assert::classExists($handler); + Assert::implementsInterface($handler, Tag::class); + + if (strpos($tagName, '\\') && $tagName[0] !== '\\') { + throw new InvalidArgumentException( + 'A namespaced tag must have a leading backslash as it must be fully qualified' + ); + } + + $this->tagHandlerMappings[$tagName] = $handler; + } + + /** + * Extracts all components for a tag. + * + * @return string[] + */ + private function extractTagParts(string $tagLine) : array + { + $matches = []; + if (!preg_match('/^@(' . self::REGEX_TAGNAME . ')((?:[\s\(\{])\s*([^\s].*)|$)/us', $tagLine, $matches)) { + throw new InvalidArgumentException( + 'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors' + ); + } + + if (count($matches) < 3) { + $matches[] = ''; + } + + return array_slice($matches, 1); + } + + /** + * Creates a new tag object with the given name and body or returns null if the tag name was recognized but the + * body was invalid. + */ + private function createTag(string $body, string $name, TypeContext $context) : Tag + { + $handlerClassName = $this->findHandlerClassName($name, $context); + $arguments = $this->getArgumentsForParametersFromWiring( + $this->fetchParametersForHandlerFactoryMethod($handlerClassName), + $this->getServiceLocatorWithDynamicParameters($context, $name, $body) + ); + + try { + $callable = [$handlerClassName, 'create']; + Assert::isCallable($callable); + /** @phpstan-var callable(string): ?Tag $callable */ + $tag = call_user_func_array($callable, $arguments); + + return $tag ?? InvalidTag::create($body, $name); + } catch (InvalidArgumentException $e) { + return InvalidTag::create($body, $name)->withError($e); + } + } + + /** + * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`). + * + * @return class-string + */ + private function findHandlerClassName(string $tagName, TypeContext $context) : string + { + $handlerClassName = Generic::class; + if (isset($this->tagHandlerMappings[$tagName])) { + $handlerClassName = $this->tagHandlerMappings[$tagName]; + } elseif ($this->isAnnotation($tagName)) { + // TODO: Annotation support is planned for a later stage and as such is disabled for now + $tagName = (string) $this->fqsenResolver->resolve($tagName, $context); + if (isset($this->annotationMappings[$tagName])) { + $handlerClassName = $this->annotationMappings[$tagName]; + } + } + + return $handlerClassName; + } + + /** + * Retrieves the arguments that need to be passed to the Factory Method with the given Parameters. + * + * @param ReflectionParameter[] $parameters + * @param mixed[] $locator + * + * @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters + * is provided with this method. + */ + private function getArgumentsForParametersFromWiring(array $parameters, array $locator) : array + { + $arguments = []; + foreach ($parameters as $parameter) { + $type = $parameter->getType(); + $typeHint = null; + if ($type instanceof ReflectionNamedType) { + $typeHint = $type->getName(); + if ($typeHint === 'self') { + $declaringClass = $parameter->getDeclaringClass(); + if ($declaringClass !== null) { + $typeHint = $declaringClass->getName(); + } + } + } + + if (isset($locator[$typeHint])) { + $arguments[] = $locator[$typeHint]; + continue; + } + + $parameterName = $parameter->getName(); + if (isset($locator[$parameterName])) { + $arguments[] = $locator[$parameterName]; + continue; + } + + $arguments[] = null; + } + + return $arguments; + } + + /** + * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given + * tag handler class name. + * + * @param class-string $handlerClassName + * + * @return ReflectionParameter[] + */ + private function fetchParametersForHandlerFactoryMethod(string $handlerClassName) : array + { + if (!isset($this->tagHandlerParameterCache[$handlerClassName])) { + $methodReflection = new ReflectionMethod($handlerClassName, 'create'); + $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters(); + } + + return $this->tagHandlerParameterCache[$handlerClassName]; + } + + /** + * Returns a copy of this class' Service Locator with added dynamic parameters, + * such as the tag's name, body and Context. + * + * @param TypeContext $context The Context (namespace and aliasses) that may be + * passed and is used to resolve FQSENs. + * @param string $tagName The name of the tag that may be + * passed onto the factory method of the Tag class. + * @param string $tagBody The body of the tag that may be + * passed onto the factory method of the Tag class. + * + * @return mixed[] + */ + private function getServiceLocatorWithDynamicParameters( + TypeContext $context, + string $tagName, + string $tagBody + ) : array { + return array_merge( + $this->serviceLocator, + [ + 'name' => $tagName, + 'body' => $tagBody, + TypeContext::class => $context, + ] + ); + } + + /** + * Returns whether the given tag belongs to an annotation. + * + * @todo this method should be populated once we implement Annotation notation support. + */ + private function isAnnotation(string $tagContent) : bool + { + // 1. Contains a namespace separator + // 2. Contains parenthesis + // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part + // of the annotation class name matches the found tag name + + return false; + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php new file mode 100644 index 0000000000000000000000000000000000000000..f55de91695be84ce53d93b912477fd6d75aa2d2d --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php @@ -0,0 +1,32 @@ + $handler FQCN of handler. + * + * @throws InvalidArgumentException If the tag name is not a string. + * @throws InvalidArgumentException If the tag name is namespaced (contains backslashes) but + * does not start with a backslash. + * @throws InvalidArgumentException If the handler is not a string. + * @throws InvalidArgumentException If the handler is not an existing class. + * @throws InvalidArgumentException If the handler does not implement the {@see Tag} interface. + */ + public function registerTagHandler(string $tagName, string $handler) : void; +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php new file mode 100644 index 0000000000000000000000000000000000000000..d1207571dafbc1e5ad3b77cd74bdd67e65eddd24 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php @@ -0,0 +1,100 @@ +authorName = $authorName; + $this->authorEmail = $authorEmail; + } + + /** + * Gets the author's name. + * + * @return string The author's name. + */ + public function getAuthorName() : string + { + return $this->authorName; + } + + /** + * Returns the author's email. + * + * @return string The author's email. + */ + public function getEmail() : string + { + return $this->authorEmail; + } + + /** + * Returns this tag in string form. + */ + public function __toString() : string + { + if ($this->authorEmail) { + $authorEmail = '<' . $this->authorEmail . '>'; + } else { + $authorEmail = ''; + } + + $authorName = (string) $this->authorName; + + return $authorName . ($authorEmail !== '' ? ($authorName !== '' ? ' ' : '') . $authorEmail : ''); + } + + /** + * Attempts to create a new Author object based on †he tag body. + */ + public static function create(string $body) : ?self + { + $splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $body, $matches); + if (!$splitTagContent) { + return null; + } + + $authorName = trim($matches[1]); + $email = isset($matches[2]) ? trim($matches[2]) : ''; + + return new static($authorName, $email); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php new file mode 100644 index 0000000000000000000000000000000000000000..fbcd4022fcb23181684ff3aa70fd3e4bdfb40bd4 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php @@ -0,0 +1,53 @@ +name; + } + + public function getDescription() : ?Description + { + return $this->description; + } + + public function render(?Formatter $formatter = null) : string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php new file mode 100644 index 0000000000000000000000000000000000000000..9e52e5e4ca0107c0d5afac75ccb4d7950589be58 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php @@ -0,0 +1,100 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?FqsenResolver $resolver = null, + ?TypeContext $context = null + ) : self { + Assert::stringNotEmpty($body); + Assert::notNull($descriptionFactory); + Assert::notNull($resolver); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + + return new static( + self::resolveFqsen($parts[0], $resolver, $context), + $descriptionFactory->create($parts[1] ?? '', $context) + ); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context) : Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the structural element this tag refers to. + */ + public function getReference() : Fqsen + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php new file mode 100644 index 0000000000000000000000000000000000000000..68e8f03639e402efc90211ece5dd4821e23d8ef7 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php @@ -0,0 +1,108 @@ +version = $version; + $this->description = $description; + } + + /** + * @return static + */ + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + if (empty($body)) { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return new static( + null, + $descriptionFactory !== null ? $descriptionFactory->create($body, $context) : null + ); + } + + Assert::notNull($descriptionFactory); + + return new static( + $matches[1], + $descriptionFactory->create($matches[2] ?? '', $context) + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion() : ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php new file mode 100644 index 0000000000000000000000000000000000000000..3face1efa620753b0b02213c136654bd78b43f61 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php @@ -0,0 +1,199 @@ +filePath = $filePath; + $this->startingLine = $startingLine; + $this->lineCount = $lineCount; + if ($content !== null) { + $this->content = trim($content); + } + + $this->isURI = $isURI; + } + + public function getContent() : string + { + if ($this->content === null || $this->content === '') { + $filePath = $this->filePath; + if ($this->isURI) { + $filePath = $this->isUriRelative($this->filePath) + ? str_replace('%2F', '/', rawurlencode($this->filePath)) + : $this->filePath; + } + + return trim($filePath); + } + + return $this->content; + } + + public function getDescription() : ?string + { + return $this->content; + } + + public static function create(string $body) : ?Tag + { + // File component: File path in quotes or File URI / Source information + if (!preg_match('/^\s*(?:(\"[^\"]+\")|(\S+))(?:\s+(.*))?$/sux', $body, $matches)) { + return null; + } + + $filePath = null; + $fileUri = null; + if ($matches[1] !== '') { + $filePath = $matches[1]; + } else { + $fileUri = $matches[2]; + } + + $startingLine = 1; + $lineCount = 0; + $description = null; + + if (array_key_exists(3, $matches)) { + $description = $matches[3]; + + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\d*)(?:\s+((?1))\s*)?(.*)$/sux', $matches[3], $contentMatches)) { + $startingLine = (int) $contentMatches[1]; + if (isset($contentMatches[2])) { + $lineCount = (int) $contentMatches[2]; + } + + if (array_key_exists(3, $contentMatches)) { + $description = $contentMatches[3]; + } + } + } + + return new static( + $filePath ?? ($fileUri ?? ''), + $fileUri !== null, + $startingLine, + $lineCount, + $description + ); + } + + /** + * Returns the file path. + * + * @return string Path to a file to use as an example. + * May also be an absolute URI. + */ + public function getFilePath() : string + { + return trim($this->filePath, '"'); + } + + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + $filePath = (string) $this->filePath; + $isDefaultLine = $this->startingLine === 1 && $this->lineCount === 0; + $startingLine = !$isDefaultLine ? (string) $this->startingLine : ''; + $lineCount = !$isDefaultLine ? (string) $this->lineCount : ''; + $content = (string) $this->content; + + return $filePath + . ($startingLine !== '' + ? ($filePath !== '' ? ' ' : '') . $startingLine + : '') + . ($lineCount !== '' + ? ($filePath !== '' || $startingLine !== '' ? ' ' : '') . $lineCount + : '') + . ($content !== '' + ? ($filePath !== '' || $startingLine !== '' || $lineCount !== '' ? ' ' : '') . $content + : ''); + } + + /** + * Returns true if the provided URI is relative or contains a complete scheme (and thus is absolute). + */ + private function isUriRelative(string $uri) : bool + { + return strpos($uri, ':') === false; + } + + public function getStartingLine() : int + { + return $this->startingLine; + } + + public function getLineCount() : int + { + return $this->lineCount; + } + + public function getName() : string + { + return 'example'; + } + + public function render(?Formatter $formatter = null) : string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php new file mode 100644 index 0000000000000000000000000000000000000000..f6f0bb5a4b1cd16cdd526e6d4799fe296460a428 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php @@ -0,0 +1,25 @@ +maxLen = max($this->maxLen, strlen($tag->getName())); + } + } + + /** + * Formats the given tag to return a simple plain text version. + */ + public function format(Tag $tag) : string + { + return '@' . $tag->getName() . + str_repeat( + ' ', + $this->maxLen - strlen($tag->getName()) + 1 + ) . + $tag; + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..f26d22fb685a7035f6c820a90c6b628f5b95cb48 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php @@ -0,0 +1,29 @@ +getName() . ' ' . $tag); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php new file mode 100644 index 0000000000000000000000000000000000000000..a7b423f5f7250b0084f3378701ef19aacb517b0b --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php @@ -0,0 +1,88 @@ +validateTagName($name); + + $this->name = $name; + $this->description = $description; + } + + /** + * Creates a new tag that represents any unknown tag type. + * + * @return static + */ + public static function create( + string $body, + string $name = '', + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::stringNotEmpty($name); + Assert::notNull($descriptionFactory); + + $description = $body !== '' ? $descriptionFactory->create($body, $context) : null; + + return new static($name, $description); + } + + /** + * Returns the tag as a serialized string + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + return $description; + } + + /** + * Validates if the tag name matches the expected format, otherwise throws an exception. + */ + private function validateTagName(string $name) : void + { + if (!preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) { + throw new InvalidArgumentException( + 'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, ' + . 'hyphens and backslashes.' + ); + } + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php new file mode 100644 index 0000000000000000000000000000000000000000..e3deb5a8d01e90784836bb5fe512bb7936fb6673 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php @@ -0,0 +1,144 @@ +name = $name; + $this->body = $body; + } + + public function getException() : ?Throwable + { + return $this->throwable; + } + + public function getName() : string + { + return $this->name; + } + + public static function create(string $body, string $name = '') : self + { + return new self($name, $body); + } + + public function withError(Throwable $exception) : self + { + $this->flattenExceptionBacktrace($exception); + $tag = new self($this->name, $this->body); + $tag->throwable = $exception; + + return $tag; + } + + /** + * Removes all complex types from backtrace + * + * Not all objects are serializable. So we need to remove them from the + * stored exception to be sure that we do not break existing library usage. + */ + private function flattenExceptionBacktrace(Throwable $exception) : void + { + $traceProperty = (new ReflectionClass(Exception::class))->getProperty('trace'); + $traceProperty->setAccessible(true); + + do { + $trace = $exception->getTrace(); + if (isset($trace[0]['args'])) { + $trace = array_map( + function (array $call) : array { + $call['args'] = array_map([$this, 'flattenArguments'], $call['args']); + + return $call; + }, + $trace + ); + } + + $traceProperty->setValue($exception, $trace); + $exception = $exception->getPrevious(); + } while ($exception !== null); + + $traceProperty->setAccessible(false); + } + + /** + * @param mixed $value + * + * @return mixed + * + * @throws ReflectionException + */ + private function flattenArguments($value) + { + if ($value instanceof Closure) { + $closureReflection = new ReflectionFunction($value); + $value = sprintf( + '(Closure at %s:%s)', + $closureReflection->getFileName(), + $closureReflection->getStartLine() + ); + } elseif (is_object($value)) { + $value = sprintf('object(%s)', get_class($value)); + } elseif (is_resource($value)) { + $value = sprintf('resource(%s)', get_resource_type($value)); + } elseif (is_array($value)) { + $value = array_map([$this, 'flattenArguments'], $value); + } + + return $value; + } + + public function render(?Formatter $formatter = null) : string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } + + public function __toString() : string + { + return $this->body; + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php new file mode 100644 index 0000000000000000000000000000000000000000..226bbe0f9eb4591f883b60874c4dd2ad7a57328f --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php @@ -0,0 +1,78 @@ +link = $link; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + + return new static($parts[0], $description); + } + + /** + * Gets the link + */ + public function getLink() : string + { + return $this->link; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $link = (string) $this->link; + + return $link . ($description !== '' ? ($link !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php new file mode 100644 index 0000000000000000000000000000000000000000..08c0407ea93a557de04da73b83bc7e7949dd8b64 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php @@ -0,0 +1,279 @@ + + * @var array> + */ + private $arguments; + + /** @var bool */ + private $isStatic; + + /** @var Type */ + private $returnType; + + /** + * @param array> $arguments + * + * @phpstan-param array $arguments + */ + public function __construct( + string $methodName, + array $arguments = [], + ?Type $returnType = null, + bool $static = false, + ?Description $description = null + ) { + Assert::stringNotEmpty($methodName); + + if ($returnType === null) { + $returnType = new Void_(); + } + + $this->methodName = $methodName; + $this->arguments = $this->filterArguments($arguments); + $this->returnType = $returnType; + $this->isStatic = $static; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : ?self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + // 1. none or more whitespace + // 2. optionally the keyword "static" followed by whitespace + // 3. optionally a word with underscores followed by whitespace : as + // type for the return value + // 4. then optionally a word with underscores followed by () and + // whitespace : as method name as used by phpDocumentor + // 5. then a word with underscores, followed by ( and any character + // until a ) and whitespace : as method name with signature + // 6. any remaining text : as description + if (!preg_match( + '/^ + # Static keyword + # Declares a static method ONLY if type is also present + (?: + (static) + \s+ + )? + # Return type + (?: + ( + (?:[\w\|_\\\\]*\$this[\w\|_\\\\]*) + | + (?: + (?:[\w\|_\\\\]+) + # array notation + (?:\[\])* + )*+ + ) + \s+ + )? + # Method name + ([\w_]+) + # Arguments + (?: + \(([^\)]*)\) + )? + \s* + # Description + (.*) + $/sux', + $body, + $matches + )) { + return null; + } + + [, $static, $returnType, $methodName, $argumentLines, $description] = $matches; + + $static = $static === 'static'; + + if ($returnType === '') { + $returnType = 'void'; + } + + $returnType = $typeResolver->resolve($returnType, $context); + $description = $descriptionFactory->create($description, $context); + + /** @phpstan-var array $arguments */ + $arguments = []; + if ($argumentLines !== '') { + $argumentsExploded = explode(',', $argumentLines); + foreach ($argumentsExploded as $argument) { + $argument = explode(' ', self::stripRestArg(trim($argument)), 2); + if (strpos($argument[0], '$') === 0) { + $argumentName = substr($argument[0], 1); + $argumentType = new Mixed_(); + } else { + $argumentType = $typeResolver->resolve($argument[0], $context); + $argumentName = ''; + if (isset($argument[1])) { + $argument[1] = self::stripRestArg($argument[1]); + $argumentName = substr($argument[1], 1); + } + } + + $arguments[] = ['name' => $argumentName, 'type' => $argumentType]; + } + } + + return new static($methodName, $arguments, $returnType, $static, $description); + } + + /** + * Retrieves the method name. + */ + public function getMethodName() : string + { + return $this->methodName; + } + + /** + * @return array> + * + * @phpstan-return array + */ + public function getArguments() : array + { + return $this->arguments; + } + + /** + * Checks whether the method tag describes a static method or not. + * + * @return bool TRUE if the method declaration is for a static method, FALSE otherwise. + */ + public function isStatic() : bool + { + return $this->isStatic; + } + + public function getReturnType() : Type + { + return $this->returnType; + } + + public function __toString() : string + { + $arguments = []; + foreach ($this->arguments as $argument) { + $arguments[] = $argument['type'] . ' $' . $argument['name']; + } + + $argumentStr = '(' . implode(', ', $arguments) . ')'; + + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $static = $this->isStatic ? 'static' : ''; + + $returnType = (string) $this->returnType; + + $methodName = (string) $this->methodName; + + return $static + . ($returnType !== '' ? ($static !== '' ? ' ' : '') . $returnType : '') + . ($methodName !== '' ? ($static !== '' || $returnType !== '' ? ' ' : '') . $methodName : '') + . $argumentStr + . ($description !== '' ? ' ' . $description : ''); + } + + /** + * @param mixed[][]|string[] $arguments + * + * @return mixed[][] + * + * @phpstan-param array $arguments + * @phpstan-return array + */ + private function filterArguments(array $arguments = []) : array + { + $result = []; + foreach ($arguments as $argument) { + if (is_string($argument)) { + $argument = ['name' => $argument]; + } + + if (!isset($argument['type'])) { + $argument['type'] = new Mixed_(); + } + + $keys = array_keys($argument); + sort($keys); + if ($keys !== ['name', 'type']) { + throw new InvalidArgumentException( + 'Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, true) + ); + } + + $result[] = $argument; + } + + return $result; + } + + private static function stripRestArg(string $argument) : string + { + if (strpos($argument, '...') === 0) { + $argument = trim(substr($argument, 3)); + } + + return $argument; + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php new file mode 100644 index 0000000000000000000000000000000000000000..83419e9c5dcdbe14abb0380bb8607a3978301d8c --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php @@ -0,0 +1,172 @@ +name = 'param'; + $this->variableName = $variableName; + $this->type = $type; + $this->isVariadic = $isVariadic; + $this->description = $description; + $this->isReference = $isReference; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + $isVariadic = false; + $isReference = false; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && !self::strStartsWithVariable($firstPart)) { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ or ...$ or &$ or &...$ it must be the variable name + if (isset($parts[0]) && self::strStartsWithVariable($parts[0])) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + if (strpos($variableName, '$') === 0) { + $variableName = substr($variableName, 1); + } elseif (strpos($variableName, '&$') === 0) { + $isReference = true; + $variableName = substr($variableName, 2); + } elseif (strpos($variableName, '...$') === 0) { + $isVariadic = true; + $variableName = substr($variableName, 4); + } elseif (strpos($variableName, '&...$') === 0) { + $isVariadic = true; + $isReference = true; + $variableName = substr($variableName, 5); + } + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $isVariadic, $description, $isReference); + } + + /** + * Returns the variable's name. + */ + public function getVariableName() : ?string + { + return $this->variableName; + } + + /** + * Returns whether this tag is variadic. + */ + public function isVariadic() : bool + { + return $this->isVariadic; + } + + /** + * Returns whether this tag is passed by reference. + */ + public function isReference() : bool + { + return $this->isReference; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $variableName = ''; + if ($this->variableName) { + $variableName .= ($this->isReference ? '&' : '') . ($this->isVariadic ? '...' : ''); + $variableName .= '$' . $this->variableName; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } + + private static function strStartsWithVariable(string $str) : bool + { + return strpos($str, '$') === 0 + || + strpos($str, '...$') === 0 + || + strpos($str, '&$') === 0 + || + strpos($str, '&...$') === 0; + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php new file mode 100644 index 0000000000000000000000000000000000000000..03897578b982a849988ebe431ddc933f052e6502 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php @@ -0,0 +1,119 @@ +name = 'property'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName() : ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php new file mode 100644 index 0000000000000000000000000000000000000000..7ff55d50b53d1ddb45a7eef83feec621bd9868ce --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php @@ -0,0 +1,119 @@ +name = 'property-read'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName() : ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php new file mode 100644 index 0000000000000000000000000000000000000000..cc1e4b6108338fd7660e2ba4f4452baac87f0db1 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php @@ -0,0 +1,119 @@ +name = 'property-write'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName() : ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php new file mode 100644 index 0000000000000000000000000000000000000000..cede74c17401ab8c5e17848dd42b1ba4f3ec7002 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php @@ -0,0 +1,38 @@ +fqsen = $fqsen; + } + + /** + * @return string string representation of the referenced fqsen + */ + public function __toString() : string + { + return (string) $this->fqsen; + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php new file mode 100644 index 0000000000000000000000000000000000000000..5eedcbc366c567a3b17c66c7d2b73c43ea2b727e --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php @@ -0,0 +1,22 @@ +uri = $uri; + } + + public function __toString() : string + { + return $this->uri; + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php new file mode 100644 index 0000000000000000000000000000000000000000..546a0eaa5a97a1f1eaea144d47758da20609494b --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php @@ -0,0 +1,64 @@ +name = 'return'; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$type, $description] = self::extractTypeFromBody($body); + + $type = $typeResolver->resolve($type, $context); + $description = $descriptionFactory->create($description, $context); + + return new static($type, $description); + } + + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $type = $this->type ? '' . $this->type : 'mixed'; + + return $type . ($description !== '' ? ($type !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php new file mode 100644 index 0000000000000000000000000000000000000000..73311df78ac20082099aa77e2d81aa45d619979d --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php @@ -0,0 +1,105 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?FqsenResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + + // https://tools.ietf.org/html/rfc2396#section-3 + if (preg_match('/\w:\/\/\w/i', $parts[0])) { + return new static(new Url($parts[0]), $description); + } + + return new static(new FqsenRef(self::resolveFqsen($parts[0], $typeResolver, $context)), $description); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context) : Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the ref of this tag. + */ + public function getReference() : Reference + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php new file mode 100644 index 0000000000000000000000000000000000000000..32de527b0e27559c501989b938cc34bbc13405ff --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php @@ -0,0 +1,102 @@ +version = $version; + $this->description = $description; + } + + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : ?self { + if (empty($body)) { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return null; + } + + Assert::notNull($descriptionFactory); + + return new static( + $matches[1], + $descriptionFactory->create($matches[2] ?? '', $context) + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion() : ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php new file mode 100644 index 0000000000000000000000000000000000000000..f0c3101411deabe285f9d1395ac7acb047cc5c69 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php @@ -0,0 +1,117 @@ +startingLine = (int) $startingLine; + $this->lineCount = $lineCount !== null ? (int) $lineCount : null; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::stringNotEmpty($body); + Assert::notNull($descriptionFactory); + + $startingLine = 1; + $lineCount = null; + $description = null; + + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $body, $matches)) { + $startingLine = (int) $matches[1]; + if (isset($matches[2]) && $matches[2] !== '') { + $lineCount = (int) $matches[2]; + } + + $description = $matches[3]; + } + + return new static($startingLine, $lineCount, $descriptionFactory->create($description??'', $context)); + } + + /** + * Gets the starting line. + * + * @return int The starting line, relative to the structural element's + * location. + */ + public function getStartingLine() : int + { + return $this->startingLine; + } + + /** + * Returns the number of lines. + * + * @return int|null The number of lines, relative to the starting line. NULL + * means "to the end". + */ + public function getLineCount() : ?int + { + return $this->lineCount; + } + + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $startingLine = (string) $this->startingLine; + + $lineCount = $this->lineCount !== null ? '' . $this->lineCount : ''; + + return $startingLine + . ($lineCount !== '' + ? ($startingLine || $startingLine === '0' ? ' ' : '') . $lineCount + : '') + . ($description !== '' + ? ($startingLine || $startingLine === '0' || $lineCount !== '' ? ' ' : '') . $description + : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php new file mode 100644 index 0000000000000000000000000000000000000000..0083d34b287747299959c1d5297d0b4154734135 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php @@ -0,0 +1,65 @@ +type; + } + + /** + * @return string[] + */ + protected static function extractTypeFromBody(string $body) : array + { + $type = ''; + $nestingLevel = 0; + for ($i = 0, $iMax = strlen($body); $i < $iMax; $i++) { + $character = $body[$i]; + + if ($nestingLevel === 0 && trim($character) === '') { + break; + } + + $type .= $character; + if (in_array($character, ['<', '(', '[', '{'])) { + $nestingLevel++; + continue; + } + + if (in_array($character, ['>', ')', ']', '}'])) { + $nestingLevel--; + continue; + } + } + + $description = trim(substr($body, strlen($type))); + + return [$type, $description]; + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php new file mode 100644 index 0000000000000000000000000000000000000000..d4dc9472c3caf32c9396f33ba03ebb0b8d71681e --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php @@ -0,0 +1,64 @@ +name = 'throws'; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$type, $description] = self::extractTypeFromBody($body); + + $type = $typeResolver->resolve($type, $context); + $description = $descriptionFactory->create($description, $context); + + return new static($type, $description); + } + + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $type = (string) $this->type; + + return $type . ($description !== '' ? ($type !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php new file mode 100644 index 0000000000000000000000000000000000000000..4d52afccd0067dbcd4e962c3a501e2100b7f909b --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php @@ -0,0 +1,99 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?FqsenResolver $resolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::notNull($resolver); + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + + return new static( + self::resolveFqsen($parts[0], $resolver, $context), + $descriptionFactory->create($parts[1] ?? '', $context) + ); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context) : Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the structural element this tag refers to. + */ + public function getReference() : Fqsen + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php new file mode 100644 index 0000000000000000000000000000000000000000..762c262f9efb73013e9708495e94aba808f9cf22 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php @@ -0,0 +1,120 @@ +name = 'var'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $type = null; + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName() : ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php new file mode 100644 index 0000000000000000000000000000000000000000..460c86d72e5aa37f0a4f1e760f554ca39e4893ae --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php @@ -0,0 +1,105 @@ +version = $version; + $this->description = $description; + } + + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ) : ?self { + if (empty($body)) { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return null; + } + + $description = null; + if ($descriptionFactory !== null) { + $description = $descriptionFactory->create($matches[2] ?? '', $context); + } + + return new static( + $matches[1], + $description + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion() : ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlockFactory.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlockFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..cf04e5a552f12f3c8f3965fa352d595c60cd2ad4 --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlockFactory.php @@ -0,0 +1,286 @@ +descriptionFactory = $descriptionFactory; + $this->tagFactory = $tagFactory; + } + + /** + * Factory method for easy instantiation. + * + * @param array> $additionalTags + */ + public static function createInstance(array $additionalTags = []) : self + { + $fqsenResolver = new FqsenResolver(); + $tagFactory = new StandardTagFactory($fqsenResolver); + $descriptionFactory = new DescriptionFactory($tagFactory); + + $tagFactory->addService($descriptionFactory); + $tagFactory->addService(new TypeResolver($fqsenResolver)); + + $docBlockFactory = new self($descriptionFactory, $tagFactory); + foreach ($additionalTags as $tagName => $tagHandler) { + $docBlockFactory->registerTagHandler($tagName, $tagHandler); + } + + return $docBlockFactory; + } + + /** + * @param object|string $docblock A string containing the DocBlock to parse or an object supporting the + * getDocComment method (such as a ReflectionClass object). + */ + public function create($docblock, ?Types\Context $context = null, ?Location $location = null) : DocBlock + { + if (is_object($docblock)) { + if (!method_exists($docblock, 'getDocComment')) { + $exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method'; + + throw new InvalidArgumentException($exceptionMessage); + } + + $docblock = $docblock->getDocComment(); + Assert::string($docblock); + } + + Assert::stringNotEmpty($docblock); + + if ($context === null) { + $context = new Types\Context(''); + } + + $parts = $this->splitDocBlock($this->stripDocComment($docblock)); + + [$templateMarker, $summary, $description, $tags] = $parts; + + return new DocBlock( + $summary, + $description ? $this->descriptionFactory->create($description, $context) : null, + $this->parseTagBlock($tags, $context), + $context, + $location, + $templateMarker === '#@+', + $templateMarker === '#@-' + ); + } + + /** + * @param class-string $handler + */ + public function registerTagHandler(string $tagName, string $handler) : void + { + $this->tagFactory->registerTagHandler($tagName, $handler); + } + + /** + * Strips the asterisks from the DocBlock comment. + * + * @param string $comment String containing the comment text. + */ + private function stripDocComment(string $comment) : string + { + $comment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]?(.*)?#u', '$1', $comment); + Assert::string($comment); + $comment = trim($comment); + + // reg ex above is not able to remove */ from a single line docblock + if (substr($comment, -2) === '*/') { + $comment = trim(substr($comment, 0, -2)); + } + + return str_replace(["\r\n", "\r"], "\n", $comment); + } + + // phpcs:disable + /** + * Splits the DocBlock into a template marker, summary, description and block of tags. + * + * @param string $comment Comment to split into the sub-parts. + * + * @return string[] containing the template marker (if any), summary, description and a string containing the tags. + * + * @author Mike van Riel for extending the regex with template marker support. + * + * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split. + */ + private function splitDocBlock(string $comment) : array + { + // phpcs:enable + // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This + // method does not split tags so we return this verbatim as the fourth result (tags). This saves us the + // performance impact of running a regular expression + if (strpos($comment, '@') === 0) { + return ['', '', '', $comment]; + } + + // clears all extra horizontal whitespace from the line endings to prevent parsing issues + $comment = preg_replace('/\h*$/Sum', '', $comment); + Assert::string($comment); + /* + * Splits the docblock into a template marker, summary, description and tags section. + * + * - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may + * occur after it and will be stripped). + * - The short description is started from the first character until a dot is encountered followed by a + * newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing + * errors). This is optional. + * - The long description, any character until a new line is encountered followed by an @ and word + * characters (a tag). This is optional. + * - Tags; the remaining characters + * + * Big thanks to RichardJ for contributing this Regular Expression + */ + preg_match( + '/ + \A + # 1. Extract the template marker + (?:(\#\@\+|\#\@\-)\n?)? + + # 2. Extract the summary + (?: + (?! @\pL ) # The summary may not start with an @ + ( + [^\n.]+ + (?: + (?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines + [\n.]* (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line + [^\n.]+ # Include anything else + )* + \.? + )? + ) + + # 3. Extract the description + (?: + \s* # Some form of whitespace _must_ precede a description because a summary must be there + (?! @\pL ) # The description may not start with an @ + ( + [^\n]+ + (?: \n+ + (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line + [^\n]+ # Include anything else + )* + ) + )? + + # 4. Extract the tags (anything that follows) + (\s+ [\s\S]*)? # everything that follows + /ux', + $comment, + $matches + ); + array_shift($matches); + + while (count($matches) < 4) { + $matches[] = ''; + } + + return $matches; + } + + /** + * Creates the tag objects. + * + * @param string $tags Tag block to parse. + * @param Types\Context $context Context of the parsed Tag + * + * @return DocBlock\Tag[] + */ + private function parseTagBlock(string $tags, Types\Context $context) : array + { + $tags = $this->filterTagBlock($tags); + if ($tags === null) { + return []; + } + + $result = []; + $lines = $this->splitTagBlockIntoTagLines($tags); + foreach ($lines as $key => $tagLine) { + $result[$key] = $this->tagFactory->create(trim($tagLine), $context); + } + + return $result; + } + + /** + * @return string[] + */ + private function splitTagBlockIntoTagLines(string $tags) : array + { + $result = []; + foreach (explode("\n", $tags) as $tagLine) { + if ($tagLine !== '' && strpos($tagLine, '@') === 0) { + $result[] = $tagLine; + } else { + $result[count($result) - 1] .= "\n" . $tagLine; + } + } + + return $result; + } + + private function filterTagBlock(string $tags) : ?string + { + $tags = trim($tags); + if (!$tags) { + return null; + } + + if ($tags[0] !== '@') { + // @codeCoverageIgnoreStart + // Can't simulate this; this only happens if there is an error with the parsing of the DocBlock that + // we didn't foresee. + + throw new LogicException('A tag block started with text instead of an at-sign(@): ' . $tags); + + // @codeCoverageIgnoreEnd + } + + return $tags; + } +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..ef039a4d83c8808f6f5661c9db6623c32dc9b11b --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php @@ -0,0 +1,23 @@ +> $additionalTags + */ + public static function createInstance(array $additionalTags = []) : DocBlockFactory; + + /** + * @param string|object $docblock + */ + public function create($docblock, ?Types\Context $context = null, ?Location $location = null) : DocBlock; +} diff --git a/lib/composer/phpdocumentor/reflection-docblock/src/Exception/PcreException.php b/lib/composer/phpdocumentor/reflection-docblock/src/Exception/PcreException.php new file mode 100644 index 0000000000000000000000000000000000000000..77aa40e13502ee09679bb0105b7b355d44095cbe --- /dev/null +++ b/lib/composer/phpdocumentor/reflection-docblock/src/Exception/PcreException.php @@ -0,0 +1,38 @@ + please note that if you want to pass partial class names that additional steps are necessary, see the + > chapter `Resolving partial classes and FQSENs` for more information. + +Where the FqsenResolver can resolve: + +- Constant expressions (i.e. `@see \MyNamespace\MY_CONSTANT`) +- Function expressions (i.e. `@see \MyNamespace\myFunction()`) +- Class expressions (i.e. `@see \MyNamespace\MyClass`) +- Interface expressions (i.e. `@see \MyNamespace\MyInterface`) +- Trait expressions (i.e. `@see \MyNamespace\MyTrait`) +- Class constant expressions (i.e. `@see \MyNamespace\MyClass::MY_CONSTANT`) +- Property expressions (i.e. `@see \MyNamespace\MyClass::$myProperty`) +- Method expressions (i.e. `@see \MyNamespace\MyClass::myMethod()`) + +## Resolving a type + +In order to resolve a type you will have to instantiate the class `\phpDocumentor\Reflection\TypeResolver` and call its `resolve` method like this: + +```php +$typeResolver = new \phpDocumentor\Reflection\TypeResolver(); +$type = $typeResolver->resolve('string|integer'); +``` + +In this example you will receive a Value Object of class `\phpDocumentor\Reflection\Types\Compound` that has two +elements, one of type `\phpDocumentor\Reflection\Types\String_` and one of type +`\phpDocumentor\Reflection\Types\Integer`. + +The real power of this resolver is in its capability to expand partial class names into fully qualified class names; but in order to do that we need an additional `\phpDocumentor\Reflection\Types\Context` class that will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply. + +### Resolving nullable types + +Php 7.1 introduced nullable types e.g. `?string`. Type resolver will resolve the original type without the nullable notation `?` +just like it would do without the `?`. After that the type is wrapped in a `\phpDocumentor\Reflection\Types\Nullable` object. +The `Nullable` type has a method to fetch the actual type. + +## Resolving an FQSEN + +A Fully Qualified Structural Element Name is a reference to another element in your code bases and can be resolved using the `\phpDocumentor\Reflection\FqsenResolver` class' `resolve` method, like this: + +```php +$fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver(); +$fqsen = $fqsenResolver->resolve('\phpDocumentor\Reflection\FqsenResolver::resolve()'); +``` + +In this example we resolve a Fully Qualified Structural Element Name (meaning that it includes the full namespace, class name and element name) and receive a Value Object of type `\phpDocumentor\Reflection\Fqsen`. + +The real power of this resolver is in its capability to expand partial element names into Fully Qualified Structural Element Names; but in order to do that we need an additional `\phpDocumentor\Reflection\Types\Context` class that will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply. + +## Resolving partial Classes and Structural Element Names + +Perhaps the best feature of this library is that it knows how to resolve partial class names into fully qualified class names. + +For example, you have this file: + +```php +namespace My\Example; + +use phpDocumentor\Reflection\Types; + +class Classy +{ + /** + * @var Types\Context + * @see Classy::otherFunction() + */ + public function __construct($context) {} + + public function otherFunction(){} +} +``` + +Suppose that you would want to resolve (and expand) the type in the `@var` tag and the element name in the `@see` tag. + +For the resolvers to know how to expand partial names you have to provide a bit of _Context_ for them by instantiating a new class named `\phpDocumentor\Reflection\Types\Context` with the name of the namespace and the aliases that are in play. + +### Creating a Context + +You can do this by manually creating a Context like this: + +```php +$context = new \phpDocumentor\Reflection\Types\Context( + '\My\Example', + [ 'Types' => '\phpDocumentor\Reflection\Types'] +); +``` + +Or by using the `\phpDocumentor\Reflection\Types\ContextFactory` to instantiate a new context based on a Reflector object or by providing the namespace that you'd like to extract and the source code of the file in which the given type expression occurs. + +```php +$contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory(); +$context = $contextFactory->createFromReflector(new ReflectionMethod('\My\Example\Classy', '__construct')); +``` + +or + +```php +$contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory(); +$context = $contextFactory->createForNamespace('\My\Example', file_get_contents('My/Example/Classy.php')); +``` + +### Using the Context + +After you have obtained a Context it is just a matter of passing it along with the `resolve` method of either Resolver class as second argument and the Resolvers will take this into account when resolving partial names. + +To obtain the resolved class name for the `@var` tag in the example above you can do: + +```php +$typeResolver = new \phpDocumentor\Reflection\TypeResolver(); +$type = $typeResolver->resolve('Types\Context', $context); +``` + +When you do this you will receive an object of class `\phpDocumentor\Reflection\Types\Object_` for which you can call the `getFqsen` method to receive a Value Object that represents the complete FQSEN. So that would be `phpDocumentor\Reflection\Types\Context`. + +> Why is the FQSEN wrapped in another object `Object_`? +> +> The resolve method of the TypeResolver only returns object with the interface `Type` and the FQSEN is a common type that does not represent a Type. Also: in some cases a type can represent an "Untyped Object", meaning that it is an object (signified by the `object` keyword) but does not refer to a specific element using an FQSEN. + +Another example is on how to resolve the FQSEN of a method as can be seen with the `@see` tag in the example above. To resolve that you can do the following: + +```php +$fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver(); +$type = $fqsenResolver->resolve('Classy::otherFunction()', $context); +``` + +Because Classy is a Class in the current namespace its FQSEN will have the `My\Example` namespace and by calling the `resolve` method of the FQSEN Resolver you will receive an `Fqsen` object that refers to `\My\Example\Classy::otherFunction()`. diff --git a/lib/composer/phpdocumentor/type-resolver/composer.json b/lib/composer/phpdocumentor/type-resolver/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..242ecbe39a4f3478adb2e1f9ae509bfadd7045b5 --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/composer.json @@ -0,0 +1,34 @@ +{ + "name": "phpdocumentor/type-resolver", + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "phpDocumentor\\Reflection\\": ["tests/unit", "tests/benchmark"] + } + }, + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/composer.lock b/lib/composer/phpdocumentor/type-resolver/composer.lock new file mode 100644 index 0000000000000000000000000000000000000000..8fa8b87479d29e75ff019238ff0d746cbfb7c1df --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/composer.lock @@ -0,0 +1,71 @@ +{ + "_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": "ee8aea1f755e1772266bc7e041d8ee5b", + "packages": [ + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2020-06-27T09:03:43+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.2 || ^8.0" + }, + "platform-dev": { + "ext-tokenizer": "*" + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/phpbench.json b/lib/composer/phpdocumentor/type-resolver/phpbench.json new file mode 100644 index 0000000000000000000000000000000000000000..ced1ebab47037ac2e82996d39e1eef831781852f --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/phpbench.json @@ -0,0 +1,10 @@ +{ + "bootstrap": "vendor/autoload.php", + "path": "tests/benchmark", + "extensions": [ + "Jaapio\\Blackfire\\Extension" + ], + "blackfire" : { + "env": "c12030d0-c177-47e2-b466-4994c40dc993" + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/FqsenResolver.php b/lib/composer/phpdocumentor/type-resolver/src/FqsenResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..6447a0159d7102f4ca9ce089ddcc9ac8c6925b64 --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/FqsenResolver.php @@ -0,0 +1,79 @@ +isFqsen($fqsen)) { + return new Fqsen($fqsen); + } + + return $this->resolvePartialStructuralElementName($fqsen, $context); + } + + /** + * Tests whether the given type is a Fully Qualified Structural Element Name. + */ + private function isFqsen(string $type) : bool + { + return strpos($type, self::OPERATOR_NAMESPACE) === 0; + } + + /** + * Resolves a partial Structural Element Name (i.e. `Reflection\DocBlock`) to its FQSEN representation + * (i.e. `\phpDocumentor\Reflection\DocBlock`) based on the Namespace and aliases mentioned in the Context. + * + * @throws InvalidArgumentException When type is not a valid FQSEN. + */ + private function resolvePartialStructuralElementName(string $type, Context $context) : Fqsen + { + $typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2); + + $namespaceAliases = $context->getNamespaceAliases(); + + // if the first segment is not an alias; prepend namespace name and return + if (!isset($namespaceAliases[$typeParts[0]])) { + $namespace = $context->getNamespace(); + if ($namespace !== '') { + $namespace .= self::OPERATOR_NAMESPACE; + } + + return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type); + } + + $typeParts[0] = $namespaceAliases[$typeParts[0]]; + + return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts)); + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/PseudoType.php b/lib/composer/phpdocumentor/type-resolver/src/PseudoType.php new file mode 100644 index 0000000000000000000000000000000000000000..f94cff5b35778c0a4c8c15c793b8dde7502cc24d --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/PseudoType.php @@ -0,0 +1,19 @@ + List of recognized keywords and unto which Value Object they map + * @psalm-var array> + */ + private $keywords = [ + 'string' => Types\String_::class, + 'class-string' => Types\ClassString::class, + 'int' => Types\Integer::class, + 'integer' => Types\Integer::class, + 'bool' => Types\Boolean::class, + 'boolean' => Types\Boolean::class, + 'real' => Types\Float_::class, + 'float' => Types\Float_::class, + 'double' => Types\Float_::class, + 'object' => Object_::class, + 'mixed' => Types\Mixed_::class, + 'array' => Array_::class, + 'resource' => Types\Resource_::class, + 'void' => Types\Void_::class, + 'null' => Types\Null_::class, + 'scalar' => Types\Scalar::class, + 'callback' => Types\Callable_::class, + 'callable' => Types\Callable_::class, + 'false' => PseudoTypes\False_::class, + 'true' => PseudoTypes\True_::class, + 'self' => Types\Self_::class, + '$this' => Types\This::class, + 'static' => Types\Static_::class, + 'parent' => Types\Parent_::class, + 'iterable' => Iterable_::class, + ]; + + /** + * @var FqsenResolver + * @psalm-readonly + */ + private $fqsenResolver; + + /** + * Initializes this TypeResolver with the means to create and resolve Fqsen objects. + */ + public function __construct(?FqsenResolver $fqsenResolver = null) + { + $this->fqsenResolver = $fqsenResolver ?: new FqsenResolver(); + } + + /** + * Analyzes the given type and returns the FQCN variant. + * + * When a type is provided this method checks whether it is not a keyword or + * Fully Qualified Class Name. If so it will use the given namespace and + * aliases to expand the type to a FQCN representation. + * + * This method only works as expected if the namespace and aliases are set; + * no dynamic reflection is being performed here. + * + * @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be + * replaced with another namespace. + * @uses Context::getNamespace() to determine with what to prefix the type name. + * + * @param string $type The relative or absolute type. + */ + public function resolve(string $type, ?Context $context = null) : Type + { + $type = trim($type); + if (!$type) { + throw new InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty'); + } + + if ($context === null) { + $context = new Context(''); + } + + // split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)`, `[]`, '<', '>' and type names + $tokens = preg_split( + '/(\\||\\?|<|>|&|, ?|\\(|\\)|\\[\\]+)/', + $type, + -1, + PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE + ); + + if ($tokens === false) { + throw new InvalidArgumentException('Unable to split the type string "' . $type . '" into tokens'); + } + + /** @var ArrayIterator $tokenIterator */ + $tokenIterator = new ArrayIterator($tokens); + + return $this->parseTypes($tokenIterator, $context, self::PARSER_IN_COMPOUND); + } + + /** + * Analyse each tokens and creates types + * + * @param ArrayIterator $tokens the iterator on tokens + * @param int $parserContext on of self::PARSER_* constants, indicating + * the context where we are in the parsing + */ + private function parseTypes(ArrayIterator $tokens, Context $context, int $parserContext) : Type + { + $types = []; + $token = ''; + $compoundToken = '|'; + while ($tokens->valid()) { + $token = $tokens->current(); + if ($token === null) { + throw new RuntimeException( + 'Unexpected nullable character' + ); + } + + if ($token === '|' || $token === '&') { + if (count($types) === 0) { + throw new RuntimeException( + 'A type is missing before a type separator' + ); + } + + if (!in_array($parserContext, [ + self::PARSER_IN_COMPOUND, + self::PARSER_IN_ARRAY_EXPRESSION, + self::PARSER_IN_COLLECTION_EXPRESSION, + ], true) + ) { + throw new RuntimeException( + 'Unexpected type separator' + ); + } + + $compoundToken = $token; + $tokens->next(); + } elseif ($token === '?') { + if (!in_array($parserContext, [ + self::PARSER_IN_COMPOUND, + self::PARSER_IN_ARRAY_EXPRESSION, + self::PARSER_IN_COLLECTION_EXPRESSION, + ], true) + ) { + throw new RuntimeException( + 'Unexpected nullable character' + ); + } + + $tokens->next(); + $type = $this->parseTypes($tokens, $context, self::PARSER_IN_NULLABLE); + $types[] = new Nullable($type); + } elseif ($token === '(') { + $tokens->next(); + $type = $this->parseTypes($tokens, $context, self::PARSER_IN_ARRAY_EXPRESSION); + + $token = $tokens->current(); + if ($token === null) { // Someone did not properly close their array expression .. + break; + } + + $tokens->next(); + + $resolvedType = new Expression($type); + + $types[] = $resolvedType; + } elseif ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION && $token[0] === ')') { + break; + } elseif ($token === '<') { + if (count($types) === 0) { + throw new RuntimeException( + 'Unexpected collection operator "<", class name is missing' + ); + } + + $classType = array_pop($types); + if ($classType !== null) { + if ((string) $classType === 'class-string') { + $types[] = $this->resolveClassString($tokens, $context); + } else { + $types[] = $this->resolveCollection($tokens, $classType, $context); + } + } + + $tokens->next(); + } elseif ($parserContext === self::PARSER_IN_COLLECTION_EXPRESSION + && ($token === '>' || trim($token) === ',') + ) { + break; + } elseif ($token === self::OPERATOR_ARRAY) { + end($types); + $last = key($types); + $lastItem = $types[$last]; + if ($lastItem instanceof Expression) { + $lastItem = $lastItem->getValueType(); + } + + $types[$last] = new Array_($lastItem); + + $tokens->next(); + } else { + $type = $this->resolveSingleType($token, $context); + $tokens->next(); + if ($parserContext === self::PARSER_IN_NULLABLE) { + return $type; + } + + $types[] = $type; + } + } + + if ($token === '|' || $token === '&') { + throw new RuntimeException( + 'A type is missing after a type separator' + ); + } + + if (count($types) === 0) { + if ($parserContext === self::PARSER_IN_NULLABLE) { + throw new RuntimeException( + 'A type is missing after a nullable character' + ); + } + + if ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION) { + throw new RuntimeException( + 'A type is missing in an array expression' + ); + } + + if ($parserContext === self::PARSER_IN_COLLECTION_EXPRESSION) { + throw new RuntimeException( + 'A type is missing in a collection expression' + ); + } + } elseif (count($types) === 1) { + return $types[0]; + } + + if ($compoundToken === '|') { + return new Compound(array_values($types)); + } + + return new Intersection(array_values($types)); + } + + /** + * resolve the given type into a type object + * + * @param string $type the type string, representing a single type + * + * @return Type|Array_|Object_ + * + * @psalm-mutation-free + */ + private function resolveSingleType(string $type, Context $context) : object + { + switch (true) { + case $this->isKeyword($type): + return $this->resolveKeyword($type); + case $this->isFqsen($type): + return $this->resolveTypedObject($type); + case $this->isPartialStructuralElementName($type): + return $this->resolveTypedObject($type, $context); + + // @codeCoverageIgnoreStart + default: + // I haven't got the foggiest how the logic would come here but added this as a defense. + throw new RuntimeException( + 'Unable to resolve type "' . $type . '", there is no known method to resolve it' + ); + } + + // @codeCoverageIgnoreEnd + } + + /** + * Adds a keyword to the list of Keywords and associates it with a specific Value Object. + * + * @psalm-param class-string $typeClassName + */ + public function addKeyword(string $keyword, string $typeClassName) : void + { + if (!class_exists($typeClassName)) { + throw new InvalidArgumentException( + 'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class' + . ' but we could not find the class ' . $typeClassName + ); + } + + if (!in_array(Type::class, class_implements($typeClassName), true)) { + throw new InvalidArgumentException( + 'The class "' . $typeClassName . '" must implement the interface "phpDocumentor\Reflection\Type"' + ); + } + + $this->keywords[$keyword] = $typeClassName; + } + + /** + * Detects whether the given type represents a PHPDoc keyword. + * + * @param string $type A relative or absolute type as defined in the phpDocumentor documentation. + * + * @psalm-mutation-free + */ + private function isKeyword(string $type) : bool + { + return array_key_exists(strtolower($type), $this->keywords); + } + + /** + * Detects whether the given type represents a relative structural element name. + * + * @param string $type A relative or absolute type as defined in the phpDocumentor documentation. + * + * @psalm-mutation-free + */ + private function isPartialStructuralElementName(string $type) : bool + { + return ($type[0] !== self::OPERATOR_NAMESPACE) && !$this->isKeyword($type); + } + + /** + * Tests whether the given type is a Fully Qualified Structural Element Name. + * + * @psalm-mutation-free + */ + private function isFqsen(string $type) : bool + { + return strpos($type, self::OPERATOR_NAMESPACE) === 0; + } + + /** + * Resolves the given keyword (such as `string`) into a Type object representing that keyword. + * + * @psalm-mutation-free + */ + private function resolveKeyword(string $type) : Type + { + $className = $this->keywords[strtolower($type)]; + + return new $className(); + } + + /** + * Resolves the given FQSEN string into an FQSEN object. + * + * @psalm-mutation-free + */ + private function resolveTypedObject(string $type, ?Context $context = null) : Object_ + { + return new Object_($this->fqsenResolver->resolve($type, $context)); + } + + /** + * Resolves class string + * + * @param ArrayIterator $tokens + */ + private function resolveClassString(ArrayIterator $tokens, Context $context) : Type + { + $tokens->next(); + + $classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); + + if (!$classType instanceof Object_ || $classType->getFqsen() === null) { + throw new RuntimeException( + $classType . ' is not a class string' + ); + } + + $token = $tokens->current(); + if ($token !== '>') { + if (empty($token)) { + throw new RuntimeException( + 'class-string: ">" is missing' + ); + } + + throw new RuntimeException( + 'Unexpected character "' . $token . '", ">" is missing' + ); + } + + return new ClassString($classType->getFqsen()); + } + + /** + * Resolves the collection values and keys + * + * @param ArrayIterator $tokens + * + * @return Array_|Iterable_|Collection + */ + private function resolveCollection(ArrayIterator $tokens, Type $classType, Context $context) : Type + { + $isArray = ((string) $classType === 'array'); + $isIterable = ((string) $classType === 'iterable'); + + // allow only "array", "iterable" or class name before "<" + if (!$isArray && !$isIterable + && (!$classType instanceof Object_ || $classType->getFqsen() === null)) { + throw new RuntimeException( + $classType . ' is not a collection' + ); + } + + $tokens->next(); + + $valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); + $keyType = null; + + $token = $tokens->current(); + if ($token !== null && trim($token) === ',') { + // if we have a comma, then we just parsed the key type, not the value type + $keyType = $valueType; + if ($isArray) { + // check the key type for an "array" collection. We allow only + // strings or integers. + if (!$keyType instanceof String_ && + !$keyType instanceof Integer && + !$keyType instanceof Compound + ) { + throw new RuntimeException( + 'An array can have only integers or strings as keys' + ); + } + + if ($keyType instanceof Compound) { + foreach ($keyType->getIterator() as $item) { + if (!$item instanceof String_ && + !$item instanceof Integer + ) { + throw new RuntimeException( + 'An array can have only integers or strings as keys' + ); + } + } + } + } + + $tokens->next(); + // now let's parse the value type + $valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); + } + + $token = $tokens->current(); + if ($token !== '>') { + if (empty($token)) { + throw new RuntimeException( + 'Collection: ">" is missing' + ); + } + + throw new RuntimeException( + 'Unexpected character "' . $token . '", ">" is missing' + ); + } + + if ($isArray) { + return new Array_($valueType, $keyType); + } + + if ($isIterable) { + return new Iterable_($valueType, $keyType); + } + + if ($classType instanceof Object_) { + return $this->makeCollectionFromObject($classType, $valueType, $keyType); + } + + throw new RuntimeException('Invalid $classType provided'); + } + + /** + * @psalm-pure + */ + private function makeCollectionFromObject(Object_ $object, Type $valueType, ?Type $keyType = null) : Collection + { + return new Collection($object->getFqsen(), $valueType, $keyType); + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/AbstractList.php b/lib/composer/phpdocumentor/type-resolver/src/Types/AbstractList.php new file mode 100644 index 0000000000000000000000000000000000000000..bbea4f1414711b5a51ceaa51ea625651ab4e94af --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/AbstractList.php @@ -0,0 +1,83 @@ +valueType = $valueType; + $this->defaultKeyType = new Compound([new String_(), new Integer()]); + $this->keyType = $keyType; + } + + /** + * Returns the type for the keys of this array. + */ + public function getKeyType() : Type + { + return $this->keyType ?? $this->defaultKeyType; + } + + /** + * Returns the value for the keys of this array. + */ + public function getValueType() : Type + { + return $this->valueType; + } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + if ($this->keyType) { + return 'array<' . $this->keyType . ',' . $this->valueType . '>'; + } + + if ($this->valueType instanceof Mixed_) { + return 'array'; + } + + if ($this->valueType instanceof Compound) { + return '(' . $this->valueType . ')[]'; + } + + return $this->valueType . '[]'; + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/AggregatedType.php b/lib/composer/phpdocumentor/type-resolver/src/Types/AggregatedType.php new file mode 100644 index 0000000000000000000000000000000000000000..95222958310c1f4e5ae3a0644971efa23c942460 --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/AggregatedType.php @@ -0,0 +1,124 @@ + + */ +abstract class AggregatedType implements Type, IteratorAggregate +{ + /** + * @psalm-allow-private-mutation + * @var array + */ + private $types = []; + + /** @var string */ + private $token; + + /** + * @param array $types + */ + public function __construct(array $types, string $token) + { + foreach ($types as $type) { + $this->add($type); + } + + $this->token = $token; + } + + /** + * Returns the type at the given index. + */ + public function get(int $index) : ?Type + { + if (!$this->has($index)) { + return null; + } + + return $this->types[$index]; + } + + /** + * Tests if this compound type has a type with the given index. + */ + public function has(int $index) : bool + { + return array_key_exists($index, $this->types); + } + + /** + * Tests if this compound type contains the given type. + */ + public function contains(Type $type) : bool + { + foreach ($this->types as $typePart) { + // if the type is duplicate; do not add it + if ((string) $typePart === (string) $type) { + return true; + } + } + + return false; + } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + return implode($this->token, $this->types); + } + + /** + * @return ArrayIterator + */ + public function getIterator() : ArrayIterator + { + return new ArrayIterator($this->types); + } + + /** + * @psalm-suppress ImpureMethodCall + */ + private function add(Type $type) : void + { + if ($type instanceof self) { + foreach ($type->getIterator() as $subType) { + $this->add($subType); + } + + return; + } + + // if the type is duplicate; do not add it + if ($this->contains($type)) { + return; + } + + $this->types[] = $type; + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/Array_.php b/lib/composer/phpdocumentor/type-resolver/src/Types/Array_.php new file mode 100644 index 0000000000000000000000000000000000000000..7f880e2db2732ab41c70ee078268025541802fae --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/Array_.php @@ -0,0 +1,29 @@ +fqsen = $fqsen; + } + + /** + * Returns the FQSEN associated with this object. + */ + public function getFqsen() : ?Fqsen + { + return $this->fqsen; + } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + if ($this->fqsen === null) { + return 'class-string'; + } + + return 'class-string<' . (string) $this->fqsen . '>'; + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/Collection.php b/lib/composer/phpdocumentor/type-resolver/src/Types/Collection.php new file mode 100644 index 0000000000000000000000000000000000000000..84b4463a6e26bc682bbb91f9845830c72d554edf --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/Collection.php @@ -0,0 +1,68 @@ +` + * 2. `ACollectionObject` + * + * - ACollectionObject can be 'array' or an object that can act as an array + * - aValueType and aKeyType can be any type expression + * + * @psalm-immutable + */ +final class Collection extends AbstractList +{ + /** @var Fqsen|null */ + private $fqsen; + + /** + * Initializes this representation of an array with the given Type or Fqsen. + */ + public function __construct(?Fqsen $fqsen, Type $valueType, ?Type $keyType = null) + { + parent::__construct($valueType, $keyType); + + $this->fqsen = $fqsen; + } + + /** + * Returns the FQSEN associated with this object. + */ + public function getFqsen() : ?Fqsen + { + return $this->fqsen; + } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + $objectType = (string) ($this->fqsen ?? 'object'); + + if ($this->keyType === null) { + return $objectType . '<' . $this->valueType . '>'; + } + + return $objectType . '<' . $this->keyType . ',' . $this->valueType . '>'; + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/Compound.php b/lib/composer/phpdocumentor/type-resolver/src/Types/Compound.php new file mode 100644 index 0000000000000000000000000000000000000000..ad426cc2c03e43a05b4c50509920e219bc1d09a7 --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/Compound.php @@ -0,0 +1,38 @@ + $types + */ + public function __construct(array $types) + { + parent::__construct($types, '|'); + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/Context.php b/lib/composer/phpdocumentor/type-resolver/src/Types/Context.php new file mode 100644 index 0000000000000000000000000000000000000000..c134d7cfd206b42ee4166c7d6bb7ecc059eebfa5 --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/Context.php @@ -0,0 +1,97 @@ + Fully Qualified Namespace. + * @psalm-var array + */ + private $namespaceAliases; + + /** + * Initializes the new context and normalizes all passed namespaces to be in Qualified Namespace Name (QNN) + * format (without a preceding `\`). + * + * @param string $namespace The namespace where this DocBlock resides in. + * @param string[] $namespaceAliases List of namespace aliases => Fully Qualified Namespace. + * + * @psalm-param array $namespaceAliases + */ + public function __construct(string $namespace, array $namespaceAliases = []) + { + $this->namespace = $namespace !== 'global' && $namespace !== 'default' + ? trim($namespace, '\\') + : ''; + + foreach ($namespaceAliases as $alias => $fqnn) { + if ($fqnn[0] === '\\') { + $fqnn = substr($fqnn, 1); + } + + if ($fqnn[strlen($fqnn) - 1] === '\\') { + $fqnn = substr($fqnn, 0, -1); + } + + $namespaceAliases[$alias] = $fqnn; + } + + $this->namespaceAliases = $namespaceAliases; + } + + /** + * Returns the Qualified Namespace Name (thus without `\` in front) where the associated element is in. + */ + public function getNamespace() : string + { + return $this->namespace; + } + + /** + * Returns a list of Qualified Namespace Names (thus without `\` in front) that are imported, the keys represent + * the alias for the imported Namespace. + * + * @return string[] + * + * @psalm-return array + */ + public function getNamespaceAliases() : array + { + return $this->namespaceAliases; + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/ContextFactory.php b/lib/composer/phpdocumentor/type-resolver/src/Types/ContextFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..5d09d565ec73748d768c260a46cbd37b5b7bbb27 --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/ContextFactory.php @@ -0,0 +1,423 @@ + $reflector */ + + return $this->createFromReflectionClass($reflector); + } + + if ($reflector instanceof ReflectionParameter) { + return $this->createFromReflectionParameter($reflector); + } + + if ($reflector instanceof ReflectionMethod) { + return $this->createFromReflectionMethod($reflector); + } + + if ($reflector instanceof ReflectionProperty) { + return $this->createFromReflectionProperty($reflector); + } + + if ($reflector instanceof ReflectionClassConstant) { + return $this->createFromReflectionClassConstant($reflector); + } + + throw new UnexpectedValueException('Unhandled \Reflector instance given: ' . get_class($reflector)); + } + + private function createFromReflectionParameter(ReflectionParameter $parameter) : Context + { + $class = $parameter->getDeclaringClass(); + if (!$class) { + throw new InvalidArgumentException('Unable to get class of ' . $parameter->getName()); + } + + //phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable + /** @var ReflectionClass $class */ + + return $this->createFromReflectionClass($class); + } + + private function createFromReflectionMethod(ReflectionMethod $method) : Context + { + //phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable + /** @var ReflectionClass $class */ + $class = $method->getDeclaringClass(); + + return $this->createFromReflectionClass($class); + } + + private function createFromReflectionProperty(ReflectionProperty $property) : Context + { + //phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable + /** @var ReflectionClass $class */ + $class = $property->getDeclaringClass(); + + return $this->createFromReflectionClass($class); + } + + private function createFromReflectionClassConstant(ReflectionClassConstant $constant) : Context + { + //phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable + /** @var ReflectionClass $class */ + $class = $constant->getDeclaringClass(); + + return $this->createFromReflectionClass($class); + } + + /** + * @param ReflectionClass $class + */ + private function createFromReflectionClass(ReflectionClass $class) : Context + { + $fileName = $class->getFileName(); + $namespace = $class->getNamespaceName(); + + if (is_string($fileName) && file_exists($fileName)) { + $contents = file_get_contents($fileName); + if ($contents === false) { + throw new RuntimeException('Unable to read file "' . $fileName . '"'); + } + + return $this->createForNamespace($namespace, $contents); + } + + return new Context($namespace, []); + } + + /** + * Build a Context for a namespace in the provided file contents. + * + * @see Context for more information on Contexts. + * + * @param string $namespace It does not matter if a `\` precedes the namespace name, + * this method first normalizes. + * @param string $fileContents The file's contents to retrieve the aliases from with the given namespace. + */ + public function createForNamespace(string $namespace, string $fileContents) : Context + { + $namespace = trim($namespace, '\\'); + $useStatements = []; + $currentNamespace = ''; + $tokens = new ArrayIterator(token_get_all($fileContents)); + + while ($tokens->valid()) { + $currentToken = $tokens->current(); + switch ($currentToken[0]) { + case T_NAMESPACE: + $currentNamespace = $this->parseNamespace($tokens); + break; + case T_CLASS: + // Fast-forward the iterator through the class so that any + // T_USE tokens found within are skipped - these are not + // valid namespace use statements so should be ignored. + $braceLevel = 0; + $firstBraceFound = false; + while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) { + $currentToken = $tokens->current(); + if ($currentToken === '{' + || in_array($currentToken[0], [T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES], true)) { + if (!$firstBraceFound) { + $firstBraceFound = true; + } + + ++$braceLevel; + } + + if ($currentToken === '}') { + --$braceLevel; + } + + $tokens->next(); + } + + break; + case T_USE: + if ($currentNamespace === $namespace) { + $useStatements += $this->parseUseStatement($tokens); + } + + break; + } + + $tokens->next(); + } + + return new Context($namespace, $useStatements); + } + + /** + * Deduce the name from tokens when we are at the T_NAMESPACE token. + * + * @param ArrayIterator $tokens + */ + private function parseNamespace(ArrayIterator $tokens) : string + { + // skip to the first string or namespace separator + $this->skipToNextStringOrNamespaceSeparator($tokens); + + $name = ''; + $acceptedTokens = [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED]; + while ($tokens->valid() && in_array($tokens->current()[0], $acceptedTokens, true)) { + $name .= $tokens->current()[1]; + $tokens->next(); + } + + return $name; + } + + /** + * Deduce the names of all imports when we are at the T_USE token. + * + * @param ArrayIterator $tokens + * + * @return string[] + * + * @psalm-return array + */ + private function parseUseStatement(ArrayIterator $tokens) : array + { + $uses = []; + + while ($tokens->valid()) { + $this->skipToNextStringOrNamespaceSeparator($tokens); + + $uses += $this->extractUseStatements($tokens); + $currentToken = $tokens->current(); + if ($currentToken[0] === self::T_LITERAL_END_OF_USE) { + return $uses; + } + } + + return $uses; + } + + /** + * Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token. + * + * @param ArrayIterator $tokens + */ + private function skipToNextStringOrNamespaceSeparator(ArrayIterator $tokens) : void + { + while ($tokens->valid()) { + $currentToken = $tokens->current(); + if (in_array($currentToken[0], [T_STRING, T_NS_SEPARATOR], true)) { + break; + } + + if ($currentToken[0] === T_NAME_QUALIFIED) { + break; + } + + if (defined('T_NAME_FULLY_QUALIFIED') && $currentToken[0] === T_NAME_FULLY_QUALIFIED) { + break; + } + + $tokens->next(); + } + } + + /** + * Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of + * a USE statement yet. This will return a key/value array of the alias => namespace. + * + * @param ArrayIterator $tokens + * + * @return string[] + * + * @psalm-suppress TypeDoesNotContainType + * + * @psalm-return array + */ + private function extractUseStatements(ArrayIterator $tokens) : array + { + $extractedUseStatements = []; + $groupedNs = ''; + $currentNs = ''; + $currentAlias = ''; + $state = 'start'; + + while ($tokens->valid()) { + $currentToken = $tokens->current(); + $tokenId = is_string($currentToken) ? $currentToken : $currentToken[0]; + $tokenValue = is_string($currentToken) ? null : $currentToken[1]; + switch ($state) { + case 'start': + switch ($tokenId) { + case T_STRING: + case T_NS_SEPARATOR: + $currentNs .= (string) $tokenValue; + $currentAlias = $tokenValue; + break; + case T_NAME_QUALIFIED: + case T_NAME_FULLY_QUALIFIED: + $currentNs .= (string) $tokenValue; + $currentAlias = substr( + (string) $tokenValue, + (int) (strrpos((string) $tokenValue, '\\')) + 1 + ); + break; + case T_CURLY_OPEN: + case '{': + $state = 'grouped'; + $groupedNs = $currentNs; + break; + case T_AS: + $state = 'start-alias'; + break; + case self::T_LITERAL_USE_SEPARATOR: + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } + + break; + case 'start-alias': + switch ($tokenId) { + case T_STRING: + $currentAlias = $tokenValue; + break; + case self::T_LITERAL_USE_SEPARATOR: + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } + + break; + case 'grouped': + switch ($tokenId) { + case T_STRING: + case T_NS_SEPARATOR: + $currentNs .= (string) $tokenValue; + $currentAlias = $tokenValue; + break; + case T_AS: + $state = 'grouped-alias'; + break; + case self::T_LITERAL_USE_SEPARATOR: + $state = 'grouped'; + $extractedUseStatements[(string) $currentAlias] = $currentNs; + $currentNs = $groupedNs; + $currentAlias = ''; + break; + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } + + break; + case 'grouped-alias': + switch ($tokenId) { + case T_STRING: + $currentAlias = $tokenValue; + break; + case self::T_LITERAL_USE_SEPARATOR: + $state = 'grouped'; + $extractedUseStatements[(string) $currentAlias] = $currentNs; + $currentNs = $groupedNs; + $currentAlias = ''; + break; + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } + } + + if ($state === 'end') { + break; + } + + $tokens->next(); + } + + if ($groupedNs !== $currentNs) { + $extractedUseStatements[(string) $currentAlias] = $currentNs; + } + + return $extractedUseStatements; + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/Expression.php b/lib/composer/phpdocumentor/type-resolver/src/Types/Expression.php new file mode 100644 index 0000000000000000000000000000000000000000..4a8ae1fcf783ef5589509259167552d6db631486 --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/Expression.php @@ -0,0 +1,51 @@ +valueType = $valueType; + } + + /** + * Returns the value for the keys of this array. + */ + public function getValueType() : Type + { + return $this->valueType; + } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + return '(' . $this->valueType . ')'; + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/Float_.php b/lib/composer/phpdocumentor/type-resolver/src/Types/Float_.php new file mode 100644 index 0000000000000000000000000000000000000000..e70ce7dd5d3ed446464e2f45e045fa569e05ddb2 --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/Float_.php @@ -0,0 +1,32 @@ + $types + */ + public function __construct(array $types) + { + parent::__construct($types, '&'); + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/Iterable_.php b/lib/composer/phpdocumentor/type-resolver/src/Types/Iterable_.php new file mode 100644 index 0000000000000000000000000000000000000000..a03a7cd3f0195372319fbf273aef6b5d3637346e --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/Iterable_.php @@ -0,0 +1,38 @@ +keyType) { + return 'iterable<' . $this->keyType . ',' . $this->valueType . '>'; + } + + if ($this->valueType instanceof Mixed_) { + return 'iterable'; + } + + return 'iterable<' . $this->valueType . '>'; + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/Mixed_.php b/lib/composer/phpdocumentor/type-resolver/src/Types/Mixed_.php new file mode 100644 index 0000000000000000000000000000000000000000..2fedff4009b559e8d61b759ac93c62ab0e2be424 --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/Mixed_.php @@ -0,0 +1,32 @@ +realType = $realType; + } + + /** + * Provide access to the actual type directly, if needed. + */ + public function getActualType() : Type + { + return $this->realType; + } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + return '?' . $this->realType->__toString(); + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/Object_.php b/lib/composer/phpdocumentor/type-resolver/src/Types/Object_.php new file mode 100644 index 0000000000000000000000000000000000000000..4cfe2a0c32273651e62050c407a9078deb15607c --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/Object_.php @@ -0,0 +1,68 @@ +fqsen = $fqsen; + } + + /** + * Returns the FQSEN associated with this object. + */ + public function getFqsen() : ?Fqsen + { + return $this->fqsen; + } + + public function __toString() : string + { + if ($this->fqsen) { + return (string) $this->fqsen; + } + + return 'object'; + } +} diff --git a/lib/composer/phpdocumentor/type-resolver/src/Types/Parent_.php b/lib/composer/phpdocumentor/type-resolver/src/Types/Parent_.php new file mode 100644 index 0000000000000000000000000000000000000000..08900abce9950a3dbcbeeee79c3e70c1ec537e65 --- /dev/null +++ b/lib/composer/phpdocumentor/type-resolver/src/Types/Parent_.php @@ -0,0 +1,34 @@ +=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/lib/composer/psr/container/src/ContainerExceptionInterface.php b/lib/composer/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..d35c6b4d864d9870f1517f47c2917b9e7e2b9a35 --- /dev/null +++ b/lib/composer/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,13 @@ +=7.2.0" + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/lib/composer/psr/event-dispatcher/src/EventDispatcherInterface.php b/lib/composer/psr/event-dispatcher/src/EventDispatcherInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..4306fa91560605ee1419f4535275dea32db19e04 --- /dev/null +++ b/lib/composer/psr/event-dispatcher/src/EventDispatcherInterface.php @@ -0,0 +1,21 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/lib/composer/psr/log/Psr/Log/InvalidArgumentException.php b/lib/composer/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..67f852d1dbc660ba05e703fe9b727727da2b3f78 --- /dev/null +++ b/lib/composer/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/lib/composer/psr/log/Psr/Log/LoggerInterface.php b/lib/composer/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..2206cfde41aec794089817f90269a03d251db018 --- /dev/null +++ b/lib/composer/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/lib/composer/psr/log/Psr/Log/NullLogger.php b/lib/composer/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000000000000000000000000000000000000..c8f7293b1c66886bcb2d47f30f5a8decc67712ba --- /dev/null +++ b/lib/composer/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/lib/composer/psr/log/Psr/Log/Test/DummyTest.php b/lib/composer/psr/log/Psr/Log/Test/DummyTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9638c11018611d55562401c5159513211ca1c377 --- /dev/null +++ b/lib/composer/psr/log/Psr/Log/Test/DummyTest.php @@ -0,0 +1,18 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $closed = fopen('php://memory', 'r'); + fclose($closed); + + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + 'closed' => $closed, + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} diff --git a/lib/composer/psr/log/Psr/Log/Test/TestLogger.php b/lib/composer/psr/log/Psr/Log/Test/TestLogger.php new file mode 100644 index 0000000000000000000000000000000000000000..1be3230496b704fe90bc5e6cf69e7a615863c7d1 --- /dev/null +++ b/lib/composer/psr/log/Psr/Log/Test/TestLogger.php @@ -0,0 +1,147 @@ + $level, + 'message' => $message, + 'context' => $context, + ]; + + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = ['message' => $record]; + } + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + return true; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses(callable $predicate, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + return false; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = strtolower($matches[2]); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + return call_user_func_array([$this, $genericMethod], $args); + } + } + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } + + public function reset() + { + $this->records = []; + $this->recordsByLevel = []; + } +} diff --git a/lib/composer/psr/log/README.md b/lib/composer/psr/log/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a9f20c437b385e5afab15096bde84c51e31bc812 --- /dev/null +++ b/lib/composer/psr/log/README.md @@ -0,0 +1,58 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Installation +------------ + +```bash +composer require psr/log +``` + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/lib/composer/psr/log/composer.json b/lib/composer/psr/log/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..3f6d4eea4cb348c964c7642291dd7c608697c028 --- /dev/null +++ b/lib/composer/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/lib/composer/sebastian/diff/ChangeLog.md b/lib/composer/sebastian/diff/ChangeLog.md new file mode 100644 index 0000000000000000000000000000000000000000..d65d13dfeebce80965a5ec92424c0fc257a8e54b --- /dev/null +++ b/lib/composer/sebastian/diff/ChangeLog.md @@ -0,0 +1,81 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [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.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/lib/composer/sebastian/diff/LICENSE b/lib/composer/sebastian/diff/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f22f31cf0cf7433dad9af78d870e63a976a135c2 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/README.md b/lib/composer/sebastian/diff/README.md new file mode 100644 index 0000000000000000000000000000000000000000..734b852de7c68d165a44a2a81dd02764ba7c4534 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/composer.json b/lib/composer/sebastian/diff/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..cf92202ba3cb08162c8058e7ea85b4002443c145 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/Chunk.php b/lib/composer/sebastian/diff/src/Chunk.php new file mode 100644 index 0000000000000000000000000000000000000000..16ae34f41484fa83494bb07ad0a5167ad6737d03 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/Diff.php b/lib/composer/sebastian/diff/src/Diff.php new file mode 100644 index 0000000000000000000000000000000000000000..17b2084f9062bc0fb1016a1c18c9c85da3d167e9 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/Differ.php b/lib/composer/sebastian/diff/src/Differ.php new file mode 100644 index 0000000000000000000000000000000000000000..5a4d9d10242ad565e170a81dc4575def7cbf0878 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/Exception/ConfigurationException.php b/lib/composer/sebastian/diff/src/Exception/ConfigurationException.php new file mode 100644 index 0000000000000000000000000000000000000000..b767b2194a2742374c16cbf2189f2b5256d303a9 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/Exception/Exception.php b/lib/composer/sebastian/diff/src/Exception/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..618d0b9d64f588b7171e4c34544f99e5cdae286d --- /dev/null +++ b/lib/composer/sebastian/diff/src/Exception/Exception.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; + +interface Exception +{ +} diff --git a/lib/composer/sebastian/diff/src/Exception/InvalidArgumentException.php b/lib/composer/sebastian/diff/src/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..846ac3fbd6e7d4348a57609cdb375523ae555f3f --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/Line.php b/lib/composer/sebastian/diff/src/Line.php new file mode 100644 index 0000000000000000000000000000000000000000..3596ed264c26e246b68135297b2a3f51c0b71cf7 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/LongestCommonSubsequenceCalculator.php b/lib/composer/sebastian/diff/src/LongestCommonSubsequenceCalculator.php new file mode 100644 index 0000000000000000000000000000000000000000..dea8fe1cb8b265360c7fe02be78749ed960dc5de --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php b/lib/composer/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php new file mode 100644 index 0000000000000000000000000000000000000000..0b626eaff9240dd380abc6baf18fdfdaafa47e48 --- /dev/null +++ b/lib/composer/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.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\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 { + $current[$j + 1] = max($current[$j], $prev[$j + 1]); + } + } + } + + return $current; + } +} diff --git a/lib/composer/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php b/lib/composer/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..e55757c3800c9b22f0817db367d45b9816056832 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php b/lib/composer/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..f79a935cb822ca35fa6ebaf660ed7ce531556c24 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/Output/DiffOutputBuilderInterface.php b/lib/composer/sebastian/diff/src/Output/DiffOutputBuilderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..0e18f9f2e65286db1b76f9d8081c7c4b538d45d7 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php b/lib/composer/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..9c55ab2aafd16abc2206933aa3f21e80f2211fa3 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php b/lib/composer/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..8aae645043fe557b18aad5477f61f898e1499ae4 --- /dev/null +++ b/lib/composer/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/lib/composer/sebastian/diff/src/Parser.php b/lib/composer/sebastian/diff/src/Parser.php new file mode 100644 index 0000000000000000000000000000000000000000..6090e814ec30d8513ca3a75ad5343faea565d301 --- /dev/null +++ b/lib/composer/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('(^---\\s+(?P\\S+))', $lines[$i], $fromMatch) && + preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $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/lib/composer/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php b/lib/composer/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php new file mode 100644 index 0000000000000000000000000000000000000000..fd19cac76393c91f86671bedba2a774fd2a223b6 --- /dev/null +++ b/lib/composer/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.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 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; + $matrix[$o] = max( + $matrix[$o - 1], + $matrix[$o - $width], + $from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0 + ); + } + } + + $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/lib/composer/symfony/console/Application.php b/lib/composer/symfony/console/Application.php new file mode 100644 index 0000000000000000000000000000000000000000..784af89b3189a190b1b0a2c5e7837c2939b4d591 --- /dev/null +++ b/lib/composer/symfony/console/Application.php @@ -0,0 +1,1183 @@ + + * + * 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\HelpCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +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\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\DebugFormatterHelper; +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\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 $commands = []; + private $wantHelps = false; + private $runningCommand; + private $name; + private $version; + private $commandLoader; + private $catchExceptions = true; + private $autoExit = true; + private $definition; + private $helperSet; + private $dispatcher; + private $terminal; + private $defaultCommand; + private $singleCommand = false; + private $initialized; + + public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN') + { + $this->name = $name; + $this->version = $version; + $this->terminal = new Terminal(); + $this->defaultCommand = 'list'; + } + + /** + * @final + */ + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + public function setCommandLoader(CommandLoaderInterface $commandLoader) + { + $this->commandLoader = $commandLoader; + } + + /** + * 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) + { + if (\function_exists('putenv')) { + @putenv('LINES='.$this->terminal->getHeight()); + @putenv('COLUMNS='.$this->terminal->getWidth()); + } + + if (null === $input) { + $input = new ArgvInput(); + } + + if (null === $output) { + $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 (\Exception $e) { + if (!$this->catchExceptions) { + throw $e; + } + + $renderException($e); + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if (0 === $exitCode) { + $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) + { + 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 $e) { + // 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()) { + 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(); + } + + throw $e; + } + + $alternative = $alternatives[0]; + + $style = new SymfonyStyle($input, $output); + $style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error'); + 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); + } + + $this->runningCommand = $command; + $exitCode = $this->doRunCommand($command, $input, $output); + $this->runningCommand = null; + + return $exitCode; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + } + + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Get the helper set associated with the command. + * + * @return HelperSet The HelperSet instance associated with this command + */ + public function getHelperSet() + { + if (!$this->helperSet) { + $this->helperSet = $this->getDefaultHelperSet(); + } + + return $this->helperSet; + } + + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + } + + /** + * Gets the InputDefinition related to this Application. + * + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition() + { + if (!$this->definition) { + $this->definition = $this->getDefaultInputDefinition(); + } + + if ($this->singleCommand) { + $inputDefinition = $this->definition; + $inputDefinition->setArguments(); + + return $inputDefinition; + } + + return $this->definition; + } + + /** + * Gets the help message. + * + * @return string A help message + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * Gets whether to catch exceptions or not during commands execution. + * + * @return bool Whether to catch exceptions or not during commands execution + */ + public function areExceptionsCaught() + { + return $this->catchExceptions; + } + + /** + * Sets whether to catch exceptions or not during commands execution. + */ + public function setCatchExceptions(bool $boolean) + { + $this->catchExceptions = $boolean; + } + + /** + * Gets whether to automatically exit after a command execution or not. + * + * @return bool Whether to automatically exit after a command execution or not + */ + public function isAutoExitEnabled() + { + return $this->autoExit; + } + + /** + * Sets whether to automatically exit after a command execution or not. + */ + public function setAutoExit(bool $boolean) + { + $this->autoExit = $boolean; + } + + /** + * Gets the name of the application. + * + * @return string The application name + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the application name. + **/ + public function setName(string $name) + { + $this->name = $name; + } + + /** + * Gets the application version. + * + * @return string The application version + */ + public function getVersion() + { + return $this->version; + } + + /** + * Sets the application version. + */ + public function setVersion(string $version) + { + $this->version = $version; + } + + /** + * Returns the long version of the application. + * + * @return string The long application version + */ + public function getLongVersion() + { + 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. + * + * @return Command The newly created command + */ + public function register(string $name) + { + 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) + { + 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. + * + * @return Command|null The registered command if enabled or null + */ + public function add(Command $command) + { + $this->init(); + + $command->setApplication($this); + + if (!$command->isEnabled()) { + $command->setApplication(null); + + return null; + } + + // 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. + * + * @return Command A Command object + * + * @throws CommandNotFoundException When given command name does not exist + */ + public function get(string $name) + { + $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. + * + * @return bool true if the command exists, false otherwise + */ + public function has(string $name) + { + $this->init(); + + return isset($this->commands[$name]) || ($this->commandLoader && $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[] An array of namespaces + */ + public function getNamespaces() + { + $namespaces = []; + foreach ($this->all() as $command) { + if ($command->isHidden()) { + continue; + } + + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + } + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * Finds a registered namespace by a name or an abbreviation. + * + * @return string A registered namespace + * + * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous + */ + public function findNamespace(string $namespace) + { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $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. + * + * @return Command A Command instance + * + * @throws CommandNotFoundException When command name is incorrect or ambiguous + */ + public function find(string $name) + { + $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 = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $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, function ($name) { + return !$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::strlen($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::strlen($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[] An array of Command instances + */ + public function all(string $namespace = null) + { + $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[][] An array of abbreviations + */ + public static function getAbbreviations(array $names) + { + $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', 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::strlen($title); + } else { + $len = 0; + } + + if (false !== strpos($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]++/', function ($m) { + return 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::strlen($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::strlen($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 = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = isset($trace[$i]['function']) ? $trace[$i]['function'] : ''; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['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) + { + 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) + { + foreach ($command->getHelperSet() as $helper) { + if ($helper instanceof InputAwareInterface) { + $helper->setInput($input); + } + } + + 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 $e) { + // 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. + * + * @return string|null + */ + protected function getCommandName(InputInterface $input) + { + return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); + } + + /** + * Gets the default input definition. + * + * @return InputDefinition An InputDefinition instance + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), + 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_NONE, 'Force ANSI output'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + 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[] An array of default Command instances + */ + protected function getDefaultCommands() + { + return [new HelpCommand(), new ListCommand()]; + } + + /** + * Gets the default helper set with the helpers that should always be available. + * + * @return HelperSet A HelperSet instance + */ + protected function getDefaultHelperSet() + { + 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. + * + * @return string The namespace of the command + */ + public function extractNamespace(string $name, int $limit = null) + { + $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[] A sorted array of similar 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 && false !== strpos($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 || false !== strpos($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); + + return array_keys($alternatives); + } + + /** + * Sets the default Command name. + * + * @return self + */ + public function setDefaultCommand(string $commandName, bool $isSingleCommand = false) + { + $this->defaultCommand = $commandName; + + 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[] The namespaces of the command + */ + 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() + { + if ($this->initialized) { + return; + } + $this->initialized = true; + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } +} diff --git a/lib/composer/symfony/console/CHANGELOG.md b/lib/composer/symfony/console/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..788bf4279a40cbc7e79ed20a0fd2eb80ab90a8cc --- /dev/null +++ b/lib/composer/symfony/console/CHANGELOG.md @@ -0,0 +1,185 @@ +CHANGELOG +========= + +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/lib/composer/symfony/console/Command/Command.php b/lib/composer/symfony/console/Command/Command.php new file mode 100644 index 0000000000000000000000000000000000000000..bcb86cdfbebbc97b43f56cfd8f6f074e0f5e34eb --- /dev/null +++ b/lib/composer/symfony/console/Command/Command.php @@ -0,0 +1,646 @@ + + * + * 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\Exception\ExceptionInterface; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +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 +{ + public const SUCCESS = 0; + public const FAILURE = 1; + + /** + * @var string|null The default command name + */ + protected static $defaultName; + + private $application; + private $name; + private $processTitle; + private $aliases = []; + private $definition; + private $hidden = false; + private $help = ''; + private $description = ''; + private $ignoreValidationErrors = false; + private $applicationDefinitionMerged = false; + private $applicationDefinitionMergedWithArgs = false; + private $code; + private $synopsis = []; + private $usages = []; + private $helperSet; + + /** + * @return string|null The default command name or null when no default name is set + */ + public static function getDefaultName() + { + $class = static::class; + $r = new \ReflectionProperty($class, 'defaultName'); + + return $class === $r->class ? static::$defaultName : 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()) { + $this->setName($name); + } + + $this->configure(); + } + + /** + * Ignores validation errors. + * + * This is mainly useful for the help command. + */ + public function ignoreValidationErrors() + { + $this->ignoreValidationErrors = true; + } + + public function setApplication(Application $application = null) + { + $this->application = $application; + if ($application) { + $this->setHelperSet($application->getHelperSet()); + } else { + $this->helperSet = null; + } + } + + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set. + * + * @return HelperSet|null A HelperSet instance + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Gets the application instance for this command. + * + * @return Application|null An Application instance + */ + public function getApplication() + { + 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 can not + * run properly under the current conditions. + * + * @return bool + */ + public function isEnabled() + { + return true; + } + + /** + * Configures the current command. + */ + 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) + { + 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. + */ + 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() + */ + 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 \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}. + * + * @see setCode() + * @see execute() + */ + public function run(InputInterface $input, OutputInterface $output) + { + // force the creation of the synopsis before the merge with the app definition + $this->getSynopsis(true); + $this->getSynopsis(false); + + // add the application arguments and options + $this->mergeApplicationDefinition(); + + // bind the input against the command specific arguments/options + try { + $input->bind($this->definition); + } 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; + } + + /** + * 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) + { + if ($code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + $code = \Closure::bind($code, $this); + } + } + + $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 + */ + public function mergeApplicationDefinition(bool $mergeArgs = true) + { + if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { + return; + } + + $this->definition->addOptions($this->application->getDefinition()->getOptions()); + + $this->applicationDefinitionMerged = true; + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->application->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + + $this->applicationDefinitionMergedWithArgs = true; + } + } + + /** + * Sets an array of argument and option instances. + * + * @param array|InputDefinition $definition An array of argument and option instances or a definition instance + * + * @return $this + */ + public function setDefinition($definition) + { + if ($definition instanceof InputDefinition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->applicationDefinitionMerged = false; + + return $this; + } + + /** + * Gets the InputDefinition attached to this Command. + * + * @return InputDefinition An InputDefinition instance + */ + public function getDefinition() + { + if (null === $this->definition) { + throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); + } + + return $this->definition; + } + + /** + * 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. + * + * @return InputDefinition An InputDefinition instance + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * Adds an argument. + * + * @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param string|string[]|null $default The default value (for InputArgument::OPTIONAL mode only) + * + * @throws InvalidArgumentException When argument mode is not valid + * + * @return $this + */ + public function addArgument(string $name, int $mode = null, string $description = '', $default = null) + { + $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); + + return $this; + } + + /** + * Adds an option. + * + * @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 InputOption::VALUE_* constants + * @param string|string[]|int|bool|null $default The default value (must be null for InputOption::VALUE_NONE) + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + * + * @return $this + */ + public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null) + { + $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); + + 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) + { + $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) + { + $this->processTitle = $title; + + return $this; + } + + /** + * Returns the command name. + * + * @return string|null + */ + public function getName() + { + return $this->name; + } + + /** + * @param bool $hidden Whether or not the command should be hidden from the list of commands + * The default value will be true in Symfony 6.0 + * + * @return Command The current instance + * + * @final since Symfony 5.1 + */ + public function setHidden(bool $hidden /*= true*/) + { + $this->hidden = $hidden; + + return $this; + } + + /** + * @return bool whether the command should be publicly shown or not + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Sets the description for the command. + * + * @return $this + */ + public function setDescription(string $description) + { + $this->description = $description; + + return $this; + } + + /** + * Returns the description for the command. + * + * @return string The description for the command + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the help for the command. + * + * @return $this + */ + public function setHelp(string $help) + { + $this->help = $help; + + return $this; + } + + /** + * Returns the help for the command. + * + * @return string The help for the command + */ + public function getHelp() + { + return $this->help; + } + + /** + * Returns the processed help for the command replacing the %command.name% and + * %command.full_name% patterns with the real values dynamically. + * + * @return string The processed help for the command + */ + public function getProcessedHelp() + { + $name = $this->name; + $isSingleCommand = $this->application && $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) + { + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * Returns the aliases for the command. + * + * @return array An array of aliases for the command + */ + public function getAliases() + { + 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 + * + * @return string The synopsis + */ + public function getSynopsis(bool $short = false) + { + $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) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * Returns alternative usages of the command. + * + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * Gets a helper instance by name. + * + * @return mixed The helper value + * + * @throws LogicException if no HelperSet is defined + * @throws InvalidArgumentException if the helper is not defined + */ + public function getHelper(string $name) + { + 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) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/lib/composer/symfony/console/Command/HelpCommand.php b/lib/composer/symfony/console/Command/HelpCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..b32be4c95cce42434ea149c2641e590108320f38 --- /dev/null +++ b/lib/composer/symfony/console/Command/HelpCommand.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\Command; + +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; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this + ->setName('help') + ->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + ]) + ->setDescription('Displays help for a command') + ->setHelp(<<<'EOF' +The %command.name% command displays help for a given command: + + php %command.full_name% list + +You can also output the help in other formats by using the --format option: + + php %command.full_name% --format=xml list + +To display the list of available commands, please use the list command. +EOF + ) + ; + } + + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if (null === $this->command) { + $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'), + ]); + + $this->command = null; + + return 0; + } +} diff --git a/lib/composer/symfony/console/Command/ListCommand.php b/lib/composer/symfony/console/Command/ListCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..8af952652872a1310e1b84beaa63ea2a7d52000b --- /dev/null +++ b/lib/composer/symfony/console/Command/ListCommand.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\Command; + +use Symfony\Component\Console\Helper\DescriptorHelper; +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; + +/** + * ListCommand displays the list of all available commands for the application. + * + * @author Fabien Potencier + */ +class ListCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('list') + ->setDefinition($this->createDefinition()) + ->setDescription('Lists commands') + ->setHelp(<<<'EOF' +The %command.name% command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +You can also output the information in other formats by using the --format option: + + php %command.full_name% --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $helper = new DescriptorHelper(); + $helper->describe($output, $this->getApplication(), [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + ]); + + return 0; + } + + private function createDefinition(): InputDefinition + { + return new InputDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + 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'), + ]); + } +} diff --git a/lib/composer/symfony/console/Command/LockableTrait.php b/lib/composer/symfony/console/Command/LockableTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..60cfe360f74af3cdb36debe7167b94d54f30ee93 --- /dev/null +++ b/lib/composer/symfony/console/Command/LockableTrait.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 Symfony\Component\Console\Command; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Lock\Lock; +use Symfony\Component\Lock\LockFactory; +use Symfony\Component\Lock\Store\FlockStore; +use Symfony\Component\Lock\Store\SemaphoreStore; + +/** + * Basic lock feature for commands. + * + * @author Geoffrey Brier + */ +trait LockableTrait +{ + /** @var Lock */ + private $lock; + + /** + * 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.'); + } + + 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() + { + if ($this->lock) { + $this->lock->release(); + $this->lock = null; + } + } +} diff --git a/lib/composer/symfony/console/CommandLoader/CommandLoaderInterface.php b/lib/composer/symfony/console/CommandLoader/CommandLoaderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..d4f44e88fd9742f949dc4f25200e2cf36efd7487 --- /dev/null +++ b/lib/composer/symfony/console/CommandLoader/CommandLoaderInterface.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 Symfony\Component\Console\CommandLoader; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * @author Robin Chalas + */ +interface CommandLoaderInterface +{ + /** + * Loads a command. + * + * @return Command + * + * @throws CommandNotFoundException + */ + public function get(string $name); + + /** + * Checks if a command exists. + * + * @return bool + */ + public function has(string $name); + + /** + * @return string[] All registered command names + */ + public function getNames(); +} diff --git a/lib/composer/symfony/console/CommandLoader/ContainerCommandLoader.php b/lib/composer/symfony/console/CommandLoader/ContainerCommandLoader.php new file mode 100644 index 0000000000000000000000000000000000000000..ddccb3d45f416924cf448a94e14c4527c66b581e --- /dev/null +++ b/lib/composer/symfony/console/CommandLoader/ContainerCommandLoader.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 Symfony\Component\Console\CommandLoader; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * Loads commands from a PSR-11 container. + * + * @author Robin Chalas + */ +class ContainerCommandLoader implements CommandLoaderInterface +{ + private $container; + private $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; + } + + /** + * {@inheritdoc} + */ + public function get(string $name) + { + if (!$this->has($name)) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + return $this->container->get($this->commandMap[$name]); + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]); + } + + /** + * {@inheritdoc} + */ + public function getNames() + { + return array_keys($this->commandMap); + } +} diff --git a/lib/composer/symfony/console/CommandLoader/FactoryCommandLoader.php b/lib/composer/symfony/console/CommandLoader/FactoryCommandLoader.php new file mode 100644 index 0000000000000000000000000000000000000000..7e2db346471a2fd2c5f3b5331fb1ff41856847d9 --- /dev/null +++ b/lib/composer/symfony/console/CommandLoader/FactoryCommandLoader.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\Console\CommandLoader; + +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * A simple command loader using factories to instantiate commands lazily. + * + * @author Maxime Steinhausser + */ +class FactoryCommandLoader implements CommandLoaderInterface +{ + private $factories; + + /** + * @param callable[] $factories Indexed by command names + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + return isset($this->factories[$name]); + } + + /** + * {@inheritdoc} + */ + public function get(string $name) + { + if (!isset($this->factories[$name])) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + $factory = $this->factories[$name]; + + return $factory(); + } + + /** + * {@inheritdoc} + */ + public function getNames() + { + return array_keys($this->factories); + } +} diff --git a/lib/composer/symfony/console/ConsoleEvents.php b/lib/composer/symfony/console/ConsoleEvents.php new file mode 100644 index 0000000000000000000000000000000000000000..4975643aedf2bb2ea96a92163489743f44e0b13f --- /dev/null +++ b/lib/composer/symfony/console/ConsoleEvents.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\Component\Console; + +/** + * 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 handled to the command. + * + * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent") + */ + const COMMAND = 'console.command'; + + /** + * The TERMINATE event allows you to attach listeners after a command is + * executed by the console. + * + * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent") + */ + 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") + */ + const ERROR = 'console.error'; +} diff --git a/lib/composer/symfony/console/Cursor.php b/lib/composer/symfony/console/Cursor.php new file mode 100644 index 0000000000000000000000000000000000000000..dcb5b5ad39854cb163bbc5d6d356f03916ae5c98 --- /dev/null +++ b/lib/composer/symfony/console/Cursor.php @@ -0,0 +1,168 @@ + + * + * 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 $output; + private $input; + + public function __construct(OutputInterface $output, $input = null) + { + $this->output = $output; + $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+')); + } + + public function moveUp(int $lines = 1): self + { + $this->output->write(sprintf("\x1b[%dA", $lines)); + + return $this; + } + + public function moveDown(int $lines = 1): self + { + $this->output->write(sprintf("\x1b[%dB", $lines)); + + return $this; + } + + public function moveRight(int $columns = 1): self + { + $this->output->write(sprintf("\x1b[%dC", $columns)); + + return $this; + } + + public function moveLeft(int $columns = 1): self + { + $this->output->write(sprintf("\x1b[%dD", $columns)); + + return $this; + } + + public function moveToColumn(int $column): self + { + $this->output->write(sprintf("\x1b[%dG", $column)); + + return $this; + } + + public function moveToPosition(int $column, int $row): self + { + $this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column)); + + return $this; + } + + public function savePosition(): self + { + $this->output->write("\x1b7"); + + return $this; + } + + public function restorePosition(): self + { + $this->output->write("\x1b8"); + + return $this; + } + + public function hide(): self + { + $this->output->write("\x1b[?25l"); + + return $this; + } + + public function show(): self + { + $this->output->write("\x1b[?25h\x1b[?0c"); + + return $this; + } + + /** + * Clears all the output from the current line. + */ + public function clearLine(): self + { + $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. + */ + public function clearOutput(): self + { + $this->output->write("\x1b[0J"); + + return $this; + } + + /** + * Clears the entire screen. + */ + public function clearScreen(): self + { + $this->output->write("\x1b[2J"); + + return $this; + } + + /** + * Returns the current cursor position as x,y coordinates. + */ + public function getCurrentPosition(): array + { + static $isTtySupported; + + if (null === $isTtySupported && \function_exists('proc_open')) { + $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); + } + + if (!$isTtySupported) { + 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/lib/composer/symfony/console/DependencyInjection/AddConsoleCommandPass.php b/lib/composer/symfony/console/DependencyInjection/AddConsoleCommandPass.php new file mode 100644 index 0000000000000000000000000000000000000000..f4cd3874c5759703de78af0928828de9137923be --- /dev/null +++ b/lib/composer/symfony/console/DependencyInjection/AddConsoleCommandPass.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 Symfony\Component\Console\DependencyInjection; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; +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\TypedReference; + +/** + * Registers console commands. + * + * @author Grégoire Pineau + */ +class AddConsoleCommandPass implements CompilerPassInterface +{ + private $commandLoaderServiceId; + private $commandTag; + private $noPreloadTag; + + public function __construct(string $commandLoaderServiceId = 'console.command_loader', string $commandTag = 'console.command', string $noPreloadTag = 'container.no_preload') + { + $this->commandLoaderServiceId = $commandLoaderServiceId; + $this->commandTag = $commandTag; + $this->noPreloadTag = $noPreloadTag; + } + + public function process(ContainerBuilder $container) + { + $commandServices = $container->findTaggedServiceIds($this->commandTag, true); + $lazyCommandMap = []; + $lazyCommandRefs = []; + $serviceIds = []; + + foreach ($commandServices as $id => $tags) { + $definition = $container->getDefinition($id); + $definition->addTag($this->noPreloadTag); + $class = $container->getParameterBag()->resolveValue($definition->getClass()); + + if (isset($tags[0]['command'])) { + $commandName = $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, $this->commandTag, Command::class)); + } + $commandName = $class::getDefaultName(); + } + + if (null === $commandName) { + if (!$definition->isPublic() || $definition->isPrivate()) { + $commandId = 'console.command.public_alias.'.$id; + $container->setAlias($commandId, $id)->setPublic(true); + $id = $commandId; + } + $serviceIds[] = $id; + + continue; + } + + unset($tags[0]); + $lazyCommandMap[$commandName] = $id; + $lazyCommandRefs[$id] = new TypedReference($id, $class); + $aliases = []; + + foreach ($tags as $tag) { + if (isset($tag['command'])) { + $aliases[] = $tag['command']; + $lazyCommandMap[$tag['command']] = $id; + } + } + + $definition->addMethodCall('setName', [$commandName]); + + if ($aliases) { + $definition->addMethodCall('setAliases', [$aliases]); + } + } + + $container + ->register($this->commandLoaderServiceId, ContainerCommandLoader::class) + ->setPublic(true) + ->addTag($this->noPreloadTag) + ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]); + + $container->setParameter('console.command.ids', $serviceIds); + } +} diff --git a/lib/composer/symfony/console/Descriptor/ApplicationDescription.php b/lib/composer/symfony/console/Descriptor/ApplicationDescription.php new file mode 100644 index 0000000000000000000000000000000000000000..d361b489013e483b46b7f235abda9720ed611c1b --- /dev/null +++ b/lib/composer/symfony/console/Descriptor/ApplicationDescription.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 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 +{ + const GLOBAL_NAMESPACE = '_global'; + + private $application; + private $namespace; + private $showHidden; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $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 (null === $this->namespaces) { + $this->inspectApplication(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands(): array + { + if (null === $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 isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; + } + + private function inspectApplication() + { + $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); + foreach ($namespacedCommands as $key => $commandsSet) { + ksort($commandsSet); + $sortedCommands[$key] = $commandsSet; + } + } + + return $sortedCommands; + } +} diff --git a/lib/composer/symfony/console/Descriptor/Descriptor.php b/lib/composer/symfony/console/Descriptor/Descriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..2834cd0aa66bd5c625ffd872087cdfcadac5a15c --- /dev/null +++ b/lib/composer/symfony/console/Descriptor/Descriptor.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\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 +{ + /** + * @var OutputInterface + */ + protected $output; + + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, $object, array $options = []) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Application: + $this->describeApplication($object, $options); + break; + default: + throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))); + } + } + + /** + * Writes content to output. + */ + protected function write(string $content, bool $decorated = false) + { + $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); + } + + /** + * Describes an InputArgument instance. + * + * @return string|mixed + */ + abstract protected function describeInputArgument(InputArgument $argument, array $options = []); + + /** + * Describes an InputOption instance. + * + * @return string|mixed + */ + abstract protected function describeInputOption(InputOption $option, array $options = []); + + /** + * Describes an InputDefinition instance. + * + * @return string|mixed + */ + abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []); + + /** + * Describes a Command instance. + * + * @return string|mixed + */ + abstract protected function describeCommand(Command $command, array $options = []); + + /** + * Describes an Application instance. + * + * @return string|mixed + */ + abstract protected function describeApplication(Application $application, array $options = []); +} diff --git a/lib/composer/symfony/console/Descriptor/DescriptorInterface.php b/lib/composer/symfony/console/Descriptor/DescriptorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..e3184a6a5a2084eae7433aac89cd2d340f636067 --- /dev/null +++ b/lib/composer/symfony/console/Descriptor/DescriptorInterface.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\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Descriptor interface. + * + * @author Jean-François Simon + */ +interface DescriptorInterface +{ + /** + * Describes an object if supported. + * + * @param object $object + */ + public function describe(OutputInterface $output, $object, array $options = []); +} diff --git a/lib/composer/symfony/console/Descriptor/JsonDescriptor.php b/lib/composer/symfony/console/Descriptor/JsonDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..5ba588ee98a3e822598fd3aba0c5310fec36ca21 --- /dev/null +++ b/lib/composer/symfony/console/Descriptor/JsonDescriptor.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 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 +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + $this->writeData($this->getInputArgumentData($argument), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + $this->writeData($this->getInputOptionData($option), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $this->writeData($this->getInputDefinitionData($definition), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + $this->writeData($this->getCommandData($command), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace, true); + $commands = []; + + foreach ($description->getCommands() as $command) { + $commands[] = $this->getCommandData($command); + } + + $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) + { + $flags = isset($options['json_encoding']) ? $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): array + { + return [ + '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); + } + + return ['arguments' => $inputArguments, 'options' => $inputOptions]; + } + + private function getCommandData(Command $command): array + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + return [ + 'name' => $command->getName(), + 'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()), + 'description' => $command->getDescription(), + 'help' => $command->getProcessedHelp(), + 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), + 'hidden' => $command->isHidden(), + ]; + } +} diff --git a/lib/composer/symfony/console/Descriptor/MarkdownDescriptor.php b/lib/composer/symfony/console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..8b3e182efc358f38f9cd7691378b97793b715c28 --- /dev/null +++ b/lib/composer/symfony/console/Descriptor/MarkdownDescriptor.php @@ -0,0 +1,188 @@ + + * + * 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 +{ + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, $object, array $options = []) + { + $decorated = $output->isDecorated(); + $output->setDecorated(false); + + parent::describe($output, $object, $options); + + $output->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + protected function write(string $content, bool $decorated = true) + { + parent::write($content, $decorated); + } + + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + $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)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + $name = '--'.$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" + .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + if ($showArguments = \count($definition->getArguments()) > 0) { + $this->write('### Arguments'); + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + if (null !== $describeInputArgument = $this->describeInputArgument($argument)) { + $this->write($describeInputArgument); + } + } + } + + if (\count($definition->getOptions()) > 0) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write('### Options'); + foreach ($definition->getOptions() as $option) { + $this->write("\n\n"); + if (null !== $describeInputOption = $this->describeInputOption($option)) { + $this->write($describeInputOption); + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $this->write( + '`'.$command->getName()."`\n" + .str_repeat('-', Helper::strlen($command->getName()) + 2)."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + .'### Usage'."\n\n" + .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) { + return $carry.'* `'.$usage.'`'."\n"; + }) + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + if ($command->getNativeDefinition()) { + $this->write("\n\n"); + $this->describeInputDefinition($command->getNativeDefinition()); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + $title = $this->getApplicationTitle($application); + + $this->write($title."\n".str_repeat('=', Helper::strlen($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(function ($commandName) use ($description) { + return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())); + }, $namespace['commands']))); + } + + foreach ($description->getCommands() as $command) { + $this->write("\n\n"); + if (null !== $describeCommand = $this->describeCommand($command)) { + $this->write($describeCommand); + } + } + } + + 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/lib/composer/symfony/console/Descriptor/TextDescriptor.php b/lib/composer/symfony/console/Descriptor/TextDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..05a32443d935bc0dc599027bda252110dce0e1c3 --- /dev/null +++ b/lib/composer/symfony/console/Descriptor/TextDescriptor.php @@ -0,0 +1,342 @@ + + * + * 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 +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($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); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + 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 = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]); + $synopsis = sprintf('%s%s', + $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', + sprintf('--%s%s', $option->getName(), $value) + ); + + $spacingWidth = $totalWidth - Helper::strlen($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); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, Helper::strlen($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])); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $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->getNativeDefinition(); + 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"); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $describedNamespace = isset($options['namespace']) ? $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(function ($namespace) use ($commands) { + return 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'], function ($name) use ($commands) { + return 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::strlen($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"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText(string $content, array $options = []) + { + $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. + * + * @param mixed $default + */ + private function formatDefaultValue($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 (Command|string)[] $commands + */ + private function getColumnWidth(array $commands): int + { + $widths = []; + + foreach ($commands as $command) { + if ($command instanceof Command) { + $widths[] = Helper::strlen($command->getName()); + foreach ($command->getAliases() as $alias) { + $widths[] = Helper::strlen($alias); + } + } else { + $widths[] = Helper::strlen($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::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); + + if ($option->acceptValue()) { + $valueLength = 1 + Helper::strlen($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/lib/composer/symfony/console/Descriptor/XmlDescriptor.php b/lib/composer/symfony/console/Descriptor/XmlDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..3d5dce1d6aff9e73c209f8f402fcd43c9282fbb2 --- /dev/null +++ b/lib/composer/symfony/console/Descriptor/XmlDescriptor.php @@ -0,0 +1,231 @@ + + * + * 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): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($commandXML = $dom->createElement('command')); + + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $commandXML->setAttribute('id', $command->getName()); + $commandXML->setAttribute('name', $command->getName()); + $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0); + + $commandXML->appendChild($usagesXML = $dom->createElement('usages')); + + foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + + $commandXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); + + $commandXML->appendChild($helpXML = $dom->createElement('help')); + $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); + + $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition()); + $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); + + return $dom; + } + + public function getApplicationDocument(Application $application, string $namespace = null): \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)); + } + + 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; + } + + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + $this->writeDocument($this->getInputArgumentDocument($argument)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + $this->writeDocument($this->getInputOptionDocument($option)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $this->writeDocument($this->getInputDefinitionDocument($definition)); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + $this->writeDocument($this->getCommandDocument($command)); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null)); + } + + /** + * Appends document children to parent node. + */ + private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) + { + foreach ($importedParent->childNodes as $childNode) { + $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); + } + } + + /** + * Writes DOM document. + */ + private function writeDocument(\DOMDocument $dom) + { + $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)); + } + } + } + + return $dom; + } +} diff --git a/lib/composer/symfony/console/Event/ConsoleCommandEvent.php b/lib/composer/symfony/console/Event/ConsoleCommandEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..a24697cdc950ac128af1ba97c4129e56027b52b6 --- /dev/null +++ b/lib/composer/symfony/console/Event/ConsoleCommandEvent.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\Event; + +/** + * Allows to do things before the command is executed, like skipping the command or changing the input. + * + * @author Fabien Potencier + */ +final class ConsoleCommandEvent extends ConsoleEvent +{ + /** + * The return code for skipped commands, this will also be passed into the terminate event. + */ + const RETURN_CODE_DISABLED = 113; + + /** + * Indicates if the command should be run or skipped. + */ + private $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/lib/composer/symfony/console/Event/ConsoleErrorEvent.php b/lib/composer/symfony/console/Event/ConsoleErrorEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..25d9b88127cff713fa2e1a9a22db11abf21f16fb --- /dev/null +++ b/lib/composer/symfony/console/Event/ConsoleErrorEvent.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\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 $error; + private $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->setAccessible(true); + $r->setValue($this->error, $this->exitCode); + } + + public function getExitCode(): int + { + return null !== $this->exitCode ? $this->exitCode : (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1); + } +} diff --git a/lib/composer/symfony/console/Event/ConsoleEvent.php b/lib/composer/symfony/console/Event/ConsoleEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..89ab645594ce1505e11b611d6e82069c6e415260 --- /dev/null +++ b/lib/composer/symfony/console/Event/ConsoleEvent.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\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; + + private $input; + private $output; + + public function __construct(Command $command = null, InputInterface $input, OutputInterface $output) + { + $this->command = $command; + $this->input = $input; + $this->output = $output; + } + + /** + * Gets the command that is executed. + * + * @return Command|null A Command instance + */ + public function getCommand() + { + return $this->command; + } + + /** + * Gets the input instance. + * + * @return InputInterface An InputInterface instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance. + * + * @return OutputInterface An OutputInterface instance + */ + public function getOutput() + { + return $this->output; + } +} diff --git a/lib/composer/symfony/console/Event/ConsoleTerminateEvent.php b/lib/composer/symfony/console/Event/ConsoleTerminateEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..190038d1a27c9f2fc948e3bfca2e2cb33823368e --- /dev/null +++ b/lib/composer/symfony/console/Event/ConsoleTerminateEvent.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\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 + */ +final class ConsoleTerminateEvent extends ConsoleEvent +{ + private $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode) + { + parent::__construct($command, $input, $output); + + $this->setExitCode($exitCode); + } + + public function setExitCode(int $exitCode): void + { + $this->exitCode = $exitCode; + } + + public function getExitCode(): int + { + return $this->exitCode; + } +} diff --git a/lib/composer/symfony/console/EventListener/ErrorListener.php b/lib/composer/symfony/console/EventListener/ErrorListener.php new file mode 100644 index 0000000000000000000000000000000000000000..a34075793e165b4ef5801b5f072ad69ce4fb8d5a --- /dev/null +++ b/lib/composer/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 $logger; + + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + } + + public function onConsoleError(ConsoleErrorEvent $event) + { + if (null === $this->logger) { + return; + } + + $error = $event->getError(); + + if (!$inputString = $this->getInputString($event)) { + $this->logger->error('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]); + + return; + } + + $this->logger->error('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]); + } + + public function onConsoleTerminate(ConsoleTerminateEvent $event) + { + 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() + { + return [ + ConsoleEvents::ERROR => ['onConsoleError', -128], + ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128], + ]; + } + + private static function getInputString(ConsoleEvent $event): ?string + { + $commandName = $event->getCommand() ? $event->getCommand()->getName() : null; + $input = $event->getInput(); + + if (method_exists($input, '__toString')) { + if ($commandName) { + return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input); + } + + return (string) $input; + } + + return $commandName; + } +} diff --git a/lib/composer/symfony/console/Exception/CommandNotFoundException.php b/lib/composer/symfony/console/Exception/CommandNotFoundException.php new file mode 100644 index 0000000000000000000000000000000000000000..69d5cb996a084811af007e18f06e3eef7474d3cf --- /dev/null +++ b/lib/composer/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 $alternatives; + + /** + * @param string $message Exception message to throw + * @param array $alternatives List of similar defined names + * @param int $code Exception code + * @param \Throwable $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 array A list of similar defined names + */ + public function getAlternatives() + { + return $this->alternatives; + } +} diff --git a/lib/composer/symfony/console/Exception/ExceptionInterface.php b/lib/composer/symfony/console/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1624e13d0b0caedf58d76a9751fdfac7a303fd61 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/console/Exception/InvalidArgumentException.php b/lib/composer/symfony/console/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..07cc0b61d6dc82fa100d14246b96b41642af0b74 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/console/Exception/InvalidOptionException.php b/lib/composer/symfony/console/Exception/InvalidOptionException.php new file mode 100644 index 0000000000000000000000000000000000000000..b2eec61658d33bbc2ddb1b6bc3c8f00209527308 --- /dev/null +++ b/lib/composer/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 typed in the console. + * + * @author Jérôme Tamarelle + */ +class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/lib/composer/symfony/console/Exception/LogicException.php b/lib/composer/symfony/console/Exception/LogicException.php new file mode 100644 index 0000000000000000000000000000000000000000..fc37b8d8ae4b635e0990ea4605c21ff51224f594 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/console/Exception/MissingInputException.php b/lib/composer/symfony/console/Exception/MissingInputException.php new file mode 100644 index 0000000000000000000000000000000000000000..04f02ade45001b4700a29347c8917f8d22005382 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/console/Exception/NamespaceNotFoundException.php b/lib/composer/symfony/console/Exception/NamespaceNotFoundException.php new file mode 100644 index 0000000000000000000000000000000000000000..dd16e45086a73681650a0730162f18c21012e89b --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/console/Exception/RuntimeException.php b/lib/composer/symfony/console/Exception/RuntimeException.php new file mode 100644 index 0000000000000000000000000000000000000000..51d7d80ac659c0a0edc3f30a7c72c13ec6d5045f --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/console/Formatter/NullOutputFormatter.php b/lib/composer/symfony/console/Formatter/NullOutputFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..0aa0a5c2523279a382ef0c31f2139024663c0c13 --- /dev/null +++ b/lib/composer/symfony/console/Formatter/NullOutputFormatter.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\Formatter; + +/** + * @author Tien Xuan Vo + */ +final class NullOutputFormatter implements OutputFormatterInterface +{ + private $style; + + /** + * {@inheritdoc} + */ + public function format(?string $message): void + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getStyle(string $name): OutputFormatterStyleInterface + { + if ($this->style) { + return $this->style; + } + // to comply with the interface we must return a OutputFormatterStyleInterface + return $this->style = new NullOutputFormatterStyle(); + } + + /** + * {@inheritdoc} + */ + public function hasStyle(string $name): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isDecorated(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated): void + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function setStyle(string $name, OutputFormatterStyleInterface $style): void + { + // do nothing + } +} diff --git a/lib/composer/symfony/console/Formatter/NullOutputFormatterStyle.php b/lib/composer/symfony/console/Formatter/NullOutputFormatterStyle.php new file mode 100644 index 0000000000000000000000000000000000000000..bfd0afedd47d8eec4447eab72868303c9629f614 --- /dev/null +++ b/lib/composer/symfony/console/Formatter/NullOutputFormatterStyle.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 Symfony\Component\Console\Formatter; + +/** + * @author Tien Xuan Vo + */ +final class NullOutputFormatterStyle implements OutputFormatterStyleInterface +{ + /** + * {@inheritdoc} + */ + public function apply(string $text): string + { + return $text; + } + + /** + * {@inheritdoc} + */ + public function setBackground(string $color = null): void + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function setForeground(string $color = null): void + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function setOption(string $option): void + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options): void + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function unsetOption(string $option): void + { + // do nothing + } +} diff --git a/lib/composer/symfony/console/Formatter/OutputFormatter.php b/lib/composer/symfony/console/Formatter/OutputFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..edc6a1d71d6f91f5dbcf5d4090e8b720f8749391 --- /dev/null +++ b/lib/composer/symfony/console/Formatter/OutputFormatter.php @@ -0,0 +1,275 @@ + + * + * 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; + +/** + * Formatter class for console output. + * + * @author Konstantin Kudryashov + * @author Roland Franssen + */ +class OutputFormatter implements WrappableOutputFormatterInterface +{ + private $decorated; + private $styles = []; + private $styleStack; + + /** + * Escapes "<" special char in given text. + * + * @return string Escaped text + */ + public static function escape(string $text) + { + $text = preg_replace('/([^\\\\]?) 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(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated) + { + $this->decorated = $decorated; + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * {@inheritdoc} + */ + public function setStyle(string $name, OutputFormatterStyleInterface $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * {@inheritdoc} + */ + public function hasStyle(string $name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * {@inheritdoc} + */ + public function getStyle(string $name) + { + if (!$this->hasStyle($name)) { + throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * {@inheritdoc} + */ + public function format(?string $message) + { + return $this->formatAndWrap($message, 0); + } + + /** + * {@inheritdoc} + */ + public function formatAndWrap(?string $message, int $width) + { + $offset = 0; + $output = ''; + $tagRegex = '[a-z][^<>]*+'; + $currentLineLength = 0; + preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#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 = isset($matches[3][$i][0]) ? $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); + + if (false !== strpos($output, "\0")) { + return strtr($output, ["\0" => '\\', '\\<' => '<']); + } + + return str_replace('\\<', '<', $output); + } + + /** + * @return OutputFormatterStyleStack + */ + public function getStyleStack() + { + 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]) { + $style->setHref($match[1]); + } 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.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text); + $text = rtrim($text, "\n").($matches[1] ?? ''); + + if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) { + $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); + } +} diff --git a/lib/composer/symfony/console/Formatter/OutputFormatterInterface.php b/lib/composer/symfony/console/Formatter/OutputFormatterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8c50d41964d76617a102c81765998c3c355abb43 --- /dev/null +++ b/lib/composer/symfony/console/Formatter/OutputFormatterInterface.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\Formatter; + +/** + * Formatter interface for console output. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterInterface +{ + /** + * Sets the decorated flag. + */ + public function setDecorated(bool $decorated); + + /** + * Gets the decorated flag. + * + * @return bool true if the output will decorate messages, false otherwise + */ + public function isDecorated(); + + /** + * Sets a new style. + */ + public function setStyle(string $name, OutputFormatterStyleInterface $style); + + /** + * Checks if output formatter has style with specified name. + * + * @return bool + */ + public function hasStyle(string $name); + + /** + * Gets style options from style with specified name. + * + * @return OutputFormatterStyleInterface + * + * @throws \InvalidArgumentException When style isn't defined + */ + public function getStyle(string $name); + + /** + * Formats a message according to the given styles. + */ + public function format(?string $message); +} diff --git a/lib/composer/symfony/console/Formatter/OutputFormatterStyle.php b/lib/composer/symfony/console/Formatter/OutputFormatterStyle.php new file mode 100644 index 0000000000000000000000000000000000000000..b1291cb03166770874c8b6cbe76f2c0537444b7d --- /dev/null +++ b/lib/composer/symfony/console/Formatter/OutputFormatterStyle.php @@ -0,0 +1,196 @@ + + * + * 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; + +/** + * Formatter style class for defining styles. + * + * @author Konstantin Kudryashov + */ +class OutputFormatterStyle implements OutputFormatterStyleInterface +{ + private static $availableForegroundColors = [ + 'black' => ['set' => 30, 'unset' => 39], + 'red' => ['set' => 31, 'unset' => 39], + 'green' => ['set' => 32, 'unset' => 39], + 'yellow' => ['set' => 33, 'unset' => 39], + 'blue' => ['set' => 34, 'unset' => 39], + 'magenta' => ['set' => 35, 'unset' => 39], + 'cyan' => ['set' => 36, 'unset' => 39], + 'white' => ['set' => 37, 'unset' => 39], + 'default' => ['set' => 39, 'unset' => 39], + ]; + private static $availableBackgroundColors = [ + 'black' => ['set' => 40, 'unset' => 49], + 'red' => ['set' => 41, 'unset' => 49], + 'green' => ['set' => 42, 'unset' => 49], + 'yellow' => ['set' => 43, 'unset' => 49], + 'blue' => ['set' => 44, 'unset' => 49], + 'magenta' => ['set' => 45, 'unset' => 49], + 'cyan' => ['set' => 46, 'unset' => 49], + 'white' => ['set' => 47, 'unset' => 49], + 'default' => ['set' => 49, 'unset' => 49], + ]; + private static $availableOptions = [ + '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 $foreground; + private $background; + private $href; + private $options = []; + private $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 = []) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (\count($options)) { + $this->setOptions($options); + } + } + + /** + * {@inheritdoc} + */ + public function setForeground(string $color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s).', $color, implode(', ', array_keys(static::$availableForegroundColors)))); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * {@inheritdoc} + */ + public function setBackground(string $color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s).', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + public function setHref(string $url): void + { + $this->href = $url; + } + + /** + * {@inheritdoc} + */ + public function setOption(string $option) + { + if (!isset(static::$availableOptions[$option])) { + throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + if (!\in_array(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * {@inheritdoc} + */ + public function unsetOption(string $option) + { + if (!isset(static::$availableOptions[$option])) { + throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) + { + $this->options = []; + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * {@inheritdoc} + */ + public function apply(string $text) + { + $setCodes = []; + $unsetCodes = []; + + if (null === $this->handlesHrefGracefully) { + $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') && !getenv('KONSOLE_VERSION'); + } + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + + if (null !== $this->href && $this->handlesHrefGracefully) { + $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\"; + } + + if (0 === \count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/lib/composer/symfony/console/Formatter/OutputFormatterStyleInterface.php b/lib/composer/symfony/console/Formatter/OutputFormatterStyleInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b30560d22e1617cc716db2e38a987149f866acf5 --- /dev/null +++ b/lib/composer/symfony/console/Formatter/OutputFormatterStyleInterface.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 style interface for defining styles. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterStyleInterface +{ + /** + * Sets style foreground color. + */ + public function setForeground(string $color = null); + + /** + * Sets style background color. + */ + public function setBackground(string $color = null); + + /** + * Sets some specific style option. + */ + public function setOption(string $option); + + /** + * Unsets some specific style option. + */ + public function unsetOption(string $option); + + /** + * Sets multiple style options at once. + */ + public function setOptions(array $options); + + /** + * Applies the style to a given text. + * + * @return string + */ + public function apply(string $text); +} diff --git a/lib/composer/symfony/console/Formatter/OutputFormatterStyleStack.php b/lib/composer/symfony/console/Formatter/OutputFormatterStyleStack.php new file mode 100644 index 0000000000000000000000000000000000000000..33f7d5222a4cc82a77f95cb41365eec83470d824 --- /dev/null +++ b/lib/composer/symfony/console/Formatter/OutputFormatterStyleStack.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\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Jean-François Simon + */ +class OutputFormatterStyleStack implements ResetInterface +{ + /** + * @var OutputFormatterStyleInterface[] + */ + private $styles; + + private $emptyStyle; + + public function __construct(OutputFormatterStyleInterface $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle(); + $this->reset(); + } + + /** + * Resets stack (ie. empty internal arrays). + */ + public function reset() + { + $this->styles = []; + } + + /** + * Pushes a style in the stack. + */ + public function push(OutputFormatterStyleInterface $style) + { + $this->styles[] = $style; + } + + /** + * Pops a style from the stack. + * + * @return OutputFormatterStyleInterface + * + * @throws InvalidArgumentException When style tags incorrectly nested + */ + public function pop(OutputFormatterStyleInterface $style = null) + { + if (empty($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. + * + * @return OutputFormatterStyle + */ + public function getCurrent() + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[\count($this->styles) - 1]; + } + + /** + * @return $this + */ + public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return OutputFormatterStyleInterface + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} diff --git a/lib/composer/symfony/console/Formatter/WrappableOutputFormatterInterface.php b/lib/composer/symfony/console/Formatter/WrappableOutputFormatterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..42319ee5543f0cd48cc3b79fe4c5eee349b26a5a --- /dev/null +++ b/lib/composer/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); +} diff --git a/lib/composer/symfony/console/Helper/DebugFormatterHelper.php b/lib/composer/symfony/console/Helper/DebugFormatterHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..9d07ec2441eaca21ab3684b3ce274fc4c727eee4 --- /dev/null +++ b/lib/composer/symfony/console/Helper/DebugFormatterHelper.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\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 $colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default']; + private $started = []; + private $count = -1; + + /** + * Starts a debug formatting session. + * + * @return string + */ + public function start(string $id, string $message, string $prefix = 'RUN') + { + $this->started[$id] = ['border' => ++$this->count % \count($this->colors)]; + + return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); + } + + /** + * Adds progress to a formatting session. + * + * @return string + */ + public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR') + { + $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. + * + * @return string + */ + public function stop(string $id, string $message, bool $successful, string $prefix = 'RES') + { + $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(' ', $this->colors[$this->started[$id]['border']]); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'debug_formatter'; + } +} diff --git a/lib/composer/symfony/console/Helper/DescriptorHelper.php b/lib/composer/symfony/console/Helper/DescriptorHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..f2ad9db7a194c4b6ccb2557893292e4c73c0fb0c --- /dev/null +++ b/lib/composer/symfony/console/Helper/DescriptorHelper.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 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\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 $descriptors = []; + + public function __construct() + { + $this + ->register('txt', new TextDescriptor()) + ->register('xml', new XmlDescriptor()) + ->register('json', new JsonDescriptor()) + ->register('md', new MarkdownDescriptor()) + ; + } + + /** + * 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 = []) + { + $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) + { + $this->descriptors[$format] = $descriptor; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'descriptor'; + } +} diff --git a/lib/composer/symfony/console/Helper/Dumper.php b/lib/composer/symfony/console/Helper/Dumper.php new file mode 100644 index 0000000000000000000000000000000000000000..b013b6c527b6c758c49d036a7a427f49e79c6af0 --- /dev/null +++ b/lib/composer/symfony/console/Helper/Dumper.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\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 $output; + private $dumper; + private $cloner; + private $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 ?? $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 ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true)); + }; + } else { + $this->handler = function ($var): string { + switch (true) { + case null === $var: + return 'null'; + case true === $var: + return 'true'; + case false === $var: + return 'false'; + case \is_string($var): + return '"'.$var.'"'; + default: + return rtrim(print_r($var, true)); + } + }; + } + } + + public function __invoke($var): string + { + return ($this->handler)($var); + } +} diff --git a/lib/composer/symfony/console/Helper/FormatterHelper.php b/lib/composer/symfony/console/Helper/FormatterHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..a505415cffdc60193bee5fd17bb93288afeab091 --- /dev/null +++ b/lib/composer/symfony/console/Helper/FormatterHelper.php @@ -0,0 +1,92 @@ + + * + * 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. + * + * @return string The format section + */ + public function formatSection(string $section, string $message, string $style = 'info') + { + return sprintf('<%s>[%s] %s', $style, $section, $style, $message); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * + * @return string The formatter message + */ + public function formatBlock($messages, string $style, bool $large = false) + { + 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::strlen($message) + ($large ? 4 : 2), $len); + } + + $messages = $large ? [str_repeat(' ', $len)] : []; + for ($i = 0; isset($lines[$i]); ++$i) { + $messages[] = $lines[$i].str_repeat(' ', $len - self::strlen($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. + * + * @return string + */ + public function truncate(string $message, int $length, string $suffix = '...') + { + $computedLength = $length - self::strlen($suffix); + + if ($computedLength > self::strlen($message)) { + return $message; + } + + return self::substr($message, 0, $length).$suffix; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'formatter'; + } +} diff --git a/lib/composer/symfony/console/Helper/Helper.php b/lib/composer/symfony/console/Helper/Helper.php new file mode 100644 index 0000000000000000000000000000000000000000..e52e31515e968cbe60624544c5f9d603380f85f1 --- /dev/null +++ b/lib/composer/symfony/console/Helper/Helper.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 Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * Helper is the base class for all helper classes. + * + * @author Fabien Potencier + */ +abstract class Helper implements HelperInterface +{ + protected $helperSet = null; + + /** + * {@inheritdoc} + */ + public function setHelperSet(HelperSet $helperSet = null) + { + $this->helperSet = $helperSet; + } + + /** + * {@inheritdoc} + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Returns the length of a string, using mb_strwidth if it is available. + * + * @return int The length of the string + */ + public static function strlen(?string $string) + { + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return \strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + /** + * Returns the subset of a string, using mb_substr if it is available. + * + * @return string The string subset + */ + public static function substr(string $string, int $from, int $length = null) + { + 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($secs) + { + static $timeFormats = [ + [0, '< 1 sec'], + [1, '1 sec'], + [2, 'secs', 1], + [60, '1 min'], + [120, 'mins', 60], + [3600, '1 hr'], + [7200, 'hrs', 3600], + [86400, '1 day'], + [172800, 'days', 86400], + ]; + + foreach ($timeFormats as $index => $format) { + if ($secs >= $format[0]) { + if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0]) + || $index == \count($timeFormats) - 1 + ) { + if (2 == \count($format)) { + return $format[1]; + } + + return floor($secs / $format[2]).' '.$format[1]; + } + } + } + } + + public static function formatMemory(int $memory) + { + 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 strlenWithoutDecoration(OutputFormatterInterface $formatter, $string) + { + return self::strlen(self::removeDecoration($formatter, $string)); + } + + public static function removeDecoration(OutputFormatterInterface $formatter, $string) + { + $isDecorated = $formatter->isDecorated(); + $formatter->setDecorated(false); + // remove <...> formatting + $string = $formatter->format($string); + // remove already formatted characters + $string = preg_replace("/\033\[[^m]*m/", '', $string); + $formatter->setDecorated($isDecorated); + + return $string; + } +} diff --git a/lib/composer/symfony/console/Helper/HelperInterface.php b/lib/composer/symfony/console/Helper/HelperInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1ce823587e4a7538e85479b1d3acd164d96e8748 --- /dev/null +++ b/lib/composer/symfony/console/Helper/HelperInterface.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\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 = null); + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet A HelperSet instance + */ + public function getHelperSet(); + + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + */ + public function getName(); +} diff --git a/lib/composer/symfony/console/Helper/HelperSet.php b/lib/composer/symfony/console/Helper/HelperSet.php new file mode 100644 index 0000000000000000000000000000000000000000..5c08a7606d28960e65c191b75d7b8582afeeb9bf --- /dev/null +++ b/lib/composer/symfony/console/Helper/HelperSet.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; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * HelperSet represents a set of helpers to be used with a command. + * + * @author Fabien Potencier + */ +class HelperSet implements \IteratorAggregate +{ + /** + * @var Helper[] + */ + private $helpers = []; + private $command; + + /** + * @param Helper[] $helpers An array of helper + */ + 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) + { + $this->helpers[$helper->getName()] = $helper; + if (null !== $alias) { + $this->helpers[$alias] = $helper; + } + + $helper->setHelperSet($this); + } + + /** + * Returns true if the helper if defined. + * + * @return bool true if the helper is defined, false otherwise + */ + public function has(string $name) + { + return isset($this->helpers[$name]); + } + + /** + * Gets a helper value. + * + * @return HelperInterface The helper instance + * + * @throws InvalidArgumentException if the helper is not defined + */ + public function get(string $name) + { + if (!$this->has($name)) { + throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); + } + + return $this->helpers[$name]; + } + + public function setCommand(Command $command = null) + { + $this->command = $command; + } + + /** + * Gets the command associated with this helper set. + * + * @return Command A Command instance + */ + public function getCommand() + { + return $this->command; + } + + /** + * @return Helper[] + */ + public function getIterator() + { + return new \ArrayIterator($this->helpers); + } +} diff --git a/lib/composer/symfony/console/Helper/InputAwareHelper.php b/lib/composer/symfony/console/Helper/InputAwareHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..0d0dba23e330be9fcbfa3778ec971b6b96e5f4f5 --- /dev/null +++ b/lib/composer/symfony/console/Helper/InputAwareHelper.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\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 $input; + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + } +} diff --git a/lib/composer/symfony/console/Helper/ProcessHelper.php b/lib/composer/symfony/console/Helper/ProcessHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..f82c16bae84a0e082e41121d83eefe35d046a363 --- /dev/null +++ b/lib/composer/symfony/console/Helper/ProcessHelper.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\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 + * + * @return Process The process that ran + */ + public function run(OutputInterface $output, $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_array($cmd)) { + throw new \TypeError(sprintf('The "command" argument of "%s()" must be an array or a "%s" instance, "%s" given.', __METHOD__, Process::class, get_debug_type($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 string|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 + * + * @return Process The process that ran + * + * @throws ProcessFailedException + * + * @see run() + */ + public function mustRun(OutputInterface $output, $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); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return 'process'; + } +} diff --git a/lib/composer/symfony/console/Helper/ProgressBar.php b/lib/composer/symfony/console/Helper/ProgressBar.php new file mode 100644 index 0000000000000000000000000000000000000000..3fce6886e58bc061edb166f57e883bc2971dcae9 --- /dev/null +++ b/lib/composer/symfony/console/Helper/ProgressBar.php @@ -0,0 +1,600 @@ + + * + * 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 +{ + private $barWidth = 28; + private $barChar; + private $emptyBarChar = '-'; + private $progressChar = '>'; + private $format; + private $internalFormat; + private $redrawFreq = 1; + private $writeCount; + private $lastWriteTime; + private $minSecondsBetweenRedraws = 0; + private $maxSecondsBetweenRedraws = 1; + private $output; + private $step = 0; + private $max; + private $startTime; + private $stepWidth; + private $percent = 0.0; + private $formatLineCount; + private $messages = []; + private $overwrite = true; + private $terminal; + private $previousMessage; + private $cursor; + + private static $formatters; + private static $formats; + + /** + * @param int $max Maximum steps (0 if unknown) + */ + public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 0.1) + { + 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. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void + { + if (!self::$formatters) { + 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 %) + * + * @return callable|null A PHP callable + */ + public static function getPlaceholderFormatterDefinition(string $name): ?callable + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; + } + + /** + * 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 + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + self::$formats[$name] = $format; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + * + * @return string|null A format string + */ + public static function getFormatDefinition(string $name): ?string + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + return isset(self::$formats[$name]) ? 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') + { + $this->messages[$name] = $message; + } + + public function getMessage(string $name = 'message') + { + 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 ? min(5, $this->barWidth / 15) * $this->writeCount : $this->step) % $this->barWidth); + } + + public function getEstimated(): float + { + if (!$this->step) { + return 0; + } + + return round((time() - $this->startTime) / $this->step * $this->max); + } + + public function getRemaining(): float + { + if (!$this->step) { + return 0; + } + + return round((time() - $this->startTime) / $this->step * ($this->max - $this->step)); + } + + public function setBarWidth(int $size) + { + $this->barWidth = max(1, $size); + } + + public function getBarWidth(): int + { + return $this->barWidth; + } + + public function setBarCharacter(string $char) + { + $this->barChar = $char; + } + + public function getBarCharacter(): string + { + if (null === $this->barChar) { + return $this->max ? '=' : $this->emptyBarChar; + } + + return $this->barChar; + } + + public function setEmptyBarCharacter(string $char) + { + $this->emptyBarChar = $char; + } + + public function getEmptyBarCharacter(): string + { + return $this->emptyBarChar; + } + + public function setProgressCharacter(string $char) + { + $this->progressChar = $char; + } + + public function getProgressCharacter(): string + { + return $this->progressChar; + } + + public function setFormat(string $format) + { + $this->format = null; + $this->internalFormat = $format; + } + + /** + * Sets the redraw frequency. + * + * @param int|float $freq The frequency in steps + */ + public function setRedrawFrequency(?int $freq) + { + $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. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $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 + */ + public function start(int $max = null) + { + $this->startTime = time(); + $this->step = 0; + $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) + { + $this->setProgress($this->step + $step); + } + + /** + * Sets whether to overwrite the progressbar, false for new line. + */ + public function setOverwrite(bool $overwrite) + { + $this->overwrite = $overwrite; + } + + public function setProgress(int $step) + { + 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) + { + $this->format = null; + $this->max = max(0, $max); + $this->stepWidth = $this->max ? Helper::strlen((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) + { + // 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; + } + + $this->formatLineCount = substr_count($this->format, "\n"); + } + + /** + * 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) { + $lines = floor(Helper::strlen($message) / $this->terminal->getWidth()) + $this->formatLineCount + 1; + $this->output->clear($lines); + } else { + if ($this->formatLineCount > 0) { + $this->cursor->moveUp($this->formatLineCount); + } + + $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 + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->max ? 'verbose' : 'verbose_nomax'; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + return $this->max ? 'very_verbose' : 'very_verbose_nomax'; + case OutputInterface::VERBOSITY_DEBUG: + return $this->max ? 'debug' : 'debug_nomax'; + default: + return $this->max ? 'normal' : '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::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter()); + $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); + } + + return $display; + }, + 'elapsed' => function (self $bar) { + return Helper::formatTime(time() - $bar->getStartTime()); + }, + '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()); + }, + '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()); + }, + 'memory' => function (self $bar) { + return Helper::formatMemory(memory_get_usage(true)); + }, + 'current' => function (self $bar) { + return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT); + }, + 'max' => function (self $bar) { + return $bar->getMaxSteps(); + }, + 'percent' => function (self $bar) { + return floor($bar->getProgressPercent() * 100); + }, + ]; + } + + private static function initFormats(): array + { + return [ + 'normal' => ' %current%/%max% [%bar%] %percent:3s%%', + 'normal_nomax' => ' %current% [%bar%]', + + 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', + 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', + 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', + 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%', + ]; + } + + private function buildLine(): string + { + $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; + $callback = function ($matches) { + if ($formatter = $this::getPlaceholderFormatterDefinition($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(function ($subLine) { + return Helper::strlenWithoutDecoration($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/lib/composer/symfony/console/Helper/ProgressIndicator.php b/lib/composer/symfony/console/Helper/ProgressIndicator.php new file mode 100644 index 0000000000000000000000000000000000000000..81cb783ea484baea6b7906f943e092cf74216e1a --- /dev/null +++ b/lib/composer/symfony/console/Helper/ProgressIndicator.php @@ -0,0 +1,254 @@ + + * + * 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 $output; + private $startTime; + private $format; + private $message; + private $indicatorValues; + private $indicatorCurrent; + private $indicatorChangeInterval; + private $indicatorUpdateTime; + private $started = false; + + private static $formatters; + private static $formats; + + /** + * @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; + + if (null === $format) { + $format = $this->determineBestFormat(); + } + + if (null === $indicatorValues) { + $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) + { + $this->message = $message; + + $this->display(); + } + + /** + * Starts the indicator output. + */ + public function start(string $message) + { + 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() + { + 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. + * + * @param $message + */ + public function finish(string $message) + { + 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. + * + * @return string|null A format string + */ + public static function getFormatDefinition(string $name) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + return isset(self::$formats[$name]) ? 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) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name (including the delimiter char like %). + * + * @return callable|null A PHP callable + */ + public static function getPlaceholderFormatterDefinition(string $name) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; + } + + private function display() + { + 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 + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi'; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + case OutputInterface::VERBOSITY_DEBUG: + return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi'; + default: + return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi'; + } + } + + /** + * Overwrites a previous message to the output. + */ + private function overwrite(string $message) + { + 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); + } + + private static function initPlaceholderFormatters(): array + { + return [ + 'indicator' => function (self $indicator) { + return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)]; + }, + 'message' => function (self $indicator) { + return $indicator->message; + }, + 'elapsed' => function (self $indicator) { + return Helper::formatTime(time() - $indicator->startTime); + }, + 'memory' => function () { + return Helper::formatMemory(memory_get_usage(true)); + }, + ]; + } + + private static function initFormats(): array + { + return [ + '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%)', + ]; + } +} diff --git a/lib/composer/symfony/console/Helper/QuestionHelper.php b/lib/composer/symfony/console/Helper/QuestionHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..1d3483a3f1fe9f78aec60bfd6ec1b2e0509061dc --- /dev/null +++ b/lib/composer/symfony/console/Helper/QuestionHelper.php @@ -0,0 +1,505 @@ + + * + * 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 +{ + private $inputStream; + private static $shell; + private static $stty = true; + private static $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) + { + 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 = function () use ($output, $question) { + return $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; + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'question'; + } + + /** + * Prevents usage of stty. + */ + public static function disableStty() + { + self::$stty = false; + } + + /** + * Asks the question to the user. + * + * @return bool|mixed|string|null + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function doAsk(OutputInterface $output, Question $question) + { + $this->writePrompt($output, $question); + + $inputStream = $this->inputStream ?: \STDIN; + $autocomplete = $question->getAutocompleterCallback(); + + if (\function_exists('sapi_windows_cp_set')) { + // Codepage used by cmd.exe on Windows to allow special characters (éàüñ). + @sapi_windows_cp_set(1252); + } + + 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) { + $ret = fgets($inputStream, 4096); + 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($ret); + } + + $ret = \strlen($ret) > 0 ? $ret : $question->getDefault(); + + if ($normalizer = $question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + /** + * @return mixed + */ + private function getDefaultAnswer(Question $question) + { + $default = $question->getDefault(); + + if (null === $default) { + return $default; + } + + if ($validator = $question->getValidator()) { + return \call_user_func($question->getValidator(), $default); + } elseif ($question instanceof ChoiceQuestion) { + $choices = $question->getChoices(); + + if (!$question->isMultiselect()) { + return isset($choices[$default]) ? $choices[$default] : $default; + } + + $default = explode(',', $default); + foreach ($default as $k => $v) { + $v = $question->isTrimmable() ? trim($v) : $v; + $default[$k] = isset($choices[$v]) ? $choices[$v] : $v; + } + } + + return $default; + } + + /** + * Outputs the question prompt. + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $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) + { + $messages = []; + + $maxWidth = max(array_map('self::strlen', array_keys($choices = $question->getChoices()))); + + foreach ($choices as $key => $value) { + $padding = str_repeat(' ', $maxWidth - self::strlen($key)); + + $messages[] = sprintf(" [<$tag>%s$padding] %s", $key, $value); + } + + return $messages; + } + + /** + * Outputs an error message. + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + 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'); + + // 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)) { + $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(sprintf('stty %s', $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 = self::strlen($fullChoice); + + $matches = array_filter( + $autocomplete($ret), + function ($match) use ($ret) { + return '' === $ret || 0 === strpos($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 (0 === strpos($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(sprintf('stty %s', $sttyMode)); + + return $fullChoice; + } + + private function mostRecentlyEnteredValue(string $entered): string + { + // Determine the most recent value that the user entered + if (false === strpos($entered, ',')) { + return $entered; + } + + $choices = explode(',', $entered); + if (\strlen($lastChoice = trim($choices[\count($choices) - 1])) > 0) { + 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 ('phar:' === substr(__FILE__, 0, 5)) { + $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 (self::$stty && Terminal::hasSttyAvailable()) { + shell_exec(sprintf('stty %s', $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 + * + * @return mixed The validated response + * + * @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) + { + $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 (null !== self::$stdinIsInteractive) { + return self::$stdinIsInteractive; + } + + if (\function_exists('stream_isatty')) { + return self::$stdinIsInteractive = stream_isatty(fopen('php://stdin', 'r')); + } + + if (\function_exists('posix_isatty')) { + return self::$stdinIsInteractive = posix_isatty(fopen('php://stdin', 'r')); + } + + if (!\function_exists('exec')) { + return self::$stdinIsInteractive = true; + } + + exec('stty 2> /dev/null', $output, $status); + + return self::$stdinIsInteractive = 1 !== $status; + } +} diff --git a/lib/composer/symfony/console/Helper/SymfonyQuestionHelper.php b/lib/composer/symfony/console/Helper/SymfonyQuestionHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..e4e87b2f9918892e5f85eed1294514ce2c1256c1 --- /dev/null +++ b/lib/composer/symfony/console/Helper/SymfonyQuestionHelper.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\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 +{ + /** + * {@inheritdoc} + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); + $default = $question->getDefault(); + + 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(isset($choices[$default]) ? $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); + } + + /** + * {@inheritdoc} + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if ($output instanceof SymfonyStyle) { + $output->newLine(); + $output->error($error->getMessage()); + + return; + } + + parent::writeError($output, $error); + } +} diff --git a/lib/composer/symfony/console/Helper/Table.php b/lib/composer/symfony/console/Helper/Table.php new file mode 100644 index 0000000000000000000000000000000000000000..fee5a416b73d9a6eb1a21057ccf55b95bb571a4c --- /dev/null +++ b/lib/composer/symfony/console/Helper/Table.php @@ -0,0 +1,840 @@ + + * + * 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 $headerTitle; + private $footerTitle; + + /** + * Table headers. + */ + private $headers = []; + + /** + * Table rows. + */ + private $rows = []; + private $horizontal = false; + + /** + * Column widths cache. + */ + private $effectiveColumnWidths = []; + + /** + * Number of columns cache. + * + * @var int + */ + private $numberOfColumns; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var TableStyle + */ + private $style; + + /** + * @var array + */ + private $columnStyles = []; + + /** + * User set column widths. + * + * @var array + */ + private $columnWidths = []; + private $columnMaxWidths = []; + + private static $styles; + + private $rendered = false; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + $this->setStyle('default'); + } + + /** + * Sets a style definition. + */ + public static function setStyleDefinition(string $name, TableStyle $style) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + self::$styles[$name] = $style; + } + + /** + * Gets a style definition by name. + * + * @return TableStyle + */ + public static function getStyleDefinition(string $name) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } + + /** + * Sets table style. + * + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setStyle($name) + { + $this->style = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current table style. + * + * @return TableStyle + */ + public function getStyle() + { + 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, $name) + { + $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. + * + * @return TableStyle + */ + public function getColumnStyle(int $columnIndex) + { + return $this->columnStyles[$columnIndex] ?? $this->getStyle(); + } + + /** + * Sets the minimum width of a column. + * + * @return $this + */ + public function setColumnWidth(int $columnIndex, int $width) + { + $this->columnWidths[$columnIndex] = $width; + + return $this; + } + + /** + * Sets the minimum width of all columns. + * + * @return $this + */ + public function setColumnWidths(array $widths) + { + $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): self + { + 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; + } + + public function setHeaders(array $headers) + { + $headers = array_values($headers); + if (!empty($headers) && !\is_array($headers[0])) { + $headers = [$headers]; + } + + $this->headers = $headers; + + return $this; + } + + public function setRows(array $rows) + { + $this->rows = []; + + return $this->addRows($rows); + } + + public function addRows(array $rows) + { + foreach ($rows as $row) { + $this->addRow($row); + } + + return $this; + } + + public function addRow($row) + { + if ($row instanceof TableSeparator) { + $this->rows[] = $row; + + return $this; + } + + if (!\is_array($row)) { + throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); + } + + $this->rows[] = array_values($row); + + return $this; + } + + /** + * Adds a row to the table, and re-renders the table. + */ + public function appendRow($row): self + { + 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; + } + + public function setRow($column, array $row) + { + $this->rows[$column] = $row; + + return $this; + } + + public function setHeaderTitle(?string $title): self + { + $this->headerTitle = $title; + + return $this; + } + + public function setFooterTitle(?string $title): self + { + $this->footerTitle = $title; + + return $this; + } + + public function setHorizontal(bool $horizontal = true): self + { + $this->horizontal = $horizontal; + + 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() + { + $divider = new TableSeparator(); + if ($this->horizontal) { + $rows = []; + 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 ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) { + // Noop, there is a "title" + } else { + $rows[$i][] = null; + } + } + } + } else { + $rows = array_merge($this->headers, [$divider], $this->rows); + } + + $this->calculateNumberOfColumns($rows); + + $rows = $this->buildTableRows($rows); + $this->calculateColumnsWidth($rows); + + $isHeader = !$this->horizontal; + $isFirstRow = $this->horizontal; + foreach ($rows as $row) { + if ($divider === $row) { + $isHeader = false; + $isFirstRow = true; + + continue; + } + if ($row instanceof TableSeparator) { + $this->renderRowSeparator(); + + continue; + } + if (!$row) { + continue; + } + + if ($isHeader || $isFirstRow) { + if ($isFirstRow) { + $this->renderRowSeparator(self::SEPARATOR_TOP_BOTTOM); + $isFirstRow = false; + } else { + $this->renderRowSeparator(self::SEPARATOR_TOP, $this->headerTitle, $this->style->getHeaderTitleFormat()); + } + } + if ($this->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) + { + if (0 === $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) { + list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; + } elseif (self::SEPARATOR_TOP === $type) { + list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; + } elseif (self::SEPARATOR_TOP_BOTTOM === $type) { + list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; + } else { + list($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::strlenWithoutDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title)); + $markupLength = Helper::strlen($markup); + if ($titleLength > $limit = $markupLength - 4) { + $titleLength = $limit; + $formatLength = Helper::strlenWithoutDecoration($formatter, sprintf($titleFormat, '')); + $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...'); + } + + $titleStart = ($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) + { + $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 = isset($row[$column]) ? $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::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); + $content = sprintf($style->getCellRowContentFormat(), $cell); + + return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType())); + } + + /** + * Calculate number of columns for this table. + */ + private function calculateNumberOfColumns(array $rows) + { + $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::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) { + $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan); + } + if (!strstr($cell, "\n")) { + continue; + } + $escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell))); + $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped; + $lines = explode("\n", str_replace("\n", "\n", $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) { + yield $this->fillCells($row); + + if (isset($unmergedRows[$rowKey])) { + foreach ($unmergedRows[$rowKey] as $unmergedRow) { + yield $this->fillCells($unmergedRow); + } + } + } + }); + } + + 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 (\count($this->rows) > 0) { + ++$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) && !(\is_object($cell) && method_exists($cell, '__toString'))) { + 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 (strstr($cell, "\n")) { + $lines = explode("\n", str_replace("\n", "\n", $cell)); + $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; + + $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]); + 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 = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : ''; + $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan()]); + 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($row) + { + $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 $rows) + { + for ($column = 0; $column < $this->numberOfColumns; ++$column) { + $lengths = []; + foreach ($rows 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::strlen($textContent); + if ($textLength > 0) { + $contentColumns = 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::strlen($this->style->getCellRowContentFormat()) - 2; + } + } + + private function getColumnSeparatorWidth(): int + { + return Helper::strlen(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::strlenWithoutDecoration($this->output->getFormatter(), $cell); + } + + $columnWidth = isset($this->columnWidths[$column]) ? $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() + { + $this->effectiveColumnWidths = []; + $this->numberOfColumns = null; + } + + 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($name): TableStyle + { + if ($name instanceof TableStyle) { + return $name; + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } +} diff --git a/lib/composer/symfony/console/Helper/TableCell.php b/lib/composer/symfony/console/Helper/TableCell.php new file mode 100644 index 0000000000000000000000000000000000000000..5b6af4a933b37ec54d9c5afc851d36565b0f5305 --- /dev/null +++ b/lib/composer/symfony/console/Helper/TableCell.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\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Abdellatif Ait boudad + */ +class TableCell +{ + private $value; + private $options = [ + 'rowspan' => 1, + 'colspan' => 1, + ]; + + 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))); + } + + $this->options = array_merge($this->options, $options); + } + + /** + * Returns the cell value. + * + * @return string + */ + public function __toString() + { + return $this->value; + } + + /** + * Gets number of colspan. + * + * @return int + */ + public function getColspan() + { + return (int) $this->options['colspan']; + } + + /** + * Gets number of rowspan. + * + * @return int + */ + public function getRowspan() + { + return (int) $this->options['rowspan']; + } +} diff --git a/lib/composer/symfony/console/Helper/TableRows.php b/lib/composer/symfony/console/Helper/TableRows.php new file mode 100644 index 0000000000000000000000000000000000000000..16aabb3fc9350686955e7e96310a4093879f0525 --- /dev/null +++ b/lib/composer/symfony/console/Helper/TableRows.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\Helper; + +/** + * @internal + */ +class TableRows implements \IteratorAggregate +{ + private $generator; + + public function __construct(callable $generator) + { + $this->generator = $generator; + } + + public function getIterator(): \Traversable + { + $g = $this->generator; + + return $g(); + } +} diff --git a/lib/composer/symfony/console/Helper/TableSeparator.php b/lib/composer/symfony/console/Helper/TableSeparator.php new file mode 100644 index 0000000000000000000000000000000000000000..e541c53156017fc16a0b3e2d3741014ecc37832e --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/console/Helper/TableStyle.php b/lib/composer/symfony/console/Helper/TableStyle.php new file mode 100644 index 0000000000000000000000000000000000000000..07265b467ae5e30c4662fbd8c36bdb7c890eab34 --- /dev/null +++ b/lib/composer/symfony/console/Helper/TableStyle.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\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 $paddingChar = ' '; + private $horizontalOutsideBorderChar = '-'; + private $horizontalInsideBorderChar = '-'; + private $verticalOutsideBorderChar = '|'; + private $verticalInsideBorderChar = '|'; + private $crossingChar = '+'; + private $crossingTopRightChar = '+'; + private $crossingTopMidChar = '+'; + private $crossingTopLeftChar = '+'; + private $crossingMidRightChar = '+'; + private $crossingBottomRightChar = '+'; + private $crossingBottomMidChar = '+'; + private $crossingBottomLeftChar = '+'; + private $crossingMidLeftChar = '+'; + private $crossingTopLeftBottomChar = '+'; + private $crossingTopMidBottomChar = '+'; + private $crossingTopRightBottomChar = '+'; + private $headerTitleFormat = ' %s '; + private $footerTitleFormat = ' %s '; + private $cellHeaderFormat = '%s'; + private $cellRowFormat = '%s'; + private $cellRowContentFormat = ' %s '; + private $borderFormat = '%s'; + private $padType = \STR_PAD_RIGHT; + + /** + * Sets padding character, used for cell padding. + * + * @return $this + */ + public function setPaddingChar(string $paddingChar) + { + if (!$paddingChar) { + throw new LogicException('The padding char must not be empty.'); + } + + $this->paddingChar = $paddingChar; + + return $this; + } + + /** + * Gets padding character, used for cell padding. + * + * @return string + */ + public function getPaddingChar() + { + 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 ║ + * ╚═══════════════╧══════════════════════════╧══════════════════╝ + * + */ + public function setHorizontalBorderChars(string $outside, string $inside = null): self + { + $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 ║ + * ╚═══════════════╧══════════════════════════╧══════════════════╝ + * + */ + public function setVerticalBorderChars(string $outside, string $inside = null): self + { + $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 + */ + 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): self + { + $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. + * + * @return string + */ + public function getCrossingChar() + { + 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) + { + $this->cellHeaderFormat = $cellHeaderFormat; + + return $this; + } + + /** + * Gets header cell format. + * + * @return string + */ + public function getCellHeaderFormat() + { + return $this->cellHeaderFormat; + } + + /** + * Sets row cell format. + * + * @return $this + */ + public function setCellRowFormat(string $cellRowFormat) + { + $this->cellRowFormat = $cellRowFormat; + + return $this; + } + + /** + * Gets row cell format. + * + * @return string + */ + public function getCellRowFormat() + { + return $this->cellRowFormat; + } + + /** + * Sets row cell content format. + * + * @return $this + */ + public function setCellRowContentFormat(string $cellRowContentFormat) + { + $this->cellRowContentFormat = $cellRowContentFormat; + + return $this; + } + + /** + * Gets row cell content format. + * + * @return string + */ + public function getCellRowContentFormat() + { + return $this->cellRowContentFormat; + } + + /** + * Sets table border format. + * + * @return $this + */ + public function setBorderFormat(string $borderFormat) + { + $this->borderFormat = $borderFormat; + + return $this; + } + + /** + * Gets table border format. + * + * @return string + */ + public function getBorderFormat() + { + return $this->borderFormat; + } + + /** + * Sets cell padding type. + * + * @return $this + */ + public function setPadType(int $padType) + { + 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. + * + * @return int + */ + public function getPadType() + { + return $this->padType; + } + + public function getHeaderTitleFormat(): string + { + return $this->headerTitleFormat; + } + + public function setHeaderTitleFormat(string $format): self + { + $this->headerTitleFormat = $format; + + return $this; + } + + public function getFooterTitleFormat(): string + { + return $this->footerTitleFormat; + } + + public function setFooterTitleFormat(string $format): self + { + $this->footerTitleFormat = $format; + + return $this; + } +} diff --git a/lib/composer/symfony/console/Input/ArgvInput.php b/lib/composer/symfony/console/Input/ArgvInput.php new file mode 100644 index 0000000000000000000000000000000000000000..c93bda5a9bf0a55d98244bb4176634b832bbeea8 --- /dev/null +++ b/lib/composer/symfony/console/Input/ArgvInput.php @@ -0,0 +1,347 @@ + + * + * 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 $tokens; + private $parsed; + + public function __construct(array $argv = null, InputDefinition $definition = null) + { + $argv = $argv ?? $_SERVER['argv'] ?? []; + + // strip the application name + array_shift($argv); + + $this->tokens = $argv; + + parent::__construct($definition); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * Parses a short option. + */ + private function parseShortOption(string $token) + { + $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) + { + $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) + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + if (0 === \strlen($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) + { + $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(); + if (\count($all)) { + throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)))); + } + + throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token)); + } + } + + /** + * Adds a short option value. + * + * @throws RuntimeException When option given doesn't exist + */ + private function addShortOption(string $shortcut, $value) + { + 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, $value) + { + if (!$this->definition->hasOption($name)) { + throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $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; + } + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + $isOption = false; + foreach ($this->tokens as $i => $token) { + if ($token && '-' === $token[0]) { + if (false !== strpos($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; + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values, bool $onlyParams = false) + { + $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 = 0 === strpos($value, '--') ? $value.'=' : $value; + if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) { + return true; + } + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false, bool $onlyParams = false) + { + $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 = 0 === strpos($value, '--') ? $value.'=' : $value; + if ('' !== $leading && 0 === strpos($token, $leading)) { + return substr($token, \strlen($leading)); + } + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + * + * @return string + */ + public function __toString() + { + $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/lib/composer/symfony/console/Input/ArrayInput.php b/lib/composer/symfony/console/Input/ArrayInput.php new file mode 100644 index 0000000000000000000000000000000000000000..66f0bedc3626baba6d99837ed557843134cbffb0 --- /dev/null +++ b/lib/composer/symfony/console/Input/ArrayInput.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\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 $parameters; + + public function __construct(array $parameters, InputDefinition $definition = null) + { + $this->parameters = $parameters; + + parent::__construct($definition); + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + foreach ($this->parameters as $param => $value) { + if ($param && \is_string($param) && '-' === $param[0]) { + continue; + } + + return $value; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values, bool $onlyParams = false) + { + $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; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false, bool $onlyParams = false) + { + $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. + * + * @return string + */ + public function __toString() + { + $params = []; + foreach ($this->parameters as $param => $val) { + if ($param && \is_string($param) && '-' === $param[0]) { + if (\is_array($val)) { + foreach ($val as $v) { + $params[] = $param.('' != $v ? '='.$this->escapeToken($v) : ''); + } + } else { + $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : ''); + } + } else { + $params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val); + } + } + + return implode(' ', $params); + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + foreach ($this->parameters as $key => $value) { + if ('--' === $key) { + return; + } + if (0 === strpos($key, '--')) { + $this->addLongOption(substr($key, 2), $value); + } elseif (0 === strpos($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, $value) + { + 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, $value) + { + if (!$this->definition->hasOption($name)) { + throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); + } + + $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. + * + * @param string|int $name The argument name + * @param mixed $value The value for the argument + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + private function addArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } +} diff --git a/lib/composer/symfony/console/Input/Input.php b/lib/composer/symfony/console/Input/Input.php new file mode 100644 index 0000000000000000000000000000000000000000..ff5d3170f4e041bd4839f9c4d60c198354d2aaab --- /dev/null +++ b/lib/composer/symfony/console/Input/Input.php @@ -0,0 +1,201 @@ + + * + * 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 $definition; + protected $stream; + protected $options = []; + protected $arguments = []; + protected $interactive = true; + + public function __construct(InputDefinition $definition = null) + { + if (null === $definition) { + $this->definition = new InputDefinition(); + } else { + $this->bind($definition); + $this->validate(); + } + } + + /** + * {@inheritdoc} + */ + public function bind(InputDefinition $definition) + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * Processes command line arguments. + */ + abstract protected function parse(); + + /** + * {@inheritdoc} + */ + public function validate() + { + $definition = $this->definition; + $givenArguments = $this->arguments; + + $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { + return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); + }); + + if (\count($missingArguments) > 0) { + throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); + } + } + + /** + * {@inheritdoc} + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * {@inheritdoc} + */ + public function setInteractive(bool $interactive) + { + $this->interactive = $interactive; + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * {@inheritdoc} + */ + public function getArgument(string $name) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function setArgument(string $name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasArgument($name) + { + return $this->definition->hasArgument($name); + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * {@inheritdoc} + */ + public function getOption(string $name) + { + 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(); + } + + /** + * {@inheritdoc} + */ + public function setOption(string $name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasOption(string $name) + { + return $this->definition->hasOption($name); + } + + /** + * Escapes a token through escapeshellarg if it contains unsafe chars. + * + * @return string + */ + public function escapeToken(string $token) + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * {@inheritdoc} + */ + public function setStream($stream) + { + $this->stream = $stream; + } + + /** + * {@inheritdoc} + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/lib/composer/symfony/console/Input/InputArgument.php b/lib/composer/symfony/console/Input/InputArgument.php new file mode 100644 index 0000000000000000000000000000000000000000..b6aa6452a0a0afd10803ca22f0951d293165ab65 --- /dev/null +++ b/lib/composer/symfony/console/Input/InputArgument.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 Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line argument. + * + * @author Fabien Potencier + */ +class InputArgument +{ + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * @param string $name The argument name + * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL + * @param string $description A description text + * @param string|string[]|null $default The default value (for self::OPTIONAL mode only) + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function __construct(string $name, int $mode = null, string $description = '', $default = null) + { + 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->setDefault($default); + } + + /** + * Returns the argument name. + * + * @return string The argument name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the argument is required. + * + * @return bool true if parameter mode is self::REQUIRED, false otherwise + */ + public function isRequired() + { + 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() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param string|string[]|null $default The default value + * + * @throws LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + if (self::REQUIRED === $this->mode && 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. + * + * @return string|string[]|null The default value + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/lib/composer/symfony/console/Input/InputAwareInterface.php b/lib/composer/symfony/console/Input/InputAwareInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..5a288de5d45fa88bb0f7c3924280afc918f97404 --- /dev/null +++ b/lib/composer/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); +} diff --git a/lib/composer/symfony/console/Input/InputDefinition.php b/lib/composer/symfony/console/Input/InputDefinition.php new file mode 100644 index 0000000000000000000000000000000000000000..a32e913b7d5f9d6f0b2641593d6720fc0fc9227a --- /dev/null +++ b/lib/composer/symfony/console/Input/InputDefinition.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 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 $arguments; + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + private $options; + private $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) + { + $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 = []) + { + $this->arguments = []; + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * Adds an array of InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function addArguments(?array $arguments = []) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * @throws LogicException When incorrect argument is given + */ + public function addArgument(InputArgument $argument) + { + if (isset($this->arguments[$argument->getName()])) { + throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * Returns an InputArgument by name or by position. + * + * @param string|int $name The InputArgument name or position + * + * @return InputArgument An InputArgument object + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument($name) + { + 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. + * + * @param string|int $name The InputArgument name or position + * + * @return bool true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name) + { + $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * Gets the array of InputArgument objects. + * + * @return InputArgument[] An array of InputArgument objects + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Returns the number of InputArguments. + * + * @return int The number of InputArguments + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? \PHP_INT_MAX : \count($this->arguments); + } + + /** + * Returns the number of required InputArguments. + * + * @return int The number of required InputArguments + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * Gets the default values. + * + * @return array An array of default values + */ + public function getArgumentDefaults() + { + $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 = []) + { + $this->options = []; + $this->shortcuts = []; + $this->addOptions($options); + } + + /** + * Adds an array of InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function addOptions(array $options = []) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * @throws LogicException When option given already exist + */ + public function addOption(InputOption $option) + { + 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 ($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(); + } + } + } + + /** + * Returns an InputOption by name. + * + * @return InputOption A InputOption object + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption(string $name) + { + 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). + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasOption(string $name) + { + return isset($this->options[$name]); + } + + /** + * Gets the array of InputOption objects. + * + * @return InputOption[] An array of InputOption objects + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns true if an InputOption object exists by shortcut. + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasShortcut(string $name) + { + return isset($this->shortcuts[$name]); + } + + /** + * Gets an InputOption by shortcut. + * + * @return InputOption An InputOption object + */ + public function getOptionForShortcut(string $shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * Gets an array of default values. + * + * @return array An array of all default values + */ + public function getOptionDefaults() + { + $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]; + } + + /** + * Gets the synopsis. + * + * @return string The synopsis + */ + public function getSynopsis(bool $short = false) + { + $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()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + 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/lib/composer/symfony/console/Input/InputInterface.php b/lib/composer/symfony/console/Input/InputInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..f6ad722a0e35004630905375e98d13650a8fa731 --- /dev/null +++ b/lib/composer/symfony/console/Input/InputInterface.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\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). + * + * @return string|null The value of the first argument or null otherwise + */ + public function getFirstArgument(); + + /** + * 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 + * + * @return bool true if the value is contained in the raw parameters + */ + public function hasParameterOption($values, bool $onlyParams = false); + + /** + * 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 mixed $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 + * + * @return mixed The option value + */ + public function getParameterOption($values, $default = false, bool $onlyParams = false); + + /** + * Binds the current Input instance with the given arguments and options. + * + * @throws RuntimeException + */ + public function bind(InputDefinition $definition); + + /** + * Validates the input. + * + * @throws RuntimeException When not enough arguments are given + */ + public function validate(); + + /** + * Returns all the given arguments merged with the default values. + * + * @return array + */ + public function getArguments(); + + /** + * Returns the argument value for a given argument name. + * + * @return string|string[]|null The argument value + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument(string $name); + + /** + * Sets an argument value by name. + * + * @param string|string[]|null $value The argument value + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function setArgument(string $name, $value); + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|int $name The InputArgument name or position + * + * @return bool true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name); + + /** + * Returns all the given options merged with the default values. + * + * @return array + */ + public function getOptions(); + + /** + * Returns the option value for a given option name. + * + * @return string|string[]|bool|null The option value + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption(string $name); + + /** + * Sets an option value by name. + * + * @param string|string[]|bool|null $value The option value + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function setOption(string $name, $value); + + /** + * Returns true if an InputOption object exists by name. + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasOption(string $name); + + /** + * Is this input means interactive? + * + * @return bool + */ + public function isInteractive(); + + /** + * Sets the input interactivity. + */ + public function setInteractive(bool $interactive); +} diff --git a/lib/composer/symfony/console/Input/InputOption.php b/lib/composer/symfony/console/Input/InputOption.php new file mode 100644 index 0000000000000000000000000000000000000000..d62e0aea02706c26ccf20ee5426a0c1cf28c4019 --- /dev/null +++ b/lib/composer/symfony/console/Input/InputOption.php @@ -0,0 +1,208 @@ + + * + * 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; + +/** + * Represents a command line option. + * + * @author Fabien Potencier + */ +class InputOption +{ + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * @param string $name The option name + * @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 $description A description text + * @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE) + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null) + { + if (0 === strpos($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (\is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif ($mode > 15 || $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; + + 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.'); + } + + $this->setDefault($default); + } + + /** + * Returns the option shortcut. + * + * @return string|null The shortcut + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * Returns the option name. + * + * @return string The name + */ + public function getName() + { + 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() + { + 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() + { + 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() + { + 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() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param string|string[]|int|bool|null $default The default value + * + * @throws LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + 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() ? $default : false; + } + + /** + * Returns the default value. + * + * @return string|string[]|int|bool|null The default value + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } + + /** + * Checks whether the given option equals this one. + * + * @return bool + */ + public function equals(self $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional() + ; + } +} diff --git a/lib/composer/symfony/console/Input/StreamableInputInterface.php b/lib/composer/symfony/console/Input/StreamableInputInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..d7e462f24443132dce367525f6e29619c5455dcf --- /dev/null +++ b/lib/composer/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); + + /** + * Returns the input stream. + * + * @return resource|null + */ + public function getStream(); +} diff --git a/lib/composer/symfony/console/Input/StringInput.php b/lib/composer/symfony/console/Input/StringInput.php new file mode 100644 index 0000000000000000000000000000000000000000..6fddf6488d4a407aa019ffdfaf6a104d9900b5d3 --- /dev/null +++ b/lib/composer/symfony/console/Input/StringInput.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\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 +{ + const REGEX_STRING = '([^\s]+?)(?:\s|(?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; + while ($cursor < $length) { + if (preg_match('/\s+/A', $input, $match, null, $cursor)) { + } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) { + $tokens[] = $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, \strlen($match[3]) - 2))); + } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) { + $tokens[] = stripcslashes(substr($match[0], 1, \strlen($match[0]) - 2)); + } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) { + $tokens[] = stripcslashes($match[1]); + } else { + // should never happen + throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10))); + } + + $cursor += \strlen($match[0]); + } + + return $tokens; + } +} diff --git a/lib/composer/symfony/console/LICENSE b/lib/composer/symfony/console/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9e936ec0448b8549e5edf08e5ac5f01491a8bfc8 --- /dev/null +++ b/lib/composer/symfony/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2020 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/lib/composer/symfony/console/Logger/ConsoleLogger.php b/lib/composer/symfony/console/Logger/ConsoleLogger.php new file mode 100644 index 0000000000000000000000000000000000000000..32361189f28237966cd4167730dd85318f1d64c2 --- /dev/null +++ b/lib/composer/symfony/console/Logger/ConsoleLogger.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 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 +{ + const INFO = 'info'; + const ERROR = 'error'; + + private $output; + private $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 $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 $errored = false; + + public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) + { + $this->output = $output; + $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; + $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; + } + + /** + * {@inheritdoc} + * + * @return void + */ + public function log($level, $message, array $context = []) + { + 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. + * + * @return bool + */ + public function hasErrored() + { + return $this->errored; + } + + /** + * Interpolates context values into the message placeholders. + * + * @author PHP Framework Interoperability Group + */ + private function interpolate(string $message, array $context): string + { + if (false === strpos($message, '{')) { + return $message; + } + + $replacements = []; + foreach ($context as $key => $val) { + if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { + $replacements["{{$key}}"] = $val; + } elseif ($val instanceof \DateTimeInterface) { + $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); + } elseif (\is_object($val)) { + $replacements["{{$key}}"] = '[object '.\get_class($val).']'; + } else { + $replacements["{{$key}}"] = '['.\gettype($val).']'; + } + } + + return strtr($message, $replacements); + } +} diff --git a/lib/composer/symfony/console/Output/BufferedOutput.php b/lib/composer/symfony/console/Output/BufferedOutput.php new file mode 100644 index 0000000000000000000000000000000000000000..d37c6e323805a5014085beaf7194c48665e8d7e3 --- /dev/null +++ b/lib/composer/symfony/console/Output/BufferedOutput.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\Console\Output; + +/** + * @author Jean-François Simon + */ +class BufferedOutput extends Output +{ + private $buffer = ''; + + /** + * Empties buffer and returns its content. + * + * @return string + */ + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $message, bool $newline) + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + } +} diff --git a/lib/composer/symfony/console/Output/ConsoleOutput.php b/lib/composer/symfony/console/Output/ConsoleOutput.php new file mode 100644 index 0000000000000000000000000000000000000000..96664f1508abb5ce2a3445c60ccbe92842c3cc6d --- /dev/null +++ b/lib/composer/symfony/console/Output/ConsoleOutput.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\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 $stderr; + private $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); + + $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()); + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated) + { + parent::setDecorated($decorated); + $this->stderr->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->stderr->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity(int $level) + { + parent::setVerbosity($level); + $this->stderr->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getErrorOutput() + { + return $this->stderr; + } + + /** + * {@inheritdoc} + */ + public function setErrorOutput(OutputInterface $error) + { + $this->stderr = $error; + } + + /** + * Returns true if current environment supports writing console output to + * STDOUT. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Returns true if current environment supports writing console output to + * STDERR. + * + * @return bool + */ + protected function hasStderrSupport() + { + 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'); + } + + return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); + } +} diff --git a/lib/composer/symfony/console/Output/ConsoleOutputInterface.php b/lib/composer/symfony/console/Output/ConsoleOutputInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..6b6635f580ffa50399d32b7aefb09293e8f22ea8 --- /dev/null +++ b/lib/composer/symfony/console/Output/ConsoleOutputInterface.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\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. + * + * @return OutputInterface + */ + public function getErrorOutput(); + + public function setErrorOutput(OutputInterface $error); + + public function section(): ConsoleSectionOutput; +} diff --git a/lib/composer/symfony/console/Output/ConsoleSectionOutput.php b/lib/composer/symfony/console/Output/ConsoleSectionOutput.php new file mode 100644 index 0000000000000000000000000000000000000000..c19edbf95e9d71add79091d1783c074a5cc6b3ca --- /dev/null +++ b/lib/composer/symfony/console/Output/ConsoleSectionOutput.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 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 $content = []; + private $lines = 0; + private $sections; + private $terminal; + + /** + * @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(); + } + + /** + * 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) + { + if (empty($this->content) || !$this->isDecorated()) { + return; + } + + if ($lines) { + array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content + } else { + $lines = $this->lines; + $this->content = []; + } + + $this->lines -= $lines; + + parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false); + } + + /** + * Overwrites the previous output with a new message. + * + * @param array|string $message + */ + public function overwrite($message) + { + $this->clear(); + $this->writeln($message); + } + + public function getContent(): string + { + return implode('', $this->content); + } + + /** + * @internal + */ + public function addContent(string $input) + { + foreach (explode(\PHP_EOL, $input) as $lineContent) { + $this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1; + $this->content[] = $lineContent; + $this->content[] = \PHP_EOL; + } + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + if (!$this->isDecorated()) { + parent::doWrite($message, $newline); + + return; + } + + $erasedContent = $this->popStreamContentUntilCurrentSection(); + + $this->addContent($message); + + parent::doWrite($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->lines; + $erasedContent[] = $section->getContent(); + } + + 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): string + { + return Helper::strlenWithoutDecoration($this->getFormatter(), str_replace("\t", ' ', $text)); + } +} diff --git a/lib/composer/symfony/console/Output/NullOutput.php b/lib/composer/symfony/console/Output/NullOutput.php new file mode 100644 index 0000000000000000000000000000000000000000..3bbe63ea0a007b4e0a5d6d6396a39d5e511281d0 --- /dev/null +++ b/lib/composer/symfony/console/Output/NullOutput.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 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 $formatter; + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if ($this->formatter) { + return $this->formatter; + } + // to comply with the interface we must return a OutputFormatterInterface + return $this->formatter = new NullOutputFormatter(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function setVerbosity(int $level) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return self::VERBOSITY_QUIET; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, int $options = self::OUTPUT_NORMAL) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) + { + // do nothing + } +} diff --git a/lib/composer/symfony/console/Output/Output.php b/lib/composer/symfony/console/Output/Output.php new file mode 100644 index 0000000000000000000000000000000000000000..ed13d58fc0d28ad18615f802d9f908f8da64316b --- /dev/null +++ b/lib/composer/symfony/console/Output/Output.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\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 $verbosity; + private $formatter; + + /** + * @param int $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 = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; + $this->formatter = $formatter ?: new OutputFormatter(); + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->formatter; + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated) + { + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->formatter->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity(int $level) + { + $this->verbosity = $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, int $options = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $options); + } + + /** + * {@inheritdoc} + */ + public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) + { + 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); +} diff --git a/lib/composer/symfony/console/Output/OutputInterface.php b/lib/composer/symfony/console/Output/OutputInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..38f82d869aa258abdbe19f6478a3dd9007fb2b7d --- /dev/null +++ b/lib/composer/symfony/console/Output/OutputInterface.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\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * OutputInterface is the interface implemented by all Output classes. + * + * @author Fabien Potencier + */ +interface OutputInterface +{ + const VERBOSITY_QUIET = 16; + const VERBOSITY_NORMAL = 32; + const VERBOSITY_VERBOSE = 64; + const VERBOSITY_VERY_VERBOSE = 128; + const VERBOSITY_DEBUG = 256; + + const OUTPUT_NORMAL = 1; + const OUTPUT_RAW = 2; + const OUTPUT_PLAIN = 4; + + /** + * Writes a message to the output. + * + * @param string|iterable $messages The message as an iterable of strings or a single string + * @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($messages, bool $newline = false, int $options = 0); + + /** + * Writes a message to the output and adds a newline at the end. + * + * @param string|iterable $messages The message as an iterable of strings or a single string + * @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($messages, int $options = 0); + + /** + * Sets the verbosity of the output. + */ + public function setVerbosity(int $level); + + /** + * Gets the current verbosity of the output. + * + * @return int The current level of verbosity (one of the VERBOSITY constants) + */ + public function getVerbosity(); + + /** + * Returns whether verbosity is quiet (-q). + * + * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise + */ + public function isQuiet(); + + /** + * Returns whether verbosity is verbose (-v). + * + * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise + */ + public function isVerbose(); + + /** + * Returns whether verbosity is very verbose (-vv). + * + * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise + */ + public function isVeryVerbose(); + + /** + * Returns whether verbosity is debug (-vvv). + * + * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise + */ + public function isDebug(); + + /** + * Sets the decorated flag. + */ + public function setDecorated(bool $decorated); + + /** + * Gets the decorated flag. + * + * @return bool true if the output will decorate messages, false otherwise + */ + public function isDecorated(); + + public function setFormatter(OutputFormatterInterface $formatter); + + /** + * Returns current output formatter instance. + * + * @return OutputFormatterInterface + */ + public function getFormatter(); +} diff --git a/lib/composer/symfony/console/Output/StreamOutput.php b/lib/composer/symfony/console/Output/StreamOutput.php new file mode 100644 index 0000000000000000000000000000000000000000..ea434527b9053d24e19ee2aad90b434393a3a954 --- /dev/null +++ b/lib/composer/symfony/console/Output/StreamOutput.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\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 +{ + 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; + + if (null === $decorated) { + $decorated = $this->hasColorSupport(); + } + + parent::__construct($verbosity, $decorated, $formatter); + } + + /** + * Gets the stream attached to this StreamOutput instance. + * + * @return resource A stream resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $message, bool $newline) + { + 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() + { + // Follow https://no-color.org/ + if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { + return false; + } + + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if (\DIRECTORY_SEPARATOR === '\\') { + return (\function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($this->stream)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return stream_isatty($this->stream); + } +} diff --git a/lib/composer/symfony/console/Question/ChoiceQuestion.php b/lib/composer/symfony/console/Question/ChoiceQuestion.php new file mode 100644 index 0000000000000000000000000000000000000000..24e6604946672bdf4ed445420d94aac1b306fec2 --- /dev/null +++ b/lib/composer/symfony/console/Question/ChoiceQuestion.php @@ -0,0 +1,182 @@ + + * + * 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 $choices; + private $multiselect = false; + private $prompt = ' > '; + private $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, $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. + * + * @return array + */ + public function getChoices() + { + return $this->choices; + } + + /** + * Sets multiselect option. + * + * When multiselect is set to true, multiple choices can be answered. + * + * @return $this + */ + public function setMultiselect(bool $multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns whether the choices are multiselect. + * + * @return bool + */ + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * Gets the prompt for choices. + * + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * Sets the prompt for choices. + * + * @return $this + */ + public function setPrompt(string $prompt) + { + $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) + { + $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('/^[^,]+(?:,[^,]+)*$/', $selected, $matches)) { + throw new InvalidArgumentException(sprintf($errorMessage, $selected)); + } + + $selectedChoices = explode(',', $selected); + } else { + $selectedChoices = [$selected]; + } + + if ($this->isTrimmable()) { + foreach ($selectedChoices as $k => $v) { + $selectedChoices[$k] = trim($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)); + } + + $multiselectChoices[] = (string) $result; + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/lib/composer/symfony/console/Question/ConfirmationQuestion.php b/lib/composer/symfony/console/Question/ConfirmationQuestion.php new file mode 100644 index 0000000000000000000000000000000000000000..4228521b9f859215d7bcc19aa2cb76beef288fa0 --- /dev/null +++ b/lib/composer/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 $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/lib/composer/symfony/console/Question/Question.php b/lib/composer/symfony/console/Question/Question.php new file mode 100644 index 0000000000000000000000000000000000000000..17d890d7634743b49732877975058c113112e680 --- /dev/null +++ b/lib/composer/symfony/console/Question/Question.php @@ -0,0 +1,285 @@ + + * + * 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 $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterCallback; + private $validator; + private $default; + private $normalizer; + private $trimmable = true; + + /** + * @param string $question The question to ask to the user + * @param mixed $default The default answer to return if the user enters nothing + */ + public function __construct(string $question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * Returns the question. + * + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * Returns the default answer. + * + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns whether the user response must be hidden. + * + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Sets whether the user response must be hidden or not. + * + * @param bool $hidden + * + * @return $this + * + * @throws LogicException In case the autocompleter is also used + */ + public function setHidden($hidden) + { + if ($this->autocompleterCallback) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * In case the response can not be hidden, whether to fallback on non-hidden question or not. + * + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * Sets whether to fallback on non-hidden question if the response can not be hidden. + * + * @param bool $fallback + * + * @return $this + */ + public function setHiddenFallback($fallback) + { + $this->hiddenFallback = (bool) $fallback; + + return $this; + } + + /** + * Gets values for the autocompleter. + * + * @return iterable|null + */ + public function getAutocompleterValues() + { + $callback = $this->getAutocompleterCallback(); + + return $callback ? $callback('') : null; + } + + /** + * Sets values for the autocompleter. + * + * @return $this + * + * @throws LogicException + */ + public function setAutocompleterValues(?iterable $values) + { + if (\is_array($values)) { + $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); + + $callback = static function () use ($values) { + return $values; + }; + } elseif ($values instanceof \Traversable) { + $valueCache = null; + $callback = static function () use ($values, &$valueCache) { + return $valueCache ?? $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 = null): self + { + if ($this->hidden && null !== $callback) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterCallback = $callback; + + return $this; + } + + /** + * Sets a validator for the question. + * + * @return $this + */ + public function setValidator(callable $validator = null) + { + $this->validator = $validator; + + return $this; + } + + /** + * Gets the validator for the question. + * + * @return callable|null + */ + public function getValidator() + { + 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) + { + if (null !== $attempts) { + $attempts = (int) $attempts; + if ($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. + * + * @return int|null + */ + public function getMaxAttempts() + { + 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) + { + $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. + * + * @return callable|null + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc(array $array) + { + return (bool) \count(array_filter(array_keys($array), 'is_string')); + } + + public function isTrimmable(): bool + { + return $this->trimmable; + } + + /** + * @return $this + */ + public function setTrimmable(bool $trimmable): self + { + $this->trimmable = $trimmable; + + return $this; + } +} diff --git a/lib/composer/symfony/console/README.md b/lib/composer/symfony/console/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3e2fc605e5bfd1466698b79bbbf86aa9fa024b7b --- /dev/null +++ b/lib/composer/symfony/console/README.md @@ -0,0 +1,20 @@ +Console Component +================= + +The Console component eases the creation of beautiful and testable command line +interfaces. + +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. diff --git a/lib/composer/symfony/console/Resources/bin/hiddeninput.exe b/lib/composer/symfony/console/Resources/bin/hiddeninput.exe new file mode 100644 index 0000000000000000000000000000000000000000..c8cf65e8d819e6e525121cf6b21f1c2429746038 Binary files /dev/null and b/lib/composer/symfony/console/Resources/bin/hiddeninput.exe differ diff --git a/lib/composer/symfony/console/SingleCommandApplication.php b/lib/composer/symfony/console/SingleCommandApplication.php new file mode 100644 index 0000000000000000000000000000000000000000..ffa176fbd0bc8e7fcf6715ea4d073df28c852259 --- /dev/null +++ b/lib/composer/symfony/console/SingleCommandApplication.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; + +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 $version = 'UNKNOWN'; + private $running = false; + + public function setVersion(string $version): self + { + $this->version = $version; + + 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); + // 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/lib/composer/symfony/console/Style/OutputStyle.php b/lib/composer/symfony/console/Style/OutputStyle.php new file mode 100644 index 0000000000000000000000000000000000000000..67a98ff073fefc4e699f833dfddb7b857ec278d6 --- /dev/null +++ b/lib/composer/symfony/console/Style/OutputStyle.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\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 $output; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + /** + * {@inheritdoc} + */ + public function newLine(int $count = 1) + { + $this->output->write(str_repeat(\PHP_EOL, $count)); + } + + /** + * @return ProgressBar + */ + public function createProgressBar(int $max = 0) + { + return new ProgressBar($this->output, $max); + } + + /** + * {@inheritdoc} + */ + public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) + { + $this->output->write($messages, $newline, $type); + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, int $type = self::OUTPUT_NORMAL) + { + $this->output->writeln($messages, $type); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity(int $level) + { + $this->output->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->output->getVerbosity(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated) + { + $this->output->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->output->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->output->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->output->getFormatter(); + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return $this->output->isQuiet(); + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return $this->output->isVerbose(); + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return $this->output->isVeryVerbose(); + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return $this->output->isDebug(); + } + + protected function getErrorOutput() + { + if (!$this->output instanceof ConsoleOutputInterface) { + return $this->output; + } + + return $this->output->getErrorOutput(); + } +} diff --git a/lib/composer/symfony/console/Style/StyleInterface.php b/lib/composer/symfony/console/Style/StyleInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..afb841c0d089054edd7983ac84c426d466c4227e --- /dev/null +++ b/lib/composer/symfony/console/Style/StyleInterface.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 Symfony\Component\Console\Style; + +/** + * Output style helpers. + * + * @author Kevin Bond + */ +interface StyleInterface +{ + /** + * Formats a command title. + */ + public function title(string $message); + + /** + * Formats a section title. + */ + public function section(string $message); + + /** + * Formats a list. + */ + public function listing(array $elements); + + /** + * Formats informational text. + * + * @param string|array $message + */ + public function text($message); + + /** + * Formats a success result bar. + * + * @param string|array $message + */ + public function success($message); + + /** + * Formats an error result bar. + * + * @param string|array $message + */ + public function error($message); + + /** + * Formats an warning result bar. + * + * @param string|array $message + */ + public function warning($message); + + /** + * Formats a note admonition. + * + * @param string|array $message + */ + public function note($message); + + /** + * Formats a caution admonition. + * + * @param string|array $message + */ + public function caution($message); + + /** + * Formats a table. + */ + public function table(array $headers, array $rows); + + /** + * Asks a question. + * + * @return mixed + */ + public function ask(string $question, ?string $default = null, callable $validator = null); + + /** + * Asks a question with the user input hidden. + * + * @return mixed + */ + public function askHidden(string $question, callable $validator = null); + + /** + * Asks for confirmation. + * + * @return bool + */ + public function confirm(string $question, bool $default = true); + + /** + * Asks a choice question. + * + * @param string|int|null $default + * + * @return mixed + */ + public function choice(string $question, array $choices, $default = null); + + /** + * Add newline(s). + */ + public function newLine(int $count = 1); + + /** + * Starts the progress output. + */ + public function progressStart(int $max = 0); + + /** + * Advances the progress output X steps. + */ + public function progressAdvance(int $step = 1); + + /** + * Finishes the progress output. + */ + public function progressFinish(); +} diff --git a/lib/composer/symfony/console/Style/SymfonyStyle.php b/lib/composer/symfony/console/Style/SymfonyStyle.php new file mode 100644 index 0000000000000000000000000000000000000000..fa4dfa018bd1550dcfa6882771c063d293e2628b --- /dev/null +++ b/lib/composer/symfony/console/Style/SymfonyStyle.php @@ -0,0 +1,499 @@ + + * + * 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\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\BufferedOutput; +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\Terminal; + +/** + * Output decorator helpers for the Symfony Style Guide. + * + * @author Kevin Bond + */ +class SymfonyStyle extends OutputStyle +{ + const MAX_LINE_LENGTH = 120; + + private $input; + private $questionHelper; + private $progressBar; + private $lineLength; + private $bufferedOutput; + + public function __construct(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->bufferedOutput = new BufferedOutput($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($output); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + */ + public function block($messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true) + { + $messages = \is_array($messages) ? array_values($messages) : [$messages]; + + $this->autoPrependBlock(); + $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function title(string $message) + { + $this->autoPrependBlock(); + $this->writeln([ + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), + ]); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function section(string $message) + { + $this->autoPrependBlock(); + $this->writeln([ + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), + ]); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function listing(array $elements) + { + $this->autoPrependText(); + $elements = array_map(function ($element) { + return sprintf(' * %s', $element); + }, $elements); + + $this->writeln($elements); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function text($message) + { + $this->autoPrependText(); + + $messages = \is_array($message) ? array_values($message) : [$message]; + foreach ($messages as $message) { + $this->writeln(sprintf(' %s', $message)); + } + } + + /** + * Formats a command comment. + * + * @param string|array $message + */ + public function comment($message) + { + $this->block($message, null, null, ' // ', false, false); + } + + /** + * {@inheritdoc} + */ + public function success($message) + { + $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function error($message) + { + $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function warning($message) + { + $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function note($message) + { + $this->block($message, 'NOTE', 'fg=yellow', ' ! '); + } + + /** + * {@inheritdoc} + */ + public function caution($message) + { + $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); + } + + /** + * {@inheritdoc} + */ + public function table(array $headers, array $rows) + { + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('%s'); + + $table = new Table($this); + $table->setHeaders($headers); + $table->setRows($rows); + $table->setStyle($style); + + $table->render(); + $this->newLine(); + } + + /** + * Formats a horizontal table. + */ + public function horizontalTable(array $headers, array $rows) + { + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('%s'); + + $table = new Table($this); + $table->setHeaders($headers); + $table->setRows($rows); + $table->setStyle($style); + $table->setHorizontal(true); + + $table->render(); + $this->newLine(); + } + + /** + * Formats a list of key/value horizontally. + * + * Each row can be one of: + * * 'A title' + * * ['key' => 'value'] + * * new TableSeparator() + * + * @param string|array|TableSeparator ...$list + */ + public function definitionList(...$list) + { + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('%s'); + + $table = new Table($this); + $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); + } + + $table->setHeaders($headers); + $table->setRows([$row]); + $table->setHorizontal(); + $table->setStyle($style); + + $table->render(); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function ask(string $question, ?string $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function askHidden(string $question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function confirm($question, $default = true) + { + return $this->askQuestion(new ConfirmationQuestion($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice(string $question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = isset($values[$default]) ? $values[$default] : $default; + } + + return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); + } + + /** + * {@inheritdoc} + */ + public function progressStart(int $max = 0) + { + $this->progressBar = $this->createProgressBar($max); + $this->progressBar->start(); + } + + /** + * {@inheritdoc} + */ + public function progressAdvance(int $step = 1) + { + $this->getProgressBar()->advance($step); + } + + /** + * {@inheritdoc} + */ + public function progressFinish() + { + $this->getProgressBar()->finish(); + $this->newLine(2); + $this->progressBar = null; + } + + /** + * {@inheritdoc} + */ + public function createProgressBar(int $max = 0) + { + $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; + } + + /** + * @return mixed + */ + public function askQuestion(Question $question) + { + if ($this->input->isInteractive()) { + $this->autoPrependBlock(); + } + + if (!$this->questionHelper) { + $this->questionHelper = new SymfonyQuestionHelper(); + } + + $answer = $this->questionHelper->ask($this->input, $this, $question); + + if ($this->input->isInteractive()) { + $this->newLine(); + $this->bufferedOutput->write("\n"); + } + + return $answer; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, int $type = self::OUTPUT_NORMAL) + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + foreach ($messages as $message) { + parent::writeln($message, $type); + $this->writeBuffer($message, true, $type); + } + } + + /** + * {@inheritdoc} + */ + public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + foreach ($messages as $message) { + parent::write($message, $newline, $type); + $this->writeBuffer($message, $newline, $type); + } + } + + /** + * {@inheritdoc} + */ + public function newLine(int $count = 1) + { + parent::newLine($count); + $this->bufferedOutput->write(str_repeat("\n", $count)); + } + + /** + * Returns a new instance which makes use of stderr if available. + * + * @return self + */ + public function getErrorStyle() + { + return new self($this->input, $this->getErrorOutput()); + } + + private function getProgressBar(): ProgressBar + { + if (!$this->progressBar) { + throw new RuntimeException('The ProgressBar is not started.'); + } + + return $this->progressBar; + } + + 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 ("\n" !== substr($fetched, -1)) { + $this->newLine(); + } + } + + private function writeBuffer(string $message, bool $newLine, int $type): void + { + // We need to know if the two last chars are PHP_EOL + // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer + $this->bufferedOutput->write(substr($message, -4), $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::strlenWithoutDecoration($this->getFormatter(), $prefix); + $lines = []; + + if (null !== $type) { + $type = sprintf('[%s] ', $type); + $indentLength = \strlen($type); + $lineIndentation = str_repeat(' ', $indentLength); + } + + // wrap and add newlines for each element + foreach ($messages as $key => $message) { + if ($escape) { + $message = OutputFormatter::escape($message); + } + + $lines = array_merge($lines, explode(\PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, \PHP_EOL, true))); + + 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(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line)); + + if ($style) { + $line = sprintf('<%s>%s', $style, $line); + } + } + + return $lines; + } +} diff --git a/lib/composer/symfony/console/Terminal.php b/lib/composer/symfony/console/Terminal.php new file mode 100644 index 0000000000000000000000000000000000000000..5e5a3c2f767dbab6ed08e75b8ee62d8527e2f4d9 --- /dev/null +++ b/lib/composer/symfony/console/Terminal.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; + +class Terminal +{ + private static $width; + private static $height; + private static $stty; + + /** + * Gets the terminal width. + * + * @return int + */ + public function getWidth() + { + $width = getenv('COLUMNS'); + if (false !== $width) { + return (int) trim($width); + } + + if (null === self::$width) { + self::initDimensions(); + } + + return self::$width ?: 80; + } + + /** + * Gets the terminal height. + * + * @return int + */ + public function getHeight() + { + $height = getenv('LINES'); + if (false !== $height) { + return (int) trim($height); + } + + if (null === self::$height) { + self::initDimensions(); + } + + return self::$height ?: 50; + } + + /** + * @internal + * + * @return bool + */ + public static function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + // skip check if exec function is disabled + if (!\function_exists('exec')) { + return false; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = 0 === $exitcode; + } + + private static function initDimensions() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('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 (!self::hasVt100Support() && 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(); + } + } + + /** + * Returns whether STDOUT has vt100 support (some Windows 10+ configurations). + */ + private static function hasVt100Support(): bool + { + return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w')); + } + + /** + * Initializes dimensions using the output of an stty columns line. + */ + private static function initDimensionsUsingStty() + { + if ($sttyString = self::getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $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/i', $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 | grep columns'); + } + + private static function readFromProcess(string $command): ?string + { + if (!\function_exists('proc_open')) { + return null; + } + + $descriptorspec = [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + + $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); + + return $info; + } +} diff --git a/lib/composer/symfony/console/Tester/ApplicationTester.php b/lib/composer/symfony/console/Tester/ApplicationTester.php new file mode 100644 index 0000000000000000000000000000000000000000..d021c14358f2d8dc46998847f4ccc719a659b3d5 --- /dev/null +++ b/lib/composer/symfony/console/Tester/ApplicationTester.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\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; + private $input; + private $statusCode; + + 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 = []) + { + $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); + } +} diff --git a/lib/composer/symfony/console/Tester/CommandTester.php b/lib/composer/symfony/console/Tester/CommandTester.php new file mode 100644 index 0000000000000000000000000000000000000000..57efc9a6754b2f7aa4e103c132ac6a0de9e05b17 --- /dev/null +++ b/lib/composer/symfony/console/Tester/CommandTester.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\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; + private $input; + private $statusCode; + + 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 = []) + { + // 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/lib/composer/symfony/console/Tester/TesterTrait.php b/lib/composer/symfony/console/Tester/TesterTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..4a344ca2e91ba56e115b9ff625ebbc859ff40d23 --- /dev/null +++ b/lib/composer/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 Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * @author Amrouche Hamza + */ +trait TesterTrait +{ + /** @var StreamOutput */ + private $output; + private $inputs = []; + private $captureStreamsIndependently = false; + + /** + * Gets the display returned by the last execution of the command or application. + * + * @return string The display + */ + public function getDisplay(bool $normalize = false) + { + if (null === $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 + * + * @return string + */ + public function getErrorOutput(bool $normalize = false) + { + 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. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the command or application. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the command or application. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * 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) + { + $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) + { + $this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately']; + 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( + isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL, + isset($options['decorated']) ? $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->setAccessible(true); + $strErrProperty->setValue($this->output, $errorOutput); + + $reflectedParent = $reflectedOutput->getParentClass(); + $streamProperty = $reflectedParent->getProperty('stream'); + $streamProperty->setAccessible(true); + $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/lib/composer/symfony/console/composer.json b/lib/composer/symfony/console/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..3f7e36aaeefdd8300aa5a7832c1aad0de8ab62e9 --- /dev/null +++ b/lib/composer/symfony/console/composer.json @@ -0,0 +1,63 @@ +{ + "name": "symfony/console", + "type": "library", + "description": "Symfony Console Component", + "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": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "require-dev": { + "symfony/config": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "suggest": { + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "", + "psr/log": "For using the console logger" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Console\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + } +} diff --git a/lib/composer/symfony/deprecation-contracts/.gitignore b/lib/composer/symfony/deprecation-contracts/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c49a5d8df5c6548379f00c77fe572a7217bce218 --- /dev/null +++ b/lib/composer/symfony/deprecation-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/lib/composer/symfony/deprecation-contracts/CHANGELOG.md b/lib/composer/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..e9847779ba985366b4bff79d369c09db31d620eb --- /dev/null +++ b/lib/composer/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/master/CHANGELOG.md diff --git a/lib/composer/symfony/deprecation-contracts/LICENSE b/lib/composer/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..5593b1d84f74a170e02b3e58408dc189ea838434 --- /dev/null +++ b/lib/composer/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 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/lib/composer/symfony/deprecation-contracts/README.md b/lib/composer/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4957933a6cc50402853ba29e6a7ce00d3e597470 --- /dev/null +++ b/lib/composer/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 necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/lib/composer/symfony/deprecation-contracts/composer.json b/lib/composer/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..c8ace825c325bedff0406f74d44067f476ab04c2 --- /dev/null +++ b/lib/composer/symfony/deprecation-contracts/composer.json @@ -0,0 +1,31 @@ +{ + "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": ">=7.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + } +} diff --git a/lib/composer/symfony/deprecation-contracts/function.php b/lib/composer/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000000000000000000000000000000000000..0d3451f65eeb4839ebd745c7ebc0c7e16e3d6eef --- /dev/null +++ b/lib/composer/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, ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), E_USER_DEPRECATED); + } +} diff --git a/lib/composer/symfony/event-dispatcher-contracts/.gitignore b/lib/composer/symfony/event-dispatcher-contracts/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c49a5d8df5c6548379f00c77fe572a7217bce218 --- /dev/null +++ b/lib/composer/symfony/event-dispatcher-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/lib/composer/symfony/event-dispatcher-contracts/CHANGELOG.md b/lib/composer/symfony/event-dispatcher-contracts/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..e9847779ba985366b4bff79d369c09db31d620eb --- /dev/null +++ b/lib/composer/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/master/CHANGELOG.md diff --git a/lib/composer/symfony/event-dispatcher-contracts/Event.php b/lib/composer/symfony/event-dispatcher-contracts/Event.php new file mode 100644 index 0000000000000000000000000000000000000000..46dcb2ba06337779e8065f5a20ec9f8f266d5f07 --- /dev/null +++ b/lib/composer/symfony/event-dispatcher-contracts/Event.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\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 $propagationStopped = false; + + /** + * {@inheritdoc} + */ + 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/lib/composer/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/lib/composer/symfony/event-dispatcher-contracts/EventDispatcherInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..351dc51312cc64f12c925f6b670cad5df7aeaaaa --- /dev/null +++ b/lib/composer/symfony/event-dispatcher-contracts/EventDispatcherInterface.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 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. + * + * @param object $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 object The passed $event MUST be returned + */ + public function dispatch(object $event, string $eventName = null): object; +} diff --git a/lib/composer/symfony/event-dispatcher-contracts/LICENSE b/lib/composer/symfony/event-dispatcher-contracts/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..69d925ba7511e664a16b2af1fabce06b8767455d --- /dev/null +++ b/lib/composer/symfony/event-dispatcher-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2020 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/lib/composer/symfony/event-dispatcher-contracts/README.md b/lib/composer/symfony/event-dispatcher-contracts/README.md new file mode 100644 index 0000000000000000000000000000000000000000..fb051c73fc74eae97b6f896f051955aa08f7ea38 --- /dev/null +++ b/lib/composer/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/master/README.md for more information. diff --git a/lib/composer/symfony/event-dispatcher-contracts/composer.json b/lib/composer/symfony/event-dispatcher-contracts/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..6730f492a718a7398d8f358ef16d3c8d6182fb7d --- /dev/null +++ b/lib/composer/symfony/event-dispatcher-contracts/composer.json @@ -0,0 +1,34 @@ +{ + "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": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + } +} diff --git a/lib/composer/symfony/event-dispatcher/CHANGELOG.md b/lib/composer/symfony/event-dispatcher/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..92a3b8bfc4d9e30f0f8aaa7975aceeb35937bcf1 --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/CHANGELOG.md @@ -0,0 +1,81 @@ +CHANGELOG +========= + +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/lib/composer/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/lib/composer/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php new file mode 100644 index 0000000000000000000000000000000000000000..11dce4897bc65ee67b1381c891cf7f6283323550 --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/Debug/TraceableEventDispatcher.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 Symfony\Component\EventDispatcher\Debug; + +use Psr\EventDispatcher\StoppableEventInterface; +use Psr\Log\LoggerInterface; +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 $logger; + protected $stopwatch; + + private $callStack; + private $dispatcher; + private $wrappedListeners; + private $orphanedEvents; + private $requestStack; + private $currentRequestHash = ''; + + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null) + { + $this->dispatcher = $dispatcher; + $this->stopwatch = $stopwatch; + $this->logger = $logger; + $this->wrappedListeners = []; + $this->orphanedEvents = []; + $this->requestStack = $requestStack; + } + + /** + * {@inheritdoc} + */ + public function addListener(string $eventName, $listener, int $priority = 0) + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function removeListener(string $eventName, $listener) + { + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener) { + $listener = $wrappedListener; + unset($this->wrappedListeners[$eventName][$index]); + break; + } + } + } + + return $this->dispatcher->removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->removeSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function getListeners(string $eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority(string $eventName, $listener) + { + // 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 $index => $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener) { + return $this->dispatcher->getListenerPriority($eventName, $wrappedListener); + } + } + } + + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners(string $eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function dispatch(object $event, string $eventName = null): object + { + $eventName = $eventName ?? \get_class($event); + + if (null === $this->callStack) { + $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; + } + + /** + * @return array + */ + public function getCalledListeners(Request $request = null) + { + if (null === $this->callStack) { + return []; + } + + $hash = $request ? spl_object_hash($request) : null; + $called = []; + foreach ($this->callStack as $listener) { + list($eventName, $requestHash) = $this->callStack->getInfo(); + if (null === $hash || $hash === $requestHash) { + $called[] = $listener->getInfo($eventName); + } + } + + return $called; + } + + /** + * @return array + */ + public function getNotCalledListeners(Request $request = null) + { + try { + $allListeners = $this->getListeners(); + } catch (\Exception $e) { + if (null !== $this->logger) { + $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) { + list(, $requestHash) = $this->callStack->getInfo(); + + if (null === $hash || $hash === $requestHash) { + $calledListeners[] = $calledListener->getWrappedListener(); + } + } + } + + $notCalled = []; + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as $listener) { + if (!\in_array($listener, $calledListeners, true)) { + if (!$listener instanceof WrappedListener) { + $listener = new WrappedListener($listener, null, $this->stopwatch, $this); + } + $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() + { + $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 + * + * @return mixed + */ + public function __call(string $method, array $arguments) + { + return $this->dispatcher->{$method}(...$arguments); + } + + /** + * Called before dispatching the event. + */ + protected function beforeDispatch(string $eventName, object $event) + { + } + + /** + * Called after dispatching the event. + */ + protected function afterDispatch(string $eventName, object $event) + { + } + + 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()) { + if (null !== $this->logger) { + $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()) { + if (null !== $this->logger) { + $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context); + } + + $skipped = true; + } + } + } + + private function sortNotCalledListeners(array $a, array $b) + { + 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; + } +} diff --git a/lib/composer/symfony/event-dispatcher/Debug/WrappedListener.php b/lib/composer/symfony/event-dispatcher/Debug/WrappedListener.php new file mode 100644 index 0000000000000000000000000000000000000000..58a5ed9813f3746727524f7cb0578a674ea5f51d --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/Debug/WrappedListener.php @@ -0,0 +1,127 @@ + + * + * 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 $listener; + private $optimizedListener; + private $name; + private $called; + private $stoppedPropagation; + private $stopwatch; + private $dispatcher; + private $pretty; + private $stub; + private $priority; + private static $hasClassStub; + + public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + { + $this->listener = $listener; + $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null); + $this->stopwatch = $stopwatch; + $this->dispatcher = $dispatcher; + $this->called = false; + $this->stoppedPropagation = false; + + if (\is_array($listener)) { + $this->name = \is_object($listener[0]) ? get_debug_type($listener[0]) : $listener[0]; + $this->pretty = $this->name.'::'.$listener[1]; + } elseif ($listener instanceof \Closure) { + $r = new \ReflectionFunction($listener); + if (false !== strpos($r->name, '{closure}')) { + $this->pretty = $this->name = 'closure'; + } elseif ($class = $r->getClosureScopeClass()) { + $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'; + } + + if (null !== $name) { + $this->name = $name; + } + + if (null === self::$hasClassStub) { + self::$hasClassStub = class_exists(ClassStub::class); + } + } + + public function getWrappedListener() + { + 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 + { + if (null === $this->stub) { + $this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()'; + } + + return [ + 'event' => $eventName, + 'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null), + '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'); + + ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); + + if ($e->isStarted()) { + $e->stop(); + } + + if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } +} diff --git a/lib/composer/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php b/lib/composer/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php new file mode 100644 index 0000000000000000000000000000000000000000..c4ea50f7868ed50e14ec1ea22954d71d8e14688e --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.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 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 $eventAliases; + private $eventAliasesParameter; + + public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases') + { + $this->eventAliases = $eventAliases; + $this->eventAliasesParameter = $eventAliasesParameter; + } + + public function process(ContainerBuilder $container): void + { + $eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : []; + + $container->setParameter( + $this->eventAliasesParameter, + array_merge($eventAliases, $this->eventAliases) + ); + } +} diff --git a/lib/composer/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/lib/composer/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 0000000000000000000000000000000000000000..c2b8b0d4187e882e955eb43e4c9676cc0604ab91 --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.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 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 +{ + protected $dispatcherService; + protected $listenerTag; + protected $subscriberTag; + protected $eventAliasesParameter; + + private $hotPathEvents = []; + private $hotPathTagName; + private $noPreloadEvents = []; + private $noPreloadTagName; + + public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases') + { + $this->dispatcherService = $dispatcherService; + $this->listenerTag = $listenerTag; + $this->subscriberTag = $subscriberTag; + $this->eventAliasesParameter = $eventAliasesParameter; + } + + /** + * @return $this + */ + public function setHotPathEvents(array $hotPathEvents, string $tagName = 'container.hot_path') + { + $this->hotPathEvents = array_flip($hotPathEvents); + $this->hotPathTagName = $tagName; + + return $this; + } + + /** + * @return $this + */ + public function setNoPreloadEvents(array $noPreloadEvents, string $tagName = 'container.no_preload'): self + { + $this->noPreloadEvents = array_flip($noPreloadEvents); + $this->noPreloadTagName = $tagName; + + return $this; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { + return; + } + + $aliases = []; + + if ($container->hasParameter($this->eventAliasesParameter)) { + $aliases = $container->getParameter($this->eventAliasesParameter); + } + + $globalDispatcherDefinition = $container->findDefinition($this->dispatcherService); + + foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) { + $noPreload = 0; + + foreach ($events as $event) { + $priority = isset($event['priority']) ? $event['priority'] : 0; + + if (!isset($event['event'])) { + if ($container->getDefinition($id)->hasTag($this->subscriberTag)) { + continue; + } + + $event['method'] = $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', + ], function ($matches) { return 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']) && $r->hasMethod('__invoke')) { + $event['method'] = '__invoke'; + } + } + + $dispatcherDefinition = $globalDispatcherDefinition; + if (isset($event['dispatcher'])) { + $dispatcherDefinition = $container->getDefinition($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($this->hotPathTagName); + } elseif (isset($this->noPreloadEvents[$event['event']])) { + ++$noPreload; + } + } + + if ($noPreload && \count($events) === $noPreload) { + $container->getDefinition($id)->addTag($this->noPreloadTagName); + } + } + + $extractingDispatcher = new ExtractingEventDispatcher(); + + foreach ($container->findTaggedServiceIds($this->subscriberTag, 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[] = $container->getDefinition($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($this->hotPathTagName); + } elseif (isset($this->noPreloadEvents[$args[0]])) { + ++$noPreload; + } + } + if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) { + $container->getDefinition($id)->addTag($this->noPreloadTagName); + } + $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()) + || $type->isBuiltin() + || Event::class === ($name = $type->getName()) + ) { + throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + } + + return $name; + } +} + +/** + * @internal + */ +class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface +{ + public $listeners = []; + + public static $aliases = []; + public static $subscriber; + + public function addListener(string $eventName, $listener, int $priority = 0) + { + $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/lib/composer/symfony/event-dispatcher/EventDispatcher.php b/lib/composer/symfony/event-dispatcher/EventDispatcher.php new file mode 100644 index 0000000000000000000000000000000000000000..8dba33d0d5278c0989454cb79b5337ad2ba1aa71 --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/EventDispatcher.php @@ -0,0 +1,280 @@ + + * + * 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 $listeners = []; + private $sorted = []; + private $optimized; + + public function __construct() + { + if (__CLASS__ === static::class) { + $this->optimized = []; + } + } + + /** + * {@inheritdoc} + */ + public function dispatch(object $event, string $eventName = null): object + { + $eventName = $eventName ?? \get_class($event); + + if (null !== $this->optimized && null !== $eventName) { + $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; + } + + /** + * {@inheritdoc} + */ + public function getListeners(string $eventName = null) + { + 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); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority(string $eventName, $listener) + { + 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] = $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] = $v[1] ?? '__invoke'; + } + if ($v === $listener) { + return $priority; + } + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function hasListeners(string $eventName = null) + { + if (null !== $eventName) { + return !empty($this->listeners[$eventName]); + } + + foreach ($this->listeners as $eventListeners) { + if ($eventListeners) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addListener(string $eventName, $listener, int $priority = 0) + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName], $this->optimized[$eventName]); + } + + /** + * {@inheritdoc} + */ + public function removeListener(string $eventName, $listener) + { + 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] = $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] = $v[1] ?? '__invoke'; + } + if ($v === $listener) { + unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]); + } + } + + if (!$listeners) { + unset($this->listeners[$eventName][$priority]); + } + } + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + 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]], isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + 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) + { + $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) + { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as $k => &$listener) { + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] = $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] = $listener[1] ?? '__invoke'; + } + ($closure = \Closure::fromCallable($listener))(...$args); + }; + } else { + $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener); + } + } + } + + return $this->optimized[$eventName]; + } +} diff --git a/lib/composer/symfony/event-dispatcher/EventDispatcherInterface.php b/lib/composer/symfony/event-dispatcher/EventDispatcherInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..88c707c9a360be1091833515d5adb599e5eed1b0 --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/EventDispatcherInterface.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 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 callable $listener The listener + * @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, $listener, int $priority = 0); + + /** + * 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); + + /** + * Removes an event listener from the specified events. + * + * @param callable $listener The listener to remove + */ + public function removeListener(string $eventName, $listener); + + public function removeSubscriber(EventSubscriberInterface $subscriber); + + /** + * Gets the listeners of a specific event or all listeners sorted by descending priority. + * + * @return array The event listeners for the specified event, or all event listeners by event name + */ + public function getListeners(string $eventName = null); + + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + * + * @param callable $listener The listener + * + * @return int|null The event listener priority + */ + public function getListenerPriority(string $eventName, $listener); + + /** + * Checks whether an event has any registered listeners. + * + * @return bool true if the specified event has any listeners, false otherwise + */ + public function hasListeners(string $eventName = null); +} diff --git a/lib/composer/symfony/event-dispatcher/EventSubscriberInterface.php b/lib/composer/symfony/event-dispatcher/EventSubscriberInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..741590b1bf3a3d8971ebbc5dc2985aad5ae1f8d1 --- /dev/null +++ b/lib/composer/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 The event names to listen to + */ + public static function getSubscribedEvents(); +} diff --git a/lib/composer/symfony/event-dispatcher/GenericEvent.php b/lib/composer/symfony/event-dispatcher/GenericEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..34b95cedee8172922f1b394fcb1cf351fac327d4 --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/GenericEvent.php @@ -0,0 +1,170 @@ + + * + * 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 + */ +class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate +{ + protected $subject; + protected $arguments; + + /** + * Encapsulate an event with $subject and $args. + * + * @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($subject = null, array $arguments = []) + { + $this->subject = $subject; + $this->arguments = $arguments; + } + + /** + * Getter for subject property. + * + * @return mixed The observer subject + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Get argument by key. + * + * @return mixed Contents of array key + * + * @throws \InvalidArgumentException if key is not found + */ + public function getArgument(string $key) + { + if ($this->hasArgument($key)) { + return $this->arguments[$key]; + } + + throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); + } + + /** + * Add argument to event. + * + * @param mixed $value Value + * + * @return $this + */ + public function setArgument(string $key, $value) + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Getter for all arguments. + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Set args property. + * + * @return $this + */ + public function setArguments(array $args = []) + { + $this->arguments = $args; + + return $this; + } + + /** + * Has argument. + * + * @return bool + */ + public function hasArgument(string $key) + { + return \array_key_exists($key, $this->arguments); + } + + /** + * ArrayAccess for argument getter. + * + * @param string $key Array key + * + * @return mixed + * + * @throws \InvalidArgumentException if key does not exist in $this->args + */ + public function offsetGet($key) + { + return $this->getArgument($key); + } + + /** + * ArrayAccess for argument setter. + * + * @param string $key Array key to set + * @param mixed $value Value + */ + public function offsetSet($key, $value) + { + $this->setArgument($key, $value); + } + + /** + * ArrayAccess for unset argument. + * + * @param string $key Array key + */ + public function offsetUnset($key) + { + if ($this->hasArgument($key)) { + unset($this->arguments[$key]); + } + } + + /** + * ArrayAccess has argument. + * + * @param string $key Array key + * + * @return bool + */ + public function offsetExists($key) + { + return $this->hasArgument($key); + } + + /** + * IteratorAggregate for iterating over the object like an array. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->arguments); + } +} diff --git a/lib/composer/symfony/event-dispatcher/ImmutableEventDispatcher.php b/lib/composer/symfony/event-dispatcher/ImmutableEventDispatcher.php new file mode 100644 index 0000000000000000000000000000000000000000..568d79c3a291695e88f05334ae611b1bb313add2 --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/ImmutableEventDispatcher.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\EventDispatcher; + +/** + * A read-only proxy for an event dispatcher. + * + * @author Bernhard Schussek + */ +class ImmutableEventDispatcher implements EventDispatcherInterface +{ + private $dispatcher; + + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function dispatch(object $event, string $eventName = null): object + { + return $this->dispatcher->dispatch($event, $eventName); + } + + /** + * {@inheritdoc} + */ + public function addListener(string $eventName, $listener, int $priority = 0) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeListener(string $eventName, $listener) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function getListeners(string $eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority(string $eventName, $listener) + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners(string $eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } +} diff --git a/lib/composer/symfony/event-dispatcher/LICENSE b/lib/composer/symfony/event-dispatcher/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9e936ec0448b8549e5edf08e5ac5f01491a8bfc8 --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2020 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/lib/composer/symfony/event-dispatcher/LegacyEventDispatcherProxy.php b/lib/composer/symfony/event-dispatcher/LegacyEventDispatcherProxy.php new file mode 100644 index 0000000000000000000000000000000000000000..6e17c8fcc9c2463c72f7383e9018a4c3e87b02cc --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/LegacyEventDispatcherProxy.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 Symfony\Component\EventDispatcher; + +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +trigger_deprecation('symfony/event-dispatcher', '5.1', '%s is deprecated, use the event dispatcher without the proxy.', LegacyEventDispatcherProxy::class); + +/** + * A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch(). + * + * @author Nicolas Grekas + * + * @deprecated since Symfony 5.1 + */ +final class LegacyEventDispatcherProxy +{ + public static function decorate(?EventDispatcherInterface $dispatcher): ?EventDispatcherInterface + { + return $dispatcher; + } +} diff --git a/lib/composer/symfony/event-dispatcher/README.md b/lib/composer/symfony/event-dispatcher/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e0d38eed017f8f46eac131d33e05ac2bdac4929b --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/event-dispatcher/composer.json b/lib/composer/symfony/event-dispatcher/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..a67f66a42aa7f0447fc0c805f827587ad0caae83 --- /dev/null +++ b/lib/composer/symfony/event-dispatcher/composer.json @@ -0,0 +1,56 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Symfony EventDispatcher Component", + "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": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/event-dispatcher-contracts": "^2", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/config": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^4.4|^5.0", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + } +} diff --git a/lib/composer/symfony/filesystem/CHANGELOG.md b/lib/composer/symfony/filesystem/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..4a0755bfe0a83b3789e3d9e169dc1fd43f39b7b7 --- /dev/null +++ b/lib/composer/symfony/filesystem/CHANGELOG.md @@ -0,0 +1,76 @@ +CHANGELOG +========= + +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/lib/composer/symfony/filesystem/Exception/ExceptionInterface.php b/lib/composer/symfony/filesystem/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..fc438d9f313853eada7be7a3a7afc2fc898ec7bf --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/filesystem/Exception/FileNotFoundException.php b/lib/composer/symfony/filesystem/Exception/FileNotFoundException.php new file mode 100644 index 0000000000000000000000000000000000000000..48b6408095a13f524f47cadd5347934ad807a3bc --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/filesystem/Exception/IOException.php b/lib/composer/symfony/filesystem/Exception/IOException.php new file mode 100644 index 0000000000000000000000000000000000000000..fea26e4ddc40c87e2f43f031bc073784a3eb6dad --- /dev/null +++ b/lib/composer/symfony/filesystem/Exception/IOException.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\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 $path; + + public function __construct(string $message, int $code = 0, \Throwable $previous = null, string $path = null) + { + $this->path = $path; + + parent::__construct($message, $code, $previous); + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + return $this->path; + } +} diff --git a/lib/composer/symfony/filesystem/Exception/IOExceptionInterface.php b/lib/composer/symfony/filesystem/Exception/IOExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..f9d4644a8727946c24276c9fa648d2f372bb48e9 --- /dev/null +++ b/lib/composer/symfony/filesystem/Exception/IOExceptionInterface.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\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. + * + * @return string|null The path + */ + public function getPath(); +} diff --git a/lib/composer/symfony/filesystem/Exception/InvalidArgumentException.php b/lib/composer/symfony/filesystem/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..abadc200297635622b72635608b02ec9ce6e5c9a --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/filesystem/Filesystem.php b/lib/composer/symfony/filesystem/Filesystem.php new file mode 100644 index 0000000000000000000000000000000000000000..b6df1bd967737ca896e600c213c4cbf0400bb03a --- /dev/null +++ b/lib/composer/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 $lastError; + + /** + * 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) + { + $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 (false === $source = @fopen($originFile, 'r')) { + throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile); + } + + // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default + if (false === $target = @fopen($targetFile, 'w', null, 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), 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 + @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. + * + * @param string|iterable $dirs The directory path + * + * @throws IOException On any directory creation failure + */ + public function mkdir($dirs, int $mode = 0777) + { + foreach ($this->toIterable($dirs) as $dir) { + if (is_dir($dir)) { + continue; + } + + if (!self::box('mkdir', $dir, $mode, true)) { + if (!is_dir($dir)) { + // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one + if (self::$lastError) { + throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir); + } + throw new IOException(sprintf('Failed to create "%s".', $dir), 0, null, $dir); + } + } + } + } + + /** + * Checks the existence of files or directories. + * + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check + * + * @return bool true if the file exists, false otherwise + */ + public function exists($files) + { + $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 string|iterable $files A filename, an array of files, or a \Traversable instance to create + * @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($files, int $time = null, int $atime = null) + { + foreach ($this->toIterable($files) as $file) { + $touch = $time ? @touch($file, $time, $atime) : @touch($file); + if (true !== $touch) { + throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file); + } + } + } + + /** + * Removes files or directories. + * + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove + * + * @throws IOException When removal fails + */ + public function remove($files) + { + if ($files instanceof \Traversable) { + $files = iterator_to_array($files, false); + } elseif (!\is_array($files)) { + $files = [$files]; + } + $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)) { + $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)); + + if (!self::box('rmdir', $file) && file_exists($file)) { + throw new IOException(sprintf('Failed to remove directory "%s": ', $file).self::$lastError); + } + } elseif (!self::box('unlink', $file) && 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 string|iterable $files A filename, an array of files, or a \Traversable instance to change mode + * @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($files, int $mode, int $umask = 0000, bool $recursive = false) + { + foreach ($this->toIterable($files) as $file) { + if (true !== @chmod($file, $mode & ~$umask)) { + throw new IOException(sprintf('Failed to chmod file "%s".', $file), 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|iterable $files A filename, an array of files, or a \Traversable instance to change owner + * @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($files, $user, bool $recursive = false) + { + 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 (true !== @lchown($file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file); + } + } else { + if (true !== @chown($file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file); + } + } + } + } + + /** + * Change the group of an array of files or directories. + * + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group + * @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($files, $group, bool $recursive = false) + { + 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 (true !== @lchgrp($file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); + } + } else { + if (true !== @chgrp($file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 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) + { + // 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 (true !== @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), 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) + { + 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, $targetFiles) + { + 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) + { + if (self::$lastError) { + if ('\\' === \DIRECTORY_SEPARATOR && false !== strpos(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), 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 + * + * @return string|null + */ + public function readlink(string $path, bool $canonicalize = false) + { + if (!$canonicalize && !is_link($path)) { + return null; + } + + if ($canonicalize) { + if (!$this->exists($path)) { + return null; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $path = readlink($path); + } + + return realpath($path); + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + return realpath($path); + } + + return readlink($path); + } + + /** + * Given an existing path, convert it to a path relative to a given starting path. + * + * @return string Path of target relative to starting path + */ + public function makePathRelative(string $endPath, string $startPath) + { + 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 = function ($path) { + return (\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; + }; + + list($endPath, $endDriveLetter) = $splitDriveLetter($endPath); + list($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 = []) + { + $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, isset($options['override']) ? $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. + * + * @return bool + */ + public function isAbsolutePath(string $file) + { + return 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 = ''*/) + { + $suffix = \func_num_args() > 2 ? func_get_arg(2) : ''; + list($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) { + $tmpFile = @tempnam($hierarchy, $prefix); + + // If tempnam failed or no scheme return the filename otherwise prepend the scheme + if (false !== $tmpFile) { + if (null !== $scheme && 'gs' !== $scheme) { + return $scheme.'://'.$tmpFile; + } + + return $tmpFile; + } + + throw new IOException('A temporary file could not be created.'); + } + + // 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 + $handle = @fopen($tmpFile, 'x+'); + + // If unsuccessful restart the loop + if (false === $handle) { + continue; + } + + // Close the file if it was successfully opened + @fclose($handle); + + return $tmpFile; + } + + throw new IOException('A temporary file could not be created.'); + } + + /** + * 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) + { + 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 (!is_writable($dir)) { + throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir); + } + + // Will create a temp file with 0600 access rights + // when the filesystem supports chmod. + $tmpFile = $this->tempnam($dir, basename($filename)); + + if (false === @file_put_contents($tmpFile, $content)) { + throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename); + } + + @chmod($tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask()); + + $this->rename($tmpFile, $filename, true); + } + + /** + * Appends content to an existing file. + * + * @param string|resource $content The content to append + * + * @throws IOException If the file is not writable + */ + public function appendToFile(string $filename, $content) + { + 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 (!is_writable($dir)) { + throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir); + } + + if (false === @file_put_contents($filename, $content, FILE_APPEND)) { + throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename); + } + } + + private function toIterable($files): iterable + { + return \is_array($files) || $files instanceof \Traversable ? $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]]; + } + + /** + * @return mixed + */ + private static function box(callable $func) + { + self::$lastError = null; + set_error_handler(__CLASS__.'::handleError'); + try { + $result = $func(...\array_slice(\func_get_args(), 1)); + restore_error_handler(); + + return $result; + } catch (\Throwable $e) { + } + restore_error_handler(); + + throw $e; + } + + /** + * @internal + */ + public static function handleError($type, $msg) + { + self::$lastError = $msg; + } +} diff --git a/lib/composer/symfony/filesystem/LICENSE b/lib/composer/symfony/filesystem/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9e936ec0448b8549e5edf08e5ac5f01491a8bfc8 --- /dev/null +++ b/lib/composer/symfony/filesystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2020 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/lib/composer/symfony/filesystem/README.md b/lib/composer/symfony/filesystem/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cb03d43c15dd25fabc83feb7b129b7037c6c6c8b --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/filesystem/composer.json b/lib/composer/symfony/filesystem/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..7ec870a91108ae9bd724840abc5ef1df80381d64 --- /dev/null +++ b/lib/composer/symfony/filesystem/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/filesystem", + "type": "library", + "description": "Symfony Filesystem Component", + "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": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Filesystem\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + } +} diff --git a/lib/composer/symfony/finder/CHANGELOG.md b/lib/composer/symfony/finder/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..33f5bd589eb7b84258ef7eae450727c98e7f884b --- /dev/null +++ b/lib/composer/symfony/finder/CHANGELOG.md @@ -0,0 +1,79 @@ +CHANGELOG +========= + +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/lib/composer/symfony/finder/Comparator/Comparator.php b/lib/composer/symfony/finder/Comparator/Comparator.php new file mode 100644 index 0000000000000000000000000000000000000000..cfe3965fac98fa97d101169bab10329d87c54d63 --- /dev/null +++ b/lib/composer/symfony/finder/Comparator/Comparator.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\Comparator; + +/** + * Comparator. + * + * @author Fabien Potencier + */ +class Comparator +{ + private $target; + private $operator = '=='; + + /** + * Gets the target value. + * + * @return string The target value + */ + public function getTarget() + { + return $this->target; + } + + public function setTarget(string $target) + { + $this->target = $target; + } + + /** + * Gets the comparison operator. + * + * @return string The operator + */ + public function getOperator() + { + return $this->operator; + } + + /** + * Sets the comparison operator. + * + * @throws \InvalidArgumentException + */ + public function setOperator(string $operator) + { + if ('' === $operator) { + $operator = '=='; + } + + if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->operator = $operator; + } + + /** + * Tests against the target. + * + * @param mixed $test A test value + * + * @return bool + */ + public function test($test) + { + switch ($this->operator) { + case '>': + return $test > $this->target; + case '>=': + return $test >= $this->target; + case '<': + return $test < $this->target; + case '<=': + return $test <= $this->target; + case '!=': + return $test != $this->target; + } + + return $test == $this->target; + } +} diff --git a/lib/composer/symfony/finder/Comparator/DateComparator.php b/lib/composer/symfony/finder/Comparator/DateComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..d17c77a9d3fd048b112c32b7ed6b6c18b2440c9b --- /dev/null +++ b/lib/composer/symfony/finder/Comparator/DateComparator.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\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 \DateTime($matches[2]); + $target = $date->format('U'); + } catch (\Exception $e) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); + } + + $operator = isset($matches[1]) ? $matches[1] : '=='; + if ('since' === $operator || 'after' === $operator) { + $operator = '>'; + } + + if ('until' === $operator || 'before' === $operator) { + $operator = '<'; + } + + $this->setOperator($operator); + $this->setTarget($target); + } +} diff --git a/lib/composer/symfony/finder/Comparator/NumberComparator.php b/lib/composer/symfony/finder/Comparator/NumberComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..80667c9dddd51518951fd7af8ea825f1f8ccb499 --- /dev/null +++ b/lib/composer/symfony/finder/Comparator/NumberComparator.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 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|int $test A comparison string or an integer + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(?string $test) + { + if (!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)); + } + + $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; + } + } + + $this->setTarget($target); + $this->setOperator(isset($matches[1]) ? $matches[1] : '=='); + } +} diff --git a/lib/composer/symfony/finder/Exception/AccessDeniedException.php b/lib/composer/symfony/finder/Exception/AccessDeniedException.php new file mode 100644 index 0000000000000000000000000000000000000000..ee195ea8d740126b55573e7dc12a14af7d73def1 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/finder/Exception/DirectoryNotFoundException.php b/lib/composer/symfony/finder/Exception/DirectoryNotFoundException.php new file mode 100644 index 0000000000000000000000000000000000000000..c6cc0f2736370cf796d7e2d34c6f1efff85db3a6 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/finder/Finder.php b/lib/composer/symfony/finder/Finder.php new file mode 100644 index 0000000000000000000000000000000000000000..e1bcea35d265607c448bac34c3116f6539349344 --- /dev/null +++ b/lib/composer/symfony/finder/Finder.php @@ -0,0 +1,797 @@ + + * + * 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\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 + */ +class Finder implements \IteratorAggregate, \Countable +{ + const IGNORE_VCS_FILES = 1; + const IGNORE_DOT_FILES = 2; + const IGNORE_VCS_IGNORED_FILES = 4; + + private $mode = 0; + private $names = []; + private $notNames = []; + private $exclude = []; + private $filters = []; + private $depths = []; + private $sizes = []; + private $followLinks = false; + private $reverseSorting = false; + private $sort = false; + private $ignore = 0; + private $dirs = []; + private $dates = []; + private $iterators = []; + private $contains = []; + private $notContains = []; + private $paths = []; + private $notPaths = []; + private $ignoreUnreadableDirs = false; + + private static $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. + * + * @return static + */ + public static function create() + { + return new static(); + } + + /** + * Restricts the matching to directories only. + * + * @return $this + */ + public function directories() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return $this + */ + public function files() + { + $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($levels) + { + 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($dates) + { + 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 + * $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($patterns) + { + $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($patterns) + { + $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($patterns) + { + $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($patterns) + { + $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($patterns) + { + $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($patterns) + { + $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($sizes) + { + 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($dirs) + { + $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) + { + 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) + { + 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) + { + 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($pattern) + { + 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) + { + $this->sort = $closure; + + 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) + { + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; + + 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() + { + $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() + { + $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; + + return $this; + } + + /** + * Reverses the sorting. + * + * @return $this + */ + public function reverseSorting() + { + $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() + { + $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() + { + $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. + * + * @return $this + * + * @see CustomFilterIterator + */ + public function filter(\Closure $closure) + { + $this->filters[] = $closure; + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return $this + */ + public function followLinks() + { + $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) + { + $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($dirs) + { + $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_merge($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|SplFileInfo[] An iterator + * + * @throws \LogicException if the in() method has not been called + */ + public function getIterator() + { + 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)) { + return $this->searchInDirectory($this->dirs[0]); + } + + $iterator = new \AppendIterator(); + foreach ($this->dirs as $dir) { + $iterator->append($this->searchInDirectory($dir)); + } + + foreach ($this->iterators as $it) { + $iterator->append($it); + } + + 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) + { + if ($iterator instanceof \IteratorAggregate) { + $this->iterators[] = $iterator->getIterator(); + } elseif ($iterator instanceof \Iterator) { + $this->iterators[] = $iterator; + } elseif ($iterator instanceof \Traversable || \is_array($iterator)) { + $it = new \ArrayIterator(); + foreach ($iterator as $file) { + $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file)); + } + $this->iterators[] = $it; + } else { + throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); + } + + return $this; + } + + /** + * Check if the any results were found. + * + * @return bool + */ + public function hasResults() + { + foreach ($this->getIterator() as $_) { + return true; + } + + return false; + } + + /** + * Counts all the results collected by the iterators. + * + * @return int + */ + public function count() + { + return iterator_count($this->getIterator()); + } + + private function searchInDirectory(string $dir): \Iterator + { + $exclude = $this->exclude; + $notPaths = $this->notPaths; + + 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[] = '#(^|/)\..+(/|$)#'; + } + + if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) { + $gitignoreFilePath = sprintf('%s/.gitignore', $dir); + if (!is_readable($gitignoreFilePath)) { + throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath)); + } + $notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]); + } + + $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 ($this->sort || $this->reverseSorting) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting); + $iterator = $iteratorAggregate->getIterator(); + } + + 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/lib/composer/symfony/finder/Gitignore.php b/lib/composer/symfony/finder/Gitignore.php new file mode 100644 index 0000000000000000000000000000000000000000..5ffe585f8aa1c7ecd58e0581cb9c498bfd4046eb --- /dev/null +++ b/lib/composer/symfony/finder/Gitignore.php @@ -0,0 +1,105 @@ + + * + * 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 Ahmed Abdou + */ +class Gitignore +{ + /** + * Returns a regexp which is the equivalent of the gitignore pattern. + * + * @return string The regexp + */ + public static function toRegex(string $gitignoreFileContent): string + { + $gitignoreFileContent = preg_replace('/^[^\\\r\n]*#.*/m', '', $gitignoreFileContent); + $gitignoreLines = preg_split('/\r\n|\r|\n/', $gitignoreFileContent); + $gitignoreLines = array_map('trim', $gitignoreLines); + $gitignoreLines = array_filter($gitignoreLines); + + $ignoreLinesPositive = array_filter($gitignoreLines, function (string $line) { + return !preg_match('/^!/', $line); + }); + + $ignoreLinesNegative = array_filter($gitignoreLines, function (string $line) { + return preg_match('/^!/', $line); + }); + + $ignoreLinesNegative = array_map(function (string $line) { + return preg_replace('/^!(.*)/', '${1}', $line); + }, $ignoreLinesNegative); + $ignoreLinesNegative = array_map([__CLASS__, 'getRegexFromGitignore'], $ignoreLinesNegative); + + $ignoreLinesPositive = array_map([__CLASS__, 'getRegexFromGitignore'], $ignoreLinesPositive); + if (empty($ignoreLinesPositive)) { + return '/^$/'; + } + + if (empty($ignoreLinesNegative)) { + return sprintf('/%s/', implode('|', $ignoreLinesPositive)); + } + + return sprintf('/(?=^(?:(?!(%s)).)*$)(%s)/', implode('|', $ignoreLinesNegative), implode('|', $ignoreLinesPositive)); + } + + private static function getRegexFromGitignore(string $gitignorePattern): string + { + $regex = '('; + if (0 === strpos($gitignorePattern, '/')) { + $gitignorePattern = substr($gitignorePattern, 1); + $regex .= '^'; + } else { + $regex .= '(^|\/)'; + } + + if ('/' === $gitignorePattern[\strlen($gitignorePattern) - 1]) { + $gitignorePattern = substr($gitignorePattern, 0, -1); + } + + $iMax = \strlen($gitignorePattern); + for ($i = 0; $i < $iMax; ++$i) { + $doubleChars = substr($gitignorePattern, $i, 2); + if ('**' === $doubleChars) { + $regex .= '.+'; + ++$i; + continue; + } + + $c = $gitignorePattern[$i]; + switch ($c) { + case '*': + $regex .= '[^\/]+'; + break; + case '/': + case '.': + case ':': + case '(': + case ')': + case '{': + case '}': + $regex .= '\\'.$c; + break; + default: + $regex .= $c; + } + } + + $regex .= '($|\/)'; + $regex .= ')'; + + return $regex; + } +} diff --git a/lib/composer/symfony/finder/Glob.php b/lib/composer/symfony/finder/Glob.php new file mode 100644 index 0000000000000000000000000000000000000000..8447932e57a722bc730addba5f79fe71476c8a02 --- /dev/null +++ b/lib/composer/symfony/finder/Glob.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\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. + * + * @return string + */ + public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#') + { + $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/lib/composer/symfony/finder/Iterator/CustomFilterIterator.php b/lib/composer/symfony/finder/Iterator/CustomFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..a30bbd0b9d265309945212914866566e84679c4f --- /dev/null +++ b/lib/composer/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 + */ +class CustomFilterIterator extends \FilterIterator +{ + private $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. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + + foreach ($this->filters as $filter) { + if (false === $filter($fileinfo)) { + return false; + } + } + + return true; + } +} diff --git a/lib/composer/symfony/finder/Iterator/DateRangeFilterIterator.php b/lib/composer/symfony/finder/Iterator/DateRangeFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..2e97e00d374562c6915df6a806ddcb00e07589aa --- /dev/null +++ b/lib/composer/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 + */ +class DateRangeFilterIterator extends \FilterIterator +{ + private $comparators = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param DateComparator[] $comparators An array of DateComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $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/lib/composer/symfony/finder/Iterator/DepthRangeFilterIterator.php b/lib/composer/symfony/finder/Iterator/DepthRangeFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..436a66d84b99b686740ddbb8e3ebdfe8a863e588 --- /dev/null +++ b/lib/composer/symfony/finder/Iterator/DepthRangeFilterIterator.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; + +/** + * DepthRangeFilterIterator limits the directory depth. + * + * @author Fabien Potencier + */ +class DepthRangeFilterIterator extends \FilterIterator +{ + private $minDepth = 0; + + /** + * @param \RecursiveIteratorIterator $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. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + return $this->getInnerIterator()->getDepth() >= $this->minDepth; + } +} diff --git a/lib/composer/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/lib/composer/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..6a1b291adea3037b071e2ffd209497b8afddf326 --- /dev/null +++ b/lib/composer/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.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 Symfony\Component\Finder\Iterator; + +/** + * ExcludeDirectoryFilterIterator filters out directories. + * + * @author Fabien Potencier + */ +class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator +{ + private $iterator; + private $isRecursive; + private $excludedDirs = []; + private $excludedPattern; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param string[] $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) { + $directory = rtrim($directory, '/'); + if (!$this->isRecursive || false !== strpos($directory, '/')) { + $patterns[] = preg_quote($directory, '#'); + } else { + $this->excludedDirs[$directory] = true; + } + } + if ($patterns) { + $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool True if the value should be kept, false otherwise + */ + public function accept() + { + 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); + } + + return true; + } + + /** + * @return bool + */ + public function hasChildren() + { + return $this->isRecursive && $this->iterator->hasChildren(); + } + + public function getChildren() + { + $children = new self($this->iterator->getChildren(), []); + $children->excludedDirs = $this->excludedDirs; + $children->excludedPattern = $this->excludedPattern; + + return $children; + } +} diff --git a/lib/composer/symfony/finder/Iterator/FileTypeFilterIterator.php b/lib/composer/symfony/finder/Iterator/FileTypeFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..a4c4eec72e90d6b7f1cd7e7e61f3854603036afd --- /dev/null +++ b/lib/composer/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 + */ +class FileTypeFilterIterator extends \FilterIterator +{ + const ONLY_FILES = 1; + const ONLY_DIRECTORIES = 2; + + private $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. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $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/lib/composer/symfony/finder/Iterator/FilecontentFilterIterator.php b/lib/composer/symfony/finder/Iterator/FilecontentFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..b26a3684832613e4e6dfcb5791ba1ceca6ad4a6e --- /dev/null +++ b/lib/composer/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; + +/** + * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class FilecontentFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + 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 + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex(string $str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/lib/composer/symfony/finder/Iterator/FilenameFilterIterator.php b/lib/composer/symfony/finder/Iterator/FilenameFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..dedd1ca55e87883b48a6673d0d39792689e019ef --- /dev/null +++ b/lib/composer/symfony/finder/Iterator/FilenameFilterIterator.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\Component\Finder\Iterator; + +use Symfony\Component\Finder\Glob; + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @author Fabien Potencier + */ +class FilenameFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + 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 + * + * @return string regexp corresponding to a given glob or regexp + */ + protected function toRegex(string $str) + { + return $this->isRegex($str) ? $str : Glob::toRegex($str); + } +} diff --git a/lib/composer/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/lib/composer/symfony/finder/Iterator/MultiplePcreFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..78a34abef8877fc2091a3330199408874a26250b --- /dev/null +++ b/lib/composer/symfony/finder/Iterator/MultiplePcreFilterIterator.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\Finder\Iterator; + +/** + * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). + * + * @author Fabien Potencier + */ +abstract class MultiplePcreFilterIterator extends \FilterIterator +{ + protected $matchRegexps = []; + protected $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. + * + * @return bool + */ + protected function isAccepted(string $string) + { + // 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. + * + * @return bool + */ + protected function isRegex(string $str) + { + if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $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. + * + * @return string + */ + abstract protected function toRegex(string $str); +} diff --git a/lib/composer/symfony/finder/Iterator/PathFilterIterator.php b/lib/composer/symfony/finder/Iterator/PathFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..67b71f46d77242027881cc144901a7e789821b5d --- /dev/null +++ b/lib/composer/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; + +/** + * PathFilterIterator filters files by path patterns (e.g. some/special/dir). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $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 + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex(string $str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/lib/composer/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/lib/composer/symfony/finder/Iterator/RecursiveDirectoryIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..7616b14a245b504d3ce0ca972ef21839d770061f --- /dev/null +++ b/lib/composer/symfony/finder/Iterator/RecursiveDirectoryIterator.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\Finder\Iterator; + +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Extends the \RecursiveDirectoryIterator to support relative paths. + * + * @author Victor Berchet + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + /** + * @var bool + */ + private $ignoreUnreadableDirs; + + /** + * @var bool + */ + private $rewindable; + + // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations + private $rootPath; + private $subPath; + private $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. + * + * @return SplFileInfo File information + */ + public function current() + { + // the logic here avoids redoing the same work in all iterations + + if (null === $subPathname = $this->subPath) { + $subPathname = $this->subPath = (string) $this->getSubPath(); + } + if ('' !== $subPathname) { + $subPathname .= $this->directorySeparator; + } + $subPathname .= $this->getFilename(); + + if ('/' !== $basePath = $this->rootPath) { + $basePath .= $this->directorySeparator; + } + + return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname); + } + + /** + * @return \RecursiveIterator + * + * @throws AccessDeniedException + */ + public function getChildren() + { + 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->rewindable = &$this->rewindable; + $children->rootPath = $this->rootPath; + } + + return $children; + } catch (\UnexpectedValueException $e) { + if ($this->ignoreUnreadableDirs) { + // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. + return new \RecursiveArrayIterator([]); + } else { + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Do nothing for non rewindable stream. + */ + public function rewind() + { + if (false === $this->isRewindable()) { + return; + } + + parent::rewind(); + } + + /** + * Checks if the stream is rewindable. + * + * @return bool true when the stream is rewindable, false otherwise + */ + public function isRewindable() + { + if (null !== $this->rewindable) { + return $this->rewindable; + } + + if (false !== $stream = @opendir($this->getPath())) { + $infos = stream_get_meta_data($stream); + closedir($stream); + + if ($infos['seekable']) { + return $this->rewindable = true; + } + } + + return $this->rewindable = false; + } +} diff --git a/lib/composer/symfony/finder/Iterator/SizeRangeFilterIterator.php b/lib/composer/symfony/finder/Iterator/SizeRangeFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..2aeef67b87f739db1741a157fb02c9d5990d604c --- /dev/null +++ b/lib/composer/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 + */ +class SizeRangeFilterIterator extends \FilterIterator +{ + private $comparators = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param NumberComparator[] $comparators An array of NumberComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $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/lib/composer/symfony/finder/Iterator/SortableIterator.php b/lib/composer/symfony/finder/Iterator/SortableIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..2aca397e8807b3f470c99fe7992ad2d292e95b7c --- /dev/null +++ b/lib/composer/symfony/finder/Iterator/SortableIterator.php @@ -0,0 +1,101 @@ + + * + * 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 + */ +class SortableIterator implements \IteratorAggregate +{ + const SORT_BY_NONE = 0; + const SORT_BY_NAME = 1; + const SORT_BY_TYPE = 2; + const SORT_BY_ACCESSED_TIME = 3; + const SORT_BY_CHANGED_TIME = 4; + const SORT_BY_MODIFIED_TIME = 5; + const SORT_BY_NAME_NATURAL = 6; + + private $iterator; + private $sort; + + /** + * @param \Traversable $iterator The Iterator to filter + * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) + * + * @throws \InvalidArgumentException + */ + public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false) + { + $this->iterator = $iterator; + $order = $reverseOrder ? -1 : 1; + + if (self::SORT_BY_NAME === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_NAME_NATURAL === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * strnatcmp($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 function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getATime() - $b->getATime()); + }; + } elseif (self::SORT_BY_CHANGED_TIME === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getCTime() - $b->getCTime()); + }; + } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getMTime() - $b->getMTime()); + }; + } elseif (self::SORT_BY_NONE === $sort) { + $this->sort = $order; + } elseif (\is_callable($sort)) { + $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort; + } else { + throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); + } + } + + /** + * @return \Traversable + */ + public function getIterator() + { + 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/lib/composer/symfony/finder/LICENSE b/lib/composer/symfony/finder/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9e936ec0448b8549e5edf08e5ac5f01491a8bfc8 --- /dev/null +++ b/lib/composer/symfony/finder/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2020 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/lib/composer/symfony/finder/README.md b/lib/composer/symfony/finder/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0b19c752572d8e842b784e5a48c7729f95ea7485 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/finder/SplFileInfo.php b/lib/composer/symfony/finder/SplFileInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..65d7423e0da3242ef6b7f84461225e979677e99d --- /dev/null +++ b/lib/composer/symfony/finder/SplFileInfo.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\Finder; + +/** + * Extends \SplFileInfo to support relative paths. + * + * @author Fabien Potencier + */ +class SplFileInfo extends \SplFileInfo +{ + private $relativePath; + private $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. + * + * @return string the relative path + */ + public function getRelativePath() + { + return $this->relativePath; + } + + /** + * Returns the relative path name. + * + * This path contains the file name. + * + * @return string the relative path name + */ + public function getRelativePathname() + { + return $this->relativePathname; + } + + public function getFilenameWithoutExtension(): string + { + $filename = $this->getFilename(); + + return pathinfo($filename, PATHINFO_FILENAME); + } + + /** + * Returns the contents of the file. + * + * @return string the contents of the file + * + * @throws \RuntimeException + */ + public function getContents() + { + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $content = file_get_contents($this->getPathname()); + restore_error_handler(); + if (false === $content) { + throw new \RuntimeException($error); + } + + return $content; + } +} diff --git a/lib/composer/symfony/finder/composer.json b/lib/composer/symfony/finder/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..c1b0f32796fbb774ac759dde2e43e7f70aff2fa8 --- /dev/null +++ b/lib/composer/symfony/finder/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/finder", + "type": "library", + "description": "Symfony Finder Component", + "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": ">=7.2.5" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Finder\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + } +} diff --git a/lib/composer/symfony/options-resolver/CHANGELOG.md b/lib/composer/symfony/options-resolver/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..d996e309f37230b6626cbcb7ff0f3cea142cca72 --- /dev/null +++ b/lib/composer/symfony/options-resolver/CHANGELOG.md @@ -0,0 +1,76 @@ +CHANGELOG +========= + +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/lib/composer/symfony/options-resolver/Debug/OptionsResolverIntrospector.php b/lib/composer/symfony/options-resolver/Debug/OptionsResolverIntrospector.php new file mode 100644 index 0000000000000000000000000000000000000000..95909f32e48e6b471c9ff398dfc3bd43a513e570 --- /dev/null +++ b/lib/composer/symfony/options-resolver/Debug/OptionsResolverIntrospector.php @@ -0,0 +1,120 @@ + + * + * 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 $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); + } + + /** + * @return mixed + * + * @throws NoConfigurationException on no configured value + */ + public function getDefault(string $option) + { + 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)); + } + + /** + * @return string|\Closure + * + * @throws NoConfigurationException on no configured deprecation + * + * @deprecated since Symfony 5.1, use "getDeprecation()" instead. + */ + public function getDeprecationMessage(string $option) + { + trigger_deprecation('symfony/options-resolver', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__); + + return $this->getDeprecation($option)['message']; + } + + /** + * @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/lib/composer/symfony/options-resolver/Exception/AccessException.php b/lib/composer/symfony/options-resolver/Exception/AccessException.php new file mode 100644 index 0000000000000000000000000000000000000000..c12b6806456f0ae3d51b19f99c73bb0bb0c50c48 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/options-resolver/Exception/ExceptionInterface.php b/lib/composer/symfony/options-resolver/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..ea99d050e4ab08409ab68f46894ab1715c8ae279 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/options-resolver/Exception/InvalidArgumentException.php b/lib/composer/symfony/options-resolver/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..6d421d68b35cb0f3b40749ccde74ebb14c2a6422 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/options-resolver/Exception/InvalidOptionsException.php b/lib/composer/symfony/options-resolver/Exception/InvalidOptionsException.php new file mode 100644 index 0000000000000000000000000000000000000000..6fd4f125f4478b555847e28ede4a8c9cdbff5668 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/options-resolver/Exception/MissingOptionsException.php b/lib/composer/symfony/options-resolver/Exception/MissingOptionsException.php new file mode 100644 index 0000000000000000000000000000000000000000..faa487f16f003354de8c0f681f806d31b44f28c9 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/options-resolver/Exception/NoConfigurationException.php b/lib/composer/symfony/options-resolver/Exception/NoConfigurationException.php new file mode 100644 index 0000000000000000000000000000000000000000..6693ec14df892d213865b404c6a3eb63e99cfb2e --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/options-resolver/Exception/NoSuchOptionException.php b/lib/composer/symfony/options-resolver/Exception/NoSuchOptionException.php new file mode 100644 index 0000000000000000000000000000000000000000..4c3280f4c7cac9a8069730ce19257b270a3de6a5 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/options-resolver/Exception/OptionDefinitionException.php b/lib/composer/symfony/options-resolver/Exception/OptionDefinitionException.php new file mode 100644 index 0000000000000000000000000000000000000000..e8e339d446efa29e2e119af80f08624d1f49d8db --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/options-resolver/Exception/UndefinedOptionsException.php b/lib/composer/symfony/options-resolver/Exception/UndefinedOptionsException.php new file mode 100644 index 0000000000000000000000000000000000000000..6ca3fce470a609d98ad5acef72298ea7645cf0f3 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/options-resolver/LICENSE b/lib/composer/symfony/options-resolver/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9e936ec0448b8549e5edf08e5ac5f01491a8bfc8 --- /dev/null +++ b/lib/composer/symfony/options-resolver/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2020 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/lib/composer/symfony/options-resolver/OptionConfigurator.php b/lib/composer/symfony/options-resolver/OptionConfigurator.php new file mode 100644 index 0000000000000000000000000000000000000000..47f5bea557b7be191776de02bd74cddf3ef5b063 --- /dev/null +++ b/lib/composer/symfony/options-resolver/OptionConfigurator.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 Symfony\Component\OptionsResolver; + +use Symfony\Component\OptionsResolver\Exception\AccessException; + +final class OptionConfigurator +{ + private $name; + private $resolver; + + public function __construct(string $name, OptionsResolver $resolver) + { + $this->name = $name; + $this->resolver = $resolver; + $this->resolver->setDefined($name); + } + + /** + * Adds allowed types for this option. + * + * @param string ...$types One or more accepted types + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function allowedTypes(string ...$types): self + { + $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(...$values): self + { + $this->resolver->setAllowedValues($this->name, $values); + + return $this; + } + + /** + * Sets the default value for this option. + * + * @param mixed $value The default value of the option + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function default($value): self + { + $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, $message = 'The option "%name%" is deprecated.'): self + { + $this->resolver->setDeprecated($this->name, $package, $version, $message); + + return $this; + } + + /** + * Sets the normalizer for this option. + * + * @param \Closure $normalizer The normalizer + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function normalize(\Closure $normalizer): self + { + $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(): self + { + $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): self + { + $this->resolver->setInfo($this->name, $info); + + return $this; + } +} diff --git a/lib/composer/symfony/options-resolver/Options.php b/lib/composer/symfony/options-resolver/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..d444ec4230d51f9e672e5845b34355cc39b4146c --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/options-resolver/OptionsResolver.php b/lib/composer/symfony/options-resolver/OptionsResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..2a484cdc32548cf496a3aca24dffdc0e8ecb9eaf --- /dev/null +++ b/lib/composer/symfony/options-resolver/OptionsResolver.php @@ -0,0 +1,1292 @@ + + * + * 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 +{ + /** + * The names of all defined options. + */ + private $defined = []; + + /** + * The default option values. + */ + private $defaults = []; + + /** + * A list of closure for nested options. + * + * @var \Closure[][] + */ + private $nested = []; + + /** + * The names of required options. + */ + private $required = []; + + /** + * The resolved option values. + */ + private $resolved = []; + + /** + * A list of normalizer closures. + * + * @var \Closure[][] + */ + private $normalizers = []; + + /** + * A list of accepted values for each option. + */ + private $allowedValues = []; + + /** + * A list of accepted types for each option. + */ + private $allowedTypes = []; + + /** + * A list of info messages for each option. + */ + private $info = []; + + /** + * A list of closures for evaluating lazy options. + */ + private $lazy = []; + + /** + * A list of lazy options whose closure is currently being called. + * + * This list helps detecting circular dependencies between lazy options. + */ + private $calling = []; + + /** + * A list of deprecated options. + */ + private $deprecated = []; + + /** + * The list of options provided by the user. + */ + private $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 $locked = false; + + private $parentsOptions = []; + + private static $typeAliases = [ + 'boolean' => 'bool', + 'integer' => 'int', + 'double' => 'float', + ]; + + /** + * 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'] + * } + * + * @param string $option The name of the option + * @param mixed $value The default value of the option + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefault(string $option, $value) + { + // 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]) || (null !== ($type = $params[1]->getType()) && 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; + } + + /** + * Sets a list of default values. + * + * @param array $defaults The default values to set + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefaults(array $defaults) + { + 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. + * + * @param string $option The option name + * + * @return bool Whether a default value is set + */ + public function hasDefault(string $option) + { + 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($optionNames) + { + 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()}. + * + * @param string $option The name of the option + * + * @return bool Whether the option is required + */ + public function isRequired(string $option) + { + return isset($this->required[$option]); + } + + /** + * Returns the names of all required options. + * + * @return string[] The names of the required options + * + * @see isRequired() + */ + public function getRequiredOptions() + { + 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. + * + * @param string $option The name of the option + * + * @return bool Whether the option is missing + */ + public function isMissing(string $option) + { + return isset($this->required[$option]) && !\array_key_exists($option, $this->defaults); + } + + /** + * Returns the names of all options missing a default value. + * + * @return string[] The names of the missing options + * + * @see isMissing() + */ + public function getMissingOptions() + { + 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($optionNames) + { + 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()}. + * + * @param string $option The option name + * + * @return bool Whether the option is defined + */ + public function isDefined(string $option) + { + return isset($this->defined[$option]); + } + + /** + * Returns the names of all defined options. + * + * @return string[] The names of the defined options + * + * @see isDefined() + */ + public function getDefinedOptions() + { + 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 + */ + public function setDeprecated(string $option/*, string $package, string $version, $message = 'The option "%name%" is deprecated.' */): self + { + 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)))); + } + + $args = \func_get_args(); + + if (\func_num_args() < 3) { + trigger_deprecation('symfony/options-resolver', '5.1', 'The signature of method "%s()" requires 2 new arguments: "string $package, string $version", not defining them is deprecated.', __METHOD__); + + $message = $args[1] ?? 'The option "%name%" is deprecated.'; + $package = $version = ''; + } else { + $package = $args[1]; + $version = $args[2]; + $message = $args[3] ?? 'The option "%name%" is deprecated.'; + } + + 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. + * + * @param string $option The option name + * @param \Closure $normalizer The normalizer + * + * @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) + { + 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. + * + * @param string $option The option name + * @param \Closure $normalizer The normalizer + * @param bool $forcePrepend If set to true, prepend instead of appending + * + * @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): self + { + 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) { + 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 string $option The option name + * @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, $allowedValues) + { + 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 string $option The option name + * @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, $allowedValues) + { + 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 $option The option name + * @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, $allowedTypes) + { + 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 $option The option name + * @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, $allowedTypes) + { + 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): self + { + 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; + } + + /** + * 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($optionNames) + { + 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() + { + 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. + * + * @param array $options A map of option names to values + * + * @return array The merged and validated options + * + * @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 = []) + { + 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 = 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) { + $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 string $option The option name + * @param bool $triggerDeprecation Whether to trigger the deprecation or not (true by default) + * + * @return mixed The option value + * + * @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($option, bool $triggerDeprecation = true) + { + 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->parentsOptions = $this->parentsOptions; + $resolver->parentsOptions[] = $option; + foreach ($this->nested[$option] as $closure) { + $closure($resolver, $this); + } + $value = $resolver->resolve($value); + } finally { + 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) { + $type = self::$typeAliases[$type] ?? $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 function ($item) { + return '[]' === substr(self::$typeAliases[$item] ?? $item, -2); + })) > 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.', + $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])))) { + $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, $value, array &$invalidTypes, int $level = 0): bool + { + if (\is_array($value) && '[]' === substr($type, -2)) { + $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) || (\function_exists($func = 'is_'.$type) && $func($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. + * + * @param string $option The option name + * + * @return bool Whether the option is set + * + * @throws AccessException If accessing this method outside of {@link resolve()} + * + * @see \ArrayAccess::offsetExists() + */ + public function offsetExists($option) + { + 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($option, $value) + { + throw new AccessException('Setting options via array access is not supported. Use setDefault() instead.'); + } + + /** + * Not supported. + * + * @throws AccessException + */ + public function offsetUnset($option) + { + 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. + * + * @return int Number of options + * + * @throws AccessException If accessing this method outside of {@link resolve()} + * + * @see \Countable::count() + */ + public function count() + { + if (!$this->locked) { + throw new AccessException('Counting is only supported within closures of lazy options and normalizers.'); + } + + return \count($this->defaults); + } + + /** + * 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 ("). + * + * @param mixed $value The value to format as string + */ + private function formatValue($value): string + { + if (\is_object($value)) { + return \get_class($value); + } + + 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)); + } + + $options = array_map(static function (string $option) use ($prefix): string { + return sprintf('%s[%s]', $prefix, $option); + }, $options); + } + + return implode('", "', $options); + } + + private function getParameterClassName(\ReflectionParameter $parameter): ?string + { + if (!($type = $parameter->getType()) || $type->isBuiltin()) { + return null; + } + + return $type->getName(); + } +} diff --git a/lib/composer/symfony/options-resolver/README.md b/lib/composer/symfony/options-resolver/README.md new file mode 100644 index 0000000000000000000000000000000000000000..245e69b548d6d62251f075d51eab8e657c1ab054 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/options-resolver/composer.json b/lib/composer/symfony/options-resolver/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..3d098cf1bdaa9cf324ae9d411665b4e2a17aa274 --- /dev/null +++ b/lib/composer/symfony/options-resolver/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/options-resolver", + "type": "library", + "description": "Symfony OptionsResolver Component", + "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": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php80": "^1.15" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + } +} diff --git a/lib/composer/symfony/polyfill-ctype/Ctype.php b/lib/composer/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 0000000000000000000000000000000000000000..58414dc73bd45b931b73a05a040d9ca23a61acf6 --- /dev/null +++ b/lib/composer/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,227 @@ + + * + * 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 string|int $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + 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 string|int $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + 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 string|int $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + 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 string|int $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + 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 string|int $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + 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 string|int $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + 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 string|int $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + 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 string|int $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + 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 string|int $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + 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 string|int $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + 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 string|int $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + 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 string|int $int + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/lib/composer/symfony/polyfill-ctype/LICENSE b/lib/composer/symfony/polyfill-ctype/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..3f853aaf35fe186d4016761eb6e8a403de3e6e0d --- /dev/null +++ b/lib/composer/symfony/polyfill-ctype/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 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/lib/composer/symfony/polyfill-ctype/README.md b/lib/composer/symfony/polyfill-ctype/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8add1ab0096e7e7ca60829b87266d2c5a884ac26 --- /dev/null +++ b/lib/composer/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/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lib/composer/symfony/polyfill-ctype/bootstrap.php b/lib/composer/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..8d6fc4beccaf4a1ba46a30fe0847b76be08a1c15 --- /dev/null +++ b/lib/composer/symfony/polyfill-ctype/bootstrap.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($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/lib/composer/symfony/polyfill-ctype/composer.json b/lib/composer/symfony/polyfill-ctype/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..90108c65bd1600c0cd3c0e8da29069ddbbdfc324 --- /dev/null +++ b/lib/composer/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": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/composer/symfony/polyfill-intl-grapheme/Grapheme.php b/lib/composer/symfony/polyfill-intl-grapheme/Grapheme.php new file mode 100644 index 0000000000000000000000000000000000000000..5f4167eb11d7c5b1e750e20b9ae0c5b763a65c6f --- /dev/null +++ b/lib/composer/symfony/polyfill-intl-grapheme/Grapheme.php @@ -0,0 +1,211 @@ + + * + * 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', PCRE_VERSION >= '8.32' ? '\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 + 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}])'; + + public static function grapheme_extract($s, $size, $type = GRAPHEME_EXTR_COUNT, $start = 0, &$next = 0) + { + if (\PHP_VERSION_ID >= 70100 && 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 (!isset($s[0]) || 0 > $size || 0 > $start || 0 > $type || 2 < $type) { + 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 = 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) { + return false; + } + if ($start >= $slen) { + return false; + } + + $rem = $slen - $start; + + if (0 > $len) { + $len += $rem; + } + if (0 === $len) { + return ''; + } + if (0 > $len) { + return 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 (!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 (\PHP_VERSION_ID < 50535 || (50600 <= \PHP_VERSION_ID && \PHP_VERSION_ID < 50621) || (70000 <= \PHP_VERSION_ID && \PHP_VERSION_ID < 70006)) { + $offset = 0; + } elseif (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; + } + } + + switch ($mode) { + case 0: $needle = iconv_strpos($s, $needle, 0, 'UTF-8'); break; + case 1: $needle = mb_stripos($s, $needle, 0, 'UTF-8'); break; + case 2: $needle = iconv_strrpos($s, $needle, 'UTF-8'); break; + default: $needle = mb_strripos($s, $needle, 0, 'UTF-8'); break; + } + + return false !== $needle ? self::grapheme_strlen(iconv_substr($s, 0, $needle, 'UTF-8')) + $offset : false; + } +} diff --git a/lib/composer/symfony/polyfill-intl-grapheme/LICENSE b/lib/composer/symfony/polyfill-intl-grapheme/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4cd8bdd3007da4d62985ec9e5ca81a1e18ae34d1 --- /dev/null +++ b/lib/composer/symfony/polyfill-intl-grapheme/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 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/lib/composer/symfony/polyfill-intl-grapheme/README.md b/lib/composer/symfony/polyfill-intl-grapheme/README.md new file mode 100644 index 0000000000000000000000000000000000000000..77523ea27044b4a71ced73aa538ce1c0162576a2 --- /dev/null +++ b/lib/composer/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/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lib/composer/symfony/polyfill-intl-grapheme/bootstrap.php b/lib/composer/symfony/polyfill-intl-grapheme/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..b290c802a91b1312d3ec0fe74ad88d788ae8e9f2 --- /dev/null +++ b/lib/composer/symfony/polyfill-intl-grapheme/bootstrap.php @@ -0,0 +1,54 @@ + + * + * 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 (!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($s, $size, $type = 0, $start = 0, &$next = 0) { return p\Grapheme::grapheme_extract($s, $size, $type, $start, $next); } +} +if (!function_exists('grapheme_stripos')) { + function grapheme_stripos($s, $needle, $offset = 0) { return p\Grapheme::grapheme_stripos($s, $needle, $offset); } +} +if (!function_exists('grapheme_stristr')) { + function grapheme_stristr($s, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_stristr($s, $needle, $beforeNeedle); } +} +if (!function_exists('grapheme_strlen')) { + function grapheme_strlen($s) { return p\Grapheme::grapheme_strlen($s); } +} +if (!function_exists('grapheme_strpos')) { + function grapheme_strpos($s, $needle, $offset = 0) { return p\Grapheme::grapheme_strpos($s, $needle, $offset); } +} +if (!function_exists('grapheme_strripos')) { + function grapheme_strripos($s, $needle, $offset = 0) { return p\Grapheme::grapheme_strripos($s, $needle, $offset); } +} +if (!function_exists('grapheme_strrpos')) { + function grapheme_strrpos($s, $needle, $offset = 0) { return p\Grapheme::grapheme_strrpos($s, $needle, $offset); } +} +if (!function_exists('grapheme_strstr')) { + function grapheme_strstr($s, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_strstr($s, $needle, $beforeNeedle); } +} +if (!function_exists('grapheme_substr')) { + function grapheme_substr($s, $start, $len = 2147483647) { return p\Grapheme::grapheme_substr($s, $start, $len); } +} diff --git a/lib/composer/symfony/polyfill-intl-grapheme/composer.json b/lib/composer/symfony/polyfill-intl-grapheme/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..47e5ff9f3e2bc4aff4b8512b6678184f646ce521 --- /dev/null +++ b/lib/composer/symfony/polyfill-intl-grapheme/composer.json @@ -0,0 +1,38 @@ +{ + "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": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Grapheme\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/composer/symfony/polyfill-intl-normalizer/LICENSE b/lib/composer/symfony/polyfill-intl-normalizer/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4cd8bdd3007da4d62985ec9e5ca81a1e18ae34d1 --- /dev/null +++ b/lib/composer/symfony/polyfill-intl-normalizer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 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/lib/composer/symfony/polyfill-intl-normalizer/Normalizer.php b/lib/composer/symfony/polyfill-intl-normalizer/Normalizer.php new file mode 100644 index 0000000000000000000000000000000000000000..a60fae620979f44625a0e9b069c41e42e2733df3 --- /dev/null +++ b/lib/composer/symfony/polyfill-intl-normalizer/Normalizer.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 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 +{ + const FORM_D = \Normalizer::FORM_D; + const FORM_KD = \Normalizer::FORM_KD; + const FORM_C = \Normalizer::FORM_C; + const FORM_KC = \Normalizer::FORM_KC; + const NFD = \Normalizer::NFD; + const NFKD = \Normalizer::NFKD; + const NFC = \Normalizer::NFC; + const NFKC = \Normalizer::NFKC; + + private static $C; + private static $D; + private static $KD; + private static $cC; + private static $ulenMask = array("\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($s, $form = self::NFC) + { + if (!\in_array($form, array(self::NFD, self::NFKD, self::NFC, self::NFKC))) { + return false; + } + $s = (string) $s; + 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($s, $form = self::NFC) + { + $s = (string) $s; + 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; + } + + return false; + } + + 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 = isset($combClass[$uchr]) ? $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 = array(); + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = array(); + } + + $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 = isset($compatMap[$uchr]) ? $compatMap[$uchr] : (isset($decompMap[$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 = array(); + } + + $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/lib/composer/symfony/polyfill-intl-normalizer/README.md b/lib/composer/symfony/polyfill-intl-normalizer/README.md new file mode 100644 index 0000000000000000000000000000000000000000..15060c5f1fa413a56739ebb3126a3b966eba29d4 --- /dev/null +++ b/lib/composer/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/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lib/composer/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php b/lib/composer/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php new file mode 100644 index 0000000000000000000000000000000000000000..ca18eff36f0b8591d36ebe56b04eda02a509afa1 --- /dev/null +++ b/lib/composer/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php @@ -0,0 +1,17 @@ + 'À', + 'Á' => 'Á', + 'Â' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Å' => 'Å', + 'Ç' => 'Ç', + 'È' => 'È', + 'É' => 'É', + 'Ê' => 'Ê', + 'Ë' => 'Ë', + 'Ì' => 'Ì', + 'Í' => 'Í', + 'Î' => 'Î', + 'Ï' => 'Ï', + 'Ñ' => 'Ñ', + 'Ò' => 'Ò', + 'Ó' => 'Ó', + 'Ô' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'Ù' => 'Ù', + 'Ú' => 'Ú', + 'Û' => 'Û', + 'Ü' => 'Ü', + 'Ý' => 'Ý', + 'à' => 'à', + 'á' => 'á', + 'â' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'å' => 'å', + 'ç' => 'ç', + 'è' => 'è', + 'é' => 'é', + 'ê' => 'ê', + 'ë' => 'ë', + 'ì' => 'ì', + 'í' => 'í', + 'î' => 'î', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'ò' => 'ò', + 'ó' => 'ó', + 'ô' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + 'ù' => 'ù', + 'ú' => 'ú', + 'û' => 'û', + 'ü' => 'ü', + 'ý' => 'ý', + 'ÿ' => 'ÿ', + 'Ā' => 'Ā', + 'ā' => 'ā', + 'Ă' => 'Ă', + 'ă' => 'ă', + 'Ą' => 'Ą', + 'ą' => 'ą', + 'Ć' => 'Ć', + 'ć' => 'ć', + 'Ĉ' => 'Ĉ', + 'ĉ' => 'ĉ', + 'Ċ' => 'Ċ', + 'ċ' => 'ċ', + 'Č' => 'Č', + 'č' => 'č', + 'Ď' => 'Ď', + 'ď' => 'ď', + 'Ē' => 'Ē', + 'ē' => 'ē', + 'Ĕ' => 'Ĕ', + 'ĕ' => 'ĕ', + 'Ė' => 'Ė', + 'ė' => 'ė', + 'Ę' => 'Ę', + 'ę' => 'ę', + 'Ě' => 'Ě', + 'ě' => 'ě', + 'Ĝ' => 'Ĝ', + 'ĝ' => 'ĝ', + 'Ğ' => 'Ğ', + 'ğ' => 'ğ', + 'Ġ' => 'Ġ', + 'ġ' => 'ġ', + 'Ģ' => 'Ģ', + 'ģ' => 'ģ', + 'Ĥ' => 'Ĥ', + 'ĥ' => 'ĥ', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'ĩ', + 'Ī' => 'Ī', + 'ī' => 'ī', + 'Ĭ' => 'Ĭ', + 'ĭ' => 'ĭ', + 'Į' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'Ĵ' => 'Ĵ', + 'ĵ' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'ķ', + 'Ĺ' => 'Ĺ', + 'ĺ' => 'ĺ', + 'Ļ' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'Ľ', + 'ľ' => 'ľ', + 'Ń' => 'Ń', + 'ń' => 'ń', + 'Ņ' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'Ň', + 'ň' => 'ň', + 'Ō' => 'Ō', + 'ō' => 'ō', + 'Ŏ' => 'Ŏ', + 'ŏ' => 'ŏ', + 'Ő' => 'Ő', + 'ő' => 'ő', + 'Ŕ' => 'Ŕ', + 'ŕ' => 'ŕ', + 'Ŗ' => 'Ŗ', + 'ŗ' => 'ŗ', + 'Ř' => 'Ř', + 'ř' => 'ř', + 'Ś' => 'Ś', + 'ś' => 'ś', + 'Ŝ' => 'Ŝ', + 'ŝ' => 'ŝ', + 'Ş' => 'Ş', + 'ş' => 'ş', + 'Š' => 'Š', + 'š' => 'š', + 'Ţ' => 'Ţ', + 'ţ' => 'ţ', + 'Ť' => 'Ť', + 'ť' => 'ť', + 'Ũ' => 'Ũ', + 'ũ' => 'ũ', + 'Ū' => 'Ū', + 'ū' => 'ū', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'ŭ', + 'Ů' => 'Ů', + 'ů' => 'ů', + 'Ű' => 'Ű', + 'ű' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Ŵ' => 'Ŵ', + 'ŵ' => 'ŵ', + 'Ŷ' => 'Ŷ', + 'ŷ' => 'ŷ', + 'Ÿ' => 'Ÿ', + 'Ź' => 'Ź', + 'ź' => 'ź', + 'Ż' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'Ž', + 'ž' => 'ž', + 'Ơ' => 'Ơ', + 'ơ' => 'ơ', + 'Ư' => 'Ư', + 'ư' => 'ư', + 'Ǎ' => 'Ǎ', + 'ǎ' => 'ǎ', + 'Ǐ' => 'Ǐ', + 'ǐ' => 'ǐ', + 'Ǒ' => 'Ǒ', + 'ǒ' => 'ǒ', + 'Ǔ' => 'Ǔ', + 'ǔ' => 'ǔ', + 'Ǖ' => 'Ǖ', + 'ǖ' => 'ǖ', + 'Ǘ' => 'Ǘ', + 'ǘ' => 'ǘ', + 'Ǚ' => 'Ǚ', + 'ǚ' => 'ǚ', + 'Ǜ' => 'Ǜ', + 'ǜ' => 'ǜ', + 'Ǟ' => 'Ǟ', + 'ǟ' => 'ǟ', + 'Ǡ' => 'Ǡ', + 'ǡ' => 'ǡ', + 'Ǣ' => 'Ǣ', + 'ǣ' => 'ǣ', + 'Ǧ' => 'Ǧ', + 'ǧ' => 'ǧ', + 'Ǩ' => 'Ǩ', + 'ǩ' => 'ǩ', + 'Ǫ' => 'Ǫ', + 'ǫ' => 'ǫ', + 'Ǭ' => 'Ǭ', + 'ǭ' => 'ǭ', + 'Ǯ' => 'Ǯ', + 'ǯ' => 'ǯ', + 'ǰ' => 'ǰ', + 'Ǵ' => 'Ǵ', + 'ǵ' => 'ǵ', + 'Ǹ' => 'Ǹ', + 'ǹ' => 'ǹ', + 'Ǻ' => 'Ǻ', + 'ǻ' => 'ǻ', + 'Ǽ' => 'Ǽ', + 'ǽ' => 'ǽ', + 'Ǿ' => 'Ǿ', + 'ǿ' => 'ǿ', + 'Ȁ' => 'Ȁ', + 'ȁ' => 'ȁ', + 'Ȃ' => 'Ȃ', + 'ȃ' => 'ȃ', + 'Ȅ' => 'Ȅ', + 'ȅ' => 'ȅ', + 'Ȇ' => 'Ȇ', + 'ȇ' => 'ȇ', + 'Ȉ' => 'Ȉ', + 'ȉ' => 'ȉ', + 'Ȋ' => 'Ȋ', + 'ȋ' => 'ȋ', + 'Ȍ' => 'Ȍ', + 'ȍ' => 'ȍ', + 'Ȏ' => 'Ȏ', + 'ȏ' => 'ȏ', + 'Ȑ' => 'Ȑ', + 'ȑ' => 'ȑ', + 'Ȓ' => 'Ȓ', + 'ȓ' => 'ȓ', + 'Ȕ' => 'Ȕ', + 'ȕ' => 'ȕ', + 'Ȗ' => 'Ȗ', + 'ȗ' => 'ȗ', + 'Ș' => 'Ș', + 'ș' => 'ș', + 'Ț' => 'Ț', + 'ț' => 'ț', + 'Ȟ' => 'Ȟ', + 'ȟ' => 'ȟ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'ȩ' => 'ȩ', + 'Ȫ' => 'Ȫ', + 'ȫ' => 'ȫ', + 'Ȭ' => 'Ȭ', + 'ȭ' => 'ȭ', + 'Ȯ' => 'Ȯ', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'Ȳ' => 'Ȳ', + 'ȳ' => 'ȳ', + '΅' => '΅', + 'Ά' => 'Ά', + 'Έ' => 'Έ', + 'Ή' => 'Ή', + 'Ί' => 'Ί', + 'Ό' => 'Ό', + 'Ύ' => 'Ύ', + 'Ώ' => 'Ώ', + 'ΐ' => 'ΐ', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'ά' => 'ά', + 'έ' => 'έ', + 'ή' => 'ή', + 'ί' => 'ί', + 'ΰ' => 'ΰ', + 'ϊ' => 'ϊ', + 'ϋ' => 'ϋ', + 'ό' => 'ό', + 'ύ' => 'ύ', + 'ώ' => 'ώ', + 'ϓ' => 'ϓ', + 'ϔ' => 'ϔ', + 'Ѐ' => 'Ѐ', + 'Ё' => 'Ё', + 'Ѓ' => 'Ѓ', + 'Ї' => 'Ї', + 'Ќ' => 'Ќ', + 'Ѝ' => 'Ѝ', + 'Ў' => 'Ў', + 'Й' => 'Й', + 'й' => 'й', + 'ѐ' => 'ѐ', + 'ё' => 'ё', + 'ѓ' => 'ѓ', + 'ї' => 'ї', + 'ќ' => 'ќ', + 'ѝ' => 'ѝ', + 'ў' => 'ў', + 'Ѷ' => 'Ѷ', + 'ѷ' => 'ѷ', + 'Ӂ' => 'Ӂ', + 'ӂ' => 'ӂ', + 'Ӑ' => 'Ӑ', + 'ӑ' => 'ӑ', + 'Ӓ' => 'Ӓ', + 'ӓ' => 'ӓ', + 'Ӗ' => 'Ӗ', + 'ӗ' => 'ӗ', + 'Ӛ' => 'Ӛ', + 'ӛ' => 'ӛ', + 'Ӝ' => 'Ӝ', + 'ӝ' => 'ӝ', + 'Ӟ' => 'Ӟ', + 'ӟ' => 'ӟ', + 'Ӣ' => 'Ӣ', + 'ӣ' => 'ӣ', + 'Ӥ' => 'Ӥ', + 'ӥ' => 'ӥ', + 'Ӧ' => 'Ӧ', + 'ӧ' => 'ӧ', + 'Ӫ' => 'Ӫ', + 'ӫ' => 'ӫ', + 'Ӭ' => 'Ӭ', + 'ӭ' => 'ӭ', + 'Ӯ' => 'Ӯ', + 'ӯ' => 'ӯ', + 'Ӱ' => 'Ӱ', + 'ӱ' => 'ӱ', + 'Ӳ' => 'Ӳ', + 'ӳ' => 'ӳ', + 'Ӵ' => 'Ӵ', + 'ӵ' => 'ӵ', + 'Ӹ' => 'Ӹ', + 'ӹ' => 'ӹ', + 'آ' => 'آ', + 'أ' => 'أ', + 'ؤ' => 'ؤ', + 'إ' => 'إ', + 'ئ' => 'ئ', + 'ۀ' => 'ۀ', + 'ۂ' => 'ۂ', + 'ۓ' => 'ۓ', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'ো' => 'ো', + 'ৌ' => 'ৌ', + 'ୈ' => 'ୈ', + 'ୋ' => 'ୋ', + 'ୌ' => 'ୌ', + 'ஔ' => 'ஔ', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'ೀ' => 'ೀ', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'ේ' => 'ේ', + 'ො' => 'ො', + 'ෝ' => 'ෝ', + 'ෞ' => 'ෞ', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'ᬎ' => 'ᬎ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'ᭀ' => 'ᭀ', + 'ᭁ' => 'ᭁ', + 'ᭃ' => 'ᭃ', + 'Ḁ' => 'Ḁ', + 'ḁ' => 'ḁ', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'Ḅ' => 'Ḅ', + 'ḅ' => 'ḅ', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'Ḉ' => 'Ḉ', + 'ḉ' => 'ḉ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'Ḍ' => 'Ḍ', + 'ḍ' => 'ḍ', + 'Ḏ' => 'Ḏ', + 'ḏ' => 'ḏ', + 'Ḑ' => 'Ḑ', + 'ḑ' => 'ḑ', + 'Ḓ' => 'Ḓ', + 'ḓ' => 'ḓ', + 'Ḕ' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ḗ' => 'Ḗ', + 'ḗ' => 'ḗ', + 'Ḙ' => 'Ḙ', + 'ḙ' => 'ḙ', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'ḝ' => 'ḝ', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'Ḡ' => 'Ḡ', + 'ḡ' => 'ḡ', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'Ḥ' => 'Ḥ', + 'ḥ' => 'ḥ', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'Ḫ' => 'Ḫ', + 'ḫ' => 'ḫ', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'Ḯ' => 'Ḯ', + 'ḯ' => 'ḯ', + 'Ḱ' => 'Ḱ', + 'ḱ' => 'ḱ', + 'Ḳ' => 'Ḳ', + 'ḳ' => 'ḳ', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'Ḷ' => 'Ḷ', + 'ḷ' => 'ḷ', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'Ḽ' => 'Ḽ', + 'ḽ' => 'ḽ', + 'Ḿ' => 'Ḿ', + 'ḿ' => 'ḿ', + 'Ṁ' => 'Ṁ', + 'ṁ' => 'ṁ', + 'Ṃ' => 'Ṃ', + 'ṃ' => 'ṃ', + 'Ṅ' => 'Ṅ', + 'ṅ' => 'ṅ', + 'Ṇ' => 'Ṇ', + 'ṇ' => 'ṇ', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'Ṋ' => 'Ṋ', + 'ṋ' => 'ṋ', + 'Ṍ' => 'Ṍ', + 'ṍ' => 'ṍ', + 'Ṏ' => 'Ṏ', + 'ṏ' => 'ṏ', + 'Ṑ' => 'Ṑ', + 'ṑ' => 'ṑ', + 'Ṓ' => 'Ṓ', + 'ṓ' => 'ṓ', + 'Ṕ' => 'Ṕ', + 'ṕ' => 'ṕ', + 'Ṗ' => 'Ṗ', + 'ṗ' => 'ṗ', + 'Ṙ' => 'Ṙ', + 'ṙ' => 'ṙ', + 'Ṛ' => 'Ṛ', + 'ṛ' => 'ṛ', + 'Ṝ' => 'Ṝ', + 'ṝ' => 'ṝ', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'Ṡ' => 'Ṡ', + 'ṡ' => 'ṡ', + 'Ṣ' => 'Ṣ', + 'ṣ' => 'ṣ', + 'Ṥ' => 'Ṥ', + 'ṥ' => 'ṥ', + 'Ṧ' => 'Ṧ', + 'ṧ' => 'ṧ', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'Ṭ' => 'Ṭ', + 'ṭ' => 'ṭ', + 'Ṯ' => 'Ṯ', + 'ṯ' => 'ṯ', + 'Ṱ' => 'Ṱ', + 'ṱ' => 'ṱ', + 'Ṳ' => 'Ṳ', + 'ṳ' => 'ṳ', + 'Ṵ' => 'Ṵ', + 'ṵ' => 'ṵ', + 'Ṷ' => 'Ṷ', + 'ṷ' => 'ṷ', + 'Ṹ' => 'Ṹ', + 'ṹ' => 'ṹ', + 'Ṻ' => 'Ṻ', + 'ṻ' => 'ṻ', + 'Ṽ' => 'Ṽ', + 'ṽ' => 'ṽ', + 'Ṿ' => 'Ṿ', + 'ṿ' => 'ṿ', + 'Ẁ' => 'Ẁ', + 'ẁ' => 'ẁ', + 'Ẃ' => 'Ẃ', + 'ẃ' => 'ẃ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'Ẉ' => 'Ẉ', + 'ẉ' => 'ẉ', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'ẍ' => 'ẍ', + 'Ẏ' => 'Ẏ', + 'ẏ' => 'ẏ', + 'Ẑ' => 'Ẑ', + 'ẑ' => 'ẑ', + 'Ẓ' => 'Ẓ', + 'ẓ' => 'ẓ', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẛ' => 'ẛ', + 'Ạ' => 'Ạ', + 'ạ' => 'ạ', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'Ấ' => 'Ấ', + 'ấ' => 'ấ', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ắ' => 'Ắ', + 'ắ' => 'ắ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'Ẹ' => 'Ẹ', + 'ẹ' => 'ẹ', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'Ế' => 'Ế', + 'ế' => 'ế', + 'Ề' => 'Ề', + 'ề' => 'ề', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'ễ' => 'ễ', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'Ị' => 'Ị', + 'ị' => 'ị', + 'Ọ' => 'Ọ', + 'ọ' => 'ọ', + 'Ỏ' => 'Ỏ', + 'ỏ' => 'ỏ', + 'Ố' => 'Ố', + 'ố' => 'ố', + 'Ồ' => 'Ồ', + 'ồ' => 'ồ', + 'Ổ' => 'Ổ', + 'ổ' => 'ổ', + 'Ỗ' => 'Ỗ', + 'ỗ' => 'ỗ', + 'Ộ' => 'Ộ', + 'ộ' => 'ộ', + 'Ớ' => 'Ớ', + 'ớ' => 'ớ', + 'Ờ' => 'Ờ', + 'ờ' => 'ờ', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'Ỡ' => 'Ỡ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'Ụ' => 'Ụ', + 'ụ' => 'ụ', + 'Ủ' => 'Ủ', + 'ủ' => 'ủ', + 'Ứ' => 'Ứ', + 'ứ' => 'ứ', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'ử' => 'ử', + 'Ữ' => 'Ữ', + 'ữ' => 'ữ', + 'Ự' => 'Ự', + 'ự' => 'ự', + 'Ỳ' => 'Ỳ', + 'ỳ' => 'ỳ', + 'Ỵ' => 'Ỵ', + 'ỵ' => 'ỵ', + 'Ỷ' => 'Ỷ', + 'ỷ' => 'ỷ', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'ἀ' => 'ἀ', + 'ἁ' => 'ἁ', + 'ἂ' => 'ἂ', + 'ἃ' => 'ἃ', + 'ἄ' => 'ἄ', + 'ἅ' => 'ἅ', + 'ἆ' => 'ἆ', + 'ἇ' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'Ἄ' => 'Ἄ', + 'Ἅ' => 'Ἅ', + 'Ἆ' => 'Ἆ', + 'Ἇ' => 'Ἇ', + 'ἐ' => 'ἐ', + 'ἑ' => 'ἑ', + 'ἒ' => 'ἒ', + 'ἓ' => 'ἓ', + 'ἔ' => 'ἔ', + 'ἕ' => 'ἕ', + 'Ἐ' => 'Ἐ', + 'Ἑ' => 'Ἑ', + 'Ἒ' => 'Ἒ', + 'Ἓ' => 'Ἓ', + 'Ἔ' => 'Ἔ', + 'Ἕ' => 'Ἕ', + 'ἠ' => 'ἠ', + 'ἡ' => 'ἡ', + 'ἢ' => 'ἢ', + 'ἣ' => 'ἣ', + 'ἤ' => 'ἤ', + 'ἥ' => 'ἥ', + 'ἦ' => 'ἦ', + 'ἧ' => 'ἧ', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'Ἤ' => 'Ἤ', + 'Ἥ' => 'Ἥ', + 'Ἦ' => 'Ἦ', + 'Ἧ' => 'Ἧ', + 'ἰ' => 'ἰ', + 'ἱ' => 'ἱ', + 'ἲ' => 'ἲ', + 'ἳ' => 'ἳ', + 'ἴ' => 'ἴ', + 'ἵ' => 'ἵ', + 'ἶ' => 'ἶ', + 'ἷ' => 'ἷ', + 'Ἰ' => 'Ἰ', + 'Ἱ' => 'Ἱ', + 'Ἲ' => 'Ἲ', + 'Ἳ' => 'Ἳ', + 'Ἴ' => 'Ἴ', + 'Ἵ' => 'Ἵ', + 'Ἶ' => 'Ἶ', + 'Ἷ' => 'Ἷ', + 'ὀ' => 'ὀ', + 'ὁ' => 'ὁ', + 'ὂ' => 'ὂ', + 'ὃ' => 'ὃ', + 'ὄ' => 'ὄ', + 'ὅ' => 'ὅ', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'Ὄ' => 'Ὄ', + 'Ὅ' => 'Ὅ', + 'ὐ' => 'ὐ', + 'ὑ' => 'ὑ', + 'ὒ' => 'ὒ', + 'ὓ' => 'ὓ', + 'ὔ' => 'ὔ', + 'ὕ' => 'ὕ', + 'ὖ' => 'ὖ', + 'ὗ' => 'ὗ', + 'Ὑ' => 'Ὑ', + 'Ὓ' => 'Ὓ', + 'Ὕ' => 'Ὕ', + 'Ὗ' => 'Ὗ', + 'ὠ' => 'ὠ', + 'ὡ' => 'ὡ', + 'ὢ' => 'ὢ', + 'ὣ' => 'ὣ', + 'ὤ' => 'ὤ', + 'ὥ' => 'ὥ', + 'ὦ' => 'ὦ', + 'ὧ' => 'ὧ', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'Ὤ' => 'Ὤ', + 'Ὥ' => 'Ὥ', + 'Ὦ' => 'Ὦ', + 'Ὧ' => 'Ὧ', + 'ὰ' => 'ὰ', + 'ὲ' => 'ὲ', + 'ὴ' => 'ὴ', + 'ὶ' => 'ὶ', + 'ὸ' => 'ὸ', + 'ὺ' => 'ὺ', + 'ὼ' => 'ὼ', + 'ᾀ' => 'ᾀ', + 'ᾁ' => 'ᾁ', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ᾄ', + 'ᾅ' => 'ᾅ', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'ᾍ' => 'ᾍ', + 'ᾎ' => 'ᾎ', + 'ᾏ' => 'ᾏ', + 'ᾐ' => 'ᾐ', + 'ᾑ' => 'ᾑ', + 'ᾒ' => 'ᾒ', + 'ᾓ' => 'ᾓ', + 'ᾔ' => 'ᾔ', + 'ᾕ' => 'ᾕ', + 'ᾖ' => 'ᾖ', + 'ᾗ' => 'ᾗ', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'ᾛ', + 'ᾜ' => 'ᾜ', + 'ᾝ' => 'ᾝ', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'ᾠ' => 'ᾠ', + 'ᾡ' => 'ᾡ', + 'ᾢ' => 'ᾢ', + 'ᾣ' => 'ᾣ', + 'ᾤ' => 'ᾤ', + 'ᾥ' => 'ᾥ', + 'ᾦ' => 'ᾦ', + 'ᾧ' => 'ᾧ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'ᾭ' => 'ᾭ', + 'ᾮ' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'ᾰ' => 'ᾰ', + 'ᾱ' => 'ᾱ', + 'ᾲ' => 'ᾲ', + 'ᾳ' => 'ᾳ', + 'ᾴ' => 'ᾴ', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾷ', + 'Ᾰ' => 'Ᾰ', + 'Ᾱ' => 'Ᾱ', + 'Ὰ' => 'Ὰ', + 'ᾼ' => 'ᾼ', + '῁' => '῁', + 'ῂ' => 'ῂ', + 'ῃ' => 'ῃ', + 'ῄ' => 'ῄ', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Ὴ' => 'Ὴ', + 'ῌ' => 'ῌ', + '῍' => '῍', + '῎' => '῎', + '῏' => '῏', + 'ῐ' => 'ῐ', + 'ῑ' => 'ῑ', + 'ῒ' => 'ῒ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'Ῐ' => 'Ῐ', + 'Ῑ' => 'Ῑ', + 'Ὶ' => 'Ὶ', + '῝' => '῝', + '῞' => '῞', + '῟' => '῟', + 'ῠ' => 'ῠ', + 'ῡ' => 'ῡ', + 'ῢ' => 'ῢ', + 'ῤ' => 'ῤ', + 'ῥ' => 'ῥ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'Ῠ' => 'Ῠ', + 'Ῡ' => 'Ῡ', + 'Ὺ' => 'Ὺ', + 'Ῥ' => 'Ῥ', + '῭' => '῭', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'ῴ' => 'ῴ', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῷ', + 'Ὸ' => 'Ὸ', + 'Ὼ' => 'Ὼ', + 'ῼ' => 'ῼ', + '↚' => '↚', + '↛' => '↛', + '↮' => '↮', + '⇍' => '⇍', + '⇎' => '⇎', + '⇏' => '⇏', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + '≁' => '≁', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + '≭' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + '⊁' => '⊁', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⋠' => '⋠', + '⋡' => '⋡', + '⋢' => '⋢', + '⋣' => '⋣', + '⋪' => '⋪', + '⋫' => '⋫', + '⋬' => '⋬', + '⋭' => '⋭', + 'が' => 'が', + 'ぎ' => 'ぎ', + 'ぐ' => 'ぐ', + 'げ' => 'げ', + 'ご' => 'ご', + 'ざ' => 'ざ', + 'じ' => 'じ', + 'ず' => 'ず', + 'ぜ' => 'ぜ', + 'ぞ' => 'ぞ', + 'だ' => 'だ', + 'ぢ' => 'ぢ', + 'づ' => 'づ', + 'で' => 'で', + 'ど' => 'ど', + 'ば' => 'ば', + 'ぱ' => 'ぱ', + 'び' => 'び', + 'ぴ' => 'ぴ', + 'ぶ' => 'ぶ', + 'ぷ' => 'ぷ', + 'べ' => 'べ', + 'ぺ' => 'ぺ', + 'ぼ' => 'ぼ', + 'ぽ' => 'ぽ', + 'ゔ' => 'ゔ', + 'ゞ' => 'ゞ', + 'ガ' => 'ガ', + 'ギ' => 'ギ', + 'グ' => 'グ', + 'ゲ' => 'ゲ', + 'ゴ' => 'ゴ', + 'ザ' => 'ザ', + 'ジ' => 'ジ', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ダ' => 'ダ', + 'ヂ' => 'ヂ', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'バ' => 'バ', + 'パ' => 'パ', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ポ' => 'ポ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '𑂚' => '𑂚', + '𑂜' => '𑂜', + '𑂫' => '𑂫', + '𑄮' => '𑄮', + '𑄯' => '𑄯', + '𑍋' => '𑍋', + '𑍌' => '𑍌', + '𑒻' => '𑒻', + '𑒼' => '𑒼', + '𑒾' => '𑒾', + '𑖺' => '𑖺', + '𑖻' => '𑖻', + '𑤸' => '𑤸', +); diff --git a/lib/composer/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php b/lib/composer/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php new file mode 100644 index 0000000000000000000000000000000000000000..5a3e8e0969d62e73b1a96cbd3f963382cc0c1111 --- /dev/null +++ b/lib/composer/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php @@ -0,0 +1,2065 @@ + 'À', + 'Á' => 'Á', + 'Â' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Å' => 'Å', + 'Ç' => 'Ç', + 'È' => 'È', + 'É' => 'É', + 'Ê' => 'Ê', + 'Ë' => 'Ë', + 'Ì' => 'Ì', + 'Í' => 'Í', + 'Î' => 'Î', + 'Ï' => 'Ï', + 'Ñ' => 'Ñ', + 'Ò' => 'Ò', + 'Ó' => 'Ó', + 'Ô' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'Ù' => 'Ù', + 'Ú' => 'Ú', + 'Û' => 'Û', + 'Ü' => 'Ü', + 'Ý' => 'Ý', + 'à' => 'à', + 'á' => 'á', + 'â' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'å' => 'å', + 'ç' => 'ç', + 'è' => 'è', + 'é' => 'é', + 'ê' => 'ê', + 'ë' => 'ë', + 'ì' => 'ì', + 'í' => 'í', + 'î' => 'î', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'ò' => 'ò', + 'ó' => 'ó', + 'ô' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + 'ù' => 'ù', + 'ú' => 'ú', + 'û' => 'û', + 'ü' => 'ü', + 'ý' => 'ý', + 'ÿ' => 'ÿ', + 'Ā' => 'Ā', + 'ā' => 'ā', + 'Ă' => 'Ă', + 'ă' => 'ă', + 'Ą' => 'Ą', + 'ą' => 'ą', + 'Ć' => 'Ć', + 'ć' => 'ć', + 'Ĉ' => 'Ĉ', + 'ĉ' => 'ĉ', + 'Ċ' => 'Ċ', + 'ċ' => 'ċ', + 'Č' => 'Č', + 'č' => 'č', + 'Ď' => 'Ď', + 'ď' => 'ď', + 'Ē' => 'Ē', + 'ē' => 'ē', + 'Ĕ' => 'Ĕ', + 'ĕ' => 'ĕ', + 'Ė' => 'Ė', + 'ė' => 'ė', + 'Ę' => 'Ę', + 'ę' => 'ę', + 'Ě' => 'Ě', + 'ě' => 'ě', + 'Ĝ' => 'Ĝ', + 'ĝ' => 'ĝ', + 'Ğ' => 'Ğ', + 'ğ' => 'ğ', + 'Ġ' => 'Ġ', + 'ġ' => 'ġ', + 'Ģ' => 'Ģ', + 'ģ' => 'ģ', + 'Ĥ' => 'Ĥ', + 'ĥ' => 'ĥ', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'ĩ', + 'Ī' => 'Ī', + 'ī' => 'ī', + 'Ĭ' => 'Ĭ', + 'ĭ' => 'ĭ', + 'Į' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'Ĵ' => 'Ĵ', + 'ĵ' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'ķ', + 'Ĺ' => 'Ĺ', + 'ĺ' => 'ĺ', + 'Ļ' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'Ľ', + 'ľ' => 'ľ', + 'Ń' => 'Ń', + 'ń' => 'ń', + 'Ņ' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'Ň', + 'ň' => 'ň', + 'Ō' => 'Ō', + 'ō' => 'ō', + 'Ŏ' => 'Ŏ', + 'ŏ' => 'ŏ', + 'Ő' => 'Ő', + 'ő' => 'ő', + 'Ŕ' => 'Ŕ', + 'ŕ' => 'ŕ', + 'Ŗ' => 'Ŗ', + 'ŗ' => 'ŗ', + 'Ř' => 'Ř', + 'ř' => 'ř', + 'Ś' => 'Ś', + 'ś' => 'ś', + 'Ŝ' => 'Ŝ', + 'ŝ' => 'ŝ', + 'Ş' => 'Ş', + 'ş' => 'ş', + 'Š' => 'Š', + 'š' => 'š', + 'Ţ' => 'Ţ', + 'ţ' => 'ţ', + 'Ť' => 'Ť', + 'ť' => 'ť', + 'Ũ' => 'Ũ', + 'ũ' => 'ũ', + 'Ū' => 'Ū', + 'ū' => 'ū', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'ŭ', + 'Ů' => 'Ů', + 'ů' => 'ů', + 'Ű' => 'Ű', + 'ű' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Ŵ' => 'Ŵ', + 'ŵ' => 'ŵ', + 'Ŷ' => 'Ŷ', + 'ŷ' => 'ŷ', + 'Ÿ' => 'Ÿ', + 'Ź' => 'Ź', + 'ź' => 'ź', + 'Ż' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'Ž', + 'ž' => 'ž', + 'Ơ' => 'Ơ', + 'ơ' => 'ơ', + 'Ư' => 'Ư', + 'ư' => 'ư', + 'Ǎ' => 'Ǎ', + 'ǎ' => 'ǎ', + 'Ǐ' => 'Ǐ', + 'ǐ' => 'ǐ', + 'Ǒ' => 'Ǒ', + 'ǒ' => 'ǒ', + 'Ǔ' => 'Ǔ', + 'ǔ' => 'ǔ', + 'Ǖ' => 'Ǖ', + 'ǖ' => 'ǖ', + 'Ǘ' => 'Ǘ', + 'ǘ' => 'ǘ', + 'Ǚ' => 'Ǚ', + 'ǚ' => 'ǚ', + 'Ǜ' => 'Ǜ', + 'ǜ' => 'ǜ', + 'Ǟ' => 'Ǟ', + 'ǟ' => 'ǟ', + 'Ǡ' => 'Ǡ', + 'ǡ' => 'ǡ', + 'Ǣ' => 'Ǣ', + 'ǣ' => 'ǣ', + 'Ǧ' => 'Ǧ', + 'ǧ' => 'ǧ', + 'Ǩ' => 'Ǩ', + 'ǩ' => 'ǩ', + 'Ǫ' => 'Ǫ', + 'ǫ' => 'ǫ', + 'Ǭ' => 'Ǭ', + 'ǭ' => 'ǭ', + 'Ǯ' => 'Ǯ', + 'ǯ' => 'ǯ', + 'ǰ' => 'ǰ', + 'Ǵ' => 'Ǵ', + 'ǵ' => 'ǵ', + 'Ǹ' => 'Ǹ', + 'ǹ' => 'ǹ', + 'Ǻ' => 'Ǻ', + 'ǻ' => 'ǻ', + 'Ǽ' => 'Ǽ', + 'ǽ' => 'ǽ', + 'Ǿ' => 'Ǿ', + 'ǿ' => 'ǿ', + 'Ȁ' => 'Ȁ', + 'ȁ' => 'ȁ', + 'Ȃ' => 'Ȃ', + 'ȃ' => 'ȃ', + 'Ȅ' => 'Ȅ', + 'ȅ' => 'ȅ', + 'Ȇ' => 'Ȇ', + 'ȇ' => 'ȇ', + 'Ȉ' => 'Ȉ', + 'ȉ' => 'ȉ', + 'Ȋ' => 'Ȋ', + 'ȋ' => 'ȋ', + 'Ȍ' => 'Ȍ', + 'ȍ' => 'ȍ', + 'Ȏ' => 'Ȏ', + 'ȏ' => 'ȏ', + 'Ȑ' => 'Ȑ', + 'ȑ' => 'ȑ', + 'Ȓ' => 'Ȓ', + 'ȓ' => 'ȓ', + 'Ȕ' => 'Ȕ', + 'ȕ' => 'ȕ', + 'Ȗ' => 'Ȗ', + 'ȗ' => 'ȗ', + 'Ș' => 'Ș', + 'ș' => 'ș', + 'Ț' => 'Ț', + 'ț' => 'ț', + 'Ȟ' => 'Ȟ', + 'ȟ' => 'ȟ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'ȩ' => 'ȩ', + 'Ȫ' => 'Ȫ', + 'ȫ' => 'ȫ', + 'Ȭ' => 'Ȭ', + 'ȭ' => 'ȭ', + 'Ȯ' => 'Ȯ', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'Ȳ' => 'Ȳ', + 'ȳ' => 'ȳ', + '̀' => '̀', + '́' => '́', + '̓' => '̓', + '̈́' => '̈́', + 'ʹ' => 'ʹ', + ';' => ';', + '΅' => '΅', + 'Ά' => 'Ά', + '·' => '·', + 'Έ' => 'Έ', + 'Ή' => 'Ή', + 'Ί' => 'Ί', + 'Ό' => 'Ό', + 'Ύ' => 'Ύ', + 'Ώ' => 'Ώ', + 'ΐ' => 'ΐ', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'ά' => 'ά', + 'έ' => 'έ', + 'ή' => 'ή', + 'ί' => 'ί', + 'ΰ' => 'ΰ', + 'ϊ' => 'ϊ', + 'ϋ' => 'ϋ', + 'ό' => 'ό', + 'ύ' => 'ύ', + 'ώ' => 'ώ', + 'ϓ' => 'ϓ', + 'ϔ' => 'ϔ', + 'Ѐ' => 'Ѐ', + 'Ё' => 'Ё', + 'Ѓ' => 'Ѓ', + 'Ї' => 'Ї', + 'Ќ' => 'Ќ', + 'Ѝ' => 'Ѝ', + 'Ў' => 'Ў', + 'Й' => 'Й', + 'й' => 'й', + 'ѐ' => 'ѐ', + 'ё' => 'ё', + 'ѓ' => 'ѓ', + 'ї' => 'ї', + 'ќ' => 'ќ', + 'ѝ' => 'ѝ', + 'ў' => 'ў', + 'Ѷ' => 'Ѷ', + 'ѷ' => 'ѷ', + 'Ӂ' => 'Ӂ', + 'ӂ' => 'ӂ', + 'Ӑ' => 'Ӑ', + 'ӑ' => 'ӑ', + 'Ӓ' => 'Ӓ', + 'ӓ' => 'ӓ', + 'Ӗ' => 'Ӗ', + 'ӗ' => 'ӗ', + 'Ӛ' => 'Ӛ', + 'ӛ' => 'ӛ', + 'Ӝ' => 'Ӝ', + 'ӝ' => 'ӝ', + 'Ӟ' => 'Ӟ', + 'ӟ' => 'ӟ', + 'Ӣ' => 'Ӣ', + 'ӣ' => 'ӣ', + 'Ӥ' => 'Ӥ', + 'ӥ' => 'ӥ', + 'Ӧ' => 'Ӧ', + 'ӧ' => 'ӧ', + 'Ӫ' => 'Ӫ', + 'ӫ' => 'ӫ', + 'Ӭ' => 'Ӭ', + 'ӭ' => 'ӭ', + 'Ӯ' => 'Ӯ', + 'ӯ' => 'ӯ', + 'Ӱ' => 'Ӱ', + 'ӱ' => 'ӱ', + 'Ӳ' => 'Ӳ', + 'ӳ' => 'ӳ', + 'Ӵ' => 'Ӵ', + 'ӵ' => 'ӵ', + 'Ӹ' => 'Ӹ', + 'ӹ' => 'ӹ', + 'آ' => 'آ', + 'أ' => 'أ', + 'ؤ' => 'ؤ', + 'إ' => 'إ', + 'ئ' => 'ئ', + 'ۀ' => 'ۀ', + 'ۂ' => 'ۂ', + 'ۓ' => 'ۓ', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'क़' => 'क़', + 'ख़' => 'ख़', + 'ग़' => 'ग़', + 'ज़' => 'ज़', + 'ड़' => 'ड़', + 'ढ़' => 'ढ़', + 'फ़' => 'फ़', + 'य़' => 'य़', + 'ো' => 'ো', + 'ৌ' => 'ৌ', + 'ড়' => 'ড়', + 'ঢ়' => 'ঢ়', + 'য়' => 'য়', + 'ਲ਼' => 'ਲ਼', + 'ਸ਼' => 'ਸ਼', + 'ਖ਼' => 'ਖ਼', + 'ਗ਼' => 'ਗ਼', + 'ਜ਼' => 'ਜ਼', + 'ਫ਼' => 'ਫ਼', + 'ୈ' => 'ୈ', + 'ୋ' => 'ୋ', + 'ୌ' => 'ୌ', + 'ଡ଼' => 'ଡ଼', + 'ଢ଼' => 'ଢ଼', + 'ஔ' => 'ஔ', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'ೀ' => 'ೀ', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'ේ' => 'ේ', + 'ො' => 'ො', + 'ෝ' => 'ෝ', + 'ෞ' => 'ෞ', + 'གྷ' => 'གྷ', + 'ཌྷ' => 'ཌྷ', + 'དྷ' => 'དྷ', + 'བྷ' => 'བྷ', + 'ཛྷ' => 'ཛྷ', + 'ཀྵ' => 'ཀྵ', + 'ཱི' => 'ཱི', + 'ཱུ' => 'ཱུ', + 'ྲྀ' => 'ྲྀ', + 'ླྀ' => 'ླྀ', + 'ཱྀ' => 'ཱྀ', + 'ྒྷ' => 'ྒྷ', + 'ྜྷ' => 'ྜྷ', + 'ྡྷ' => 'ྡྷ', + 'ྦྷ' => 'ྦྷ', + 'ྫྷ' => 'ྫྷ', + 'ྐྵ' => 'ྐྵ', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'ᬎ' => 'ᬎ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'ᭀ' => 'ᭀ', + 'ᭁ' => 'ᭁ', + 'ᭃ' => 'ᭃ', + 'Ḁ' => 'Ḁ', + 'ḁ' => 'ḁ', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'Ḅ' => 'Ḅ', + 'ḅ' => 'ḅ', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'Ḉ' => 'Ḉ', + 'ḉ' => 'ḉ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'Ḍ' => 'Ḍ', + 'ḍ' => 'ḍ', + 'Ḏ' => 'Ḏ', + 'ḏ' => 'ḏ', + 'Ḑ' => 'Ḑ', + 'ḑ' => 'ḑ', + 'Ḓ' => 'Ḓ', + 'ḓ' => 'ḓ', + 'Ḕ' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ḗ' => 'Ḗ', + 'ḗ' => 'ḗ', + 'Ḙ' => 'Ḙ', + 'ḙ' => 'ḙ', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'ḝ' => 'ḝ', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'Ḡ' => 'Ḡ', + 'ḡ' => 'ḡ', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'Ḥ' => 'Ḥ', + 'ḥ' => 'ḥ', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'Ḫ' => 'Ḫ', + 'ḫ' => 'ḫ', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'Ḯ' => 'Ḯ', + 'ḯ' => 'ḯ', + 'Ḱ' => 'Ḱ', + 'ḱ' => 'ḱ', + 'Ḳ' => 'Ḳ', + 'ḳ' => 'ḳ', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'Ḷ' => 'Ḷ', + 'ḷ' => 'ḷ', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'Ḽ' => 'Ḽ', + 'ḽ' => 'ḽ', + 'Ḿ' => 'Ḿ', + 'ḿ' => 'ḿ', + 'Ṁ' => 'Ṁ', + 'ṁ' => 'ṁ', + 'Ṃ' => 'Ṃ', + 'ṃ' => 'ṃ', + 'Ṅ' => 'Ṅ', + 'ṅ' => 'ṅ', + 'Ṇ' => 'Ṇ', + 'ṇ' => 'ṇ', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'Ṋ' => 'Ṋ', + 'ṋ' => 'ṋ', + 'Ṍ' => 'Ṍ', + 'ṍ' => 'ṍ', + 'Ṏ' => 'Ṏ', + 'ṏ' => 'ṏ', + 'Ṑ' => 'Ṑ', + 'ṑ' => 'ṑ', + 'Ṓ' => 'Ṓ', + 'ṓ' => 'ṓ', + 'Ṕ' => 'Ṕ', + 'ṕ' => 'ṕ', + 'Ṗ' => 'Ṗ', + 'ṗ' => 'ṗ', + 'Ṙ' => 'Ṙ', + 'ṙ' => 'ṙ', + 'Ṛ' => 'Ṛ', + 'ṛ' => 'ṛ', + 'Ṝ' => 'Ṝ', + 'ṝ' => 'ṝ', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'Ṡ' => 'Ṡ', + 'ṡ' => 'ṡ', + 'Ṣ' => 'Ṣ', + 'ṣ' => 'ṣ', + 'Ṥ' => 'Ṥ', + 'ṥ' => 'ṥ', + 'Ṧ' => 'Ṧ', + 'ṧ' => 'ṧ', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'Ṭ' => 'Ṭ', + 'ṭ' => 'ṭ', + 'Ṯ' => 'Ṯ', + 'ṯ' => 'ṯ', + 'Ṱ' => 'Ṱ', + 'ṱ' => 'ṱ', + 'Ṳ' => 'Ṳ', + 'ṳ' => 'ṳ', + 'Ṵ' => 'Ṵ', + 'ṵ' => 'ṵ', + 'Ṷ' => 'Ṷ', + 'ṷ' => 'ṷ', + 'Ṹ' => 'Ṹ', + 'ṹ' => 'ṹ', + 'Ṻ' => 'Ṻ', + 'ṻ' => 'ṻ', + 'Ṽ' => 'Ṽ', + 'ṽ' => 'ṽ', + 'Ṿ' => 'Ṿ', + 'ṿ' => 'ṿ', + 'Ẁ' => 'Ẁ', + 'ẁ' => 'ẁ', + 'Ẃ' => 'Ẃ', + 'ẃ' => 'ẃ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'Ẉ' => 'Ẉ', + 'ẉ' => 'ẉ', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'ẍ' => 'ẍ', + 'Ẏ' => 'Ẏ', + 'ẏ' => 'ẏ', + 'Ẑ' => 'Ẑ', + 'ẑ' => 'ẑ', + 'Ẓ' => 'Ẓ', + 'ẓ' => 'ẓ', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẛ' => 'ẛ', + 'Ạ' => 'Ạ', + 'ạ' => 'ạ', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'Ấ' => 'Ấ', + 'ấ' => 'ấ', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ắ' => 'Ắ', + 'ắ' => 'ắ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'Ẹ' => 'Ẹ', + 'ẹ' => 'ẹ', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'Ế' => 'Ế', + 'ế' => 'ế', + 'Ề' => 'Ề', + 'ề' => 'ề', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'ễ' => 'ễ', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'Ị' => 'Ị', + 'ị' => 'ị', + 'Ọ' => 'Ọ', + 'ọ' => 'ọ', + 'Ỏ' => 'Ỏ', + 'ỏ' => 'ỏ', + 'Ố' => 'Ố', + 'ố' => 'ố', + 'Ồ' => 'Ồ', + 'ồ' => 'ồ', + 'Ổ' => 'Ổ', + 'ổ' => 'ổ', + 'Ỗ' => 'Ỗ', + 'ỗ' => 'ỗ', + 'Ộ' => 'Ộ', + 'ộ' => 'ộ', + 'Ớ' => 'Ớ', + 'ớ' => 'ớ', + 'Ờ' => 'Ờ', + 'ờ' => 'ờ', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'Ỡ' => 'Ỡ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'Ụ' => 'Ụ', + 'ụ' => 'ụ', + 'Ủ' => 'Ủ', + 'ủ' => 'ủ', + 'Ứ' => 'Ứ', + 'ứ' => 'ứ', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'ử' => 'ử', + 'Ữ' => 'Ữ', + 'ữ' => 'ữ', + 'Ự' => 'Ự', + 'ự' => 'ự', + 'Ỳ' => 'Ỳ', + 'ỳ' => 'ỳ', + 'Ỵ' => 'Ỵ', + 'ỵ' => 'ỵ', + 'Ỷ' => 'Ỷ', + 'ỷ' => 'ỷ', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'ἀ' => 'ἀ', + 'ἁ' => 'ἁ', + 'ἂ' => 'ἂ', + 'ἃ' => 'ἃ', + 'ἄ' => 'ἄ', + 'ἅ' => 'ἅ', + 'ἆ' => 'ἆ', + 'ἇ' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'Ἄ' => 'Ἄ', + 'Ἅ' => 'Ἅ', + 'Ἆ' => 'Ἆ', + 'Ἇ' => 'Ἇ', + 'ἐ' => 'ἐ', + 'ἑ' => 'ἑ', + 'ἒ' => 'ἒ', + 'ἓ' => 'ἓ', + 'ἔ' => 'ἔ', + 'ἕ' => 'ἕ', + 'Ἐ' => 'Ἐ', + 'Ἑ' => 'Ἑ', + 'Ἒ' => 'Ἒ', + 'Ἓ' => 'Ἓ', + 'Ἔ' => 'Ἔ', + 'Ἕ' => 'Ἕ', + 'ἠ' => 'ἠ', + 'ἡ' => 'ἡ', + 'ἢ' => 'ἢ', + 'ἣ' => 'ἣ', + 'ἤ' => 'ἤ', + 'ἥ' => 'ἥ', + 'ἦ' => 'ἦ', + 'ἧ' => 'ἧ', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'Ἤ' => 'Ἤ', + 'Ἥ' => 'Ἥ', + 'Ἦ' => 'Ἦ', + 'Ἧ' => 'Ἧ', + 'ἰ' => 'ἰ', + 'ἱ' => 'ἱ', + 'ἲ' => 'ἲ', + 'ἳ' => 'ἳ', + 'ἴ' => 'ἴ', + 'ἵ' => 'ἵ', + 'ἶ' => 'ἶ', + 'ἷ' => 'ἷ', + 'Ἰ' => 'Ἰ', + 'Ἱ' => 'Ἱ', + 'Ἲ' => 'Ἲ', + 'Ἳ' => 'Ἳ', + 'Ἴ' => 'Ἴ', + 'Ἵ' => 'Ἵ', + 'Ἶ' => 'Ἶ', + 'Ἷ' => 'Ἷ', + 'ὀ' => 'ὀ', + 'ὁ' => 'ὁ', + 'ὂ' => 'ὂ', + 'ὃ' => 'ὃ', + 'ὄ' => 'ὄ', + 'ὅ' => 'ὅ', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'Ὄ' => 'Ὄ', + 'Ὅ' => 'Ὅ', + 'ὐ' => 'ὐ', + 'ὑ' => 'ὑ', + 'ὒ' => 'ὒ', + 'ὓ' => 'ὓ', + 'ὔ' => 'ὔ', + 'ὕ' => 'ὕ', + 'ὖ' => 'ὖ', + 'ὗ' => 'ὗ', + 'Ὑ' => 'Ὑ', + 'Ὓ' => 'Ὓ', + 'Ὕ' => 'Ὕ', + 'Ὗ' => 'Ὗ', + 'ὠ' => 'ὠ', + 'ὡ' => 'ὡ', + 'ὢ' => 'ὢ', + 'ὣ' => 'ὣ', + 'ὤ' => 'ὤ', + 'ὥ' => 'ὥ', + 'ὦ' => 'ὦ', + 'ὧ' => 'ὧ', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'Ὤ' => 'Ὤ', + 'Ὥ' => 'Ὥ', + 'Ὦ' => 'Ὦ', + 'Ὧ' => 'Ὧ', + 'ὰ' => 'ὰ', + 'ά' => 'ά', + 'ὲ' => 'ὲ', + 'έ' => 'έ', + 'ὴ' => 'ὴ', + 'ή' => 'ή', + 'ὶ' => 'ὶ', + 'ί' => 'ί', + 'ὸ' => 'ὸ', + 'ό' => 'ό', + 'ὺ' => 'ὺ', + 'ύ' => 'ύ', + 'ὼ' => 'ὼ', + 'ώ' => 'ώ', + 'ᾀ' => 'ᾀ', + 'ᾁ' => 'ᾁ', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ᾄ', + 'ᾅ' => 'ᾅ', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'ᾍ' => 'ᾍ', + 'ᾎ' => 'ᾎ', + 'ᾏ' => 'ᾏ', + 'ᾐ' => 'ᾐ', + 'ᾑ' => 'ᾑ', + 'ᾒ' => 'ᾒ', + 'ᾓ' => 'ᾓ', + 'ᾔ' => 'ᾔ', + 'ᾕ' => 'ᾕ', + 'ᾖ' => 'ᾖ', + 'ᾗ' => 'ᾗ', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'ᾛ', + 'ᾜ' => 'ᾜ', + 'ᾝ' => 'ᾝ', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'ᾠ' => 'ᾠ', + 'ᾡ' => 'ᾡ', + 'ᾢ' => 'ᾢ', + 'ᾣ' => 'ᾣ', + 'ᾤ' => 'ᾤ', + 'ᾥ' => 'ᾥ', + 'ᾦ' => 'ᾦ', + 'ᾧ' => 'ᾧ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'ᾭ' => 'ᾭ', + 'ᾮ' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'ᾰ' => 'ᾰ', + 'ᾱ' => 'ᾱ', + 'ᾲ' => 'ᾲ', + 'ᾳ' => 'ᾳ', + 'ᾴ' => 'ᾴ', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾷ', + 'Ᾰ' => 'Ᾰ', + 'Ᾱ' => 'Ᾱ', + 'Ὰ' => 'Ὰ', + 'Ά' => 'Ά', + 'ᾼ' => 'ᾼ', + 'ι' => 'ι', + '῁' => '῁', + 'ῂ' => 'ῂ', + 'ῃ' => 'ῃ', + 'ῄ' => 'ῄ', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Έ' => 'Έ', + 'Ὴ' => 'Ὴ', + 'Ή' => 'Ή', + 'ῌ' => 'ῌ', + '῍' => '῍', + '῎' => '῎', + '῏' => '῏', + 'ῐ' => 'ῐ', + 'ῑ' => 'ῑ', + 'ῒ' => 'ῒ', + 'ΐ' => 'ΐ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'Ῐ' => 'Ῐ', + 'Ῑ' => 'Ῑ', + 'Ὶ' => 'Ὶ', + 'Ί' => 'Ί', + '῝' => '῝', + '῞' => '῞', + '῟' => '῟', + 'ῠ' => 'ῠ', + 'ῡ' => 'ῡ', + 'ῢ' => 'ῢ', + 'ΰ' => 'ΰ', + 'ῤ' => 'ῤ', + 'ῥ' => 'ῥ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'Ῠ' => 'Ῠ', + 'Ῡ' => 'Ῡ', + 'Ὺ' => 'Ὺ', + 'Ύ' => 'Ύ', + 'Ῥ' => 'Ῥ', + '῭' => '῭', + '΅' => '΅', + '`' => '`', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'ῴ' => 'ῴ', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῷ', + 'Ὸ' => 'Ὸ', + 'Ό' => 'Ό', + 'Ὼ' => 'Ὼ', + 'Ώ' => 'Ώ', + 'ῼ' => 'ῼ', + '´' => '´', + ' ' => ' ', + ' ' => ' ', + 'Ω' => 'Ω', + 'K' => 'K', + 'Å' => 'Å', + '↚' => '↚', + '↛' => '↛', + '↮' => '↮', + '⇍' => '⇍', + '⇎' => '⇎', + '⇏' => '⇏', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + '≁' => '≁', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + '≭' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + '⊁' => '⊁', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⋠' => '⋠', + '⋡' => '⋡', + '⋢' => '⋢', + '⋣' => '⋣', + '⋪' => '⋪', + '⋫' => '⋫', + '⋬' => '⋬', + '⋭' => '⋭', + '〈' => '〈', + '〉' => '〉', + '⫝̸' => '⫝̸', + 'が' => 'が', + 'ぎ' => 'ぎ', + 'ぐ' => 'ぐ', + 'げ' => 'げ', + 'ご' => 'ご', + 'ざ' => 'ざ', + 'じ' => 'じ', + 'ず' => 'ず', + 'ぜ' => 'ぜ', + 'ぞ' => 'ぞ', + 'だ' => 'だ', + 'ぢ' => 'ぢ', + 'づ' => 'づ', + 'で' => 'で', + 'ど' => 'ど', + 'ば' => 'ば', + 'ぱ' => 'ぱ', + 'び' => 'び', + 'ぴ' => 'ぴ', + 'ぶ' => 'ぶ', + 'ぷ' => 'ぷ', + 'べ' => 'べ', + 'ぺ' => 'ぺ', + 'ぼ' => 'ぼ', + 'ぽ' => 'ぽ', + 'ゔ' => 'ゔ', + 'ゞ' => 'ゞ', + 'ガ' => 'ガ', + 'ギ' => 'ギ', + 'グ' => 'グ', + 'ゲ' => 'ゲ', + 'ゴ' => 'ゴ', + 'ザ' => 'ザ', + 'ジ' => 'ジ', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ダ' => 'ダ', + 'ヂ' => 'ヂ', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'バ' => 'バ', + 'パ' => 'パ', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ポ' => 'ポ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '豈' => '豈', + '更' => '更', + '車' => '車', + '賈' => '賈', + '滑' => '滑', + '串' => '串', + '句' => '句', + '龜' => '龜', + '龜' => '龜', + '契' => '契', + '金' => '金', + '喇' => '喇', + '奈' => '奈', + '懶' => '懶', + '癩' => '癩', + '羅' => '羅', + '蘿' => '蘿', + '螺' => '螺', + '裸' => '裸', + '邏' => '邏', + '樂' => '樂', + '洛' => '洛', + '烙' => '烙', + '珞' => '珞', + '落' => '落', + '酪' => '酪', + '駱' => '駱', + '亂' => '亂', + '卵' => '卵', + '欄' => '欄', + '爛' => '爛', + '蘭' => '蘭', + '鸞' => '鸞', + '嵐' => '嵐', + '濫' => '濫', + '藍' => '藍', + '襤' => '襤', + '拉' => '拉', + '臘' => '臘', + '蠟' => '蠟', + '廊' => '廊', + '朗' => '朗', + '浪' => '浪', + '狼' => '狼', + '郎' => '郎', + '來' => '來', + '冷' => '冷', + '勞' => '勞', + '擄' => '擄', + '櫓' => '櫓', + '爐' => '爐', + '盧' => '盧', + '老' => '老', + '蘆' => '蘆', + '虜' => '虜', + '路' => '路', + '露' => '露', + '魯' => '魯', + '鷺' => '鷺', + '碌' => '碌', + '祿' => '祿', + '綠' => '綠', + '菉' => '菉', + '錄' => '錄', + '鹿' => '鹿', + '論' => '論', + '壟' => '壟', + '弄' => '弄', + '籠' => '籠', + '聾' => '聾', + '牢' => '牢', + '磊' => '磊', + '賂' => '賂', + '雷' => '雷', + '壘' => '壘', + '屢' => '屢', + '樓' => '樓', + '淚' => '淚', + '漏' => '漏', + '累' => '累', + '縷' => '縷', + '陋' => '陋', + '勒' => '勒', + '肋' => '肋', + '凜' => '凜', + '凌' => '凌', + '稜' => '稜', + '綾' => '綾', + '菱' => '菱', + '陵' => '陵', + '讀' => '讀', + '拏' => '拏', + '樂' => '樂', + '諾' => '諾', + '丹' => '丹', + '寧' => '寧', + '怒' => '怒', + '率' => '率', + '異' => '異', + '北' => '北', + '磻' => '磻', + '便' => '便', + '復' => '復', + '不' => '不', + '泌' => '泌', + '數' => '數', + '索' => '索', + '參' => '參', + '塞' => '塞', + '省' => '省', + '葉' => '葉', + '說' => '說', + '殺' => '殺', + '辰' => '辰', + '沈' => '沈', + '拾' => '拾', + '若' => '若', + '掠' => '掠', + '略' => '略', + '亮' => '亮', + '兩' => '兩', + '凉' => '凉', + '梁' => '梁', + '糧' => '糧', + '良' => '良', + '諒' => '諒', + '量' => '量', + '勵' => '勵', + '呂' => '呂', + '女' => '女', + '廬' => '廬', + '旅' => '旅', + '濾' => '濾', + '礪' => '礪', + '閭' => '閭', + '驪' => '驪', + '麗' => '麗', + '黎' => '黎', + '力' => '力', + '曆' => '曆', + '歷' => '歷', + '轢' => '轢', + '年' => '年', + '憐' => '憐', + '戀' => '戀', + '撚' => '撚', + '漣' => '漣', + '煉' => '煉', + '璉' => '璉', + '秊' => '秊', + '練' => '練', + '聯' => '聯', + '輦' => '輦', + '蓮' => '蓮', + '連' => '連', + '鍊' => '鍊', + '列' => '列', + '劣' => '劣', + '咽' => '咽', + '烈' => '烈', + '裂' => '裂', + '說' => '說', + '廉' => '廉', + '念' => '念', + '捻' => '捻', + '殮' => '殮', + '簾' => '簾', + '獵' => '獵', + '令' => '令', + '囹' => '囹', + '寧' => '寧', + '嶺' => '嶺', + '怜' => '怜', + '玲' => '玲', + '瑩' => '瑩', + '羚' => '羚', + '聆' => '聆', + '鈴' => '鈴', + '零' => '零', + '靈' => '靈', + '領' => '領', + '例' => '例', + '禮' => '禮', + '醴' => '醴', + '隸' => '隸', + '惡' => '惡', + '了' => '了', + '僚' => '僚', + '寮' => '寮', + '尿' => '尿', + '料' => '料', + '樂' => '樂', + '燎' => '燎', + '療' => '療', + '蓼' => '蓼', + '遼' => '遼', + '龍' => '龍', + '暈' => '暈', + '阮' => '阮', + '劉' => '劉', + '杻' => '杻', + '柳' => '柳', + '流' => '流', + '溜' => '溜', + '琉' => '琉', + '留' => '留', + '硫' => '硫', + '紐' => '紐', + '類' => '類', + '六' => '六', + '戮' => '戮', + '陸' => '陸', + '倫' => '倫', + '崙' => '崙', + '淪' => '淪', + '輪' => '輪', + '律' => '律', + '慄' => '慄', + '栗' => '栗', + '率' => '率', + '隆' => '隆', + '利' => '利', + '吏' => '吏', + '履' => '履', + '易' => '易', + '李' => '李', + '梨' => '梨', + '泥' => '泥', + '理' => '理', + '痢' => '痢', + '罹' => '罹', + '裏' => '裏', + '裡' => '裡', + '里' => '里', + '離' => '離', + '匿' => '匿', + '溺' => '溺', + '吝' => '吝', + '燐' => '燐', + '璘' => '璘', + '藺' => '藺', + '隣' => '隣', + '鱗' => '鱗', + '麟' => '麟', + '林' => '林', + '淋' => '淋', + '臨' => '臨', + '立' => '立', + '笠' => '笠', + '粒' => '粒', + '狀' => '狀', + '炙' => '炙', + '識' => '識', + '什' => '什', + '茶' => '茶', + '刺' => '刺', + '切' => '切', + '度' => '度', + '拓' => '拓', + '糖' => '糖', + '宅' => '宅', + '洞' => '洞', + '暴' => '暴', + '輻' => '輻', + '行' => '行', + '降' => '降', + '見' => '見', + '廓' => '廓', + '兀' => '兀', + '嗀' => '嗀', + '塚' => '塚', + '晴' => '晴', + '凞' => '凞', + '猪' => '猪', + '益' => '益', + '礼' => '礼', + '神' => '神', + '祥' => '祥', + '福' => '福', + '靖' => '靖', + '精' => '精', + '羽' => '羽', + '蘒' => '蘒', + '諸' => '諸', + '逸' => '逸', + '都' => '都', + '飯' => '飯', + '飼' => '飼', + '館' => '館', + '鶴' => '鶴', + '郞' => '郞', + '隷' => '隷', + '侮' => '侮', + '僧' => '僧', + '免' => '免', + '勉' => '勉', + '勤' => '勤', + '卑' => '卑', + '喝' => '喝', + '嘆' => '嘆', + '器' => '器', + '塀' => '塀', + '墨' => '墨', + '層' => '層', + '屮' => '屮', + '悔' => '悔', + '慨' => '慨', + '憎' => '憎', + '懲' => '懲', + '敏' => '敏', + '既' => '既', + '暑' => '暑', + '梅' => '梅', + '海' => '海', + '渚' => '渚', + '漢' => '漢', + '煮' => '煮', + '爫' => '爫', + '琢' => '琢', + '碑' => '碑', + '社' => '社', + '祉' => '祉', + '祈' => '祈', + '祐' => '祐', + '祖' => '祖', + '祝' => '祝', + '禍' => '禍', + '禎' => '禎', + '穀' => '穀', + '突' => '突', + '節' => '節', + '練' => '練', + '縉' => '縉', + '繁' => '繁', + '署' => '署', + '者' => '者', + '臭' => '臭', + '艹' => '艹', + '艹' => '艹', + '著' => '著', + '褐' => '褐', + '視' => '視', + '謁' => '謁', + '謹' => '謹', + '賓' => '賓', + '贈' => '贈', + '辶' => '辶', + '逸' => '逸', + '難' => '難', + '響' => '響', + '頻' => '頻', + '恵' => '恵', + '𤋮' => '𤋮', + '舘' => '舘', + '並' => '並', + '况' => '况', + '全' => '全', + '侀' => '侀', + '充' => '充', + '冀' => '冀', + '勇' => '勇', + '勺' => '勺', + '喝' => '喝', + '啕' => '啕', + '喙' => '喙', + '嗢' => '嗢', + '塚' => '塚', + '墳' => '墳', + '奄' => '奄', + '奔' => '奔', + '婢' => '婢', + '嬨' => '嬨', + '廒' => '廒', + '廙' => '廙', + '彩' => '彩', + '徭' => '徭', + '惘' => '惘', + '慎' => '慎', + '愈' => '愈', + '憎' => '憎', + '慠' => '慠', + '懲' => '懲', + '戴' => '戴', + '揄' => '揄', + '搜' => '搜', + '摒' => '摒', + '敖' => '敖', + '晴' => '晴', + '朗' => '朗', + '望' => '望', + '杖' => '杖', + '歹' => '歹', + '殺' => '殺', + '流' => '流', + '滛' => '滛', + '滋' => '滋', + '漢' => '漢', + '瀞' => '瀞', + '煮' => '煮', + '瞧' => '瞧', + '爵' => '爵', + '犯' => '犯', + '猪' => '猪', + '瑱' => '瑱', + '甆' => '甆', + '画' => '画', + '瘝' => '瘝', + '瘟' => '瘟', + '益' => '益', + '盛' => '盛', + '直' => '直', + '睊' => '睊', + '着' => '着', + '磌' => '磌', + '窱' => '窱', + '節' => '節', + '类' => '类', + '絛' => '絛', + '練' => '練', + '缾' => '缾', + '者' => '者', + '荒' => '荒', + '華' => '華', + '蝹' => '蝹', + '襁' => '襁', + '覆' => '覆', + '視' => '視', + '調' => '調', + '諸' => '諸', + '請' => '請', + '謁' => '謁', + '諾' => '諾', + '諭' => '諭', + '謹' => '謹', + '變' => '變', + '贈' => '贈', + '輸' => '輸', + '遲' => '遲', + '醙' => '醙', + '鉶' => '鉶', + '陼' => '陼', + '難' => '難', + '靖' => '靖', + '韛' => '韛', + '響' => '響', + '頋' => '頋', + '頻' => '頻', + '鬒' => '鬒', + '龜' => '龜', + '𢡊' => '𢡊', + '𢡄' => '𢡄', + '𣏕' => '𣏕', + '㮝' => '㮝', + '䀘' => '䀘', + '䀹' => '䀹', + '𥉉' => '𥉉', + '𥳐' => '𥳐', + '𧻓' => '𧻓', + '齃' => '齃', + '龎' => '龎', + 'יִ' => 'יִ', + 'ײַ' => 'ײַ', + 'שׁ' => 'שׁ', + 'שׂ' => 'שׂ', + 'שּׁ' => 'שּׁ', + 'שּׂ' => 'שּׂ', + 'אַ' => 'אַ', + 'אָ' => 'אָ', + 'אּ' => 'אּ', + 'בּ' => 'בּ', + 'גּ' => 'גּ', + 'דּ' => 'דּ', + 'הּ' => 'הּ', + 'וּ' => 'וּ', + 'זּ' => 'זּ', + 'טּ' => 'טּ', + 'יּ' => 'יּ', + 'ךּ' => 'ךּ', + 'כּ' => 'כּ', + 'לּ' => 'לּ', + 'מּ' => 'מּ', + 'נּ' => 'נּ', + 'סּ' => 'סּ', + 'ףּ' => 'ףּ', + 'פּ' => 'פּ', + 'צּ' => 'צּ', + 'קּ' => 'קּ', + 'רּ' => 'רּ', + 'שּ' => 'שּ', + 'תּ' => 'תּ', + 'וֹ' => 'וֹ', + 'בֿ' => 'בֿ', + 'כֿ' => 'כֿ', + 'פֿ' => 'פֿ', + '𑂚' => '𑂚', + '𑂜' => '𑂜', + '𑂫' => '𑂫', + '𑄮' => '𑄮', + '𑄯' => '𑄯', + '𑍋' => '𑍋', + '𑍌' => '𑍌', + '𑒻' => '𑒻', + '𑒼' => '𑒼', + '𑒾' => '𑒾', + '𑖺' => '𑖺', + '𑖻' => '𑖻', + '𑤸' => '𑤸', + '𝅗𝅥' => '𝅗𝅥', + '𝅘𝅥' => '𝅘𝅥', + '𝅘𝅥𝅮' => '𝅘𝅥𝅮', + '𝅘𝅥𝅯' => '𝅘𝅥𝅯', + '𝅘𝅥𝅰' => '𝅘𝅥𝅰', + '𝅘𝅥𝅱' => '𝅘𝅥𝅱', + '𝅘𝅥𝅲' => '𝅘𝅥𝅲', + '𝆹𝅥' => '𝆹𝅥', + '𝆺𝅥' => '𝆺𝅥', + '𝆹𝅥𝅮' => '𝆹𝅥𝅮', + '𝆺𝅥𝅮' => '𝆺𝅥𝅮', + '𝆹𝅥𝅯' => '𝆹𝅥𝅯', + '𝆺𝅥𝅯' => '𝆺𝅥𝅯', + '丽' => '丽', + '丸' => '丸', + '乁' => '乁', + '𠄢' => '𠄢', + '你' => '你', + '侮' => '侮', + '侻' => '侻', + '倂' => '倂', + '偺' => '偺', + '備' => '備', + '僧' => '僧', + '像' => '像', + '㒞' => '㒞', + '𠘺' => '𠘺', + '免' => '免', + '兔' => '兔', + '兤' => '兤', + '具' => '具', + '𠔜' => '𠔜', + '㒹' => '㒹', + '內' => '內', + '再' => '再', + '𠕋' => '𠕋', + '冗' => '冗', + '冤' => '冤', + '仌' => '仌', + '冬' => '冬', + '况' => '况', + '𩇟' => '𩇟', + '凵' => '凵', + '刃' => '刃', + '㓟' => '㓟', + '刻' => '刻', + '剆' => '剆', + '割' => '割', + '剷' => '剷', + '㔕' => '㔕', + '勇' => '勇', + '勉' => '勉', + '勤' => '勤', + '勺' => '勺', + '包' => '包', + '匆' => '匆', + '北' => '北', + '卉' => '卉', + '卑' => '卑', + '博' => '博', + '即' => '即', + '卽' => '卽', + '卿' => '卿', + '卿' => '卿', + '卿' => '卿', + '𠨬' => '𠨬', + '灰' => '灰', + '及' => '及', + '叟' => '叟', + '𠭣' => '𠭣', + '叫' => '叫', + '叱' => '叱', + '吆' => '吆', + '咞' => '咞', + '吸' => '吸', + '呈' => '呈', + '周' => '周', + '咢' => '咢', + '哶' => '哶', + '唐' => '唐', + '啓' => '啓', + '啣' => '啣', + '善' => '善', + '善' => '善', + '喙' => '喙', + '喫' => '喫', + '喳' => '喳', + '嗂' => '嗂', + '圖' => '圖', + '嘆' => '嘆', + '圗' => '圗', + '噑' => '噑', + '噴' => '噴', + '切' => '切', + '壮' => '壮', + '城' => '城', + '埴' => '埴', + '堍' => '堍', + '型' => '型', + '堲' => '堲', + '報' => '報', + '墬' => '墬', + '𡓤' => '𡓤', + '売' => '売', + '壷' => '壷', + '夆' => '夆', + '多' => '多', + '夢' => '夢', + '奢' => '奢', + '𡚨' => '𡚨', + '𡛪' => '𡛪', + '姬' => '姬', + '娛' => '娛', + '娧' => '娧', + '姘' => '姘', + '婦' => '婦', + '㛮' => '㛮', + '㛼' => '㛼', + '嬈' => '嬈', + '嬾' => '嬾', + '嬾' => '嬾', + '𡧈' => '𡧈', + '寃' => '寃', + '寘' => '寘', + '寧' => '寧', + '寳' => '寳', + '𡬘' => '𡬘', + '寿' => '寿', + '将' => '将', + '当' => '当', + '尢' => '尢', + '㞁' => '㞁', + '屠' => '屠', + '屮' => '屮', + '峀' => '峀', + '岍' => '岍', + '𡷤' => '𡷤', + '嵃' => '嵃', + '𡷦' => '𡷦', + '嵮' => '嵮', + '嵫' => '嵫', + '嵼' => '嵼', + '巡' => '巡', + '巢' => '巢', + '㠯' => '㠯', + '巽' => '巽', + '帨' => '帨', + '帽' => '帽', + '幩' => '幩', + '㡢' => '㡢', + '𢆃' => '𢆃', + '㡼' => '㡼', + '庰' => '庰', + '庳' => '庳', + '庶' => '庶', + '廊' => '廊', + '𪎒' => '𪎒', + '廾' => '廾', + '𢌱' => '𢌱', + '𢌱' => '𢌱', + '舁' => '舁', + '弢' => '弢', + '弢' => '弢', + '㣇' => '㣇', + '𣊸' => '𣊸', + '𦇚' => '𦇚', + '形' => '形', + '彫' => '彫', + '㣣' => '㣣', + '徚' => '徚', + '忍' => '忍', + '志' => '志', + '忹' => '忹', + '悁' => '悁', + '㤺' => '㤺', + '㤜' => '㤜', + '悔' => '悔', + '𢛔' => '𢛔', + '惇' => '惇', + '慈' => '慈', + '慌' => '慌', + '慎' => '慎', + '慌' => '慌', + '慺' => '慺', + '憎' => '憎', + '憲' => '憲', + '憤' => '憤', + '憯' => '憯', + '懞' => '懞', + '懲' => '懲', + '懶' => '懶', + '成' => '成', + '戛' => '戛', + '扝' => '扝', + '抱' => '抱', + '拔' => '拔', + '捐' => '捐', + '𢬌' => '𢬌', + '挽' => '挽', + '拼' => '拼', + '捨' => '捨', + '掃' => '掃', + '揤' => '揤', + '𢯱' => '𢯱', + '搢' => '搢', + '揅' => '揅', + '掩' => '掩', + '㨮' => '㨮', + '摩' => '摩', + '摾' => '摾', + '撝' => '撝', + '摷' => '摷', + '㩬' => '㩬', + '敏' => '敏', + '敬' => '敬', + '𣀊' => '𣀊', + '旣' => '旣', + '書' => '書', + '晉' => '晉', + '㬙' => '㬙', + '暑' => '暑', + '㬈' => '㬈', + '㫤' => '㫤', + '冒' => '冒', + '冕' => '冕', + '最' => '最', + '暜' => '暜', + '肭' => '肭', + '䏙' => '䏙', + '朗' => '朗', + '望' => '望', + '朡' => '朡', + '杞' => '杞', + '杓' => '杓', + '𣏃' => '𣏃', + '㭉' => '㭉', + '柺' => '柺', + '枅' => '枅', + '桒' => '桒', + '梅' => '梅', + '𣑭' => '𣑭', + '梎' => '梎', + '栟' => '栟', + '椔' => '椔', + '㮝' => '㮝', + '楂' => '楂', + '榣' => '榣', + '槪' => '槪', + '檨' => '檨', + '𣚣' => '𣚣', + '櫛' => '櫛', + '㰘' => '㰘', + '次' => '次', + '𣢧' => '𣢧', + '歔' => '歔', + '㱎' => '㱎', + '歲' => '歲', + '殟' => '殟', + '殺' => '殺', + '殻' => '殻', + '𣪍' => '𣪍', + '𡴋' => '𡴋', + '𣫺' => '𣫺', + '汎' => '汎', + '𣲼' => '𣲼', + '沿' => '沿', + '泍' => '泍', + '汧' => '汧', + '洖' => '洖', + '派' => '派', + '海' => '海', + '流' => '流', + '浩' => '浩', + '浸' => '浸', + '涅' => '涅', + '𣴞' => '𣴞', + '洴' => '洴', + '港' => '港', + '湮' => '湮', + '㴳' => '㴳', + '滋' => '滋', + '滇' => '滇', + '𣻑' => '𣻑', + '淹' => '淹', + '潮' => '潮', + '𣽞' => '𣽞', + '𣾎' => '𣾎', + '濆' => '濆', + '瀹' => '瀹', + '瀞' => '瀞', + '瀛' => '瀛', + '㶖' => '㶖', + '灊' => '灊', + '災' => '災', + '灷' => '灷', + '炭' => '炭', + '𠔥' => '𠔥', + '煅' => '煅', + '𤉣' => '𤉣', + '熜' => '熜', + '𤎫' => '𤎫', + '爨' => '爨', + '爵' => '爵', + '牐' => '牐', + '𤘈' => '𤘈', + '犀' => '犀', + '犕' => '犕', + '𤜵' => '𤜵', + '𤠔' => '𤠔', + '獺' => '獺', + '王' => '王', + '㺬' => '㺬', + '玥' => '玥', + '㺸' => '㺸', + '㺸' => '㺸', + '瑇' => '瑇', + '瑜' => '瑜', + '瑱' => '瑱', + '璅' => '璅', + '瓊' => '瓊', + '㼛' => '㼛', + '甤' => '甤', + '𤰶' => '𤰶', + '甾' => '甾', + '𤲒' => '𤲒', + '異' => '異', + '𢆟' => '𢆟', + '瘐' => '瘐', + '𤾡' => '𤾡', + '𤾸' => '𤾸', + '𥁄' => '𥁄', + '㿼' => '㿼', + '䀈' => '䀈', + '直' => '直', + '𥃳' => '𥃳', + '𥃲' => '𥃲', + '𥄙' => '𥄙', + '𥄳' => '𥄳', + '眞' => '眞', + '真' => '真', + '真' => '真', + '睊' => '睊', + '䀹' => '䀹', + '瞋' => '瞋', + '䁆' => '䁆', + '䂖' => '䂖', + '𥐝' => '𥐝', + '硎' => '硎', + '碌' => '碌', + '磌' => '磌', + '䃣' => '䃣', + '𥘦' => '𥘦', + '祖' => '祖', + '𥚚' => '𥚚', + '𥛅' => '𥛅', + '福' => '福', + '秫' => '秫', + '䄯' => '䄯', + '穀' => '穀', + '穊' => '穊', + '穏' => '穏', + '𥥼' => '𥥼', + '𥪧' => '𥪧', + '𥪧' => '𥪧', + '竮' => '竮', + '䈂' => '䈂', + '𥮫' => '𥮫', + '篆' => '篆', + '築' => '築', + '䈧' => '䈧', + '𥲀' => '𥲀', + '糒' => '糒', + '䊠' => '䊠', + '糨' => '糨', + '糣' => '糣', + '紀' => '紀', + '𥾆' => '𥾆', + '絣' => '絣', + '䌁' => '䌁', + '緇' => '緇', + '縂' => '縂', + '繅' => '繅', + '䌴' => '䌴', + '𦈨' => '𦈨', + '𦉇' => '𦉇', + '䍙' => '䍙', + '𦋙' => '𦋙', + '罺' => '罺', + '𦌾' => '𦌾', + '羕' => '羕', + '翺' => '翺', + '者' => '者', + '𦓚' => '𦓚', + '𦔣' => '𦔣', + '聠' => '聠', + '𦖨' => '𦖨', + '聰' => '聰', + '𣍟' => '𣍟', + '䏕' => '䏕', + '育' => '育', + '脃' => '脃', + '䐋' => '䐋', + '脾' => '脾', + '媵' => '媵', + '𦞧' => '𦞧', + '𦞵' => '𦞵', + '𣎓' => '𣎓', + '𣎜' => '𣎜', + '舁' => '舁', + '舄' => '舄', + '辞' => '辞', + '䑫' => '䑫', + '芑' => '芑', + '芋' => '芋', + '芝' => '芝', + '劳' => '劳', + '花' => '花', + '芳' => '芳', + '芽' => '芽', + '苦' => '苦', + '𦬼' => '𦬼', + '若' => '若', + '茝' => '茝', + '荣' => '荣', + '莭' => '莭', + '茣' => '茣', + '莽' => '莽', + '菧' => '菧', + '著' => '著', + '荓' => '荓', + '菊' => '菊', + '菌' => '菌', + '菜' => '菜', + '𦰶' => '𦰶', + '𦵫' => '𦵫', + '𦳕' => '𦳕', + '䔫' => '䔫', + '蓱' => '蓱', + '蓳' => '蓳', + '蔖' => '蔖', + '𧏊' => '𧏊', + '蕤' => '蕤', + '𦼬' => '𦼬', + '䕝' => '䕝', + '䕡' => '䕡', + '𦾱' => '𦾱', + '𧃒' => '𧃒', + '䕫' => '䕫', + '虐' => '虐', + '虜' => '虜', + '虧' => '虧', + '虩' => '虩', + '蚩' => '蚩', + '蚈' => '蚈', + '蜎' => '蜎', + '蛢' => '蛢', + '蝹' => '蝹', + '蜨' => '蜨', + '蝫' => '蝫', + '螆' => '螆', + '䗗' => '䗗', + '蟡' => '蟡', + '蠁' => '蠁', + '䗹' => '䗹', + '衠' => '衠', + '衣' => '衣', + '𧙧' => '𧙧', + '裗' => '裗', + '裞' => '裞', + '䘵' => '䘵', + '裺' => '裺', + '㒻' => '㒻', + '𧢮' => '𧢮', + '𧥦' => '𧥦', + '䚾' => '䚾', + '䛇' => '䛇', + '誠' => '誠', + '諭' => '諭', + '變' => '變', + '豕' => '豕', + '𧲨' => '𧲨', + '貫' => '貫', + '賁' => '賁', + '贛' => '贛', + '起' => '起', + '𧼯' => '𧼯', + '𠠄' => '𠠄', + '跋' => '跋', + '趼' => '趼', + '跰' => '跰', + '𠣞' => '𠣞', + '軔' => '軔', + '輸' => '輸', + '𨗒' => '𨗒', + '𨗭' => '𨗭', + '邔' => '邔', + '郱' => '郱', + '鄑' => '鄑', + '𨜮' => '𨜮', + '鄛' => '鄛', + '鈸' => '鈸', + '鋗' => '鋗', + '鋘' => '鋘', + '鉼' => '鉼', + '鏹' => '鏹', + '鐕' => '鐕', + '𨯺' => '𨯺', + '開' => '開', + '䦕' => '䦕', + '閷' => '閷', + '𨵷' => '𨵷', + '䧦' => '䧦', + '雃' => '雃', + '嶲' => '嶲', + '霣' => '霣', + '𩅅' => '𩅅', + '𩈚' => '𩈚', + '䩮' => '䩮', + '䩶' => '䩶', + '韠' => '韠', + '𩐊' => '𩐊', + '䪲' => '䪲', + '𩒖' => '𩒖', + '頋' => '頋', + '頋' => '頋', + '頩' => '頩', + '𩖶' => '𩖶', + '飢' => '飢', + '䬳' => '䬳', + '餩' => '餩', + '馧' => '馧', + '駂' => '駂', + '駾' => '駾', + '䯎' => '䯎', + '𩬰' => '𩬰', + '鬒' => '鬒', + '鱀' => '鱀', + '鳽' => '鳽', + '䳎' => '䳎', + '䳭' => '䳭', + '鵧' => '鵧', + '𪃎' => '𪃎', + '䳸' => '䳸', + '𪄅' => '𪄅', + '𪈎' => '𪈎', + '𪊑' => '𪊑', + '麻' => '麻', + '䵖' => '䵖', + '黹' => '黹', + '黾' => '黾', + '鼅' => '鼅', + '鼏' => '鼏', + '鼖' => '鼖', + '鼻' => '鼻', + '𪘀' => '𪘀', +); diff --git a/lib/composer/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php b/lib/composer/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php new file mode 100644 index 0000000000000000000000000000000000000000..ec90f36eb65c636149d1de8f122e47649cefcbe2 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php b/lib/composer/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php new file mode 100644 index 0000000000000000000000000000000000000000..1574902893cc426f0993da8ac3918a90430eb3a7 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/polyfill-intl-normalizer/bootstrap.php b/lib/composer/symfony/polyfill-intl-normalizer/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..ba62006175f5bf9939f3ae405ef15036d57d5c26 --- /dev/null +++ b/lib/composer/symfony/polyfill-intl-normalizer/bootstrap.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($s, $form = p\Normalizer::NFC) { return p\Normalizer::isNormalized($s, $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize($s, $form = p\Normalizer::NFC) { return p\Normalizer::normalize($s, $form); } +} diff --git a/lib/composer/symfony/polyfill-intl-normalizer/composer.json b/lib/composer/symfony/polyfill-intl-normalizer/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..b0986a80a7a4d436c2ac02d17e9b647f1b2d5579 --- /dev/null +++ b/lib/composer/symfony/polyfill-intl-normalizer/composer.json @@ -0,0 +1,39 @@ +{ + "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": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/composer/symfony/polyfill-mbstring/LICENSE b/lib/composer/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4cd8bdd3007da4d62985ec9e5ca81a1e18ae34d1 --- /dev/null +++ b/lib/composer/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 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/lib/composer/symfony/polyfill-mbstring/Mbstring.php b/lib/composer/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000000000000000000000000000000000000..15503bc9dd3a0ca943356e84cffcd69069ec4566 --- /dev/null +++ b/lib/composer/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,847 @@ + + * + * 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 +{ + const MB_CASE_FOLD = PHP_INT_MAX; + + private static $encodingList = array('ASCII', 'UTF-8'); + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $caseFold = array( + array('µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"), + array('μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'), + ); + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($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]+/', array(__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, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) + { + $vars = array(&$a, &$b, &$c, &$d, &$e, &$f); + + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = Mbstring::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) || !$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 Mbstring::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) || !$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 = array("\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, array(__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) { + $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = array("\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; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { + self::$internalEncoding = $encoding; + + return true; + } + + return false; + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($lang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $lang; + + return true; + } + + return false; + } + + public static function mb_list_encodings() + { + return array('UTF-8'); + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return array('utf8'); + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); + } + + 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) { + trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); + + return false; + } + + 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 = iconv_strrpos($haystack, $needle, $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) { + trigger_error('The length of each segment must be greater than zero', E_USER_WARNING); + + return false; + } + + 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, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + } + + $result = array(); + $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 (0 === strcasecmp($c, 'none')) { + return true; + } + + return null !== $c ? false : 'none'; + } + + 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 = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $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) { + return strrchr($haystack, $needle, $part); + } + $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, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + 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 = array( + '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; + } + + 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/lib/composer/symfony/polyfill-mbstring/README.md b/lib/composer/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4efb599d81fcc2374cbf42273628660f40c6c683 --- /dev/null +++ b/lib/composer/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/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lib/composer/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/lib/composer/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000000000000000000000000000000000000..a22eca57bd99cc09a64162b32de0b143bf31be52 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/lib/composer/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000000000000000000000000000000000000..2a8f6e73b99301469991b2bb835324b39d96cd60 --- /dev/null +++ b/lib/composer/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', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', +); diff --git a/lib/composer/symfony/polyfill-mbstring/bootstrap.php b/lib/composer/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..b36a0926f2038e0e51a3646e87f10d4590b53f3a --- /dev/null +++ b/lib/composer/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,141 @@ + + * + * 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($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($s, $convmap, $enc = null) { return p\Mbstring::mb_decode_numericentity($s, $convmap, $enc); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($s, $convmap, $enc = null, $is_hex = false) { return p\Mbstring::mb_encode_numericentity($s, $convmap, $enc, $is_hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); } +} +if (!function_exists('mb_language')) { + function mb_language($lang = null) { return p\Mbstring::mb_language($lang); } +} +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($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($s, &$result = array()) { parse_str($s, $result); } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); } +} +if (!function_exists('mb_substr')) { + function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); } +} +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($enc = null) { return p\Mbstring::mb_http_output($enc); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); } +} +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); } +} +if (!function_exists('mb_ord')) { + function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); } +} +if (!function_exists('mb_chr')) { + function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $split_length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $split_length, $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/lib/composer/symfony/polyfill-mbstring/composer.json b/lib/composer/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..06e6b31a20f6b3e8b56ed76ba51142042ac69c7e --- /dev/null +++ b/lib/composer/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": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/composer/symfony/polyfill-php70/LICENSE b/lib/composer/symfony/polyfill-php70/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4cd8bdd3007da4d62985ec9e5ca81a1e18ae34d1 --- /dev/null +++ b/lib/composer/symfony/polyfill-php70/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 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/lib/composer/symfony/polyfill-php70/Php70.php b/lib/composer/symfony/polyfill-php70/Php70.php new file mode 100644 index 0000000000000000000000000000000000000000..7f1ad08a46b6ccc3038a75c95900e2c5df2afdaa --- /dev/null +++ b/lib/composer/symfony/polyfill-php70/Php70.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\Polyfill\Php70; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class Php70 +{ + public static function intdiv($dividend, $divisor) + { + $dividend = self::intArg($dividend, __FUNCTION__, 1); + $divisor = self::intArg($divisor, __FUNCTION__, 2); + + if (0 === $divisor) { + throw new \DivisionByZeroError('Division by zero'); + } + if (-1 === $divisor && ~PHP_INT_MAX === $dividend) { + throw new \ArithmeticError('Division of PHP_INT_MIN by -1 is not an integer'); + } + + return ($dividend - ($dividend % $divisor)) / $divisor; + } + + public static function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0) + { + $count = 0; + $result = (string) $subject; + if (0 === $limit = self::intArg($limit, __FUNCTION__, 3)) { + return $result; + } + + foreach ($patterns as $pattern => $callback) { + $result = preg_replace_callback($pattern, $callback, $result, $limit, $c); + $count += $c; + } + + return $result; + } + + public static function error_clear_last() + { + static $handler; + if (!$handler) { + $handler = function () { return false; }; + } + set_error_handler($handler); + @trigger_error(''); + restore_error_handler(); + } + + private static function intArg($value, $caller, $pos) + { + if (\is_int($value)) { + return $value; + } + if (!\is_numeric($value) || PHP_INT_MAX <= ($value += 0) || ~PHP_INT_MAX >= $value) { + throw new \TypeError(sprintf('%s() expects parameter %d to be integer, %s given', $caller, $pos, \gettype($value))); + } + + return (int) $value; + } +} diff --git a/lib/composer/symfony/polyfill-php70/README.md b/lib/composer/symfony/polyfill-php70/README.md new file mode 100644 index 0000000000000000000000000000000000000000..abd548823707b80864f96c5e0f630a789cf25d3f --- /dev/null +++ b/lib/composer/symfony/polyfill-php70/README.md @@ -0,0 +1,28 @@ +Symfony Polyfill / Php70 +======================== + +This component provides features unavailable in releases prior to PHP 7.0: + +- [`intdiv`](https://php.net/intdiv) +- [`preg_replace_callback_array`](https://php.net/preg_replace_callback_array) +- [`error_clear_last`](https://php.net/error_clear_last) +- `random_bytes` and `random_int` (from [paragonie/random_compat](https://github.com/paragonie/random_compat)) +- [`*Error` throwable classes](https://php.net/Error) +- [`PHP_INT_MIN`](https://php.net/reserved.constants#constant.php-int-min) +- `SessionUpdateTimestampHandlerInterface` + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +Compatibility notes +=================== + +To write portable code between PHP5 and PHP7, some care must be taken: +- `\*Error` exceptions must be caught before `\Exception`; +- after calling `error_clear_last()`, the result of `$e = error_get_last()` must be + verified using `isset($e['message'][0])` instead of `null !== $e`. + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lib/composer/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php b/lib/composer/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php new file mode 100644 index 0000000000000000000000000000000000000000..68191244625989ccc21e579a4b321b5e79d6660c --- /dev/null +++ b/lib/composer/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php @@ -0,0 +1,5 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php70 as p; + +if (PHP_VERSION_ID >= 70000) { + return; +} + +if (!defined('PHP_INT_MIN')) { + define('PHP_INT_MIN', ~PHP_INT_MAX); +} + +if (!function_exists('intdiv')) { + function intdiv($dividend, $divisor) { return p\Php70::intdiv($dividend, $divisor); } +} +if (!function_exists('preg_replace_callback_array')) { + function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0) { return p\Php70::preg_replace_callback_array($patterns, $subject, $limit, $count); } +} +if (!function_exists('error_clear_last')) { + function error_clear_last() { return p\Php70::error_clear_last(); } +} diff --git a/lib/composer/symfony/polyfill-php70/composer.json b/lib/composer/symfony/polyfill-php70/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..8a3354d4d8f2d1f8ba9e153d052de6ddabd9b90d --- /dev/null +++ b/lib/composer/symfony/polyfill-php70/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/polyfill-php70", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.0+ 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": ">=5.3.3", + "paragonie/random_compat": "~1.0|~2.0|~9.99" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php70\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/composer/symfony/polyfill-php72/LICENSE b/lib/composer/symfony/polyfill-php72/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4cd8bdd3007da4d62985ec9e5ca81a1e18ae34d1 --- /dev/null +++ b/lib/composer/symfony/polyfill-php72/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 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/lib/composer/symfony/polyfill-php72/Php72.php b/lib/composer/symfony/polyfill-php72/Php72.php new file mode 100644 index 0000000000000000000000000000000000000000..9b3edc7c7922fcf48e0e0cdafde43c1ebf5fd790 --- /dev/null +++ b/lib/composer/symfony/polyfill-php72/Php72.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php72; + +/** + * @author Nicolas Grekas + * @author Dariusz Rumiński + * + * @internal + */ +final class Php72 +{ + private static $hashMask; + + public static function utf8_encode($s) + { + $s .= $s; + $len = \strlen($s); + + for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) { + switch (true) { + case $s[$i] < "\x80": $s[$j] = $s[$i]; break; + case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; + default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break; + } + } + + return substr($s, 0, $j); + } + + public static function utf8_decode($s) + { + $s = (string) $s; + $len = \strlen($s); + + for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) { + switch ($s[$i] & "\xF0") { + case "\xC0": + case "\xD0": + $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F"); + $s[$j] = $c < 256 ? \chr($c) : '?'; + break; + + case "\xF0": + ++$i; + // no break + + case "\xE0": + $s[$j] = '?'; + $i += 2; + break; + + default: + $s[$j] = $s[$i]; + } + } + + return substr($s, 0, $j); + } + + public static function php_os_family() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + return 'Windows'; + } + + $map = array( + 'Darwin' => 'Darwin', + 'DragonFly' => 'BSD', + 'FreeBSD' => 'BSD', + 'NetBSD' => 'BSD', + 'OpenBSD' => 'BSD', + 'Linux' => 'Linux', + 'SunOS' => 'Solaris', + ); + + return isset($map[PHP_OS]) ? $map[PHP_OS] : 'Unknown'; + } + + public static function spl_object_id($object) + { + if (null === self::$hashMask) { + self::initHashMask(); + } + if (null === $hash = spl_object_hash($object)) { + return; + } + + // On 32-bit systems, PHP_INT_SIZE is 4, + return self::$hashMask ^ hexdec(substr($hash, 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); + } + + public static function sapi_windows_vt100_support($stream, $enable = null) + { + if (!\is_resource($stream)) { + trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING); + + return false; + } + + $meta = stream_get_meta_data($stream); + + if ('STDIO' !== $meta['stream_type']) { + trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', E_USER_WARNING); + + return false; + } + + // We cannot actually disable vt100 support if it is set + if (false === $enable || !self::stream_isatty($stream)) { + return false; + } + + // The native function does not apply to stdin + $meta = array_map('strtolower', $meta); + $stdin = 'php://stdin' === $meta['uri'] || 'php://fd/0' === $meta['uri']; + + return !$stdin + && (false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM')); + } + + public static function stream_isatty($stream) + { + if (!\is_resource($stream)) { + trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING); + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $stat = @fstat($stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } + + return \function_exists('posix_isatty') && @posix_isatty($stream); + } + + private static function initHashMask() + { + $obj = (object) array(); + self::$hashMask = -1; + + // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below + $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush'); + foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { + if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) { + $frame['line'] = 0; + break; + } + } + if (!empty($frame['line'])) { + ob_start(); + debug_zval_dump($obj); + self::$hashMask = (int) substr(ob_get_clean(), 17); + } + + self::$hashMask ^= hexdec(substr(spl_object_hash($obj), 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); + } + + 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) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if (null == $encoding) { + $s = mb_convert_encoding($s, 'UTF-8'); + } elseif ('UTF-8' !== $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; + } +} diff --git a/lib/composer/symfony/polyfill-php72/README.md b/lib/composer/symfony/polyfill-php72/README.md new file mode 100644 index 0000000000000000000000000000000000000000..59dec8a237f5d96cbcb969651e50a099e7ac38cd --- /dev/null +++ b/lib/composer/symfony/polyfill-php72/README.md @@ -0,0 +1,28 @@ +Symfony Polyfill / Php72 +======================== + +This component provides functions added to PHP 7.2 core: + +- [`spl_object_id`](https://php.net/spl_object_id) +- [`stream_isatty`](https://php.net/stream_isatty) + +On Windows only: + +- [`sapi_windows_vt100_support`](https://php.net/sapi_windows_vt100_support) + +Moved to core since 7.2 (was in the optional XML extension earlier): + +- [`utf8_encode`](https://php.net/utf8_encode) +- [`utf8_decode`](https://php.net/utf8_decode) + +Also, it provides constants added to PHP 7.2: +- [`PHP_FLOAT_*`](https://php.net/reserved.constants#constant.php-float-dig) +- [`PHP_OS_FAMILY`](https://php.net/reserved.constants#constant.php-os-family) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lib/composer/symfony/polyfill-php72/bootstrap.php b/lib/composer/symfony/polyfill-php72/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..a27a900a4fdeab10d89da20d777d637dd0dd4ce9 --- /dev/null +++ b/lib/composer/symfony/polyfill-php72/bootstrap.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php72 as p; + +if (PHP_VERSION_ID >= 70200) { + return; +} + +if (!defined('PHP_FLOAT_DIG')) { + define('PHP_FLOAT_DIG', 15); +} +if (!defined('PHP_FLOAT_EPSILON')) { + define('PHP_FLOAT_EPSILON', 2.2204460492503E-16); +} +if (!defined('PHP_FLOAT_MIN')) { + define('PHP_FLOAT_MIN', 2.2250738585072E-308); +} +if (!defined('PHP_FLOAT_MAX')) { + define('PHP_FLOAT_MAX', 1.7976931348623157E+308); +} +if (!defined('PHP_OS_FAMILY')) { + define('PHP_OS_FAMILY', p\Php72::php_os_family()); +} + +if ('\\' === DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) { + function sapi_windows_vt100_support($stream, $enable = null) { return p\Php72::sapi_windows_vt100_support($stream, $enable); } +} +if (!function_exists('stream_isatty')) { + function stream_isatty($stream) { return p\Php72::stream_isatty($stream); } +} +if (!function_exists('utf8_encode')) { + function utf8_encode($s) { return p\Php72::utf8_encode($s); } +} +if (!function_exists('utf8_decode')) { + function utf8_decode($s) { return p\Php72::utf8_decode($s); } +} +if (!function_exists('spl_object_id')) { + function spl_object_id($s) { return p\Php72::spl_object_id($s); } +} +if (!function_exists('mb_ord')) { + function mb_ord($s, $enc = null) { return p\Php72::mb_ord($s, $enc); } +} +if (!function_exists('mb_chr')) { + function mb_chr($code, $enc = null) { return p\Php72::mb_chr($code, $enc); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); } +} diff --git a/lib/composer/symfony/polyfill-php72/composer.json b/lib/composer/symfony/polyfill-php72/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..314d7136244acdd351069938005bf5204de2ca04 --- /dev/null +++ b/lib/composer/symfony/polyfill-php72/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/polyfill-php72", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.2+ 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": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php72\\": "" }, + "files": [ "bootstrap.php" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + } + } +} diff --git a/lib/composer/symfony/polyfill-php73/LICENSE b/lib/composer/symfony/polyfill-php73/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..3f853aaf35fe186d4016761eb6e8a403de3e6e0d --- /dev/null +++ b/lib/composer/symfony/polyfill-php73/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 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/lib/composer/symfony/polyfill-php73/Php73.php b/lib/composer/symfony/polyfill-php73/Php73.php new file mode 100644 index 0000000000000000000000000000000000000000..7c99d1972a05af1f980fab27c1f5078fd33cf14b --- /dev/null +++ b/lib/composer/symfony/polyfill-php73/Php73.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\Polyfill\Php73; + +/** + * @author Gabriel Caruso + * @author Ion Bazan + * + * @internal + */ +final class Php73 +{ + public static $startAt = 1533462603; + + /** + * @param bool $asNum + * + * @return array|float|int + */ + public static function hrtime($asNum = false) + { + $ns = microtime(false); + $s = substr($ns, 11) - self::$startAt; + $ns = 1E9 * (float) $ns; + + if ($asNum) { + $ns += $s * 1E9; + + return \PHP_INT_SIZE === 4 ? $ns : (int) $ns; + } + + return array($s, (int) $ns); + } +} diff --git a/lib/composer/symfony/polyfill-php73/README.md b/lib/composer/symfony/polyfill-php73/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b3ebbce511bcd3840e6565753a9854b6f459cefe --- /dev/null +++ b/lib/composer/symfony/polyfill-php73/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php73 +======================== + +This component provides functions added to PHP 7.3 core: + +- [`array_key_first`](https://php.net/array_key_first) +- [`array_key_last`](https://php.net/array_key_last) +- [`hrtime`](https://php.net/function.hrtime) +- [`is_countable`](https://php.net/is_countable) +- [`JsonException`](https://php.net/JsonException) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lib/composer/symfony/polyfill-php73/Resources/stubs/JsonException.php b/lib/composer/symfony/polyfill-php73/Resources/stubs/JsonException.php new file mode 100644 index 0000000000000000000000000000000000000000..673d100224854a8d9c0dc429cf4dfd5b3bef0726 --- /dev/null +++ b/lib/composer/symfony/polyfill-php73/Resources/stubs/JsonException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class JsonException extends Exception +{ +} diff --git a/lib/composer/symfony/polyfill-php73/bootstrap.php b/lib/composer/symfony/polyfill-php73/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..b3ec352af274cf42331a120866a0bc676f5037ae --- /dev/null +++ b/lib/composer/symfony/polyfill-php73/bootstrap.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php73 as p; + +if (PHP_VERSION_ID >= 70300) { + return; +} + +if (!function_exists('is_countable')) { + function is_countable($var) { return is_array($var) || $var instanceof Countable || $var instanceof ResourceBundle || $var instanceof SimpleXmlElement; } +} +if (!function_exists('hrtime')) { + require_once __DIR__.'/Php73.php'; + p\Php73::$startAt = (int) microtime(true); + function hrtime($asNum = false) { return p\Php73::hrtime($asNum); } +} +if (!function_exists('array_key_first')) { + function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } } +} +if (!function_exists('array_key_last')) { + function array_key_last(array $array) { end($array); return key($array); } +} diff --git a/lib/composer/symfony/polyfill-php73/composer.json b/lib/composer/symfony/polyfill-php73/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..5eaa9cfb383c877ec3b83c2f147e58b9f5f20fa6 --- /dev/null +++ b/lib/composer/symfony/polyfill-php73/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-php73", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.3+ 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": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php73\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/composer/symfony/polyfill-php80/LICENSE b/lib/composer/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..5593b1d84f74a170e02b3e58408dc189ea838434 --- /dev/null +++ b/lib/composer/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 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/lib/composer/symfony/polyfill-php80/Php80.php b/lib/composer/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000000000000000000000000000000000000..c03491b724db40cfc28bf28692e55309141f6eca --- /dev/null +++ b/lib/composer/symfony/polyfill-php80/Php80.php @@ -0,0 +1,105 @@ + + * + * 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 + { + return '' === $needle || ('' !== $haystack && 0 === \substr_compare($haystack, $needle, -\strlen($needle))); + } +} diff --git a/lib/composer/symfony/polyfill-php80/README.md b/lib/composer/symfony/polyfill-php80/README.md new file mode 100644 index 0000000000000000000000000000000000000000..eaa3050abc11f9852eed71f79dccaa3688f67854 --- /dev/null +++ b/lib/composer/symfony/polyfill-php80/README.md @@ -0,0 +1,24 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- `Stringable` interface +- [`fdiv`](https://php.net/fdiv) +- `ValueError` class +- `UnhandledMatchError` class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`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/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/lib/composer/symfony/polyfill-php80/Resources/stubs/Stringable.php b/lib/composer/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000000000000000000000000000000000000..ad0029a3d9c6f292afa76577a48ceeaa876a2918 --- /dev/null +++ b/lib/composer/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,9 @@ + + * + * 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 $dividend, float $divisor): float { return p\Php80::fdiv($dividend, $divisor); } +} +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($res): int { return p\Php80::get_resource_id($res); } +} diff --git a/lib/composer/symfony/polyfill-php80/composer.json b/lib/composer/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..51086cc27d514c870f20b879be0dc9584deeeb9c --- /dev/null +++ b/lib/composer/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "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.0.8" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/composer/symfony/process/CHANGELOG.md b/lib/composer/symfony/process/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..3f3a0202268c5ef2927ba8f47b79218f1b0a53b7 --- /dev/null +++ b/lib/composer/symfony/process/CHANGELOG.md @@ -0,0 +1,109 @@ +CHANGELOG +========= + +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/lib/composer/symfony/process/Exception/ExceptionInterface.php b/lib/composer/symfony/process/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..bd4a60403ba7b161871f5bf3c728ada27fb1d0e2 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/process/Exception/InvalidArgumentException.php b/lib/composer/symfony/process/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..926ee2118b03703381fabcd80e01bda817c7fa67 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/process/Exception/LogicException.php b/lib/composer/symfony/process/Exception/LogicException.php new file mode 100644 index 0000000000000000000000000000000000000000..be3d490dde8cdea7e594b6e18bc4825a1e2eee13 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/process/Exception/ProcessFailedException.php b/lib/composer/symfony/process/Exception/ProcessFailedException.php new file mode 100644 index 0000000000000000000000000000000000000000..328acfde5e88358313c365de13738d70a66755f1 --- /dev/null +++ b/lib/composer/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; + + 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() + { + return $this->process; + } +} diff --git a/lib/composer/symfony/process/Exception/ProcessSignaledException.php b/lib/composer/symfony/process/Exception/ProcessSignaledException.php new file mode 100644 index 0000000000000000000000000000000000000000..d4d322756f39b85737ff9b4adab714aeca70ebb9 --- /dev/null +++ b/lib/composer/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; + + 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/lib/composer/symfony/process/Exception/ProcessTimedOutException.php b/lib/composer/symfony/process/Exception/ProcessTimedOutException.php new file mode 100644 index 0000000000000000000000000000000000000000..e1f6445a4b3cba856ffb7d06a80c30e4b52d8aaf --- /dev/null +++ b/lib/composer/symfony/process/Exception/ProcessTimedOutException.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 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 +{ + const TYPE_GENERAL = 1; + const TYPE_IDLE = 2; + + private $process; + private $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() + { + return $this->process; + } + + public function isGeneralTimeout() + { + return self::TYPE_GENERAL === $this->timeoutType; + } + + public function isIdleTimeout() + { + return self::TYPE_IDLE === $this->timeoutType; + } + + public function getExceededTimeout() + { + switch ($this->timeoutType) { + case self::TYPE_GENERAL: + return $this->process->getTimeout(); + + case self::TYPE_IDLE: + return $this->process->getIdleTimeout(); + + default: + throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); + } + } +} diff --git a/lib/composer/symfony/process/Exception/RuntimeException.php b/lib/composer/symfony/process/Exception/RuntimeException.php new file mode 100644 index 0000000000000000000000000000000000000000..adead2536b10362f3e2c499612ce0ffeea3f6a49 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/process/ExecutableFinder.php b/lib/composer/symfony/process/ExecutableFinder.php new file mode 100644 index 0000000000000000000000000000000000000000..aa52cf367d84dafe6fe12b70d28057591738b413 --- /dev/null +++ b/lib/composer/symfony/process/ExecutableFinder.php @@ -0,0 +1,86 @@ + + * + * 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 $suffixes = ['.exe', '.bat', '.cmd', '.com']; + + /** + * Replaces default suffixes of executable. + */ + public function setSuffixes(array $suffixes) + { + $this->suffixes = $suffixes; + } + + /** + * Adds new possible suffix to check for executable. + */ + public function addSuffix(string $suffix) + { + $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 + * + * @return string|null The executable path or default value + */ + public function find(string $name, string $default = null, array $extraDirs = []) + { + if (ini_get('open_basedir')) { + $searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs); + $dirs = []; + foreach ($searchPath as $path) { + // Silencing against https://bugs.php.net/69240 + if (@is_dir($path)) { + $dirs[] = $path; + } else { + if (basename($path) == $name && @is_executable($path)) { + return $path; + } + } + } + } else { + $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; + } + } + } + + return $default; + } +} diff --git a/lib/composer/symfony/process/InputStream.php b/lib/composer/symfony/process/InputStream.php new file mode 100644 index 0000000000000000000000000000000000000000..c86fca86878dfb3eae854eb0f3d13fd0a7592a8d --- /dev/null +++ b/lib/composer/symfony/process/InputStream.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 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 + */ +class InputStream implements \IteratorAggregate +{ + /** @var callable|null */ + private $onEmpty = null; + private $input = []; + private $open = true; + + /** + * Sets a callback that is called when the write buffer becomes empty. + */ + public function onEmpty(callable $onEmpty = null) + { + $this->onEmpty = $onEmpty; + } + + /** + * 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($input) + { + 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() + { + $this->open = false; + } + + /** + * Tells whether the write buffer is closed or not. + */ + public function isClosed() + { + return !$this->open; + } + + /** + * @return \Traversable + */ + public function getIterator() + { + $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/lib/composer/symfony/process/LICENSE b/lib/composer/symfony/process/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9e936ec0448b8549e5edf08e5ac5f01491a8bfc8 --- /dev/null +++ b/lib/composer/symfony/process/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2020 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/lib/composer/symfony/process/PhpExecutableFinder.php b/lib/composer/symfony/process/PhpExecutableFinder.php new file mode 100644 index 0000000000000000000000000000000000000000..c09d49a2c2365101604b1c3132dfb20911f08647 --- /dev/null +++ b/lib/composer/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; + + public function __construct() + { + $this->executableFinder = new ExecutableFinder(); + } + + /** + * Finds The PHP executable. + * + * @return string|false The PHP executable path or false if it cannot be found + */ + public function find(bool $includeArgs = true) + { + if ($php = getenv('PHP_BINARY')) { + if (!is_executable($php)) { + $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v'; + if ($php = strtok(exec($command.' '.escapeshellarg($php)), PHP_EOL)) { + if (!is_executable($php)) { + return false; + } + } else { + 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, ['cgi-fcgi', 'cli', 'cli-server', 'phpdbg'], true)) { + return PHP_BINARY.$args; + } + + if ($php = getenv('PHP_PATH')) { + if (!@is_executable($php)) { + return false; + } + + return $php; + } + + if ($php = getenv('PHP_PEAR_PHP_BIN')) { + if (@is_executable($php)) { + return $php; + } + } + + if (@is_executable($php = PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/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. + * + * @return array The PHP executable arguments + */ + public function findArguments() + { + $arguments = []; + if ('phpdbg' === \PHP_SAPI) { + $arguments[] = '-qrr'; + } + + return $arguments; + } +} diff --git a/lib/composer/symfony/process/PhpProcess.php b/lib/composer/symfony/process/PhpProcess.php new file mode 100644 index 0000000000000000000000000000000000000000..2bc338e5e2313c8f1dc9d1c5618ffd1fb0c6a2de --- /dev/null +++ b/lib/composer/symfony/process/PhpProcess.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\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); + } + + /** + * {@inheritdoc} + */ + public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) + { + throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); + } + + /** + * {@inheritdoc} + */ + public function start(callable $callback = null, array $env = []) + { + if (null === $this->getCommandLine()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + + parent::start($callback, $env); + } +} diff --git a/lib/composer/symfony/process/Pipes/AbstractPipes.php b/lib/composer/symfony/process/Pipes/AbstractPipes.php new file mode 100644 index 0000000000000000000000000000000000000000..77636c2a6851c106d1e76353977931c27bfe18d0 --- /dev/null +++ b/lib/composer/symfony/process/Pipes/AbstractPipes.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\Process\Pipes; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * @author Romain Neutron + * + * @internal + */ +abstract class AbstractPipes implements PipesInterface +{ + public $pipes = []; + + private $inputBuffer = ''; + private $input; + private $blocked = true; + private $lastError; + + /** + * @param resource|string|int|float|bool|\Iterator|null $input + */ + public function __construct($input) + { + if (\is_resource($input) || $input instanceof \Iterator) { + $this->input = $input; + } elseif (\is_string($input)) { + $this->inputBuffer = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $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() + { + 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) { + for (;;) { + $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($type, $msg) + { + $this->lastError = $msg; + } +} diff --git a/lib/composer/symfony/process/Pipes/PipesInterface.php b/lib/composer/symfony/process/Pipes/PipesInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..2d63e2c05ad1383205adc356f1b5fb424e47bea7 --- /dev/null +++ b/lib/composer/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 +{ + 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(); +} diff --git a/lib/composer/symfony/process/Pipes/UnixPipes.php b/lib/composer/symfony/process/Pipes/UnixPipes.php new file mode 100644 index 0000000000000000000000000000000000000000..70fdd29574ec74822a71d4c55568cd999e88f493 --- /dev/null +++ b/lib/composer/symfony/process/Pipes/UnixPipes.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\Process\Pipes; + +use Symfony\Component\Process\Process; + +/** + * UnixPipes implementation uses unix pipes as handles. + * + * @author Romain Neutron + * + * @internal + */ +class UnixPipes extends AbstractPipes +{ + private $ttyMode; + private $ptyMode; + private $haveReadSupport; + + public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport) + { + $this->ttyMode = $ttyMode; + $this->ptyMode = $ptyMode; + $this->haveReadSupport = $haveReadSupport; + + parent::__construct($input); + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + 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 + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles(): array + { + return []; + } + + /** + * {@inheritdoc} + */ + 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; + } + + /** + * {@inheritdoc} + */ + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + /** + * {@inheritdoc} + */ + public function areOpen(): bool + { + return (bool) $this->pipes; + } +} diff --git a/lib/composer/symfony/process/Pipes/WindowsPipes.php b/lib/composer/symfony/process/Pipes/WindowsPipes.php new file mode 100644 index 0000000000000000000000000000000000000000..c548092c51fff4830c59754675673b69a187da89 --- /dev/null +++ b/lib/composer/symfony/process/Pipes/WindowsPipes.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 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 $files = []; + private $fileHandles = []; + private $lockHandles = []; + private $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + private $haveReadSupport; + + public function __construct($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')) { + 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 (!fclose(fopen($file, 'w')) || !$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 __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + 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'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles(): array + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + 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(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; + } + + /** + * {@inheritdoc} + */ + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + /** + * {@inheritdoc} + */ + public function areOpen(): bool + { + return $this->pipes && $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + 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/lib/composer/symfony/process/Process.php b/lib/composer/symfony/process/Process.php new file mode 100644 index 0000000000000000000000000000000000000000..e1efb9ccc80ebc0cd7edee503716c5386fffc846 --- /dev/null +++ b/lib/composer/symfony/process/Process.php @@ -0,0 +1,1636 @@ + + * + * 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\PipesInterface; +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 + */ +class Process implements \IteratorAggregate +{ + const ERR = 'err'; + const OUT = 'out'; + + const STATUS_READY = 'ready'; + const STATUS_STARTED = 'started'; + const STATUS_TERMINATED = 'terminated'; + + const STDIN = 0; + const STDOUT = 1; + const STDERR = 2; + + // Timeout Precision in seconds. + const TIMEOUT_PRECISION = 0.2; + + const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking + const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory + const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating + const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating + + private $callback; + private $hasCallback = false; + private $commandline; + private $cwd; + private $env; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $exitcode; + private $fallbackStatus = []; + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty = false; + private $pty; + + private $useFileHandles = false; + /** @var PipesInterface */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * Exit codes translation table. + * + * User-defined errors must use exit codes in the 64-113 range. + */ + public static $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|null $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, $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->useFileHandles = '\\' === \DIRECTORY_SEPARATOR; + $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|null $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 + * + * @return static + * + * @throws LogicException When proc_open is not installed + */ + public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) + { + $process = new static([], $cwd, $env, $input, $timeout); + $process->commandline = $command; + + return $process; + } + + public function __destruct() + { + $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 = []): self + { + 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 = []) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $this->hasCallback = null !== $callback; + $descriptors = $this->getDescriptors(); + + if ($this->env) { + $env += $this->env; + } + + $env += $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); + } + + $options = ['suppress_errors' => true]; + + if ('\\' === \DIRECTORY_SEPARATOR) { + $options['bypass_shell'] = true; + $commandline = $this->prepareWindowsCommandLine($commandline, $env); + } elseif (!$this->useFileHandles && $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; code=$?; echo $code >&3; exit $code'; + + // Workaround for the bug, when PTS functionality is enabled. + // @see : https://bugs.php.net/69442 + $ptsWorkaround = fopen(__FILE__, 'r'); + } + + $envPairs = []; + foreach ($env as $k => $v) { + if (false !== $v) { + $envPairs[] = $k.'='.$v; + } + } + + if (!is_dir($this->cwd)) { + throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd)); + } + + $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options); + + if (!\is_resource($this->process)) { + throw new RuntimeException('Unable to launch a new 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 + * + * @return static + * + * @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 = []): self + { + 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) + { + $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 = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $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() + { + 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) + { + $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() + { + if ($this->isRunning()) { + throw new RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new LogicException('Output can not 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() + { + 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. + * + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * Returns the current output of the process (STDOUT). + * + * @return string The process output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getOutput() + { + $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. + * + * @return string The process 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() + { + $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 + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + * + * @return \Generator + */ + public function getIterator(int $flags = 0) + { + $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() + { + ftruncate($this->stdout, 0); + fseek($this->stdout, 0); + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * Returns the current error output of the process (STDERR). + * + * @return string The process error output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getErrorOutput() + { + $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. + * + * @return string The process 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() + { + $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() + { + 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() + { + $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() + { + if (null === $exitcode = $this->getExitCode()) { + return null; + } + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * Checks if the process ended successfully. + * + * @return bool true if the process ended successfully, false otherwise + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * Returns true if the child process has been terminated by an uncaught signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenSignaled() + { + $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. + * + * @return int + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + return $this->processInformation['termsig']; + } + + /** + * Returns true if the child process has been stopped by a signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenStopped() + { + $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. + * + * @return int + * + * @throws LogicException In case the process is not terminated + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopsig']; + } + + /** + * Checks if the process is currently running. + * + * @return bool true if the process is currently running, false otherwise + */ + public function isRunning() + { + 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. + * + * @return bool true if status is ready, false otherwise + */ + public function isStarted() + { + return self::STATUS_READY != $this->status; + } + + /** + * Checks if the process is terminated. + * + * @return bool true if process is terminated, false otherwise + */ + public function isTerminated() + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * Gets the process status. + * + * The status is one of: ready, started, terminated. + * + * @return string The current process status + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * Stops the process. + * + * @param int|float $timeout The timeout in seconds + * @param int $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) + { + $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) + { + $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) + { + $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. + * + * @return float|null The last output time in seconds or null if it isn't started + */ + public function getLastOutputTime(): ?float + { + return $this->lastOutputTime; + } + + /** + * Gets the command line to be executed. + * + * @return string The command to execute + */ + public function getCommandLine() + { + return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline; + } + + /** + * Gets the process timeout (max. runtime). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Gets the process idle timeout (max. time since last output). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getIdleTimeout() + { + 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) + { + $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) + { + if (null !== $timeout && $this->outputDisabled) { + throw new LogicException('Idle timeout can not 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) + { + 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. + * + * @return bool true if the TTY mode is enabled, false otherwise + */ + public function isTty() + { + return $this->tty; + } + + /** + * Sets PTY mode. + * + * @return $this + */ + public function setPty(bool $bool) + { + $this->pty = $bool; + + return $this; + } + + /** + * Returns PTY state. + * + * @return bool + */ + public function isPty() + { + return $this->pty; + } + + /** + * Gets the working directory. + * + * @return string|null The current working directory or null on failure + */ + public function getWorkingDirectory() + { + 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) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Gets the environment variables. + * + * @return array The current environment variables + */ + public function getEnv() + { + return $this->env; + } + + /** + * Sets the environment variables. + * + * Each environment variable value should be a string. + * If it is an array, the variable is ignored. + * If it is false or null, it will be removed when + * env vars are otherwise inherited. + * + * That happens in PHP when 'argv' is registered into + * the $_ENV array for instance. + * + * @param array $env The new environment variables + * + * @return $this + */ + public function setEnv(array $env) + { + // Process can not handle env values that are arrays + $env = array_filter($env, function ($value) { + return !\is_array($value); + }); + + $this->env = $env; + + return $this; + } + + /** + * Gets the Process input. + * + * @return resource|string|\Iterator|null The Process input + */ + public function getInput() + { + return $this->input; + } + + /** + * Sets the input. + * + * This content will be passed to the underlying process standard input. + * + * @param string|int|float|bool|resource|\Traversable|null $input The content + * + * @return $this + * + * @throws LogicException In case the process is running + */ + public function setInput($input) + { + if ($this->isRunning()) { + throw new LogicException('Input can not 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() + { + 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; + } + + /** + * Returns whether TTY is supported on the current operating system. + */ + public static function isTtySupported(): bool + { + static $isTtySupported; + + if (null === $isTtySupported) { + $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); + } + + return $isTtySupported; + } + + /** + * Returns whether PTY is supported on the current operating system. + * + * @return bool + */ + public static function isPtySupported() + { + 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(): array + { + if ($this->input instanceof \Iterator) { + $this->input->rewind(); + } + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback); + } else { + $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->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 + * + * @return \Closure A PHP closure + */ + protected function buildCallback(callable $callback = null) + { + if ($this->outputDisabled) { + return function ($type, $data) use ($callback): bool { + return 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) + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $running = $this->processInformation['running']; + + $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. + * + * @return bool + */ + protected function isSigchildEnabled() + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!\function_exists('phpinfo')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(INFO_GENERAL); + + return self::$sigchild = false !== strpos(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) + { + 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) + { + $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() + { + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackStatus = []; + $this->processInformation = null; + $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b'); + $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b'); + $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 + * + * @return bool True if the signal was sent successfully, false otherwise + * + * @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('Can not 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); + $varCount = 0; + $varCache = []; + $cmd = preg_replace_callback( + '/"(?:( + [^"%!^]*+ + (?: + (?: !LF! | "(?:\^[%!^])?+" ) + [^"%!^]*+ + )++ + ) | [^"]*+ )"/x', + function ($m) use (&$env, &$varCache, &$varCount, $uid) { + if (!isset($m[1])) { + return $m[0]; + } + if (isset($varCache[$m[0]])) { + return $varCache[$m[0]]; + } + if (false !== strpos($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) + { + 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) + { + 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 (false !== strpos($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) + { + 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 = []; + + foreach ($_SERVER as $k => $v) { + if (\is_string($v) && false !== $v = getenv($k)) { + $env[$k] = $v; + } + } + + foreach ($_ENV as $k => $v) { + if (\is_string($v)) { + $env[$k] = $v; + } + } + + return $env; + } +} diff --git a/lib/composer/symfony/process/ProcessUtils.php b/lib/composer/symfony/process/ProcessUtils.php new file mode 100644 index 0000000000000000000000000000000000000000..3be7e61a707bcae162b48823d2032fd720b6ca6e --- /dev/null +++ b/lib/composer/symfony/process/ProcessUtils.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 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 + * + * @return mixed The validated input + * + * @throws InvalidArgumentException In case the input is not valid + */ + public static function validateInput(string $caller, $input) + { + if (null !== $input) { + if (\is_resource($input)) { + return $input; + } + if (\is_string($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/lib/composer/symfony/process/README.md b/lib/composer/symfony/process/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b7ca5b4254942c813ac60733057a47b1be072ec5 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/process/composer.json b/lib/composer/symfony/process/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..1957e5681d64d5948f02e1bb8bbddf10d97f8402 --- /dev/null +++ b/lib/composer/symfony/process/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/process", + "type": "library", + "description": "Symfony Process Component", + "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": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Process\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + } +} diff --git a/lib/composer/symfony/service-contracts/.gitignore b/lib/composer/symfony/service-contracts/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c49a5d8df5c6548379f00c77fe572a7217bce218 --- /dev/null +++ b/lib/composer/symfony/service-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/lib/composer/symfony/service-contracts/Attribute/Required.php b/lib/composer/symfony/service-contracts/Attribute/Required.php new file mode 100644 index 0000000000000000000000000000000000000000..8ba6183f6e32b6ead0edce8b37b4785b910bfc12 --- /dev/null +++ b/lib/composer/symfony/service-contracts/Attribute/Required.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\Contracts\Service\Attribute; + +use 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/lib/composer/symfony/service-contracts/CHANGELOG.md b/lib/composer/symfony/service-contracts/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..e9847779ba985366b4bff79d369c09db31d620eb --- /dev/null +++ b/lib/composer/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/master/CHANGELOG.md diff --git a/lib/composer/symfony/service-contracts/LICENSE b/lib/composer/symfony/service-contracts/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..69d925ba7511e664a16b2af1fabce06b8767455d --- /dev/null +++ b/lib/composer/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2020 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/lib/composer/symfony/service-contracts/README.md b/lib/composer/symfony/service-contracts/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d033a439b9a96118d6ee9c90c58ea936bc432fb3 --- /dev/null +++ b/lib/composer/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/master/README.md for more information. diff --git a/lib/composer/symfony/service-contracts/ResetInterface.php b/lib/composer/symfony/service-contracts/ResetInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1af1075eeeca74a5f4ba29377019059c9195ce7e --- /dev/null +++ b/lib/composer/symfony/service-contracts/ResetInterface.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\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 +{ + public function reset(); +} diff --git a/lib/composer/symfony/service-contracts/ServiceLocatorTrait.php b/lib/composer/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..1737f50e997abb1550dd2816612c74750abe5805 --- /dev/null +++ b/lib/composer/symfony/service-contracts/ServiceLocatorTrait.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 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 $factories; + private $loading = []; + private $providedTypes; + + /** + * @param callable[] $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has($id) + { + return isset($this->factories[$id]); + } + + /** + * {@inheritdoc} + */ + public function get($id) + { + 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]); + } + } + + /** + * {@inheritdoc} + */ + public function getProvidedServices(): array + { + if (null === $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/lib/composer/symfony/service-contracts/ServiceProviderInterface.php b/lib/composer/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c60ad0bd4bf26818c5610436793a909171e04e65 --- /dev/null +++ b/lib/composer/symfony/service-contracts/ServiceProviderInterface.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\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 + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * 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 string[] The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/lib/composer/symfony/service-contracts/ServiceSubscriberInterface.php b/lib/composer/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8bb320f5b32d8f216a463fd1908df5b517c7993d --- /dev/null +++ b/lib/composer/symfony/service-contracts/ServiceSubscriberInterface.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\Contracts\Service; + +/** + * 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 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'] + * + * @return array The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(); +} diff --git a/lib/composer/symfony/service-contracts/ServiceSubscriberTrait.php b/lib/composer/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..82fb5ab36155986c00f169c2b2b15e5b1ecbf98f --- /dev/null +++ b/lib/composer/symfony/service-contracts/ServiceSubscriberTrait.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 Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * private method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + protected $container; + + public static function getSubscribedServices(): array + { + static $services; + + if (null !== $services) { + return $services; + } + + $services = \is_callable(['parent', __FUNCTION__]) ? parent::getSubscribedServices() : []; + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + continue; + } + + if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) { + $services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $type); + } + } + + return $services; + } + + /** + * @required + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + + if (\is_callable(['parent', __FUNCTION__])) { + return parent::setContainer($container); + } + + return null; + } +} diff --git a/lib/composer/symfony/service-contracts/Test/ServiceLocatorTest.php b/lib/composer/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5ed9149529655da125a2d5c4667677da9f33f356 --- /dev/null +++ b/lib/composer/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,92 @@ + + * + * 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\ContainerInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTest extends TestCase +{ + protected function getServiceLocator(array $factories) + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + function () { return 'dummy'; }, + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return '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() + { + if (!$this->getExpectedException()) { + $this->expectException('Psr\Container\NotFoundExceptionInterface'); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $this->expectException('Psr\Container\ContainerExceptionInterface'); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + $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'); }, + ]); + + $locator->get('foo'); + } +} diff --git a/lib/composer/symfony/service-contracts/composer.json b/lib/composer/symfony/service-contracts/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..47244fbb1034a0e59a1bb2d1888526e69b2e1985 --- /dev/null +++ b/lib/composer/symfony/service-contracts/composer.json @@ -0,0 +1,38 @@ +{ + "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": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/lib/composer/symfony/stopwatch/CHANGELOG.md b/lib/composer/symfony/stopwatch/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..14b7dc6dd5ef373d4dee9501b760670c6554a3ad --- /dev/null +++ b/lib/composer/symfony/stopwatch/CHANGELOG.md @@ -0,0 +1,19 @@ +CHANGELOG +========= + +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/lib/composer/symfony/stopwatch/LICENSE b/lib/composer/symfony/stopwatch/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9e936ec0448b8549e5edf08e5ac5f01491a8bfc8 --- /dev/null +++ b/lib/composer/symfony/stopwatch/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2020 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/lib/composer/symfony/stopwatch/README.md b/lib/composer/symfony/stopwatch/README.md new file mode 100644 index 0000000000000000000000000000000000000000..eb0ebb3fa8ab0a8fddf4104fac43ae5360970319 --- /dev/null +++ b/lib/composer/symfony/stopwatch/README.md @@ -0,0 +1,13 @@ +Stopwatch Component +=================== + +The Stopwatch component provides a way to profile code. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/stopwatch.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/lib/composer/symfony/stopwatch/Section.php b/lib/composer/symfony/stopwatch/Section.php new file mode 100644 index 0000000000000000000000000000000000000000..56ff76196e72ea7001900ae1dbd22863552be564 --- /dev/null +++ b/lib/composer/symfony/stopwatch/Section.php @@ -0,0 +1,185 @@ + + * + * 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 $events = []; + + /** + * @var float|null + */ + private $origin; + + /** + * @var bool + */ + private $morePrecision; + + /** + * @var string + */ + private $id; + + /** + * @var Section[] + */ + private $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. + * + * @return self|null The child section or null when none found + */ + public function get(string $id) + { + 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 + * + * @return self + */ + public function open(?string $id) + { + if (null === $id || null === $session = $this->get($id)) { + $session = $this->children[] = new self(microtime(true) * 1000, $this->morePrecision); + } + + return $session; + } + + /** + * @return string The identifier of the section + */ + public function getId() + { + return $this->id; + } + + /** + * Sets the session identifier. + * + * @return $this + */ + public function setId(string $id) + { + $this->id = $id; + + return $this; + } + + /** + * Starts an event. + * + * @return StopwatchEvent The event + */ + public function startEvent(string $name, ?string $category) + { + if (!isset($this->events[$name])) { + $this->events[$name] = new StopwatchEvent($this->origin ?: microtime(true) * 1000, $category, $this->morePrecision); + } + + return $this->events[$name]->start(); + } + + /** + * Checks if the event was started. + * + * @return bool + */ + public function isEventStarted(string $name) + { + return isset($this->events[$name]) && $this->events[$name]->isStarted(); + } + + /** + * Stops an event. + * + * @return StopwatchEvent The event + * + * @throws \LogicException When the event has not been started + */ + public function stopEvent(string $name) + { + 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. + * + * @return StopwatchEvent The event + * + * @throws \LogicException When the event has not been started + */ + public function lap(string $name) + { + return $this->stopEvent($name)->start(); + } + + /** + * Returns a specific event by name. + * + * @return StopwatchEvent The event + * + * @throws \LogicException When the event is not known + */ + public function getEvent(string $name) + { + 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[] An array of StopwatchEvent instances + */ + public function getEvents() + { + return $this->events; + } +} diff --git a/lib/composer/symfony/stopwatch/Stopwatch.php b/lib/composer/symfony/stopwatch/Stopwatch.php new file mode 100644 index 0000000000000000000000000000000000000000..8fea588040d2edb738d6ee2c8a93b0fe6619edd4 --- /dev/null +++ b/lib/composer/symfony/stopwatch/Stopwatch.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\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 +{ + /** + * @var bool + */ + private $morePrecision; + + /** + * @var Section[] + */ + private $sections; + + /** + * @var Section[] + */ + private $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() + { + 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) + { + $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 can not 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) + { + $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. + * + * @return StopwatchEvent + */ + public function start(string $name, string $category = null) + { + return end($this->activeSections)->startEvent($name, $category); + } + + /** + * Checks if the event was started. + * + * @return bool + */ + public function isStarted(string $name) + { + return end($this->activeSections)->isEventStarted($name); + } + + /** + * Stops an event. + * + * @return StopwatchEvent + */ + public function stop(string $name) + { + return end($this->activeSections)->stopEvent($name); + } + + /** + * Stops then restarts an event. + * + * @return StopwatchEvent + */ + public function lap(string $name) + { + return end($this->activeSections)->stopEvent($name)->start(); + } + + /** + * Returns a specific event by name. + * + * @return StopwatchEvent + */ + public function getEvent(string $name) + { + return end($this->activeSections)->getEvent($name); + } + + /** + * Gets all events for a given section. + * + * @return StopwatchEvent[] + */ + public function getSectionEvents(string $id) + { + return isset($this->sections[$id]) ? $this->sections[$id]->getEvents() : []; + } + + /** + * Resets the stopwatch to its original state. + */ + public function reset() + { + $this->sections = $this->activeSections = ['__root__' => new Section(null, $this->morePrecision)]; + } +} diff --git a/lib/composer/symfony/stopwatch/StopwatchEvent.php b/lib/composer/symfony/stopwatch/StopwatchEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..241572726cbf68afc7a4bdad41ca8040b547f9dd --- /dev/null +++ b/lib/composer/symfony/stopwatch/StopwatchEvent.php @@ -0,0 +1,246 @@ + + * + * 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 $periods = []; + + /** + * @var float + */ + private $origin; + + /** + * @var string + */ + private $category; + + /** + * @var bool + */ + private $morePrecision; + + /** + * @var float[] + */ + private $started = []; + + /** + * @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 + * + * @throws \InvalidArgumentException When the raw time is not valid + */ + public function __construct(float $origin, string $category = null, bool $morePrecision = false) + { + $this->origin = $this->formatTime($origin); + $this->category = \is_string($category) ? $category : 'default'; + $this->morePrecision = $morePrecision; + } + + /** + * Gets the category. + * + * @return string The category + */ + public function getCategory() + { + return $this->category; + } + + /** + * Gets the origin. + * + * @return float The origin in milliseconds + */ + public function getOrigin() + { + return $this->origin; + } + + /** + * Starts a new event period. + * + * @return $this + */ + public function start() + { + $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() + { + 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. + * + * @return bool + */ + public function isStarted() + { + return !empty($this->started); + } + + /** + * Stops the current period and then starts a new one. + * + * @return $this + */ + public function lap() + { + return $this->stop()->start(); + } + + /** + * Stops all non already stopped periods. + */ + public function ensureStopped() + { + while (\count($this->started)) { + $this->stop(); + } + } + + /** + * Gets all event periods. + * + * @return StopwatchPeriod[] An array of StopwatchPeriod instances + */ + public function getPeriods() + { + return $this->periods; + } + + /** + * Gets the relative time of the start of the first period. + * + * @return int|float The time (in milliseconds) + */ + public function getStartTime() + { + 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. + * + * @return int|float The time (in milliseconds) + */ + public function getEndTime() + { + $count = \count($this->periods); + + return $count ? $this->periods[$count - 1]->getEndTime() : 0; + } + + /** + * Gets the duration of the events (including all periods). + * + * @return int|float The duration (in milliseconds) + */ + public function getDuration() + { + $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. + * + * @return int The memory usage (in bytes) + */ + public function getMemory() + { + $memory = 0; + foreach ($this->periods as $period) { + if ($period->getMemory() > $memory) { + $memory = $period->getMemory(); + } + } + + return $memory; + } + + /** + * Return the current time relative to origin. + * + * @return float Time in ms + */ + protected function getNow() + { + 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); + } + + /** + * @return string + */ + public function __toString() + { + return sprintf('%s: %.2F MiB - %d ms', $this->getCategory(), $this->getMemory() / 1024 / 1024, $this->getDuration()); + } +} diff --git a/lib/composer/symfony/stopwatch/StopwatchPeriod.php b/lib/composer/symfony/stopwatch/StopwatchPeriod.php new file mode 100644 index 0000000000000000000000000000000000000000..213cf59e12e89a33e975d565d9a6d7afccc75e6a --- /dev/null +++ b/lib/composer/symfony/stopwatch/StopwatchPeriod.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\Stopwatch; + +/** + * Represents an Period for an Event. + * + * @author Fabien Potencier + */ +class StopwatchPeriod +{ + private $start; + private $end; + private $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($start, $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. + * + * @return int|float The time (in milliseconds) + */ + public function getStartTime() + { + return $this->start; + } + + /** + * Gets the relative time of the end of the period. + * + * @return int|float The time (in milliseconds) + */ + public function getEndTime() + { + return $this->end; + } + + /** + * Gets the time spent in this period. + * + * @return int|float The period duration (in milliseconds) + */ + public function getDuration() + { + return $this->end - $this->start; + } + + /** + * Gets the memory usage. + * + * @return int The memory usage (in bytes) + */ + public function getMemory() + { + return $this->memory; + } +} diff --git a/lib/composer/symfony/stopwatch/composer.json b/lib/composer/symfony/stopwatch/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..bd4989dd8e01683629f4490135c475b0ebc9872a --- /dev/null +++ b/lib/composer/symfony/stopwatch/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/stopwatch", + "type": "library", + "description": "Symfony Stopwatch Component", + "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": ">=7.2.5", + "symfony/service-contracts": "^1.0|^2" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Stopwatch\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + } +} diff --git a/lib/composer/symfony/string/.gitattributes b/lib/composer/symfony/string/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..4a7ef98aba420597e6cc22a1edc7280f2b040de7 --- /dev/null +++ b/lib/composer/symfony/string/.gitattributes @@ -0,0 +1,5 @@ +/Resources/bin/update-data.php export-ignore +/Resources/WcswidthDataGenerator.php export-ignore +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitignore export-ignore diff --git a/lib/composer/symfony/string/AbstractString.php b/lib/composer/symfony/string/AbstractString.php new file mode 100644 index 0000000000000000000000000000000000000000..c87f1506fb6f838cdc6c716a60f1cfdc13f8f839 --- /dev/null +++ b/lib/composer/symfony/string/AbstractString.php @@ -0,0 +1,731 @@ + + * + * 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 = ''; + protected $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 = $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 + * + * @return static + */ + public function after($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $str->string = ''; + $i = \PHP_INT_MAX; + + foreach ((array) $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 + * + * @return static + */ + public function afterLast($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $str->string = ''; + $i = null; + + foreach ((array) $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); + } + + /** + * @return static + */ + abstract public function append(string ...$suffix): self; + + /** + * @param string|string[] $needle + * + * @return static + */ + public function before($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $str->string = ''; + $i = \PHP_INT_MAX; + + foreach ((array) $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 + * + * @return static + */ + public function beforeLast($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $str->string = ''; + $i = null; + + foreach ((array) $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)); + } + + /** + * @return static + */ + abstract public function camel(): self; + + /** + * @return static[] + */ + abstract public function chunk(int $length = 1): array; + + /** + * @return static + */ + public function collapseWhitespace(): self + { + $str = clone $this; + $str->string = trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $str->string)); + + return $str; + } + + /** + * @param string|string[] $needle + */ + public function containsAny($needle): bool + { + return null !== $this->indexOf($needle); + } + + /** + * @param string|string[] $suffix + */ + public function endsWith($suffix): bool + { + if (!\is_array($suffix) && !$suffix instanceof \Traversable) { + 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; + } + + /** + * @return static + */ + public function ensureEnd(string $suffix): self + { + if (!$this->endsWith($suffix)) { + return $this->append($suffix); + } + + $suffix = preg_quote($suffix); + $regex = '{('.$suffix.')(?:'.$suffix.')++$}D'; + + return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1'); + } + + /** + * @return static + */ + public function ensureStart(string $prefix): self + { + $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): bool + { + if (!\is_array($string) && !$string instanceof \Traversable) { + 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; + } + + /** + * @return static + */ + abstract public function folded(): self; + + /** + * @return static + */ + public function ignoreCase(): self + { + $str = clone $this; + $str->ignoreCase = true; + + return $str; + } + + /** + * @param string|string[] $needle + */ + public function indexOf($needle, int $offset = 0): ?int + { + if (!\is_array($needle) && !$needle instanceof \Traversable) { + 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($needle, int $offset = 0): ?int + { + if (!\is_array($needle) && !$needle instanceof \Traversable) { + 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; + } + + /** + * @return static + */ + abstract public function join(array $strings, string $lastGlue = null): self; + + public function jsonSerialize(): string + { + return $this->string; + } + + abstract public function length(): int; + + /** + * @return static + */ + abstract public function lower(): self; + + /** + * 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; + + /** + * @return static + */ + abstract public function padBoth(int $length, string $padStr = ' '): self; + + /** + * @return static + */ + abstract public function padEnd(int $length, string $padStr = ' '): self; + + /** + * @return static + */ + abstract public function padStart(int $length, string $padStr = ' '): self; + + /** + * @return static + */ + abstract public function prepend(string ...$prefix): self; + + /** + * @return static + */ + public function repeat(int $multiplier): self + { + 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; + } + + /** + * @return static + */ + abstract public function replace(string $from, string $to): self; + + /** + * @param string|callable $to + * + * @return static + */ + abstract public function replaceMatches(string $fromRegexp, $to): self; + + /** + * @return static + */ + abstract public function reverse(): self; + + /** + * @return static + */ + abstract public function slice(int $start = 0, int $length = null): self; + + /** + * @return static + */ + abstract public function snake(): self; + + /** + * @return static + */ + abstract public function splice(string $replacement, int $start = 0, int $length = null): self; + + /** + * @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 function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Splitting failed with '.$k.'.'); + } + } + + throw new RuntimeException('Splitting failed with unknown error code.'); + } + } 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($prefix): bool + { + if (!\is_array($prefix) && !$prefix instanceof \Traversable) { + 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; + } + + /** + * @return static + */ + abstract public function title(bool $allWords = false): self; + + 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; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + try { + $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8'); + } catch (InvalidArgumentException $e) { + if (!\function_exists('iconv')) { + throw $e; + } + + $b->string = iconv('UTF-8', $toEncoding, $this->string); + } + } finally { + restore_error_handler(); + } + + 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); + } + + /** + * @return static + */ + abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + + /** + * @return static + */ + abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + + /** + * @return static + */ + abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + + /** + * @return static + */ + public function truncate(int $length, string $ellipsis = '', bool $cut = true): self + { + $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; + } + + /** + * @return static + */ + abstract public function upper(): self; + + /** + * Returns the printable length on a terminal. + */ + abstract public function width(bool $ignoreAnsiDecoration = true): int; + + /** + * @return static + */ + public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): self + { + $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/lib/composer/symfony/string/AbstractUnicodeString.php b/lib/composer/symfony/string/AbstractUnicodeString.php new file mode 100644 index 0000000000000000000000000000000000000000..0725e1e515b8e82527ed10a1bc745ab8da0a2efc --- /dev/null +++ b/lib/composer/symfony/string/AbstractUnicodeString.php @@ -0,0 +1,578 @@ + + * + * 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', 'i̇', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ']; + + // the subset of upper case mappings that map one code point to many code points + private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'և', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ', 'ʼn', 'ΐ', 'ΰ', 'ǰ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾶ', 'ῆ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῶ']; + private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ԵՒ', 'ՄՆ', 'ՄԵ', 'ՄԻ', 'ՎՆ', 'ՄԽ', 'ʼN', 'Ϊ́', 'Ϋ́', 'J̌', 'H̱', 'T̈', 'W̊', 'Y̊', 'Aʾ', 'Υ̓', 'Υ̓̀', 'Υ̓́', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'Ϊ́', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'Ϋ́', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂']; + + // 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 $transliterators = []; + + /** + * @return static + */ + public static function fromCodePoints(int ...$codes): self + { + $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[] $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) { + 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] ?? self::$transliterators[$rule] = \Transliterator::create($rule)) { + if ('any-latin/bgn' === $rule) { + $rule = 'any-latin'; + $transliterator = self::$transliterators[$rule] ?? 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(): parent + { + $str = clone $this; + $str->string = str_replace(' ', '', preg_replace_callback('/\b./u', static function ($m) use (&$i) { + 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): parent + { + $str = clone $this; + + if (!$compat || \PHP_VERSION_ID < 70300 || !\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): parent + { + $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(): parent + { + $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 function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + return $matches; + } + + /** + * @return static + */ + public function normalize(int $form = self::NFC): self + { + 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 = ' '): parent + { + 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 = ' '): parent + { + 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 = ' '): parent + { + 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, $to): parent + { + if ($this->ignoreCase) { + $fromRegexp .= 'i'; + } + + if (\is_array($to) || $to instanceof \Closure) { + if (!\is_callable($to)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); + } + + $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 function ($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 && '_ERROR' === substr($k, -6)) { + 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(): parent + { + $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(): parent + { + $str = $this->camel()->title(); + $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): parent + { + $str = clone $this; + + $limit = $allWords ? -1 : 1; + + $str->string = preg_replace_callback('/\b./u', static function (array $m): string { + return 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}"): parent + { + 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}"): parent + { + 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 trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent + { + 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 upper(): parent + { + $str = clone $this; + $str->string = mb_strtoupper($str->string, 'UTF-8'); + + if (\PHP_VERSION_ID < 70300) { + $str->string = str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string); + } + + return $str; + } + + public function width(bool $ignoreAnsiDecoration = true): int + { + $width = 0; + $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string); + + if (false !== strpos($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]*+ [0x40-\x7E] + | [P\]X^_] .*? \x1B\\\\ + | [\x41-\x7E] + )|[\p{Cc}\x7F]++)/xu', '', $s); + } + + // Non printable characters have been dropped, so wcswidth cannot logically return -1. + $width += $this->wcswidth($s); + } + + return $width; + } + + /** + * @return static + */ + private function pad(int $len, self $pad, int $type): parent + { + $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, $freeLen / $padLen).($len ? $pad->slice(0, $len) : '')); + + case \STR_PAD_LEFT: + return $this->prepend(str_repeat($pad->string, $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, $rightLen / $padLen).($len ? $pad->slice(0, $len) : '')); + + $leftLen = floor($freeLen); + $len = $leftLen % $padLen; + + return $str->prepend(str_repeat($pad->string, $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; + } + + static $tableZero; + if (null === $tableZero) { + $tableZero = require __DIR__.'/Resources/data/wcswidth_table_zero.php'; + } + + if ($codePoint >= $tableZero[0][0] && $codePoint <= $tableZero[$ubound = \count($tableZero) - 1][1]) { + $lbound = 0; + while ($ubound >= $lbound) { + $mid = floor(($lbound + $ubound) / 2); + + if ($codePoint > $tableZero[$mid][1]) { + $lbound = $mid + 1; + } elseif ($codePoint < $tableZero[$mid][0]) { + $ubound = $mid - 1; + } else { + continue 2; + } + } + } + + static $tableWide; + if (null === $tableWide) { + $tableWide = require __DIR__.'/Resources/data/wcswidth_table_wide.php'; + } + + if ($codePoint >= $tableWide[0][0] && $codePoint <= $tableWide[$ubound = \count($tableWide) - 1][1]) { + $lbound = 0; + while ($ubound >= $lbound) { + $mid = floor(($lbound + $ubound) / 2); + + if ($codePoint > $tableWide[$mid][1]) { + $lbound = $mid + 1; + } elseif ($codePoint < $tableWide[$mid][0]) { + $ubound = $mid - 1; + } else { + $width += 2; + + continue 2; + } + } + } + + ++$width; + } + + return $width; + } +} diff --git a/lib/composer/symfony/string/ByteString.php b/lib/composer/symfony/string/ByteString.php new file mode 100644 index 0000000000000000000000000000000000000000..a7bcab5740a9cdbfeee38e4535bbe4c3e0acf845 --- /dev/null +++ b/lib/composer/symfony/string/ByteString.php @@ -0,0 +1,506 @@ + + * + * 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 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + 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 = $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): parent + { + $str = clone $this; + $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); + + return $str; + } + + public function camel(): parent + { + $str = clone $this; + $str->string = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->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 []; + } + + $str = clone $this; + $chunks = []; + + foreach (str_split($this->string, $length) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function endsWith($suffix): bool + { + if ($suffix instanceof parent) { + $suffix = $suffix->string; + } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + return parent::endsWith($suffix); + } else { + $suffix = (string) $suffix; + } + + return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase); + } + + public function equalsTo($string): bool + { + if ($string instanceof parent) { + $string = $string->string; + } elseif (\is_array($string) || $string instanceof \Traversable) { + return parent::equalsTo($string); + } else { + $string = (string) $string; + } + + if ('' !== $string && $this->ignoreCase) { + return 0 === strcasecmp($string, $this->string); + } + + return $string === $this->string; + } + + public function folded(): parent + { + $str = clone $this; + $str->string = strtolower($str->string); + + return $str; + } + + public function indexOf($needle, int $offset = 0): ?int + { + if ($needle instanceof parent) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOf($needle, $offset); + } else { + $needle = (string) $needle; + } + + 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($needle, int $offset = 0): ?int + { + if ($needle instanceof parent) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOfLast($needle, $offset); + } else { + $needle = (string) $needle; + } + + 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): parent + { + $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(): parent + { + $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 function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + return $matches; + } + + public function padBoth(int $length, string $padStr = ' '): parent + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH); + + return $str; + } + + public function padEnd(int $length, string $padStr = ' '): parent + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT); + + return $str; + } + + public function padStart(int $length, string $padStr = ' '): parent + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT); + + return $str; + } + + public function prepend(string ...$prefix): parent + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string; + + return $str; + } + + public function replace(string $from, string $to): parent + { + $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, $to): parent + { + if ($this->ignoreCase) { + $fromRegexp .= 'i'; + } + + if (\is_array($to)) { + if (!\is_callable($to)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); + } + + $replace = 'preg_replace_callback'; + } else { + $replace = $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace'; + } + + set_error_handler(static function ($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 && '_ERROR' === substr($k, -6)) { + 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(): parent + { + $str = clone $this; + $str->string = strrev($str->string); + + return $str; + } + + public function slice(int $start = 0, int $length = null): parent + { + $str = clone $this; + $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function snake(): parent + { + $str = $this->camel()->title(); + $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): parent + { + $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 = $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($prefix): bool + { + if ($prefix instanceof parent) { + $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): parent + { + $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 function ($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"): parent + { + $str = clone $this; + $str->string = trim($str->string, $chars); + + return $str; + } + + public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): parent + { + $str = clone $this; + $str->string = rtrim($str->string, $chars); + + return $str; + } + + public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): parent + { + $str = clone $this; + $str->string = ltrim($str->string, $chars); + + return $str; + } + + public function upper(): parent + { + $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/lib/composer/symfony/string/CHANGELOG.md b/lib/composer/symfony/string/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..1251fe552eb4777a73ceebc0dbc67e2351169ded --- /dev/null +++ b/lib/composer/symfony/string/CHANGELOG.md @@ -0,0 +1,20 @@ +CHANGELOG +========= + +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/lib/composer/symfony/string/CodePointString.php b/lib/composer/symfony/string/CodePointString.php new file mode 100644 index 0000000000000000000000000000000000000000..8ab9209413b50bbebd7b281a6395d2d97932a49a --- /dev/null +++ b/lib/composer/symfony/string/CodePointString.php @@ -0,0 +1,270 @@ + + * + * 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): AbstractString + { + $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($suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + return parent::endsWith($suffix); + } else { + $suffix = (string) $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): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (\is_array($string) || $string instanceof \Traversable) { + return parent::equalsTo($string); + } else { + $string = (string) $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($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOf($needle, $offset); + } else { + $needle = (string) $needle; + } + + 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($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOfLast($needle, $offset); + } else { + $needle = (string) $needle; + } + + 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): AbstractString + { + $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): AbstractString + { + $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): AbstractString + { + $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): AbstractString + { + 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 = $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($prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { + return parent::startsWith($prefix); + } else { + $prefix = (string) $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/lib/composer/symfony/string/Exception/ExceptionInterface.php b/lib/composer/symfony/string/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..361978656bfef9fbc8b985ae7cc8cc1ba68dd821 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/string/Exception/InvalidArgumentException.php b/lib/composer/symfony/string/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..6aa586bcf930f3ef97647204fe54a07ddb175280 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/string/Exception/RuntimeException.php b/lib/composer/symfony/string/Exception/RuntimeException.php new file mode 100644 index 0000000000000000000000000000000000000000..77cb091f9ca80d061baca39f35043603306c290f --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/string/Inflector/EnglishInflector.php b/lib/composer/symfony/string/Inflector/EnglishInflector.php new file mode 100644 index 0000000000000000000000000000000000000000..4cd05434d177281e79e55c6e863defbc1db97cb7 --- /dev/null +++ b/lib/composer/symfony/string/Inflector/EnglishInflector.php @@ -0,0 +1,477 @@ + + * + * 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 static $pluralMap = [ + // First entry: plural suffix, reversed + // Second entry: length of plural suffix + // Third entry: Whether the suffix may succeed a vocal + // 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']], + + // selfies (selfie) + ['seifles', 7, true, true, 'selfie'], + + // movies (movie) + ['seivom', 6, true, true, 'movie'], + + // 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'], + + // 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'], + + // roses (rose), garages (garage), cassettes (cassette), + // waltzes (waltz), heroes (hero), bushes (bush), arches (arch), + // shoes (shoe) + ['se', 2, true, true, ['', 'e']], + + // 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 static $singularMap = [ + // First entry: singular suffix, reversed + // Second entry: length of singular suffix + // Third entry: Whether the suffix may succeed a vocal + // Fourth entry: Whether the suffix may succeed a consonant + // Fifth entry: plural suffix, normal + + // 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'], + + // seasons (season), treasons (treason), poisons (poison), lessons (lesson) + ['nos', 3, true, true, 'sons'], + + // 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'], + + // 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 static $uninflected = [ + 'atad', + 'reed', + 'kcabdeef', + 'hsif', + 'ofni', + 'esoom', + 'seires', + 'peehs', + 'seiceps', + ]; + + /** + * {@inheritdoc} + */ + 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::$pluralMap 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) { + $nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]); + + if (!$map[2] && $nextIsVocal) { + // suffix may not succeed a vocal but next char is one + break; + } + + if (!$map[3] && !$nextIsVocal) { + // 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]; + } + + /** + * {@inheritdoc} + */ + 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::$singularMap 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) { + $nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]); + + if (!$map[2] && $nextIsVocal) { + // suffix may not succeed a vocal but next char is one + break; + } + + if (!$map[3] && !$nextIsVocal) { + // 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/lib/composer/symfony/string/Inflector/InflectorInterface.php b/lib/composer/symfony/string/Inflector/InflectorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..ad78070b05cc9c38db05478a9e9483e0ad17c861 --- /dev/null +++ b/lib/composer/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[] An array of possible singular forms + */ + 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[] An array of possible plural forms + */ + public function pluralize(string $singular): array; +} diff --git a/lib/composer/symfony/string/LICENSE b/lib/composer/symfony/string/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4bf0fef4ff3b0ca6c8d525c37b17e359847c0d53 --- /dev/null +++ b/lib/composer/symfony/string/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019-2020 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/lib/composer/symfony/string/LazyString.php b/lib/composer/symfony/string/LazyString.php new file mode 100644 index 0000000000000000000000000000000000000000..b3801db7711ff174f9585f25a0abdbfd71f3cd87 --- /dev/null +++ b/lib/composer/symfony/string/LazyString.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\String; + +/** + * A string whose value is computed lazily by a callback. + * + * @author Nicolas Grekas + */ +class LazyString implements \Stringable, \JsonSerializable +{ + private $value; + + /** + * @param callable|array $callback A callable or a [Closure, method] lazy-callable + * + * @return static + */ + public static function fromCallable($callback, ...$arguments): self + { + if (!\is_callable($callback) && !(\is_array($callback) && isset($callback[0]) && $callback[0] 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__, get_debug_type($callback))); + } + + $lazyString = new static(); + $lazyString->value = static function () use (&$callback, &$arguments, &$value): string { + if (null !== $arguments) { + if (!\is_callable($callback)) { + $callback[0] = $callback[0](); + $callback[1] = $callback[1] ?? '__invoke'; + } + $value = $callback(...$arguments); + $callback = self::getPrettyName($callback); + $arguments = null; + } + + return $value ?? ''; + }; + + return $lazyString; + } + + /** + * @param string|int|float|bool|\Stringable $value + * + * @return static + */ + public static function fromStringable($value): self + { + if (!self::isStringable($value)) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a scalar or a stringable object, "%s" given.', __METHOD__, get_debug_type($value))); + } + + 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($value): bool + { + return \is_string($value) || $value instanceof self || (\is_object($value) ? method_exists($value, '__toString') : is_scalar($value)); + } + + /** + * Casts scalars and stringable objects to strings. + * + * @param object|string|int|float|bool $value + * + * @throws \TypeError When the provided value is not stringable + */ + final public static function resolve($value): string + { + return $value; + } + + /** + * @return string + */ + public function __toString() + { + if (\is_string($this->value)) { + return $this->value; + } + + try { + return $this->value = ($this->value)(); + } catch (\Throwable $e) { + if (\TypeError::class === \get_class($e) && __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)); + } + + if (\PHP_VERSION_ID < 70400) { + // leverage the ErrorHandler component with graceful fallback when it's not available + return trigger_error($e, \E_USER_ERROR); + } + + 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 (false !== strpos($r->name, '{closure}') || !$class = $r->getClosureScopeClass()) { + return $r->name; + } + + $class = $class->name; + $method = $r->name; + } else { + $class = get_debug_type($callback); + $method = '__invoke'; + } + + return $class.'::'.$method; + } +} diff --git a/lib/composer/symfony/string/README.md b/lib/composer/symfony/string/README.md new file mode 100644 index 0000000000000000000000000000000000000000..23ad86ad8fe44e695df46f353f10bb991843747c --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/string/Resources/data/wcswidth_table_wide.php b/lib/composer/symfony/string/Resources/data/wcswidth_table_wide.php new file mode 100644 index 0000000000000000000000000000000000000000..e3a41cdf28fc3a4d4b01d432c351fe3623785354 --- /dev/null +++ b/lib/composer/symfony/string/Resources/data/wcswidth_table_wide.php @@ -0,0 +1,1119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +function u(string $string = ''): UnicodeString +{ + return new UnicodeString($string); +} + +function b(string $string = ''): ByteString +{ + return new ByteString($string); +} + +/** + * @return UnicodeString|ByteString + */ +function s(string $string): AbstractString +{ + return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string); +} diff --git a/lib/composer/symfony/string/Slugger/AsciiSlugger.php b/lib/composer/symfony/string/Slugger/AsciiSlugger.php new file mode 100644 index 0000000000000000000000000000000000000000..3352b04995f9f517e1b2df2644a1195e6730a974 --- /dev/null +++ b/lib/composer/symfony/string/Slugger/AsciiSlugger.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\Slugger; + +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 $defaultLocale; + private $symbolsMap = [ + 'en' => ['@' => 'at', '&' => 'and'], + ]; + + /** + * Cache of transliterators per locale. + * + * @var \Transliterator[] + */ + private $transliterators = []; + + public function __construct(string $defaultLocale = null, array $symbolsMap = null) + { + $this->defaultLocale = $defaultLocale; + $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; + } + + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->defaultLocale = $locale; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->defaultLocale; + } + + /** + * {@inheritdoc} + */ + public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString + { + $locale = $locale ?? $this->defaultLocale; + + $transliterator = []; + if ('de' === $locale || 0 === strpos($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); + } + + $unicodeString = (new UnicodeString($string))->ascii($transliterator); + + if (isset($this->symbolsMap[$locale])) { + foreach ($this->symbolsMap[$locale] 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 (false === $str = strrchr($locale, '_')) { + return $this->transliterators[$locale] = null; + } + + // Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales + $parent = substr($locale, 0, -\strlen($str)); + + 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; + } +} diff --git a/lib/composer/symfony/string/Slugger/SluggerInterface.php b/lib/composer/symfony/string/Slugger/SluggerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c679ed9331040b7619bb6854ecf07b6dadba35a4 --- /dev/null +++ b/lib/composer/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/lib/composer/symfony/string/UnicodeString.php b/lib/composer/symfony/string/UnicodeString.php new file mode 100644 index 0000000000000000000000000000000000000000..81bf8ea56b6058a205305d502496edfb35ba843b --- /dev/null +++ b/lib/composer/symfony/string/UnicodeString.php @@ -0,0 +1,369 @@ + + * + * 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 = '') + { + $this->string = normalizer_is_normalized($string) ? $string : normalizer_normalize($string); + + if (false === $this->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + } + + public function append(string ...$suffix): AbstractString + { + $str = clone $this; + $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix)); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $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 .= '\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($suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + return parent::endsWith($suffix); + } else { + $suffix = (string) $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): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (\is_array($string) || $string instanceof \Traversable) { + return parent::equalsTo($string); + } else { + $string = (string) $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($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOf($needle, $offset); + } else { + $needle = (string) $needle; + } + + $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 $e) { + return null; + } + + return false === $i ? null : $i; + } + + public function indexOfLast($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOfLast($needle, $offset); + } else { + $needle = (string) $needle; + } + + $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): AbstractString + { + $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); + } + + /** + * @return static + */ + public function normalize(int $form = self::NFC): parent + { + $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): AbstractString + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $str->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function replace(string $from, string $to): AbstractString + { + $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; + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $str->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + } + + return $str; + } + + public function replaceMatches(string $fromRegexp, $to): AbstractString + { + $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): AbstractString + { + $str = clone $this; + try { + $str->string = (string) grapheme_substr($this->string, $start, $length ?? \PHP_INT_MAX); + } catch (\ValueError $e) { + $str->string = ''; + } + + return $str; + } + + public function splice(string $replacement, int $start = 0, int $length = null): AbstractString + { + $str = clone $this; + $start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0; + $length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? \PHP_INT_MAX)) : $length; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $str->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function split(string $delimiter, int $limit = null, int $flags = null): array + { + if (1 > $limit = $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); + } + + 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($prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { + return parent::startsWith($prefix); + } else { + $prefix = (string) $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() + { + 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/lib/composer/symfony/string/composer.json b/lib/composer/symfony/string/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..75f39c6284ee98615dad5035dc0581144a7f793f --- /dev/null +++ b/lib/composer/symfony/string/composer.json @@ -0,0 +1,45 @@ +{ + "name": "symfony/string", + "type": "library", + "description": "Symfony String component", + "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": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\String\\": "" }, + "files": [ "Resources/functions.php" ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + } +} diff --git a/lib/composer/vimeo/psalm/.appveyor.yml b/lib/composer/vimeo/psalm/.appveyor.yml new file mode 100644 index 0000000000000000000000000000000000000000..e25b457e47ab8b6d9fa8dc328020ea6ecd993cd3 --- /dev/null +++ b/lib/composer/vimeo/psalm/.appveyor.yml @@ -0,0 +1,63 @@ +build: false +platform: + - x64 + +clone_folder: c:\projects\php-project-workspace + +## Set up environment variables +init: + - SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH% + - SET COMPOSER_NO_INTERACTION=1 + - SET PHP=1 # This var is connected to PHP install cache + - SET ANSICON=121x90 (121x90) + +environment: + SSL_CERT_FILE: "C:\\tools\\php\\cacert.pem" + matrix: + - php_ver_target: 7.4 + DEPS: 'high' + +cache: + - '%LOCALAPPDATA%\Composer\files' + - '%LOCALAPPDATA%\Composer\vcs' + # Cache chocolatey packages + - C:\ProgramData\chocolatey\bin + - C:\ProgramData\chocolatey\lib + # Cache php install + - c:\tools\php + +## Install PHP and composer, and run the appropriate composer command +install: + - IF EXIST c:\tools\php (SET PHP=0) # Checks for the PHP install being cached + - ps: appveyor-retry cinst --no-progress --params '""/InstallDir:C:\tools\php""' --ignore-checksums -y php --version ((choco search php --exact --all-versions -r | select-string -pattern $env:php_ver_target | sort { [version]($_ -split '\|' | select -last 1) } -Descending | Select-Object -first 1) -replace '[php|]','') + - appveyor DownloadFile https://curl.haxx.se/ca/cacert.pem -FileName C:\tools\php\cacert.pem + - cd c:\tools\php + - IF %PHP%==1 copy php.ini-production php.ini /Y + - IF %PHP%==1 echo date.timezone="UTC" >> php.ini + - IF %PHP%==1 echo extension_dir=ext >> php.ini + - IF %PHP%==1 echo extension=php_curl.dll >> php.ini + - IF NOT EXIST php-installed.txt echo curl.cainfo="C:/tools/php/cacert.pem" >> php.ini + - IF NOT EXIST php-installed.txt echo openssl.cafile="C:/tools/php/cacert.pem" >> php.ini + - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini + - IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini + - IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini + - IF %PHP%==1 echo zend.assertions=1 >> php.ini + - IF %PHP%==1 echo assert.exception=On >> php.ini + - IF %PHP%==1 echo error_reporting=E_ALL >> php.ini + - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat + - IF NOT EXIST composer.phar appveyor-retry appveyor DownloadFile https://getcomposer.org/composer-stable.phar -FileName composer.phar + - cd c:\projects\php-project-workspace + - php -r "phpinfo(INFO_GENERAL);" + - if NOT DEFINED APPVEYOR_REPO_TAG_NAME (set COMPOSER_ROOT_VERSION=dev-master) + - if %DEPS%==low appveyor-retry composer update --no-interaction --no-progress --profile --prefer-lowest --prefer-stable + - if %DEPS%==high appveyor-retry composer update --no-interaction --no-progress --profile + +## Run the actual test +test_script: + - cd c:\projects\php-project-workspace + - vendor/bin/paratest --log-junit build/phpunit/phpunit.xml + - php ./psalm --shepherd + +on_finish: + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\build\phpunit\phpunit.xml)) diff --git a/lib/composer/vimeo/psalm/.circleci/config.yml b/lib/composer/vimeo/psalm/.circleci/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..c2547912ed72506771393661a4254c4f54ba8069 --- /dev/null +++ b/lib/composer/vimeo/psalm/.circleci/config.yml @@ -0,0 +1,126 @@ +# Use the latest 2.1 version of CircleCI pipeline processing engine, see https://circleci.com/docs/2.0/configuration-reference/ +version: 2.1 +executors: + php-73: + docker: + - image: thecodingmachine/php:7.3-v2-cli + php-74: + docker: + - image: thecodingmachine/php:7.4-v3-cli +jobs: + install-and-self-analyse: + executor: php-73 + steps: + - checkout + - run: date "+%F" > /tmp/cachekey; cat composer.json >> /tmp/cachekey + - restore_cache: + keys: + - composer-v3 + - restore_cache: + keys: + - psalm-cache-{{ checksum "/tmp/cachekey" }} # speeds up diff run + - run: composer update + + - save_cache: + key: composer-v3 + paths: + - /home/docker/.composer/cache/files + - /home/docker/.composer/cache/vcs + + - run: + name: Static analysis + command: php -dextension=pcntl.so ./psalm --threads=10 + + - save_cache: + key: psalm-cache-{{ checksum "/tmp/cachekey" }} + paths: + - /tmp/psalm + + - persist_to_workspace: + root: /home/docker/project/ + paths: + - . + "Code Style Analysis": + executor: php-73 + steps: + - attach_workspace: + at: /home/docker/project/ + - run: + name: Code Style Analysis with PHPCS + command: vendor/bin/phpcs + test: + executor: php-73 + steps: + - attach_workspace: + at: /home/docker/project/ + - run: + name: PHPUnit test + command: php vendor/bin/paratest --runner=WrapperRunner --log-junit build/phpunit/phpunit.xml + - store_test_results: + path: build/ + - store_artifacts: + path: build/phpunit + - persist_to_workspace: + root: /home/docker/project/ + paths: + - . + phar-build: + executor: php-73 + steps: + - attach_workspace: + at: /home/docker/project/ + - run: + name: Build Phar file + command: bin/build-phar.sh + - run: + name: Smoke test Phar file + command: build/psalm.phar --version + - store_artifacts: + path: build/psalm.phar + - run: + name: Display link to phar file + command: | + echo "Phar build available at:" + echo https://circleci.com/api/v1.1/project/github/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BUILD_NUM}/artifacts/0/home/docker/project/build/psalm.phar + + - persist_to_workspace: + root: /home/docker/project/ + paths: + - build/psalm.phar + test-with-real-projects: + executor: php-74 + steps: + - checkout # used here just for the side effect of loading the github public ssh key so we can clone other stuff + - attach_workspace: + at: /home/docker/project/ + - run: + name: Analyse PHPUnit + command: bin/test-with-real-projects.sh phpunit + - run: + name: Analyse Collections + command: bin/test-with-real-projects.sh collections + - run: + name: Analyse ProxyManager + command: bin/test-with-real-projects.sh proxymanager + - run: + name: Analyse Psl + command: bin/test-with-real-projects.sh psl + +# Orchestrate or schedule a set of jobs, see https://circleci.com/docs/2.0/workflows/ +workflows: + Welcome: + jobs: + - install-and-self-analyse + - test: + requires: + - install-and-self-analyse + - "Code Style Analysis": + requires: + - install-and-self-analyse + - phar-build: + requires: + - test + - "Code Style Analysis" + - test-with-real-projects: + requires: + - phar-build diff --git a/lib/composer/vimeo/psalm/.editorconfig b/lib/composer/vimeo/psalm/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..a43d2a4ff751aaf0a47bf99e2c6b2c84d9dc3f12 --- /dev/null +++ b/lib/composer/vimeo/psalm/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.php] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/lib/composer/vimeo/psalm/.github/workflows/shepherd.yml b/lib/composer/vimeo/psalm/.github/workflows/shepherd.yml new file mode 100644 index 0000000000000000000000000000000000000000..6536eab38fb28669edbf638dca9e90c6151b985c --- /dev/null +++ b/lib/composer/vimeo/psalm/.github/workflows/shepherd.yml @@ -0,0 +1,18 @@ +name: Run Shepherd + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest + env: + COMPOSER_ROOT_VERSION: dev-master + + - name: Run Psalm + run: ./psalm --threads=2 --output-format=github --shepherd diff --git a/lib/composer/vimeo/psalm/CODE_OF_CONDUCT.md b/lib/composer/vimeo/psalm/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000000000000000000000000000000..6145fb58ed6ff8493d6c2e124dfd45029c62c27d --- /dev/null +++ b/lib/composer/vimeo/psalm/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at github@muglug.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/lib/composer/vimeo/psalm/CONTRIBUTING.md b/lib/composer/vimeo/psalm/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..209ac110ae1fda8885c3e003652ed1a6739d7f49 --- /dev/null +++ b/lib/composer/vimeo/psalm/CONTRIBUTING.md @@ -0,0 +1,21 @@ +# Contributing to Psalm + +The following is a set of guidelines for contributing to Psalm, which is hosted in the [Vimeo Organization](https://github.com/vimeo) on GitHub. + +Make sure to check out the [Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/) guide. + +## Submitting Issues + +You can create an issue [here](https://github.com/vimeo/psalm/issues/new), but before you do, follow these guidelines: + +* Make sure that you are using the latest version (`master`). +* It’s by no means a requirement, but if it's a bug, and you provide demonstration code that can be pasted into https://psalm.dev, it will likely get fixed faster. + +## Pull Requests + +[Here’s a guide to the codebase you may find useful](docs/how_psalm_works.md). + +Before you send a pull request, make sure you follow these guidelines: + +* Make sure to run `composer tests` and `./psalm` to ensure that Travis builds will pass +* Don’t forget to add tests! diff --git a/lib/composer/vimeo/psalm/LICENSE b/lib/composer/vimeo/psalm/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..bfb2d1c5f8d0e651b26dacd5401f34b79a5d435a --- /dev/null +++ b/lib/composer/vimeo/psalm/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Vimeo + +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/lib/composer/vimeo/psalm/PsalmLogo.png b/lib/composer/vimeo/psalm/PsalmLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..9d62255a93dbec8c6a44ac8d6fb7db99291c445b Binary files /dev/null and b/lib/composer/vimeo/psalm/PsalmLogo.png differ diff --git a/lib/composer/vimeo/psalm/README.md b/lib/composer/vimeo/psalm/README.md new file mode 100644 index 0000000000000000000000000000000000000000..013deff2bd1c72117767d4d4bb1ad994f8dbb638 --- /dev/null +++ b/lib/composer/vimeo/psalm/README.md @@ -0,0 +1,28 @@ +

Psalm

+ +[![Packagist](https://img.shields.io/packagist/v/vimeo/psalm.svg)](https://packagist.org/packages/vimeo/psalm) +[![Packagist](https://img.shields.io/packagist/dt/vimeo/psalm.svg)](https://packagist.org/packages/vimeo/psalm) +[![Travis CI](https://img.shields.io/travis/vimeo/psalm/master.svg)](https://travis-ci.org/vimeo/psalm/branches) +[![Coverage Status](https://coveralls.io/repos/github/vimeo/psalm/badge.svg)](https://coveralls.io/github/vimeo/psalm) +![Psalm coverage](https://shepherd.dev/github/vimeo/psalm/coverage.svg?) + + +Psalm is a static analysis tool for finding errors in PHP applications, built on top of [PHP Parser](https://github.com/nikic/php-parser). + +It's able to find a [large number of issues](https://github.com/vimeo/psalm/blob/master/docs/running_psalm/issues.md), but it can also be configured to only care about a small subset of those. + +[Try a live demo](https://psalm.dev/), or install it in your project by following the Quickstart Guide below. + +## Psalm documentation + +Documentation is available on [Psalm’s website](https://psalm.dev/docs), generated from the [docs](https://github.com/vimeo/psalm/blob/master/docs) folder. + +To get started, check out the [installation guide](docs/running_psalm/installation.md) + +## Interested in contributing? + +Have a look at [CONTRIBUTING.md](CONTRIBUTING.md). + +## Acknowledgements + +The engineering team [@vimeo](https://github.com/vimeo) for encouragement and patience, especially [@nbeliard](https://github.com/nbeliard), [@erunion](https://github.com/erunion) and [@nickyr](https://github.com/nickyr). diff --git a/lib/composer/vimeo/psalm/assets/psalm-phar/README.md b/lib/composer/vimeo/psalm/assets/psalm-phar/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6cf9105f511858cede49b0147afc7040995d6df9 --- /dev/null +++ b/lib/composer/vimeo/psalm/assets/psalm-phar/README.md @@ -0,0 +1 @@ +This allows you to install [Psalm](https://github.com/vimeo/psalm) without worrying about composer conflicts. diff --git a/lib/composer/vimeo/psalm/assets/psalm-phar/composer.json b/lib/composer/vimeo/psalm/assets/psalm-phar/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..c3fde166c081d80a99dba51a5716916958a55294 --- /dev/null +++ b/lib/composer/vimeo/psalm/assets/psalm-phar/composer.json @@ -0,0 +1,12 @@ +{ + "name": "psalm/phar", + "description": "Composer-based Psalm Phar", + "license": ["MIT"], + "require": { + "php": "^7.1" + }, + "conflict": { + "vimeo/psalm" : "*" + }, + "bin": ["psalm.phar"] +} diff --git a/lib/composer/vimeo/psalm/assets/psalm-phar/dot-gitignore b/lib/composer/vimeo/psalm/assets/psalm-phar/dot-gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a67d42b32f8693498f7996ecce27df94a04728f6 --- /dev/null +++ b/lib/composer/vimeo/psalm/assets/psalm-phar/dot-gitignore @@ -0,0 +1,6 @@ +composer.phar +/vendor/ + +# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control +# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file +# composer.lock diff --git a/lib/composer/vimeo/psalm/bin/build-phar.sh b/lib/composer/vimeo/psalm/bin/build-phar.sh new file mode 100755 index 0000000000000000000000000000000000000000..4c797a8b751a73e9d935de554ff308289c6ed33d --- /dev/null +++ b/lib/composer/vimeo/psalm/bin/build-phar.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -e + +composer bin box install + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +php $DIR/improve_class_alias.php + +vendor/bin/box compile + +if [[ "$GPG_ENCRYPTION" != '' ]] ; then + echo $GPG_ENCRYPTION | gpg --passphrase-fd 0 keys.asc.gpg + gpg --batch --yes --import keys.asc + echo $SIGNING_KEY | gpg --passphrase-fd 0 -u 8A03EA3B385DBAA1 --armor --detach-sig build/psalm.phar +fi diff --git a/lib/composer/vimeo/psalm/bin/composer-require-checker-config.json b/lib/composer/vimeo/psalm/bin/composer-require-checker-config.json new file mode 100644 index 0000000000000000000000000000000000000000..34acbc19e0edd7f99d19ab396bb20f8c0d204610 --- /dev/null +++ b/lib/composer/vimeo/psalm/bin/composer-require-checker-config.json @@ -0,0 +1,24 @@ +{ + "symbol-whitelist" : [ + "null", "true", "false", + "static", "self", "parent", + "array", "string", "int", "float", "bool", "iterable", "callable", "void", "object", + "igbinary_serialize", "igbinary_unserialize", "PHP_PARSER_VERSION", "PSALM_VERSION", "runkit_object_id", + "sapi_windows_cp_is_utf8" + ], + "php-core-extensions" : [ + "Core", + "date", + "pcre", + "Phar", + "Reflection", + "SPL", + "standard", + "pcntl", + "posix", + "filter", + "curl", + "PDO" + ], + "scan-files" : [] +} diff --git a/lib/composer/vimeo/psalm/bin/generate_levels_doc.php b/lib/composer/vimeo/psalm/bin/generate_levels_doc.php new file mode 100644 index 0000000000000000000000000000000000000000..b0f225373e21068d78e367ac5b97112ec2a50c6b --- /dev/null +++ b/lib/composer/vimeo/psalm/bin/generate_levels_doc.php @@ -0,0 +1,59 @@ + + +## Always treated as errors + + + +## Errors that only appear at level 1 + + + + + +## Feature-specific errors + + diff --git a/lib/composer/vimeo/psalm/bin/improve_class_alias.php b/lib/composer/vimeo/psalm/bin/improve_class_alias.php new file mode 100644 index 0000000000000000000000000000000000000000..6b6f34ff2994c3e9ab19592ca0b57dd599801199 --- /dev/null +++ b/lib/composer/vimeo/psalm/bin/improve_class_alias.php @@ -0,0 +1,42 @@ +createAliasStmt($originalName, $stmt);'; + +$replace = '/* @var FullyQualified $originalName */ + $aliasStmt = $this->createAliasStmt($originalName, $stmt); + + $stmts[] = new Node\Stmt\If_( + new Node\Expr\BooleanNot( + new Node\Expr\FuncCall( + new FullyQualified(\'class_exists\'), + [ + new Node\Arg( + new Node\Expr\ClassConstFetch( + $originalName, + \'class\' + ) + ), + new Node\Arg( + new Node\Expr\ConstFetch( + new Node\Name(\'false\') + ) + ) + ] + ) + ), + [\'stmts\' => [$aliasStmt]] + );'; + +$contents = file_get_contents($vendor_path); + +$contents = str_replace($search, $replace, $contents); + +file_put_contents($vendor_path, $contents); diff --git a/lib/composer/vimeo/psalm/bin/max_used_shortcode.php b/lib/composer/vimeo/psalm/bin/max_used_shortcode.php new file mode 100644 index 0000000000000000000000000000000000000000..cc15b7d721954d6ca98f51b19960a3c75fd29df3 --- /dev/null +++ b/lib/composer/vimeo/psalm/bin/max_used_shortcode.php @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/composer/vimeo/psalm/bin/test-with-real-projects.sh b/lib/composer/vimeo/psalm/bin/test-with-real-projects.sh new file mode 100755 index 0000000000000000000000000000000000000000..e2a98c6fb8221a198a5d34aa4e7055829dbae392 --- /dev/null +++ b/lib/composer/vimeo/psalm/bin/test-with-real-projects.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +set -e +set -x + +cd /tmp/ +mkdir -p testing-with-real-projects +cd testing-with-real-projects + +case $1 in +phpunit) + git clone git@github.com:muglug/phpunit.git + cd phpunit + composer install + ~/project/build/psalm.phar --config=.psalm/config.xml --monochrome --show-info=false + ~/project/build/psalm.phar --config=.psalm/static-analysis.xml --monochrome + ;; + +collections) + git clone git@github.com:muglug/collections.git + cd collections + composer install + ~/project/psalm --monochrome --show-info=false + ;; + +proxymanager) + git clone git@github.com:muglug/ProxyManager.git + cd ProxyManager + composer install + ~/project/psalm --monochrome + ;; + +psl) + git clone git@github.com:azjezz/psl.git + cd psl + composer install --ignore-platform-reqs + #~/project/psalm --monochrome + ;; + +laravel) + git clone git@github.com:muglug/framework.git + cd framework + composer install + ~/project/psalm --monochrome + ;; +*) + echo "Usage: test-with-real-projects.sh {phpunit|collections|proxymanager|laravel|psl}" + exit 1 +esac diff --git a/lib/composer/vimeo/psalm/bin/travis-deploy-phar.sh b/lib/composer/vimeo/psalm/bin/travis-deploy-phar.sh new file mode 100755 index 0000000000000000000000000000000000000000..ed55ff2fca286f3ba92c3b56d55467e390fa4846 --- /dev/null +++ b/lib/composer/vimeo/psalm/bin/travis-deploy-phar.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -e + + +if [[ ${TRAVIS_REPO_SLUG} != 'vimeo/psalm' && -z ${PHAR_REPO_SLUG} ]]; then + echo 'Not attempting phar deployment, as this is not vimeo/psalm, and $PHAR_REPO_SLUG is unset or empty' + exit 0; +fi; + +PHAR_REPO_SLUG=${PHAR_REPO_SLUG:=psalm/phar} + +git clone https://${GITHUB_TOKEN}@github.com/${PHAR_REPO_SLUG}.git phar > /dev/null 2>&1 + +set -x # don't do set x above this point to protect the GITHUB_TOKEN + +cd phar +rm -rf * +cp ../build/psalm.phar ../assets/psalm-phar/* . +cp ../build/psalm.phar.asc || true # not all users have GPG keys +mv dot-gitignore .gitignore +git config user.email "travis@travis-ci.org" +git config user.name "Travis CI" +git add --all . +git commit -m "Updated Psalm phar to commit ${TRAVIS_COMMIT}" +git push --quiet origin master > /dev/null 2>&1 + +if [[ "$TRAVIS_TAG" != '' ]] ; then + git tag "$TRAVIS_TAG" + git push origin "$TRAVIS_TAG" +fi diff --git a/lib/composer/vimeo/psalm/box.json.dist b/lib/composer/vimeo/psalm/box.json.dist new file mode 100644 index 0000000000000000000000000000000000000000..79d859ac573a6e1b9cf36311ff7096fc83f5f856 --- /dev/null +++ b/lib/composer/vimeo/psalm/box.json.dist @@ -0,0 +1,16 @@ +{ + "output" : "build/psalm.phar", + "files": [ + "src/command_functions.php", + "src/psalm.php", + "src/psalter.php", + "src/psalm-language-server.php", + "src/psalm_plugin.php", + "src/psalm-refactor.php" + ], + "files-bin": ["config.xsd"], + "directories-bin" : ["assets", "dictionaries", "stubs"], + "compactors" : [ + "KevinGH\\Box\\Compactor\\PhpScoper" + ] +} diff --git a/lib/composer/vimeo/psalm/build/phpunit/.gitkeep b/lib/composer/vimeo/psalm/build/phpunit/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/composer/vimeo/psalm/composer.json b/lib/composer/vimeo/psalm/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..51955e0d16fe5aa4d8572392f2c32c8eacfc50d1 --- /dev/null +++ b/lib/composer/vimeo/psalm/composer.json @@ -0,0 +1,116 @@ +{ + "name": "vimeo/psalm", + "type": "library", + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "php", + "code", + "inspection" + ], + "license": "MIT", + "authors": [ + { + "name": "Matthew Brown" + } + ], + "require": { + "php": "^7.3|^8", + "ext-SimpleXML": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-mbstring": "*", + "amphp/amp": "^2.1", + "amphp/byte-stream": "^1.5", + "composer/package-versions-deprecated": "^1.8.0", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^1.1", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "felixfbecker/advanced-json-rpc": "^3.0.3", + "felixfbecker/language-server-protocol": "^1.4", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0", + "nikic/php-parser": "^4.10.1", + "openlss/lib-array2xml": "^1.0", + "sebastian/diff": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "webmozart/glob": "^4.1", + "webmozart/path-util": "^2.3" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "ext-curl": "*", + "amphp/amp": "^2.4.2", + "bamarni/composer-bin-plugin": "^1.2", + "brianium/paratest": "^4.0.0", + "phpdocumentor/reflection-docblock": "^5", + "phpmyadmin/sql-parser": "5.1.0", + "phpspec/prophecy": ">=1.9.0", + "phpunit/phpunit": "^9.0", + "psalm/plugin-phpunit": "^0.13", + "weirdan/prophecy-shim": "^1.0 || ^2.0", + "slevomat/coding-standard": "^5.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.3" + }, + "suggest": { + "ext-igbinary": "^2.0.5" + }, + "config": { + "optimize-autoloader": true, + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + }, + "files": [ + "src/functions.php", + "src/spl_object_id.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Psalm\\Tests\\": "tests/" + } + }, + "repositories": [ + { + "type": "path", + "url": "examples/plugins/composer-based/echo-checker" + } + ], + "minimum-stability": "dev", + "prefer-stable": true, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "scripts": { + "all-tests": [ + "phpcs", + "./psalm --find-dead-code", + "phpunit" + ], + "psalm": "./psalm --find-dead-code", + "cs": "phpcs -p", + "cs-fix": "phpcbf -p", + "tests": [ + "phpcs", + "phpunit" + ] + } +} diff --git a/lib/composer/vimeo/psalm/config.xsd b/lib/composer/vimeo/psalm/config.xsd new file mode 100644 index 0000000000000000000000000000000000000000..732430130f9c5c36ba9a6c6f1724694ae198f368 --- /dev/null +++ b/lib/composer/vimeo/psalm/config.xsd @@ -0,0 +1,574 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default is runtime-specific: if not present, Psalm will only load the Xdebug stub if psalm has unloaded the extension. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/composer/vimeo/psalm/dictionaries/CallMap.php b/lib/composer/vimeo/psalm/dictionaries/CallMap.php new file mode 100644 index 0000000000000000000000000000000000000000..ab2006c7e966f7c0dc581aee0ebab2d3df23280a --- /dev/null +++ b/lib/composer/vimeo/psalm/dictionaries/CallMap.php @@ -0,0 +1,16710 @@ +' => [', ''=>''] + * alternative signature for the same function + * '' => [', ''=>''] + * + * A '&' in front of the means the arg is always passed by reference. + * (i.e. ReflectionParameter->isPassedByReference()) + * This was previously only used in cases where the function actually created the + * variable in the local scope. + * Some reference arguments will have prefixes in to indicate the way the argument is used. + * Currently, the only prefixes with meaning are 'rw_' (read-write) and 'w_' (write). + * Those prefixes don't mean anything for non-references. + * Code using these signatures should remove those prefixes from messages rendered to the user. + * 1. '&rw_' indicates that a parameter with a value is expected to be passed in, and may be modified. + * Phan will warn if the variable has an incompatible type, or is undefined. + * 2. '&w_' indicates that a parameter is expected to be passed in, and the value will be ignored, and may be overwritten. + * 3. The absence of a prefix is treated by Phan the same way as having the prefix 'w_' (Some may be changed to 'rw_name'). These will have prefixes added later. + * + * So, for functions like sort() where technically the arg is by-ref, + * indicate the reference param's signature by-ref and read-write, + * as `'&rw_array'=>'array'` + * so that Phan won't create it in the local scope + * + * However, for a function like preg_match() where the 3rd arg is an array of sub-pattern matches (and optional), + * this arg needs to be marked as by-ref and write-only, as `'&w_matches='=>'array'`. + * + * A '=' following the indicates this arg is optional. + * + * The can begin with '...' to indicate the arg is variadic. + * '...args=' indicates it is both variadic and optional. + * + * Some reference arguments will have prefixes in to indicate the way the argument is used. + * Currently, the only prefixes with meaning are 'rw_' and 'w_'. + * Code using these signatures should remove those prefixes from messages rendered to the user. + * 1. '&rw_name' indicates that a parameter with a value is expected to be passed in, and may be modified. + * 2. '&w_name' indicates that a parameter is expected to be passed in, and the value will be ignored, and may be overwritten. + * + * This file contains the signatures for the most recent minor release of PHP supported by phan (php 7.2) + * + * Changes: + * + * In Phan 0.12.3, + * + * - This started using array shapes for union types (array{...}). + * + * \Phan\Language\UnionType->withFlattenedArrayShapeOrLiteralTypeInstances() may be of help to programmatically convert these to array|array + * + * - This started using array shapes with optional fields for union types (array{key?:int}). + * A `?` after the array shape field's key indicates that the field is optional. + * + * - This started adding param signatures and return signatures to `callable` types. + * E.g. 'usort' => ['bool', '&rw_array_arg'=>'array', 'cmp_function'=>'callable(mixed,mixed):int']. + * See NEWS.md for 0.12.3 for possible syntax. A suffix of `=` within `callable(...)` means that a parameter is optional. + * + * (Phan assumes that callbacks with optional arguments can be cast to callbacks with/without those args (Similar to inheritance checks) + * (e.g. callable(T1,T2=) can be cast to callable(T1) or callable(T1,T2), in the same way that a subclass would check). + * For some signatures, e.g. set_error_handler, this results in repetition, because callable(T1=) can't cast to callable(T1). + * + * Sources of stub info: + * + * 1. Reflection + * 2. docs.php.net's SVN repo or website, and examples (See internal/internalsignatures.php) + * + * See https://secure.php.net/manual/en/copyright.php + * + * The PHP manual text and comments are covered by the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/legalcode), + * copyright (c) the PHP Documentation Group + * 3. Various websites documenting individual extensions + * 4. PHPStorm stubs (For anything missing from the above sources) + * See internal/internalsignatures.php + * + * Available from https://github.com/JetBrains/phpstorm-stubs under the [Apache 2 license](https://www.apache.org/licenses/LICENSE-2.0) + * + * @phan-file-suppress PhanPluginMixedKeyNoKey (read by Phan when analyzing this file) + * + * Note: Some of Phan's inferences about return types are written as plugins for functions/methods where the return type depends on the parameter types. + * E.g. src/Phan/Plugin/Internal/DependentReturnTypeOverridePlugin.php is one plugin + */ +return [ +'_' => ['string', 'message'=>'string'], +'__halt_compiler' => ['void'], +'abs' => ['int', 'number'=>'int'], +'abs\'1' => ['float', 'number'=>'float'], +'abs\'2' => ['numeric', 'number'=>'numeric'], +'accelerator_get_configuration' => ['array'], +'accelerator_get_scripts' => ['array'], +'accelerator_get_status' => ['array', 'fetch_scripts'=>'bool'], +'accelerator_reset' => [''], +'accelerator_set_status' => ['void', 'status'=>'bool'], +'acos' => ['float', 'number'=>'float'], +'acosh' => ['float', 'number'=>'float'], +'addcslashes' => ['string', 'string'=>'string', 'charlist'=>'string'], +'addslashes' => ['string', 'string'=>'string'], +'AMQPBasicProperties::__construct' => ['void', 'content_type='=>'string', 'content_encoding='=>'string', 'headers='=>'array', 'delivery_mode='=>'int', 'priority='=>'int', 'correlation_id='=>'string', 'reply_to='=>'string', 'expiration='=>'string', 'message_id='=>'string', 'timestamp='=>'int', 'type='=>'string', 'user_id='=>'string', 'app_id='=>'string', 'cluster_id='=>'string'], +'AMQPBasicProperties::getAppId' => ['string'], +'AMQPBasicProperties::getClusterId' => ['string'], +'AMQPBasicProperties::getContentEncoding' => ['string'], +'AMQPBasicProperties::getContentType' => ['string'], +'AMQPBasicProperties::getCorrelationId' => ['string'], +'AMQPBasicProperties::getDeliveryMode' => ['int'], +'AMQPBasicProperties::getExpiration' => ['string'], +'AMQPBasicProperties::getHeaders' => ['array'], +'AMQPBasicProperties::getMessageId' => ['string'], +'AMQPBasicProperties::getPriority' => ['int'], +'AMQPBasicProperties::getReplyTo' => ['string'], +'AMQPBasicProperties::getTimestamp' => ['string'], +'AMQPBasicProperties::getType' => ['string'], +'AMQPBasicProperties::getUserId' => ['string'], +'AMQPChannel::__construct' => ['void', 'amqp_connection'=>'AMQPConnection'], +'AMQPChannel::basicRecover' => ['', 'requeue='=>'bool'], +'AMQPChannel::close' => [''], +'AMQPChannel::commitTransaction' => ['bool'], +'AMQPChannel::confirmSelect' => [''], +'AMQPChannel::getChannelId' => ['int'], +'AMQPChannel::getConnection' => ['AMQPConnection'], +'AMQPChannel::getConsumers' => ['AMQPQueue[]'], +'AMQPChannel::getPrefetchCount' => ['int'], +'AMQPChannel::getPrefetchSize' => ['int'], +'AMQPChannel::isConnected' => ['bool'], +'AMQPChannel::qos' => ['bool', 'size'=>'int', 'count'=>'int'], +'AMQPChannel::rollbackTransaction' => ['bool'], +'AMQPChannel::setConfirmCallback' => ['', 'ack_callback='=>'?callable', 'nack_callback='=>'?callable'], +'AMQPChannel::setPrefetchCount' => ['bool', 'count'=>'int'], +'AMQPChannel::setPrefetchSize' => ['bool', 'size'=>'int'], +'AMQPChannel::setReturnCallback' => ['', 'return_callback='=>'?callable'], +'AMQPChannel::startTransaction' => ['bool'], +'AMQPChannel::waitForBasicReturn' => ['', 'timeout='=>'float'], +'AMQPChannel::waitForConfirm' => ['', 'timeout='=>'float'], +'AMQPConnection::__construct' => ['void', 'credentials='=>'array'], +'AMQPConnection::connect' => ['bool'], +'AMQPConnection::disconnect' => ['bool'], +'AMQPConnection::getCACert' => ['string'], +'AMQPConnection::getCert' => ['string'], +'AMQPConnection::getHeartbeatInterval' => ['int'], +'AMQPConnection::getHost' => ['string'], +'AMQPConnection::getKey' => ['string'], +'AMQPConnection::getLogin' => ['string'], +'AMQPConnection::getMaxChannels' => ['?int'], +'AMQPConnection::getMaxFrameSize' => ['int'], +'AMQPConnection::getPassword' => ['string'], +'AMQPConnection::getPort' => ['int'], +'AMQPConnection::getReadTimeout' => ['float'], +'AMQPConnection::getTimeout' => ['float'], +'AMQPConnection::getUsedChannels' => ['int'], +'AMQPConnection::getVerify' => ['bool'], +'AMQPConnection::getVhost' => ['string'], +'AMQPConnection::getWriteTimeout' => ['float'], +'AMQPConnection::isConnected' => ['bool'], +'AMQPConnection::isPersistent' => ['?bool'], +'AMQPConnection::pconnect' => ['bool'], +'AMQPConnection::pdisconnect' => ['bool'], +'AMQPConnection::preconnect' => ['bool'], +'AMQPConnection::reconnect' => ['bool'], +'AMQPConnection::setCACert' => ['', 'cacert'=>'string'], +'AMQPConnection::setCert' => ['', 'cert'=>'string'], +'AMQPConnection::setHost' => ['bool', 'host'=>'string'], +'AMQPConnection::setKey' => ['', 'key'=>'string'], +'AMQPConnection::setLogin' => ['bool', 'login'=>'string'], +'AMQPConnection::setPassword' => ['bool', 'password'=>'string'], +'AMQPConnection::setPort' => ['bool', 'port'=>'int'], +'AMQPConnection::setReadTimeout' => ['bool', 'timeout'=>'int'], +'AMQPConnection::setTimeout' => ['bool', 'timeout'=>'int'], +'AMQPConnection::setVerify' => ['', 'verify'=>'bool'], +'AMQPConnection::setVhost' => ['bool', 'vhost'=>'string'], +'AMQPConnection::setWriteTimeout' => ['bool', 'timeout'=>'int'], +'AMQPDecimal::__construct' => ['void', 'exponent'=>'', 'significand'=>''], +'AMQPDecimal::getExponent' => ['int'], +'AMQPDecimal::getSignificand' => ['int'], +'AMQPEnvelope::__construct' => ['void'], +'AMQPEnvelope::getAppId' => ['string'], +'AMQPEnvelope::getBody' => ['string'], +'AMQPEnvelope::getClusterId' => ['string'], +'AMQPEnvelope::getConsumerTag' => ['string'], +'AMQPEnvelope::getContentEncoding' => ['string'], +'AMQPEnvelope::getContentType' => ['string'], +'AMQPEnvelope::getCorrelationId' => ['string'], +'AMQPEnvelope::getDeliveryMode' => ['int'], +'AMQPEnvelope::getDeliveryTag' => ['string'], +'AMQPEnvelope::getExchangeName' => ['string'], +'AMQPEnvelope::getExpiration' => ['string'], +'AMQPEnvelope::getHeader' => ['string|false', 'header_key'=>'string'], +'AMQPEnvelope::getHeaders' => ['array'], +'AMQPEnvelope::getMessageId' => ['string'], +'AMQPEnvelope::getPriority' => ['int'], +'AMQPEnvelope::getReplyTo' => ['string'], +'AMQPEnvelope::getRoutingKey' => ['string'], +'AMQPEnvelope::getTimeStamp' => ['string'], +'AMQPEnvelope::getType' => ['string'], +'AMQPEnvelope::getUserId' => ['string'], +'AMQPEnvelope::hasHeader' => ['bool', 'header_key'=>'string'], +'AMQPEnvelope::isRedelivery' => ['bool'], +'AMQPExchange::__construct' => ['void', 'amqp_channel'=>'AMQPChannel'], +'AMQPExchange::bind' => ['bool', 'exchange_name'=>'string', 'routing_key='=>'string', 'arguments='=>'array'], +'AMQPExchange::declareExchange' => ['bool'], +'AMQPExchange::delete' => ['bool', 'exchangeName='=>'string', 'flags='=>'int'], +'AMQPExchange::getArgument' => ['int|string|false', 'key'=>'string'], +'AMQPExchange::getArguments' => ['array'], +'AMQPExchange::getChannel' => ['AMQPChannel'], +'AMQPExchange::getConnection' => ['AMQPConnection'], +'AMQPExchange::getFlags' => ['int'], +'AMQPExchange::getName' => ['string'], +'AMQPExchange::getType' => ['string'], +'AMQPExchange::hasArgument' => ['bool', 'key'=>'string'], +'AMQPExchange::publish' => ['bool', 'message'=>'string', 'routing_key='=>'string', 'flags='=>'int', 'attributes='=>'array'], +'AMQPExchange::setArgument' => ['bool', 'key'=>'string', 'value'=>'int|string'], +'AMQPExchange::setArguments' => ['bool', 'arguments'=>'array'], +'AMQPExchange::setFlags' => ['bool', 'flags'=>'int'], +'AMQPExchange::setName' => ['bool', 'exchange_name'=>'string'], +'AMQPExchange::setType' => ['bool', 'exchange_type'=>'string'], +'AMQPExchange::unbind' => ['bool', 'exchange_name'=>'string', 'routing_key='=>'string', 'arguments='=>'array'], +'AMQPQueue::__construct' => ['void', 'amqp_channel'=>'AMQPChannel'], +'AMQPQueue::ack' => ['bool', 'delivery_tag'=>'string', 'flags='=>'int'], +'AMQPQueue::bind' => ['bool', 'exchange_name'=>'string', 'routing_key='=>'string', 'arguments='=>'array'], +'AMQPQueue::cancel' => ['bool', 'consumer_tag='=>'string'], +'AMQPQueue::consume' => ['void', 'callback='=>'?callable', 'flags='=>'int', 'consumerTag='=>'string'], +'AMQPQueue::declareQueue' => ['int'], +'AMQPQueue::delete' => ['int', 'flags='=>'int'], +'AMQPQueue::get' => ['AMQPEnvelope|false', 'flags='=>'int'], +'AMQPQueue::getArgument' => ['int|string|false', 'key'=>'string'], +'AMQPQueue::getArguments' => ['array'], +'AMQPQueue::getChannel' => ['AMQPChannel'], +'AMQPQueue::getConnection' => ['AMQPConnection'], +'AMQPQueue::getConsumerTag' => ['?string'], +'AMQPQueue::getFlags' => ['int'], +'AMQPQueue::getName' => ['string'], +'AMQPQueue::hasArgument' => ['bool', 'key'=>'string'], +'AMQPQueue::nack' => ['bool', 'delivery_tag'=>'string', 'flags='=>'int'], +'AMQPQueue::purge' => ['bool'], +'AMQPQueue::reject' => ['bool', 'delivery_tag'=>'string', 'flags='=>'int'], +'AMQPQueue::setArgument' => ['bool', 'key'=>'string', 'value'=>'mixed'], +'AMQPQueue::setArguments' => ['bool', 'arguments'=>'array'], +'AMQPQueue::setFlags' => ['bool', 'flags'=>'int'], +'AMQPQueue::setName' => ['bool', 'queue_name'=>'string'], +'AMQPQueue::unbind' => ['bool', 'exchange_name'=>'string', 'routing_key='=>'string', 'arguments='=>'array'], +'AMQPTimestamp::__construct' => ['void', 'timestamp'=>'string'], +'AMQPTimestamp::__toString' => ['string'], +'AMQPTimestamp::getTimestamp' => ['string'], +'apache_child_terminate' => ['bool'], +'apache_get_modules' => ['array'], +'apache_get_version' => ['string|false'], +'apache_getenv' => ['string|false', 'variable'=>'string', 'walk_to_top='=>'bool'], +'apache_lookup_uri' => ['object', 'filename'=>'string'], +'apache_note' => ['string|false', 'note_name'=>'string', 'note_value='=>'string'], +'apache_request_headers' => ['array|false'], +'apache_reset_timeout' => ['bool'], +'apache_response_headers' => ['array|false'], +'apache_setenv' => ['bool', 'variable'=>'string', 'value'=>'string', 'walk_to_top='=>'bool'], +'apc_add' => ['bool', 'key'=>'string', 'var'=>'mixed', 'ttl='=>'int'], +'apc_add\'1' => ['array', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'], +'apc_bin_dump' => ['string|false|null', 'files='=>'array', 'user_vars='=>'array'], +'apc_bin_dumpfile' => ['int|false', 'files'=>'array', 'user_vars'=>'array', 'filename'=>'string', 'flags='=>'int', 'context='=>'resource'], +'apc_bin_load' => ['bool', 'data'=>'string', 'flags='=>'int'], +'apc_bin_loadfile' => ['bool', 'filename'=>'string', 'context='=>'resource', 'flags='=>'int'], +'apc_cache_info' => ['array|false', 'cache_type='=>'string', 'limited='=>'bool'], +'apc_cas' => ['bool', 'key'=>'string', 'old'=>'int', 'new'=>'int'], +'apc_clear_cache' => ['bool', 'cache_type='=>'string'], +'apc_compile_file' => ['bool', 'filename'=>'string', 'atomic='=>'bool'], +'apc_dec' => ['int|false', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool'], +'apc_define_constants' => ['bool', 'key'=>'string', 'constants'=>'array', 'case_sensitive='=>'bool'], +'apc_delete' => ['bool', 'key'=>'string|string[]|APCIterator'], +'apc_delete_file' => ['bool|string[]', 'keys'=>'mixed'], +'apc_exists' => ['bool', 'keys'=>'string'], +'apc_exists\'1' => ['array', 'keys'=>'string[]'], +'apc_fetch' => ['mixed|false', 'key'=>'string', '&w_success='=>'bool'], +'apc_fetch\'1' => ['array|false', 'key'=>'string[]', '&w_success='=>'bool'], +'apc_inc' => ['int|false', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool'], +'apc_load_constants' => ['bool', 'key'=>'string', 'case_sensitive='=>'bool'], +'apc_sma_info' => ['array|false', 'limited='=>'bool'], +'apc_store' => ['bool', 'key'=>'string', 'var'=>'', 'ttl='=>'int'], +'apc_store\'1' => ['array', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'], +'APCIterator::__construct' => ['void', 'cache'=>'string', 'search='=>'null|string|string[]', 'format='=>'int', 'chunk_size='=>'int', 'list='=>'int'], +'APCIterator::current' => ['mixed|false'], +'APCIterator::getTotalCount' => ['int|false'], +'APCIterator::getTotalHits' => ['int|false'], +'APCIterator::getTotalSize' => ['int|false'], +'APCIterator::key' => ['string'], +'APCIterator::next' => ['void'], +'APCIterator::rewind' => ['void'], +'APCIterator::valid' => ['bool'], +'apcu_add' => ['bool', 'key'=>'string', 'var'=>'', 'ttl='=>'int'], +'apcu_add\'1' => ['array', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'], +'apcu_cache_info' => ['array|false', 'limited='=>'bool'], +'apcu_cas' => ['bool', 'key'=>'string', 'old'=>'int', 'new'=>'int'], +'apcu_clear_cache' => ['bool'], +'apcu_dec' => ['int|false', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool', 'ttl='=>'int'], +'apcu_delete' => ['bool', 'key'=>'string|APCuIterator'], +'apcu_delete\'1' => ['list', 'key'=>'string[]'], +'apcu_enabled' => ['bool'], +'apcu_entry' => ['mixed', 'key'=>'string', 'generator'=>'callable', 'ttl='=>'int'], +'apcu_exists' => ['bool', 'keys'=>'string'], +'apcu_exists\'1' => ['array', 'keys'=>'string[]'], +'apcu_fetch' => ['mixed|false', 'key'=>'string', '&w_success='=>'bool'], +'apcu_fetch\'1' => ['array|false', 'key'=>'string[]', '&w_success='=>'bool'], +'apcu_inc' => ['int|false', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool', 'ttl='=>'int'], +'apcu_key_info' => ['?array', 'key'=>'string'], +'apcu_sma_info' => ['array|false', 'limited='=>'bool'], +'apcu_store' => ['bool', 'key'=>'string', 'var='=>'', 'ttl='=>'int'], +'apcu_store\'1' => ['array', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'], +'APCuIterator::__construct' => ['void', 'search='=>'string|string[]|null', 'format='=>'int', 'chunk_size='=>'int', 'list='=>'int'], +'APCuIterator::current' => ['mixed'], +'APCuIterator::getTotalCount' => ['int'], +'APCuIterator::getTotalHits' => ['int'], +'APCuIterator::getTotalSize' => ['int'], +'APCuIterator::key' => ['string'], +'APCuIterator::next' => ['void'], +'APCuIterator::rewind' => ['void'], +'APCuIterator::valid' => ['bool'], +'apd_breakpoint' => ['bool', 'debug_level'=>'int'], +'apd_callstack' => ['array'], +'apd_clunk' => ['void', 'warning'=>'string', 'delimiter='=>'string'], +'apd_continue' => ['bool', 'debug_level'=>'int'], +'apd_croak' => ['void', 'warning'=>'string', 'delimiter='=>'string'], +'apd_dump_function_table' => ['void'], +'apd_dump_persistent_resources' => ['array'], +'apd_dump_regular_resources' => ['array'], +'apd_echo' => ['bool', 'output'=>'string'], +'apd_get_active_symbols' => ['array'], +'apd_set_pprof_trace' => ['string', 'dump_directory='=>'string', 'fragment='=>'string'], +'apd_set_session' => ['void', 'debug_level'=>'int'], +'apd_set_session_trace' => ['void', 'debug_level'=>'int', 'dump_directory='=>'string'], +'apd_set_session_trace_socket' => ['bool', 'tcp_server'=>'string', 'socket_type'=>'int', 'port'=>'int', 'debug_level'=>'int'], +'AppendIterator::__construct' => ['void'], +'AppendIterator::append' => ['void', 'iterator'=>'Iterator'], +'AppendIterator::current' => ['mixed'], +'AppendIterator::getArrayIterator' => ['ArrayIterator'], +'AppendIterator::getInnerIterator' => ['Iterator'], +'AppendIterator::getIteratorIndex' => ['int'], +'AppendIterator::key' => ['int|string|float|bool'], +'AppendIterator::next' => ['void'], +'AppendIterator::rewind' => ['void'], +'AppendIterator::valid' => ['bool'], +'ArgumentCountError::__clone' => ['void'], +'ArgumentCountError::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?Error'], +'ArgumentCountError::__toString' => ['string'], +'ArgumentCountError::__wakeup' => ['void'], +'ArgumentCountError::getCode' => ['int'], +'ArgumentCountError::getFile' => ['string'], +'ArgumentCountError::getLine' => ['int'], +'ArgumentCountError::getMessage' => ['string'], +'ArgumentCountError::getPrevious' => ['?Throwable'], +'ArgumentCountError::getTrace' => ['list>'], +'ArgumentCountError::getTraceAsString' => ['string'], +'ArithmeticError::__clone' => ['void'], +'ArithmeticError::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?Error'], +'ArithmeticError::__toString' => ['string'], +'ArithmeticError::__wakeup' => ['void'], +'ArithmeticError::getCode' => ['int'], +'ArithmeticError::getFile' => ['string'], +'ArithmeticError::getLine' => ['int'], +'ArithmeticError::getMessage' => ['string'], +'ArithmeticError::getPrevious' => ['?Throwable'], +'ArithmeticError::getTrace' => ['list>'], +'ArithmeticError::getTraceAsString' => ['string'], +'array_change_key_case' => ['associative-array', 'array'=>'array', 'case='=>'int'], +'array_chunk' => ['list', 'input'=>'array', 'size'=>'int', 'preserve_keys='=>'bool'], +'array_column' => ['array', 'array'=>'array', 'column_key'=>'mixed', 'index_key='=>'mixed'], +'array_combine' => ['array|false', 'keys'=>'string[]|int[]', 'values'=>'array'], +'array_count_values' => ['associative-array', 'array'=>'array'], +'array_diff' => ['associative-array', 'array1'=>'array', 'array2'=>'array', '...args='=>'array'], +'array_diff_assoc' => ['associative-array', 'array1'=>'array', 'array2'=>'array', '...args='=>'array'], +'array_diff_key' => ['associative-array', 'array1'=>'array', 'array2'=>'array', '...args='=>'array'], +'array_diff_uassoc' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'data_comp_func'=>'callable(mixed,mixed):int'], +'array_diff_uassoc\'1' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], +'array_diff_ukey' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'key_comp_func'=>'callable(mixed,mixed):int'], +'array_diff_ukey\'1' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], +'array_fill' => ['array', 'start_key'=>'int', 'num'=>'int', 'value'=>'mixed'], +'array_fill_keys' => ['array', 'keys'=>'array', 'value'=>'mixed'], +'array_filter' => ['associative-array', 'array'=>'array', 'callback='=>'callable(mixed,mixed=):scalar', 'flag='=>'int'], +'array_flip' => ['associative-array|associative-array', 'array'=>'array'], +'array_intersect' => ['associative-array', 'array1'=>'array', 'array2'=>'array', '...args='=>'array'], +'array_intersect_assoc' => ['associative-array', 'array1'=>'array', 'array2'=>'array', '...args='=>'array'], +'array_intersect_key' => ['associative-array', 'array1'=>'array', 'array2'=>'array', '...args='=>'array'], +'array_intersect_uassoc' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'], +'array_intersect_uassoc\'1' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest'=>'array|callable(mixed,mixed):int'], +'array_intersect_ukey' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'], +'array_intersect_ukey\'1' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest'=>'array|callable(mixed,mixed):int'], +'array_key_exists' => ['bool', 'key'=>'string|int', 'search'=>'array|ArrayObject'], +'array_key_first' => ['int|string|null', 'array'=>'array'], +'array_key_last' => ['int|string|null', 'array'=>'array'], +'array_keys' => ['list', 'input'=>'array', 'search_value='=>'mixed', 'strict='=>'bool'], +'array_map' => ['array', 'callback'=>'?callable', 'input1'=>'array', '...args='=>'array'], +'array_merge' => ['array', 'array1'=>'array', '...args='=>'array'], +'array_merge_recursive' => ['array', 'array1'=>'array', '...args='=>'array'], +'array_multisort' => ['bool', '&rw_array1'=>'array', 'array1_sort_order='=>'array|int', 'array1_sort_flags='=>'array|int', '...args='=>'array|int'], +'array_pad' => ['array', 'array'=>'array', 'pad_size'=>'int', 'pad_value'=>'mixed'], +'array_pop' => ['mixed', '&rw_stack'=>'array'], +'array_product' => ['int|float', 'array'=>'array'], +'array_push' => ['int', '&rw_stack'=>'array', 'var'=>'mixed', '...vars='=>'mixed'], +'array_rand' => ['int|string|array|array', 'array'=>'non-empty-array', 'num_req'=>'int'], +'array_rand\'1' => ['int|string', 'array'=>'array'], +'array_reduce' => ['mixed', 'array'=>'array', 'callback'=>'callable(mixed,mixed):mixed', 'initial='=>'mixed'], +'array_replace' => ['?array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], +'array_replace_recursive' => ['array', 'array1'=>'array', 'array2'=>'array', '...args='=>'array'], +'array_reverse' => ['array', 'array'=>'array', 'preserve='=>'bool'], +'array_search' => ['int|string|false', 'needle'=>'mixed', 'haystack'=>'array', 'strict='=>'bool'], +'array_shift' => ['mixed|null', '&rw_stack'=>'array'], +'array_slice' => ['array', 'array'=>'array', 'offset'=>'int', 'length='=>'?int', 'preserve_keys='=>'bool'], +'array_splice' => ['array', '&rw_input'=>'array', 'offset'=>'int', 'length='=>'int', 'replacement='=>'array|string'], +'array_sum' => ['int|float', 'array'=>'array'], +'array_udiff' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'data_comp_func'=>'callable(mixed,mixed):int'], +'array_udiff\'1' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], +'array_udiff_assoc' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'key_comp_func'=>'callable(mixed,mixed):int'], +'array_udiff_assoc\'1' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], +'array_udiff_uassoc' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'data_comp_func'=>'callable(mixed,mixed):int', 'key_comp_func'=>'callable(mixed,mixed):int'], +'array_udiff_uassoc\'1' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', 'arg5'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], +'array_uintersect' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int'], +'array_uintersect\'1' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], +'array_uintersect_assoc' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int'], +'array_uintersect_assoc\'1' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable', '...rest='=>'array|callable(mixed,mixed):int'], +'array_uintersect_uassoc' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int', 'key_compare_func'=>'callable(mixed,mixed):int'], +'array_uintersect_uassoc\'1' => ['associative-array', 'array1'=>'array', 'array2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', 'arg5'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], +'array_unique' => ['associative-array', 'array'=>'array', 'sort_flags='=>'int'], +'array_unshift' => ['int', '&rw_stack'=>'array', 'var'=>'mixed', '...vars='=>'mixed'], +'array_values' => ['list', 'input'=>'array'], +'array_walk' => ['bool', '&rw_input'=>'array', 'callback'=>'callable', 'userdata='=>'mixed'], +'array_walk_recursive' => ['bool', '&rw_input'=>'array', 'callback'=>'callable', 'userdata='=>'mixed'], +'ArrayAccess::offsetExists' => ['bool', 'offset'=>'mixed'], +'ArrayAccess::offsetGet' => ['mixed', 'offset'=>'mixed'], +'ArrayAccess::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'], +'ArrayAccess::offsetUnset' => ['void', 'offset'=>'mixed'], +'ArrayIterator::__construct' => ['void', 'array='=>'array|object', 'flags='=>'int'], +'ArrayIterator::append' => ['void', 'value'=>'mixed'], +'ArrayIterator::asort' => ['void'], +'ArrayIterator::count' => ['int'], +'ArrayIterator::current' => ['mixed'], +'ArrayIterator::getArrayCopy' => ['array'], +'ArrayIterator::getFlags' => ['int'], +'ArrayIterator::key' => ['int|string|false'], +'ArrayIterator::ksort' => ['void'], +'ArrayIterator::natcasesort' => ['void'], +'ArrayIterator::natsort' => ['void'], +'ArrayIterator::next' => ['void'], +'ArrayIterator::offsetExists' => ['bool', 'index'=>'string|int'], +'ArrayIterator::offsetGet' => ['mixed', 'index'=>'string|int'], +'ArrayIterator::offsetSet' => ['void', 'index'=>'string|int', 'newval'=>'mixed'], +'ArrayIterator::offsetUnset' => ['void', 'index'=>'string|int'], +'ArrayIterator::rewind' => ['void'], +'ArrayIterator::seek' => ['void', 'position'=>'int'], +'ArrayIterator::serialize' => ['string'], +'ArrayIterator::setFlags' => ['void', 'flags'=>'string'], +'ArrayIterator::uasort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'], +'ArrayIterator::uksort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'], +'ArrayIterator::unserialize' => ['void', 'serialized'=>'string'], +'ArrayIterator::valid' => ['bool'], +'ArrayObject::__construct' => ['void', 'input='=>'array|object', 'flags='=>'int', 'iterator_class='=>'string'], +'ArrayObject::append' => ['void', 'value'=>'mixed'], +'ArrayObject::asort' => ['void'], +'ArrayObject::count' => ['int'], +'ArrayObject::exchangeArray' => ['array', 'ar'=>'mixed'], +'ArrayObject::getArrayCopy' => ['array'], +'ArrayObject::getFlags' => ['int'], +'ArrayObject::getIterator' => ['ArrayIterator'], +'ArrayObject::getIteratorClass' => ['string'], +'ArrayObject::ksort' => ['void'], +'ArrayObject::natcasesort' => ['void'], +'ArrayObject::natsort' => ['void'], +'ArrayObject::offsetExists' => ['bool', 'index'=>'int|string'], +'ArrayObject::offsetGet' => ['mixed|null', 'index'=>'int|string'], +'ArrayObject::offsetSet' => ['void', 'index'=>'int|string', 'newval'=>'mixed'], +'ArrayObject::offsetUnset' => ['void', 'index'=>'int|string'], +'ArrayObject::serialize' => ['string'], +'ArrayObject::setFlags' => ['void', 'flags'=>'int'], +'ArrayObject::setIteratorClass' => ['void', 'iterator_class'=>'string'], +'ArrayObject::uasort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'], +'ArrayObject::uksort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'], +'ArrayObject::unserialize' => ['void', 'serialized'=>'string'], +'arsort' => ['bool', '&rw_array'=>'array', 'sort_flags='=>'int'], +'asin' => ['float', 'number'=>'float'], +'asinh' => ['float', 'number'=>'float'], +'asort' => ['bool', '&rw_array'=>'array', 'sort_flags='=>'int'], +'assert' => ['bool', 'assertion'=>'string|bool', 'description='=>'string|Throwable|null'], +'assert_options' => ['mixed|false', 'what'=>'int', 'value='=>'mixed'], +'ast\get_kind_name' => ['string', 'kind'=>'int'], +'ast\get_metadata' => ['array'], +'ast\get_supported_versions' => ['array', 'exclude_deprecated='=>'bool'], +'ast\kind_uses_flags' => ['bool', 'kind'=>'int'], +'ast\Node::__construct' => ['void', 'kind='=>'int', 'flags='=>'int', 'children='=>'ast\Node\Decl[]|ast\Node[]|int[]|string[]|float[]|bool[]|null[]', 'start_line='=>'int'], +'ast\parse_code' => ['ast\Node', 'code'=>'string', 'version'=>'int', 'filename='=>'string'], +'ast\parse_file' => ['ast\Node', 'filename'=>'string', 'version'=>'int'], +'atan' => ['float', 'number'=>'float'], +'atan2' => ['float', 'y'=>'float', 'x'=>'float'], +'atanh' => ['float', 'number'=>'float'], +'BadFunctionCallException::__clone' => ['void'], +'BadFunctionCallException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?BadFunctionCallException'], +'BadFunctionCallException::__toString' => ['string'], +'BadFunctionCallException::getCode' => ['int'], +'BadFunctionCallException::getFile' => ['string'], +'BadFunctionCallException::getLine' => ['int'], +'BadFunctionCallException::getMessage' => ['string'], +'BadFunctionCallException::getPrevious' => ['?Throwable|?BadFunctionCallException'], +'BadFunctionCallException::getTrace' => ['list>'], +'BadFunctionCallException::getTraceAsString' => ['string'], +'BadMethodCallException::__clone' => ['void'], +'BadMethodCallException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?BadMethodCallException'], +'BadMethodCallException::__toString' => ['string'], +'BadMethodCallException::getCode' => ['int'], +'BadMethodCallException::getFile' => ['string'], +'BadMethodCallException::getLine' => ['int'], +'BadMethodCallException::getMessage' => ['string'], +'BadMethodCallException::getPrevious' => ['?Throwable|?BadMethodCallException'], +'BadMethodCallException::getTrace' => ['list>'], +'BadMethodCallException::getTraceAsString' => ['string'], +'base64_decode' => ['string|false', 'string'=>'string', 'strict='=>'bool'], +'base64_encode' => ['string', 'string'=>'string'], +'base_convert' => ['string', 'number'=>'string', 'frombase'=>'int', 'tobase'=>'int'], +'basename' => ['string', 'path'=>'string', 'suffix='=>'string'], +'bbcode_add_element' => ['bool', 'bbcode_container'=>'resource', 'tag_name'=>'string', 'tag_rules'=>'array'], +'bbcode_add_smiley' => ['bool', 'bbcode_container'=>'resource', 'smiley'=>'string', 'replace_by'=>'string'], +'bbcode_create' => ['resource', 'bbcode_initial_tags='=>'array'], +'bbcode_destroy' => ['bool', 'bbcode_container'=>'resource'], +'bbcode_parse' => ['string', 'bbcode_container'=>'resource', 'to_parse'=>'string'], +'bbcode_set_arg_parser' => ['bool', 'bbcode_container'=>'resource', 'bbcode_arg_parser'=>'resource'], +'bbcode_set_flags' => ['bool', 'bbcode_container'=>'resource', 'flags'=>'int', 'mode='=>'int'], +'bcadd' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], +'bccomp' => ['int', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], +'bcdiv' => ['?numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], +'bcmod' => ['?numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], +'bcmul' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], +'bcompiler_load' => ['bool', 'filename'=>'string'], +'bcompiler_load_exe' => ['bool', 'filename'=>'string'], +'bcompiler_parse_class' => ['bool', 'class'=>'string', 'callback'=>'string'], +'bcompiler_read' => ['bool', 'filehandle'=>'resource'], +'bcompiler_write_class' => ['bool', 'filehandle'=>'resource', 'classname'=>'string', 'extends='=>'string'], +'bcompiler_write_constant' => ['bool', 'filehandle'=>'resource', 'constantname'=>'string'], +'bcompiler_write_exe_footer' => ['bool', 'filehandle'=>'resource', 'startpos'=>'int'], +'bcompiler_write_file' => ['bool', 'filehandle'=>'resource', 'filename'=>'string'], +'bcompiler_write_footer' => ['bool', 'filehandle'=>'resource'], +'bcompiler_write_function' => ['bool', 'filehandle'=>'resource', 'functionname'=>'string'], +'bcompiler_write_functions_from_file' => ['bool', 'filehandle'=>'resource', 'filename'=>'string'], +'bcompiler_write_header' => ['bool', 'filehandle'=>'resource', 'write_ver='=>'string'], +'bcompiler_write_included_filename' => ['bool', 'filehandle'=>'resource', 'filename'=>'string'], +'bcpow' => ['numeric-string', 'base'=>'numeric-string', 'exponent'=>'numeric-string', 'scale='=>'int'], +'bcpowmod' => ['?numeric-string', 'base'=>'numeric-string', 'exponent'=>'numeric-string', 'modulus'=>'numeric-string', 'scale='=>'int'], +'bcscale' => ['int', 'scale='=>'int'], +'bcsqrt' => ['?numeric-string', 'operand'=>'numeric-string', 'scale='=>'int'], +'bcsub' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], +'bin2hex' => ['string', 'data'=>'string'], +'bind_textdomain_codeset' => ['string', 'domain'=>'string', 'codeset'=>'string'], +'bindec' => ['float|int', 'binary_number'=>'string'], +'bindtextdomain' => ['string', 'domain_name'=>'string', 'dir'=>'string'], +'birdstep_autocommit' => ['bool', 'index'=>'int'], +'birdstep_close' => ['bool', 'id'=>'int'], +'birdstep_commit' => ['bool', 'index'=>'int'], +'birdstep_connect' => ['int', 'server'=>'string', 'user'=>'string', 'pass'=>'string'], +'birdstep_exec' => ['int', 'index'=>'int', 'exec_str'=>'string'], +'birdstep_fetch' => ['bool', 'index'=>'int'], +'birdstep_fieldname' => ['string', 'index'=>'int', 'col'=>'int'], +'birdstep_fieldnum' => ['int', 'index'=>'int'], +'birdstep_freeresult' => ['bool', 'index'=>'int'], +'birdstep_off_autocommit' => ['bool', 'index'=>'int'], +'birdstep_result' => ['', 'index'=>'int', 'col'=>''], +'birdstep_rollback' => ['bool', 'index'=>'int'], +'blenc_encrypt' => ['string', 'plaintext'=>'string', 'encodedfile'=>'string', 'encryption_key='=>'string'], +'boolval' => ['bool', 'value'=>'mixed'], +'bson_decode' => ['array', 'bson'=>'string'], +'bson_encode' => ['string', 'anything'=>'mixed'], +'bzclose' => ['bool', 'bz'=>'resource'], +'bzcompress' => ['string|int', 'source'=>'string', 'blocksize100k='=>'int', 'workfactor='=>'int'], +'bzdecompress' => ['string|int', 'source'=>'string', 'small='=>'int'], +'bzerrno' => ['int', 'bz'=>'resource'], +'bzerror' => ['array', 'bz'=>'resource'], +'bzerrstr' => ['string', 'bz'=>'resource'], +'bzflush' => ['bool', 'bz'=>'resource'], +'bzopen' => ['resource|false', 'file'=>'string|resource', 'mode'=>'string'], +'bzread' => ['string|false', 'bz'=>'resource', 'length='=>'int'], +'bzwrite' => ['int|false', 'bz'=>'resource', 'data'=>'string', 'length='=>'int'], +'CachingIterator::__construct' => ['void', 'iterator'=>'Iterator', 'flags='=>''], +'CachingIterator::__toString' => ['string'], +'CachingIterator::count' => ['int'], +'CachingIterator::current' => ['mixed'], +'CachingIterator::getCache' => ['array'], +'CachingIterator::getFlags' => ['int'], +'CachingIterator::getInnerIterator' => ['Iterator'], +'CachingIterator::hasNext' => ['bool'], +'CachingIterator::key' => ['int|string|float|bool'], +'CachingIterator::next' => ['void'], +'CachingIterator::offsetExists' => ['bool', 'index'=>'string'], +'CachingIterator::offsetGet' => ['mixed', 'index'=>'string'], +'CachingIterator::offsetSet' => ['void', 'index'=>'string', 'newval'=>'mixed'], +'CachingIterator::offsetUnset' => ['void', 'index'=>'string'], +'CachingIterator::rewind' => ['void'], +'CachingIterator::setFlags' => ['void', 'flags'=>'int'], +'CachingIterator::valid' => ['bool'], +'Cairo::availableFonts' => ['array'], +'Cairo::availableSurfaces' => ['array'], +'Cairo::statusToString' => ['string', 'status'=>'int'], +'Cairo::version' => ['int'], +'Cairo::versionString' => ['string'], +'cairo_append_path' => ['', 'path'=>'cairopath', 'context'=>'cairocontext'], +'cairo_arc' => ['', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'angle1'=>'float', 'angle2'=>'float', 'context'=>'cairocontext'], +'cairo_arc_negative' => ['', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'angle1'=>'float', 'angle2'=>'float', 'context'=>'cairocontext'], +'cairo_available_fonts' => ['array'], +'cairo_available_surfaces' => ['array'], +'cairo_clip' => ['', 'context'=>'cairocontext'], +'cairo_clip_extents' => ['array', 'context'=>'cairocontext'], +'cairo_clip_preserve' => ['', 'context'=>'cairocontext'], +'cairo_clip_rectangle_list' => ['array', 'context'=>'cairocontext'], +'cairo_close_path' => ['', 'context'=>'cairocontext'], +'cairo_copy_page' => ['', 'context'=>'cairocontext'], +'cairo_copy_path' => ['CairoPath', 'context'=>'cairocontext'], +'cairo_copy_path_flat' => ['CairoPath', 'context'=>'cairocontext'], +'cairo_create' => ['CairoContext', 'surface'=>'cairosurface'], +'cairo_curve_to' => ['', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float', 'context'=>'cairocontext'], +'cairo_device_to_user' => ['array', 'x'=>'float', 'y'=>'float', 'context'=>'cairocontext'], +'cairo_device_to_user_distance' => ['array', 'x'=>'float', 'y'=>'float', 'context'=>'cairocontext'], +'cairo_fill' => ['', 'context'=>'cairocontext'], +'cairo_fill_extents' => ['array', 'context'=>'cairocontext'], +'cairo_fill_preserve' => ['', 'context'=>'cairocontext'], +'cairo_font_extents' => ['array', 'context'=>'cairocontext'], +'cairo_font_face_get_type' => ['int', 'fontface'=>'cairofontface'], +'cairo_font_face_status' => ['int', 'fontface'=>'cairofontface'], +'cairo_font_options_create' => ['CairoFontOptions'], +'cairo_font_options_equal' => ['bool', 'options'=>'cairofontoptions', 'other'=>'cairofontoptions'], +'cairo_font_options_get_antialias' => ['int', 'options'=>'cairofontoptions'], +'cairo_font_options_get_hint_metrics' => ['int', 'options'=>'cairofontoptions'], +'cairo_font_options_get_hint_style' => ['int', 'options'=>'cairofontoptions'], +'cairo_font_options_get_subpixel_order' => ['int', 'options'=>'cairofontoptions'], +'cairo_font_options_hash' => ['int', 'options'=>'cairofontoptions'], +'cairo_font_options_merge' => ['void', 'options'=>'cairofontoptions', 'other'=>'cairofontoptions'], +'cairo_font_options_set_antialias' => ['void', 'options'=>'cairofontoptions', 'antialias'=>'int'], +'cairo_font_options_set_hint_metrics' => ['void', 'options'=>'cairofontoptions', 'hint_metrics'=>'int'], +'cairo_font_options_set_hint_style' => ['void', 'options'=>'cairofontoptions', 'hint_style'=>'int'], +'cairo_font_options_set_subpixel_order' => ['void', 'options'=>'cairofontoptions', 'subpixel_order'=>'int'], +'cairo_font_options_status' => ['int', 'options'=>'cairofontoptions'], +'cairo_format_stride_for_width' => ['int', 'format'=>'int', 'width'=>'int'], +'cairo_get_antialias' => ['int', 'context'=>'cairocontext'], +'cairo_get_current_point' => ['array', 'context'=>'cairocontext'], +'cairo_get_dash' => ['array', 'context'=>'cairocontext'], +'cairo_get_dash_count' => ['int', 'context'=>'cairocontext'], +'cairo_get_fill_rule' => ['int', 'context'=>'cairocontext'], +'cairo_get_font_face' => ['', 'context'=>'cairocontext'], +'cairo_get_font_matrix' => ['', 'context'=>'cairocontext'], +'cairo_get_font_options' => ['', 'context'=>'cairocontext'], +'cairo_get_group_target' => ['', 'context'=>'cairocontext'], +'cairo_get_line_cap' => ['int', 'context'=>'cairocontext'], +'cairo_get_line_join' => ['int', 'context'=>'cairocontext'], +'cairo_get_line_width' => ['float', 'context'=>'cairocontext'], +'cairo_get_matrix' => ['', 'context'=>'cairocontext'], +'cairo_get_miter_limit' => ['float', 'context'=>'cairocontext'], +'cairo_get_operator' => ['int', 'context'=>'cairocontext'], +'cairo_get_scaled_font' => ['', 'context'=>'cairocontext'], +'cairo_get_source' => ['', 'context'=>'cairocontext'], +'cairo_get_target' => ['', 'context'=>'cairocontext'], +'cairo_get_tolerance' => ['float', 'context'=>'cairocontext'], +'cairo_glyph_path' => ['', 'glyphs'=>'array', 'context'=>'cairocontext'], +'cairo_has_current_point' => ['bool', 'context'=>'cairocontext'], +'cairo_identity_matrix' => ['', 'context'=>'cairocontext'], +'cairo_image_surface_create' => ['CairoImageSurface', 'format'=>'int', 'width'=>'int', 'height'=>'int'], +'cairo_image_surface_create_for_data' => ['CairoImageSurface', 'data'=>'string', 'format'=>'int', 'width'=>'int', 'height'=>'int', 'stride='=>'int'], +'cairo_image_surface_create_from_png' => ['CairoImageSurface', 'file'=>'string'], +'cairo_image_surface_get_data' => ['string', 'surface'=>'cairoimagesurface'], +'cairo_image_surface_get_format' => ['int', 'surface'=>'cairoimagesurface'], +'cairo_image_surface_get_height' => ['int', 'surface'=>'cairoimagesurface'], +'cairo_image_surface_get_stride' => ['int', 'surface'=>'cairoimagesurface'], +'cairo_image_surface_get_width' => ['int', 'surface'=>'cairoimagesurface'], +'cairo_in_fill' => ['bool', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'cairo_in_stroke' => ['bool', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'cairo_line_to' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'cairo_mask' => ['', 'pattern'=>'cairopattern', 'context'=>'cairocontext'], +'cairo_mask_surface' => ['', 'surface'=>'cairosurface', 'x='=>'string', 'y='=>'string', 'context='=>'cairocontext'], +'cairo_matrix_create_scale' => ['object', 'sx'=>'float', 'sy'=>'float'], +'cairo_matrix_init' => ['object', 'xx='=>'float', 'yx='=>'float', 'xy='=>'float', 'yy='=>'float', 'x0='=>'float', 'y0='=>'float'], +'cairo_matrix_init_identity' => ['object'], +'cairo_matrix_init_rotate' => ['object', 'radians'=>'float'], +'cairo_matrix_init_scale' => ['object', 'sx'=>'float', 'sy'=>'float'], +'cairo_matrix_init_translate' => ['object', 'tx'=>'float', 'ty'=>'float'], +'cairo_matrix_invert' => ['void', 'matrix'=>'cairomatrix'], +'cairo_matrix_multiply' => ['CairoMatrix', 'matrix1'=>'cairomatrix', 'matrix2'=>'cairomatrix'], +'cairo_matrix_rotate' => ['', 'matrix'=>'cairomatrix', 'radians'=>'float'], +'cairo_matrix_scale' => ['', 'sx'=>'float', 'sy'=>'float', 'context'=>'cairocontext'], +'cairo_matrix_transform_distance' => ['array', 'matrix'=>'cairomatrix', 'dx'=>'float', 'dy'=>'float'], +'cairo_matrix_transform_point' => ['array', 'matrix'=>'cairomatrix', 'dx'=>'float', 'dy'=>'float'], +'cairo_matrix_translate' => ['void', 'matrix'=>'cairomatrix', 'tx'=>'float', 'ty'=>'float'], +'cairo_move_to' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'cairo_new_path' => ['', 'context'=>'cairocontext'], +'cairo_new_sub_path' => ['', 'context'=>'cairocontext'], +'cairo_paint' => ['', 'context'=>'cairocontext'], +'cairo_paint_with_alpha' => ['', 'alpha'=>'string', 'context'=>'cairocontext'], +'cairo_path_extents' => ['array', 'context'=>'cairocontext'], +'cairo_pattern_add_color_stop_rgb' => ['void', 'pattern'=>'cairogradientpattern', 'offset'=>'float', 'red'=>'float', 'green'=>'float', 'blue'=>'float'], +'cairo_pattern_add_color_stop_rgba' => ['void', 'pattern'=>'cairogradientpattern', 'offset'=>'float', 'red'=>'float', 'green'=>'float', 'blue'=>'float', 'alpha'=>'float'], +'cairo_pattern_create_for_surface' => ['CairoPattern', 'surface'=>'cairosurface'], +'cairo_pattern_create_linear' => ['CairoPattern', 'x0'=>'float', 'y0'=>'float', 'x1'=>'float', 'y1'=>'float'], +'cairo_pattern_create_radial' => ['CairoPattern', 'x0'=>'float', 'y0'=>'float', 'r0'=>'float', 'x1'=>'float', 'y1'=>'float', 'r1'=>'float'], +'cairo_pattern_create_rgb' => ['CairoPattern', 'red'=>'float', 'green'=>'float', 'blue'=>'float'], +'cairo_pattern_create_rgba' => ['CairoPattern', 'red'=>'float', 'green'=>'float', 'blue'=>'float', 'alpha'=>'float'], +'cairo_pattern_get_color_stop_count' => ['int', 'pattern'=>'cairogradientpattern'], +'cairo_pattern_get_color_stop_rgba' => ['array', 'pattern'=>'cairogradientpattern', 'index'=>'int'], +'cairo_pattern_get_extend' => ['int', 'pattern'=>'string'], +'cairo_pattern_get_filter' => ['int', 'pattern'=>'cairosurfacepattern'], +'cairo_pattern_get_linear_points' => ['array', 'pattern'=>'cairolineargradient'], +'cairo_pattern_get_matrix' => ['CairoMatrix', 'pattern'=>'cairopattern'], +'cairo_pattern_get_radial_circles' => ['array', 'pattern'=>'cairoradialgradient'], +'cairo_pattern_get_rgba' => ['array', 'pattern'=>'cairosolidpattern'], +'cairo_pattern_get_surface' => ['CairoSurface', 'pattern'=>'cairosurfacepattern'], +'cairo_pattern_get_type' => ['int', 'pattern'=>'cairopattern'], +'cairo_pattern_set_extend' => ['void', 'pattern'=>'string', 'extend'=>'string'], +'cairo_pattern_set_filter' => ['void', 'pattern'=>'cairosurfacepattern', 'filter'=>'int'], +'cairo_pattern_set_matrix' => ['void', 'pattern'=>'cairopattern', 'matrix'=>'cairomatrix'], +'cairo_pattern_status' => ['int', 'pattern'=>'cairopattern'], +'cairo_pdf_surface_create' => ['CairoPdfSurface', 'file'=>'string', 'width'=>'float', 'height'=>'float'], +'cairo_pdf_surface_set_size' => ['void', 'surface'=>'cairopdfsurface', 'width'=>'float', 'height'=>'float'], +'cairo_pop_group' => ['', 'context'=>'cairocontext'], +'cairo_pop_group_to_source' => ['', 'context'=>'cairocontext'], +'cairo_ps_get_levels' => ['array'], +'cairo_ps_level_to_string' => ['string', 'level'=>'int'], +'cairo_ps_surface_create' => ['CairoPsSurface', 'file'=>'string', 'width'=>'float', 'height'=>'float'], +'cairo_ps_surface_dsc_begin_page_setup' => ['void', 'surface'=>'cairopssurface'], +'cairo_ps_surface_dsc_begin_setup' => ['void', 'surface'=>'cairopssurface'], +'cairo_ps_surface_dsc_comment' => ['void', 'surface'=>'cairopssurface', 'comment'=>'string'], +'cairo_ps_surface_get_eps' => ['bool', 'surface'=>'cairopssurface'], +'cairo_ps_surface_restrict_to_level' => ['void', 'surface'=>'cairopssurface', 'level'=>'int'], +'cairo_ps_surface_set_eps' => ['void', 'surface'=>'cairopssurface', 'level'=>'bool'], +'cairo_ps_surface_set_size' => ['void', 'surface'=>'cairopssurface', 'width'=>'float', 'height'=>'float'], +'cairo_push_group' => ['', 'context'=>'cairocontext'], +'cairo_push_group_with_content' => ['', 'content'=>'string', 'context'=>'cairocontext'], +'cairo_rectangle' => ['', 'x'=>'string', 'y'=>'string', 'width'=>'string', 'height'=>'string', 'context'=>'cairocontext'], +'cairo_rel_curve_to' => ['', 'x1'=>'string', 'y1'=>'string', 'x2'=>'string', 'y2'=>'string', 'x3'=>'string', 'y3'=>'string', 'context'=>'cairocontext'], +'cairo_rel_line_to' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'cairo_rel_move_to' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'cairo_reset_clip' => ['', 'context'=>'cairocontext'], +'cairo_restore' => ['', 'context'=>'cairocontext'], +'cairo_rotate' => ['', 'sx'=>'string', 'sy'=>'string', 'context'=>'cairocontext', 'angle'=>'string'], +'cairo_save' => ['', 'context'=>'cairocontext'], +'cairo_scale' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'cairo_scaled_font_create' => ['CairoScaledFont', 'fontface'=>'cairofontface', 'matrix'=>'cairomatrix', 'ctm'=>'cairomatrix', 'fontoptions'=>'cairofontoptions'], +'cairo_scaled_font_extents' => ['array', 'scaledfont'=>'cairoscaledfont'], +'cairo_scaled_font_get_ctm' => ['CairoMatrix', 'scaledfont'=>'cairoscaledfont'], +'cairo_scaled_font_get_font_face' => ['CairoFontFace', 'scaledfont'=>'cairoscaledfont'], +'cairo_scaled_font_get_font_matrix' => ['CairoFontOptions', 'scaledfont'=>'cairoscaledfont'], +'cairo_scaled_font_get_font_options' => ['CairoFontOptions', 'scaledfont'=>'cairoscaledfont'], +'cairo_scaled_font_get_scale_matrix' => ['CairoMatrix', 'scaledfont'=>'cairoscaledfont'], +'cairo_scaled_font_get_type' => ['int', 'scaledfont'=>'cairoscaledfont'], +'cairo_scaled_font_glyph_extents' => ['array', 'scaledfont'=>'cairoscaledfont', 'glyphs'=>'array'], +'cairo_scaled_font_status' => ['int', 'scaledfont'=>'cairoscaledfont'], +'cairo_scaled_font_text_extents' => ['array', 'scaledfont'=>'cairoscaledfont', 'text'=>'string'], +'cairo_select_font_face' => ['', 'family'=>'string', 'slant='=>'string', 'weight='=>'string', 'context='=>'cairocontext'], +'cairo_set_antialias' => ['', 'antialias='=>'string', 'context='=>'cairocontext'], +'cairo_set_dash' => ['', 'dashes'=>'array', 'offset='=>'string', 'context='=>'cairocontext'], +'cairo_set_fill_rule' => ['', 'setting'=>'string', 'context'=>'cairocontext'], +'cairo_set_font_face' => ['', 'fontface'=>'cairofontface', 'context'=>'cairocontext'], +'cairo_set_font_matrix' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'], +'cairo_set_font_options' => ['', 'fontoptions'=>'cairofontoptions', 'context'=>'cairocontext'], +'cairo_set_font_size' => ['', 'size'=>'string', 'context'=>'cairocontext'], +'cairo_set_line_cap' => ['', 'setting'=>'string', 'context'=>'cairocontext'], +'cairo_set_line_join' => ['', 'setting'=>'string', 'context'=>'cairocontext'], +'cairo_set_line_width' => ['', 'width'=>'string', 'context'=>'cairocontext'], +'cairo_set_matrix' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'], +'cairo_set_miter_limit' => ['', 'limit'=>'string', 'context'=>'cairocontext'], +'cairo_set_operator' => ['', 'setting'=>'string', 'context'=>'cairocontext'], +'cairo_set_scaled_font' => ['', 'scaledfont'=>'cairoscaledfont', 'context'=>'cairocontext'], +'cairo_set_source' => ['', 'red'=>'string', 'green'=>'string', 'blue'=>'string', 'alpha'=>'string', 'context'=>'cairocontext', 'pattern'=>'cairopattern'], +'cairo_set_source_surface' => ['', 'surface'=>'cairosurface', 'x='=>'string', 'y='=>'string', 'context='=>'cairocontext'], +'cairo_set_tolerance' => ['', 'tolerance'=>'string', 'context'=>'cairocontext'], +'cairo_show_page' => ['', 'context'=>'cairocontext'], +'cairo_show_text' => ['', 'text'=>'string', 'context'=>'cairocontext'], +'cairo_status' => ['int', 'context'=>'cairocontext'], +'cairo_status_to_string' => ['string', 'status'=>'int'], +'cairo_stroke' => ['', 'context'=>'cairocontext'], +'cairo_stroke_extents' => ['array', 'context'=>'cairocontext'], +'cairo_stroke_preserve' => ['', 'context'=>'cairocontext'], +'cairo_surface_copy_page' => ['void', 'surface'=>'cairosurface'], +'cairo_surface_create_similar' => ['CairoSurface', 'surface'=>'cairosurface', 'content'=>'int', 'width'=>'float', 'height'=>'float'], +'cairo_surface_finish' => ['void', 'surface'=>'cairosurface'], +'cairo_surface_flush' => ['void', 'surface'=>'cairosurface'], +'cairo_surface_get_content' => ['int', 'surface'=>'cairosurface'], +'cairo_surface_get_device_offset' => ['array', 'surface'=>'cairosurface'], +'cairo_surface_get_font_options' => ['CairoFontOptions', 'surface'=>'cairosurface'], +'cairo_surface_get_type' => ['int', 'surface'=>'cairosurface'], +'cairo_surface_mark_dirty' => ['void', 'surface'=>'cairosurface'], +'cairo_surface_mark_dirty_rectangle' => ['void', 'surface'=>'cairosurface', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'], +'cairo_surface_set_device_offset' => ['void', 'surface'=>'cairosurface', 'x'=>'float', 'y'=>'float'], +'cairo_surface_set_fallback_resolution' => ['void', 'surface'=>'cairosurface', 'x'=>'float', 'y'=>'float'], +'cairo_surface_show_page' => ['void', 'surface'=>'cairosurface'], +'cairo_surface_status' => ['int', 'surface'=>'cairosurface'], +'cairo_surface_write_to_png' => ['void', 'surface'=>'cairosurface', 'stream'=>'resource'], +'cairo_svg_get_versions' => ['array'], +'cairo_svg_surface_create' => ['CairoSvgSurface', 'file'=>'string', 'width'=>'float', 'height'=>'float'], +'cairo_svg_surface_get_versions' => ['array'], +'cairo_svg_surface_restrict_to_version' => ['void', 'surface'=>'cairosvgsurface', 'version'=>'int'], +'cairo_svg_version_to_string' => ['string', 'version'=>'int'], +'cairo_text_extents' => ['array', 'text'=>'string', 'context'=>'cairocontext'], +'cairo_text_path' => ['', 'string'=>'string', 'context'=>'cairocontext', 'text'=>'string'], +'cairo_transform' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'], +'cairo_translate' => ['', 'tx'=>'string', 'ty'=>'string', 'context'=>'cairocontext', 'x'=>'string', 'y'=>'string'], +'cairo_user_to_device' => ['array', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'cairo_user_to_device_distance' => ['array', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'cairo_version' => ['int'], +'cairo_version_string' => ['string'], +'CairoContext::__construct' => ['void', 'surface'=>'CairoSurface'], +'CairoContext::appendPath' => ['', 'path'=>'cairopath', 'context'=>'cairocontext'], +'CairoContext::arc' => ['', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'angle1'=>'float', 'angle2'=>'float', 'context'=>'cairocontext'], +'CairoContext::arcNegative' => ['', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'angle1'=>'float', 'angle2'=>'float', 'context'=>'cairocontext'], +'CairoContext::clip' => ['', 'context'=>'cairocontext'], +'CairoContext::clipExtents' => ['array', 'context'=>'cairocontext'], +'CairoContext::clipPreserve' => ['', 'context'=>'cairocontext'], +'CairoContext::clipRectangleList' => ['array', 'context'=>'cairocontext'], +'CairoContext::closePath' => ['', 'context'=>'cairocontext'], +'CairoContext::copyPage' => ['', 'context'=>'cairocontext'], +'CairoContext::copyPath' => ['CairoPath', 'context'=>'cairocontext'], +'CairoContext::copyPathFlat' => ['CairoPath', 'context'=>'cairocontext'], +'CairoContext::curveTo' => ['', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float', 'context'=>'cairocontext'], +'CairoContext::deviceToUser' => ['array', 'x'=>'float', 'y'=>'float', 'context'=>'cairocontext'], +'CairoContext::deviceToUserDistance' => ['array', 'x'=>'float', 'y'=>'float', 'context'=>'cairocontext'], +'CairoContext::fill' => ['', 'context'=>'cairocontext'], +'CairoContext::fillExtents' => ['array', 'context'=>'cairocontext'], +'CairoContext::fillPreserve' => ['', 'context'=>'cairocontext'], +'CairoContext::fontExtents' => ['array', 'context'=>'cairocontext'], +'CairoContext::getAntialias' => ['int', 'context'=>'cairocontext'], +'CairoContext::getCurrentPoint' => ['array', 'context'=>'cairocontext'], +'CairoContext::getDash' => ['array', 'context'=>'cairocontext'], +'CairoContext::getDashCount' => ['int', 'context'=>'cairocontext'], +'CairoContext::getFillRule' => ['int', 'context'=>'cairocontext'], +'CairoContext::getFontFace' => ['', 'context'=>'cairocontext'], +'CairoContext::getFontMatrix' => ['', 'context'=>'cairocontext'], +'CairoContext::getFontOptions' => ['', 'context'=>'cairocontext'], +'CairoContext::getGroupTarget' => ['', 'context'=>'cairocontext'], +'CairoContext::getLineCap' => ['int', 'context'=>'cairocontext'], +'CairoContext::getLineJoin' => ['int', 'context'=>'cairocontext'], +'CairoContext::getLineWidth' => ['float', 'context'=>'cairocontext'], +'CairoContext::getMatrix' => ['', 'context'=>'cairocontext'], +'CairoContext::getMiterLimit' => ['float', 'context'=>'cairocontext'], +'CairoContext::getOperator' => ['int', 'context'=>'cairocontext'], +'CairoContext::getScaledFont' => ['', 'context'=>'cairocontext'], +'CairoContext::getSource' => ['', 'context'=>'cairocontext'], +'CairoContext::getTarget' => ['', 'context'=>'cairocontext'], +'CairoContext::getTolerance' => ['float', 'context'=>'cairocontext'], +'CairoContext::glyphPath' => ['', 'glyphs'=>'array', 'context'=>'cairocontext'], +'CairoContext::hasCurrentPoint' => ['bool', 'context'=>'cairocontext'], +'CairoContext::identityMatrix' => ['', 'context'=>'cairocontext'], +'CairoContext::inFill' => ['bool', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'CairoContext::inStroke' => ['bool', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'CairoContext::lineTo' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'CairoContext::mask' => ['', 'pattern'=>'cairopattern', 'context'=>'cairocontext'], +'CairoContext::maskSurface' => ['', 'surface'=>'cairosurface', 'x='=>'string', 'y='=>'string', 'context='=>'cairocontext'], +'CairoContext::moveTo' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'CairoContext::newPath' => ['', 'context'=>'cairocontext'], +'CairoContext::newSubPath' => ['', 'context'=>'cairocontext'], +'CairoContext::paint' => ['', 'context'=>'cairocontext'], +'CairoContext::paintWithAlpha' => ['', 'alpha'=>'string', 'context'=>'cairocontext'], +'CairoContext::pathExtents' => ['array', 'context'=>'cairocontext'], +'CairoContext::popGroup' => ['', 'context'=>'cairocontext'], +'CairoContext::popGroupToSource' => ['', 'context'=>'cairocontext'], +'CairoContext::pushGroup' => ['', 'context'=>'cairocontext'], +'CairoContext::pushGroupWithContent' => ['', 'content'=>'string', 'context'=>'cairocontext'], +'CairoContext::rectangle' => ['', 'x'=>'string', 'y'=>'string', 'width'=>'string', 'height'=>'string', 'context'=>'cairocontext'], +'CairoContext::relCurveTo' => ['', 'x1'=>'string', 'y1'=>'string', 'x2'=>'string', 'y2'=>'string', 'x3'=>'string', 'y3'=>'string', 'context'=>'cairocontext'], +'CairoContext::relLineTo' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'CairoContext::relMoveTo' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'CairoContext::resetClip' => ['', 'context'=>'cairocontext'], +'CairoContext::restore' => ['', 'context'=>'cairocontext'], +'CairoContext::rotate' => ['', 'angle'=>'string', 'context'=>'cairocontext'], +'CairoContext::save' => ['', 'context'=>'cairocontext'], +'CairoContext::scale' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'CairoContext::selectFontFace' => ['', 'family'=>'string', 'slant='=>'string', 'weight='=>'string', 'context='=>'cairocontext'], +'CairoContext::setAntialias' => ['', 'antialias='=>'string', 'context='=>'cairocontext'], +'CairoContext::setDash' => ['', 'dashes'=>'array', 'offset='=>'string', 'context='=>'cairocontext'], +'CairoContext::setFillRule' => ['', 'setting'=>'string', 'context'=>'cairocontext'], +'CairoContext::setFontFace' => ['', 'fontface'=>'cairofontface', 'context'=>'cairocontext'], +'CairoContext::setFontMatrix' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'], +'CairoContext::setFontOptions' => ['', 'fontoptions'=>'cairofontoptions', 'context'=>'cairocontext'], +'CairoContext::setFontSize' => ['', 'size'=>'string', 'context'=>'cairocontext'], +'CairoContext::setLineCap' => ['', 'setting'=>'string', 'context'=>'cairocontext'], +'CairoContext::setLineJoin' => ['', 'setting'=>'string', 'context'=>'cairocontext'], +'CairoContext::setLineWidth' => ['', 'width'=>'string', 'context'=>'cairocontext'], +'CairoContext::setMatrix' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'], +'CairoContext::setMiterLimit' => ['', 'limit'=>'string', 'context'=>'cairocontext'], +'CairoContext::setOperator' => ['', 'setting'=>'string', 'context'=>'cairocontext'], +'CairoContext::setScaledFont' => ['', 'scaledfont'=>'cairoscaledfont', 'context'=>'cairocontext'], +'CairoContext::setSource' => ['', 'pattern'=>'cairopattern', 'context'=>'cairocontext'], +'CairoContext::setSourceRGB' => ['', 'red'=>'string', 'green'=>'string', 'blue'=>'string', 'context'=>'cairocontext', 'pattern'=>'cairopattern'], +'CairoContext::setSourceRGBA' => ['', 'red'=>'string', 'green'=>'string', 'blue'=>'string', 'alpha'=>'string', 'context'=>'cairocontext', 'pattern'=>'cairopattern'], +'CairoContext::setSourceSurface' => ['', 'surface'=>'cairosurface', 'x='=>'string', 'y='=>'string', 'context='=>'cairocontext'], +'CairoContext::setTolerance' => ['', 'tolerance'=>'string', 'context'=>'cairocontext'], +'CairoContext::showPage' => ['', 'context'=>'cairocontext'], +'CairoContext::showText' => ['', 'text'=>'string', 'context'=>'cairocontext'], +'CairoContext::status' => ['int', 'context'=>'cairocontext'], +'CairoContext::stroke' => ['', 'context'=>'cairocontext'], +'CairoContext::strokeExtents' => ['array', 'context'=>'cairocontext'], +'CairoContext::strokePreserve' => ['', 'context'=>'cairocontext'], +'CairoContext::textExtents' => ['array', 'text'=>'string', 'context'=>'cairocontext'], +'CairoContext::textPath' => ['', 'string'=>'string', 'context'=>'cairocontext', 'text'=>'string'], +'CairoContext::transform' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'], +'CairoContext::translate' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'CairoContext::userToDevice' => ['array', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'CairoContext::userToDeviceDistance' => ['array', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'], +'CairoFontFace::__construct' => ['void'], +'CairoFontFace::getType' => ['int'], +'CairoFontFace::status' => ['int', 'fontface'=>'cairofontface'], +'CairoFontOptions::__construct' => ['void'], +'CairoFontOptions::equal' => ['bool', 'other'=>'string'], +'CairoFontOptions::getAntialias' => ['int', 'context'=>'cairocontext'], +'CairoFontOptions::getHintMetrics' => ['int'], +'CairoFontOptions::getHintStyle' => ['int'], +'CairoFontOptions::getSubpixelOrder' => ['int'], +'CairoFontOptions::hash' => ['int'], +'CairoFontOptions::merge' => ['void', 'other'=>'string'], +'CairoFontOptions::setAntialias' => ['', 'antialias='=>'string', 'context='=>'cairocontext'], +'CairoFontOptions::setHintMetrics' => ['void', 'hint_metrics'=>'string'], +'CairoFontOptions::setHintStyle' => ['void', 'hint_style'=>'string'], +'CairoFontOptions::setSubpixelOrder' => ['void', 'subpixel_order'=>'string'], +'CairoFontOptions::status' => ['int', 'context'=>'cairocontext'], +'CairoFormat::strideForWidth' => ['int', 'format'=>'int', 'width'=>'int'], +'CairoGradientPattern::addColorStopRgb' => ['void', 'offset'=>'string', 'red'=>'string', 'green'=>'string', 'blue'=>'string'], +'CairoGradientPattern::addColorStopRgba' => ['void', 'offset'=>'string', 'red'=>'string', 'green'=>'string', 'blue'=>'string', 'alpha'=>'string'], +'CairoGradientPattern::getColorStopCount' => ['int'], +'CairoGradientPattern::getColorStopRgba' => ['array', 'index'=>'string'], +'CairoGradientPattern::getExtend' => ['int'], +'CairoGradientPattern::setExtend' => ['void', 'extend'=>'int'], +'CairoImageSurface::__construct' => ['void', 'format'=>'int', 'width'=>'int', 'height'=>'int'], +'CairoImageSurface::createForData' => ['void', 'data'=>'string', 'format'=>'int', 'width'=>'int', 'height'=>'int', 'stride='=>'int'], +'CairoImageSurface::createFromPng' => ['CairoImageSurface', 'file'=>'string'], +'CairoImageSurface::getData' => ['string'], +'CairoImageSurface::getFormat' => ['int'], +'CairoImageSurface::getHeight' => ['int'], +'CairoImageSurface::getStride' => ['int'], +'CairoImageSurface::getWidth' => ['int'], +'CairoLinearGradient::__construct' => ['void', 'x0'=>'float', 'y0'=>'float', 'x1'=>'float', 'y1'=>'float'], +'CairoLinearGradient::getPoints' => ['array'], +'CairoMatrix::__construct' => ['void', 'xx='=>'float', 'yx='=>'float', 'xy='=>'float', 'yy='=>'float', 'x0='=>'float', 'y0='=>'float'], +'CairoMatrix::initIdentity' => ['object'], +'CairoMatrix::initRotate' => ['object', 'radians'=>'float'], +'CairoMatrix::initScale' => ['object', 'sx'=>'float', 'sy'=>'float'], +'CairoMatrix::initTranslate' => ['object', 'tx'=>'float', 'ty'=>'float'], +'CairoMatrix::invert' => ['void'], +'CairoMatrix::multiply' => ['CairoMatrix', 'matrix1'=>'cairomatrix', 'matrix2'=>'cairomatrix'], +'CairoMatrix::rotate' => ['', 'sx'=>'string', 'sy'=>'string', 'context'=>'cairocontext', 'angle'=>'string'], +'CairoMatrix::scale' => ['', 'sx'=>'float', 'sy'=>'float', 'context'=>'cairocontext'], +'CairoMatrix::transformDistance' => ['array', 'dx'=>'string', 'dy'=>'string'], +'CairoMatrix::transformPoint' => ['array', 'dx'=>'string', 'dy'=>'string'], +'CairoMatrix::translate' => ['', 'tx'=>'string', 'ty'=>'string', 'context'=>'cairocontext', 'x'=>'string', 'y'=>'string'], +'CairoPattern::__construct' => ['void'], +'CairoPattern::getMatrix' => ['', 'context'=>'cairocontext'], +'CairoPattern::getType' => ['int'], +'CairoPattern::setMatrix' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'], +'CairoPattern::status' => ['int', 'context'=>'cairocontext'], +'CairoPdfSurface::__construct' => ['void', 'file'=>'string', 'width'=>'float', 'height'=>'float'], +'CairoPdfSurface::setSize' => ['void', 'width'=>'string', 'height'=>'string'], +'CairoPsSurface::__construct' => ['void', 'file'=>'string', 'width'=>'float', 'height'=>'float'], +'CairoPsSurface::dscBeginPageSetup' => ['void'], +'CairoPsSurface::dscBeginSetup' => ['void'], +'CairoPsSurface::dscComment' => ['void', 'comment'=>'string'], +'CairoPsSurface::getEps' => ['bool'], +'CairoPsSurface::getLevels' => ['array'], +'CairoPsSurface::levelToString' => ['string', 'level'=>'int'], +'CairoPsSurface::restrictToLevel' => ['void', 'level'=>'string'], +'CairoPsSurface::setEps' => ['void', 'level'=>'string'], +'CairoPsSurface::setSize' => ['void', 'width'=>'string', 'height'=>'string'], +'CairoRadialGradient::__construct' => ['void', 'x0'=>'float', 'y0'=>'float', 'r0'=>'float', 'x1'=>'float', 'y1'=>'float', 'r1'=>'float'], +'CairoRadialGradient::getCircles' => ['array'], +'CairoScaledFont::__construct' => ['void', 'font_face'=>'CairoFontFace', 'matrix'=>'CairoMatrix', 'ctm'=>'CairoMatrix', 'options'=>'CairoFontOptions'], +'CairoScaledFont::extents' => ['array'], +'CairoScaledFont::getCtm' => ['CairoMatrix'], +'CairoScaledFont::getFontFace' => ['', 'context'=>'cairocontext'], +'CairoScaledFont::getFontMatrix' => ['', 'context'=>'cairocontext'], +'CairoScaledFont::getFontOptions' => ['', 'context'=>'cairocontext'], +'CairoScaledFont::getScaleMatrix' => ['void'], +'CairoScaledFont::getType' => ['int'], +'CairoScaledFont::glyphExtents' => ['array', 'glyphs'=>'string'], +'CairoScaledFont::status' => ['int', 'context'=>'cairocontext'], +'CairoScaledFont::textExtents' => ['array', 'text'=>'string', 'context'=>'cairocontext'], +'CairoSolidPattern::__construct' => ['void', 'red'=>'float', 'green'=>'float', 'blue'=>'float', 'alpha='=>'float'], +'CairoSolidPattern::getRgba' => ['array'], +'CairoSurface::__construct' => ['void'], +'CairoSurface::copyPage' => ['', 'context'=>'cairocontext'], +'CairoSurface::createSimilar' => ['void', 'other'=>'cairosurface', 'content'=>'int', 'width'=>'string', 'height'=>'string'], +'CairoSurface::finish' => ['void'], +'CairoSurface::flush' => ['void'], +'CairoSurface::getContent' => ['int'], +'CairoSurface::getDeviceOffset' => ['array'], +'CairoSurface::getFontOptions' => ['', 'context'=>'cairocontext'], +'CairoSurface::getType' => ['int'], +'CairoSurface::markDirty' => ['void'], +'CairoSurface::markDirtyRectangle' => ['void', 'x'=>'string', 'y'=>'string', 'width'=>'string', 'height'=>'string'], +'CairoSurface::setDeviceOffset' => ['void', 'x'=>'string', 'y'=>'string'], +'CairoSurface::setFallbackResolution' => ['void', 'x'=>'string', 'y'=>'string'], +'CairoSurface::showPage' => ['', 'context'=>'cairocontext'], +'CairoSurface::status' => ['int', 'context'=>'cairocontext'], +'CairoSurface::writeToPng' => ['void', 'file'=>'string'], +'CairoSurfacePattern::__construct' => ['void', 'surface'=>'CairoSurface'], +'CairoSurfacePattern::getExtend' => ['int'], +'CairoSurfacePattern::getFilter' => ['int'], +'CairoSurfacePattern::getSurface' => ['void'], +'CairoSurfacePattern::setExtend' => ['void', 'extend'=>'int'], +'CairoSurfacePattern::setFilter' => ['void', 'filter'=>'string'], +'CairoSvgSurface::__construct' => ['void', 'file'=>'string', 'width'=>'float', 'height'=>'float'], +'CairoSvgSurface::getVersions' => ['array'], +'CairoSvgSurface::restrictToVersion' => ['void', 'version'=>'string'], +'CairoSvgSurface::versionToString' => ['string', 'version'=>'int'], +'cal_days_in_month' => ['int', 'calendar'=>'int', 'month'=>'int', 'year'=>'int'], +'cal_from_jd' => ['false|array{date:string,month:int,day:int,year:int,dow:int,abbrevdayname:string,dayname:string,abbrevmonth:string,monthname:string}', 'jd'=>'int', 'calendar'=>'int'], +'cal_info' => ['array', 'calendar='=>'int'], +'cal_to_jd' => ['int', 'calendar'=>'int', 'month'=>'int', 'day'=>'int', 'year'=>'int'], +'calcul_hmac' => ['string', 'clent'=>'string', 'siretcode'=>'string', 'price'=>'string', 'reference'=>'string', 'validity'=>'string', 'taxation'=>'string', 'devise'=>'string', 'language'=>'string'], +'calculhmac' => ['string', 'clent'=>'string', 'data'=>'string'], +'call_user_func' => ['mixed|false', 'function'=>'callable', '...args='=>'mixed'], +'call_user_func_array' => ['mixed|false', 'function'=>'callable', 'args'=>'list'], +'call_user_method' => ['mixed', 'method_name'=>'string', 'object'=>'object', 'parameter='=>'mixed', '...args='=>'mixed'], +'call_user_method_array' => ['mixed', 'method_name'=>'string', 'object'=>'object', 'params'=>'list'], +'CallbackFilterIterator::__construct' => ['void', 'iterator'=>'Iterator', 'func'=>'callable(mixed):bool|callable(mixed,mixed):bool|callable(mixed,mixed,mixed):bool'], +'CallbackFilterIterator::accept' => ['bool'], +'CallbackFilterIterator::current' => ['mixed'], +'CallbackFilterIterator::getInnerIterator' => ['Iterator'], +'CallbackFilterIterator::key' => ['mixed'], +'CallbackFilterIterator::next' => ['void'], +'CallbackFilterIterator::rewind' => ['void'], +'CallbackFilterIterator::valid' => ['bool'], +'ceil' => ['float|int', 'number'=>'float'], +'chdb::__construct' => ['void', 'pathname'=>'string'], +'chdb::get' => ['string', 'key'=>'string'], +'chdb_create' => ['bool', 'pathname'=>'string', 'data'=>'array'], +'chdir' => ['bool', 'directory'=>'string'], +'checkdate' => ['bool', 'month'=>'int', 'day'=>'int', 'year'=>'int'], +'checkdnsrr' => ['bool', 'host'=>'string', 'type='=>'string'], +'chgrp' => ['bool', 'filename'=>'string', 'group'=>'string|int'], +'chmod' => ['bool', 'filename'=>'string', 'mode'=>'int'], +'chop' => ['string', 'string'=>'string', 'character_mask='=>'string'], +'chown' => ['bool', 'filename'=>'string', 'user'=>'string|int'], +'chr' => ['string', 'ascii'=>'int'], +'chroot' => ['bool', 'directory'=>'string'], +'chunk_split' => ['string', 'string'=>'string', 'chunklen='=>'int', 'ending='=>'string'], +'class_alias' => ['bool', 'user_class_name'=>'string', 'alias_name'=>'string', 'autoload='=>'bool'], +'class_exists' => ['bool', 'classname'=>'string', 'autoload='=>'bool'], +'class_implements' => ['array|false', 'what'=>'object|string', 'autoload='=>'bool'], +'class_parents' => ['array|false', 'instance'=>'object|string', 'autoload='=>'bool'], +'class_uses' => ['array|false', 'what'=>'object|string', 'autoload='=>'bool'], +'classkit_import' => ['array', 'filename'=>'string'], +'classkit_method_add' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'args'=>'string', 'code'=>'string', 'flags='=>'int'], +'classkit_method_copy' => ['bool', 'dclass'=>'string', 'dmethod'=>'string', 'sclass'=>'string', 'smethod='=>'string'], +'classkit_method_redefine' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'args'=>'string', 'code'=>'string', 'flags='=>'int'], +'classkit_method_remove' => ['bool', 'classname'=>'string', 'methodname'=>'string'], +'classkit_method_rename' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'newname'=>'string'], +'classObj::__construct' => ['void', 'layer'=>'layerObj', 'class'=>'classObj'], +'classObj::addLabel' => ['int', 'label'=>'labelObj'], +'classObj::convertToString' => ['string'], +'classObj::createLegendIcon' => ['imageObj', 'width'=>'int', 'height'=>'int'], +'classObj::deletestyle' => ['int', 'index'=>'int'], +'classObj::drawLegendIcon' => ['int', 'width'=>'int', 'height'=>'int', 'im'=>'imageObj', 'dstX'=>'int', 'dstY'=>'int'], +'classObj::free' => ['void'], +'classObj::getExpressionString' => ['string'], +'classObj::getLabel' => ['labelObj', 'index'=>'int'], +'classObj::getMetaData' => ['int', 'name'=>'string'], +'classObj::getStyle' => ['styleObj', 'index'=>'int'], +'classObj::getTextString' => ['string'], +'classObj::movestyledown' => ['int', 'index'=>'int'], +'classObj::movestyleup' => ['int', 'index'=>'int'], +'classObj::ms_newClassObj' => ['classObj', 'layer'=>'layerObj', 'class'=>'classObj'], +'classObj::removeLabel' => ['labelObj', 'index'=>'int'], +'classObj::removeMetaData' => ['int', 'name'=>'string'], +'classObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'classObj::setExpression' => ['int', 'expression'=>'string'], +'classObj::setMetaData' => ['int', 'name'=>'string', 'value'=>'string'], +'classObj::settext' => ['int', 'text'=>'string'], +'classObj::updateFromString' => ['int', 'snippet'=>'string'], +'clearstatcache' => ['void', 'clear_realpath_cache='=>'bool', 'filename='=>'string'], +'cli_get_process_title' => ['string'], +'cli_set_process_title' => ['bool', 'title'=>'string'], +'ClosedGeneratorException::__clone' => ['void'], +'ClosedGeneratorException::__toString' => ['string'], +'ClosedGeneratorException::getCode' => ['int'], +'ClosedGeneratorException::getFile' => ['string'], +'ClosedGeneratorException::getLine' => ['int'], +'ClosedGeneratorException::getMessage' => ['string'], +'ClosedGeneratorException::getPrevious' => ['Throwable|ClosedGeneratorException|null'], +'ClosedGeneratorException::getTrace' => ['list>'], +'ClosedGeneratorException::getTraceAsString' => ['string'], +'closedir' => ['void', 'dir_handle='=>'resource'], +'closelog' => ['bool'], +'Closure::__construct' => ['void'], +'Closure::__invoke' => ['', '...args='=>''], +'Closure::bind' => ['Closure|false', 'old'=>'Closure', 'to'=>'?object', 'scope='=>'object|string'], +'Closure::bindTo' => ['Closure|false', 'new'=>'?object', 'newscope='=>'object|string'], +'Closure::call' => ['', 'to'=>'object', '...parameters='=>''], +'Closure::fromCallable' => ['Closure', 'callable'=>'callable'], +'clusterObj::convertToString' => ['string'], +'clusterObj::getFilterString' => ['string'], +'clusterObj::getGroupString' => ['string'], +'clusterObj::setFilter' => ['int', 'expression'=>'string'], +'clusterObj::setGroup' => ['int', 'expression'=>'string'], +'Collator::__construct' => ['void', 'locale'=>'string'], +'Collator::asort' => ['bool', '&rw_arr'=>'array', 'sort_flag='=>'int'], +'Collator::compare' => ['int|false', 'string1'=>'string', 'string2'=>'string'], +'Collator::create' => ['?Collator', 'locale'=>'string'], +'Collator::getAttribute' => ['int|false', 'attr'=>'int'], +'Collator::getErrorCode' => ['int'], +'Collator::getErrorMessage' => ['string'], +'Collator::getLocale' => ['string', 'type'=>'int'], +'Collator::getSortKey' => ['string|false', 'string'=>'string'], +'Collator::getStrength' => ['int|false'], +'Collator::setAttribute' => ['bool', 'attr'=>'int', 'value'=>'int'], +'Collator::setStrength' => ['bool', 'strength'=>'int'], +'Collator::sort' => ['bool', '&rw_arr'=>'array', 'sort_flags='=>'int'], +'Collator::sortWithSortKeys' => ['bool', '&rw_arr'=>'array'], +'collator_asort' => ['bool', 'coll'=>'collator', '&rw_arr'=>'array', 'sort_flag='=>'int'], +'collator_compare' => ['int', 'coll'=>'collator', 'string1'=>'string', 'string2'=>'string'], +'collator_create' => ['Collator', 'locale'=>'string'], +'collator_get_attribute' => ['int|false', 'coll'=>'collator', 'attr'=>'int'], +'collator_get_error_code' => ['int', 'coll'=>'collator'], +'collator_get_error_message' => ['string', 'coll'=>'collator'], +'collator_get_locale' => ['string', 'coll'=>'collator', 'type'=>'int'], +'collator_get_sort_key' => ['string', 'coll'=>'collator', 'string'=>'string'], +'collator_get_strength' => ['int|false', 'coll'=>'collator'], +'collator_set_attribute' => ['bool', 'coll'=>'collator', 'attr'=>'int', 'value'=>'int'], +'collator_set_strength' => ['bool', 'coll'=>'collator', 'strength'=>'int'], +'collator_sort' => ['bool', 'coll'=>'collator', '&rw_arr'=>'array', 'sort_flag='=>'int'], +'collator_sort_with_sort_keys' => ['bool', 'coll'=>'collator', '&rw_arr'=>'array'], +'Collectable::isGarbage' => ['bool'], +'Collectable::setGarbage' => ['void'], +'colorObj::setHex' => ['int', 'hex'=>'string'], +'colorObj::toHex' => ['string'], +'COM::__call' => ['', 'name'=>'', 'args'=>''], +'COM::__construct' => ['void', 'module_name'=>'string', 'server_name='=>'mixed', 'codepage='=>'int', 'typelib='=>'string'], +'COM::__get' => ['', 'name'=>''], +'COM::__set' => ['void', 'name'=>'', 'value'=>''], +'com_addref' => [''], +'com_create_guid' => ['string'], +'com_event_sink' => ['bool', 'comobject'=>'VARIANT', 'sinkobject'=>'object', 'sinkinterface='=>'mixed'], +'com_get_active_object' => ['VARIANT', 'progid'=>'string', 'code_page='=>'int'], +'com_isenum' => ['bool', 'com_module'=>'variant'], +'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'int'], +'com_message_pump' => ['bool', 'timeoutms='=>'int'], +'com_print_typeinfo' => ['bool', 'comobject_or_typelib'=>'object', 'dispinterface='=>'string', 'wantsink='=>'bool'], +'commonmark\cql::__invoke' => ['', 'root'=>'CommonMark\Node', 'handler'=>'callable'], +'commonmark\interfaces\ivisitable::accept' => ['void', 'visitor'=>'CommonMark\Interfaces\IVisitor'], +'commonmark\interfaces\ivisitor::enter' => ['?int|IVisitable', 'visitable'=>'IVisitable'], +'commonmark\interfaces\ivisitor::leave' => ['?int|IVisitable', 'visitable'=>'IVisitable'], +'commonmark\node::accept' => ['void', 'visitor'=>'CommonMark\Interfaces\IVisitor'], +'commonmark\node::appendChild' => ['CommonMark\Node', 'child'=>'CommonMark\Node'], +'commonmark\node::insertAfter' => ['CommonMark\Node', 'sibling'=>'CommonMark\Node'], +'commonmark\node::insertBefore' => ['CommonMark\Node', 'sibling'=>'CommonMark\Node'], +'commonmark\node::prependChild' => ['CommonMark\Node', 'child'=>'CommonMark\Node'], +'commonmark\node::replace' => ['CommonMark\Node', 'target'=>'CommonMark\Node'], +'commonmark\node::unlink' => ['void'], +'commonmark\parse' => ['CommonMark\Node', 'content'=>'string', 'options='=>'int'], +'commonmark\parser::finish' => ['CommonMark\Node'], +'commonmark\parser::parse' => ['void', 'buffer'=>'string'], +'commonmark\render' => ['string', 'node'=>'CommonMark\Node', 'options='=>'int', 'width='=>'int'], +'commonmark\render\html' => ['string', 'node'=>'CommonMark\Node', 'options='=>'int'], +'commonmark\render\latex' => ['string', 'node'=>'CommonMark\Node', 'options='=>'int', 'width='=>'int'], +'commonmark\render\man' => ['string', 'node'=>'CommonMark\Node', 'options='=>'int', 'width='=>'int'], +'commonmark\render\xml' => ['string', 'node'=>'CommonMark\Node', 'options='=>'int'], +'compact' => ['array', 'var_name'=>'string|array', '...var_names='=>'string|array'], +'COMPersistHelper::__construct' => ['void', 'com_object'=>'object'], +'COMPersistHelper::GetCurFile' => ['string'], +'COMPersistHelper::GetCurFileName' => ['string'], +'COMPersistHelper::GetMaxStreamSize' => ['int'], +'COMPersistHelper::InitNew' => ['int'], +'COMPersistHelper::LoadFromFile' => ['bool', 'filename'=>'string', 'flags'=>'int'], +'COMPersistHelper::LoadFromStream' => ['', 'stream'=>''], +'COMPersistHelper::SaveToFile' => ['bool', 'filename'=>'string', 'remember'=>'bool'], +'COMPersistHelper::SaveToStream' => ['int', 'stream'=>''], +'componere\abstract\definition::addInterface' => ['Componere\Abstract\Definition', 'interface'=>'string'], +'componere\abstract\definition::addMethod' => ['Componere\Abstract\Definition', 'name'=>'string', 'method'=>'Componere\Method'], +'componere\abstract\definition::addTrait' => ['Componere\Abstract\Definition', 'trait'=>'string'], +'componere\abstract\definition::getReflector' => ['ReflectionClass'], +'componere\cast' => ['object', 'arg1'=>'string', 'object'=>'object'], +'componere\cast_by_ref' => ['object', 'arg1'=>'string', 'object'=>'object'], +'componere\definition::addConstant' => ['Componere\Definition', 'name'=>'string', 'value'=>'Componere\Value'], +'componere\definition::addProperty' => ['Componere\Definition', 'name'=>'string', 'value'=>'Componere\Value'], +'componere\definition::getClosure' => ['Closure', 'name'=>'string'], +'componere\definition::getClosures' => ['Closure[]'], +'componere\definition::isRegistered' => ['bool'], +'componere\definition::register' => ['void'], +'componere\method::getReflector' => ['ReflectionMethod'], +'componere\method::setPrivate' => ['Method'], +'componere\method::setProtected' => ['Method'], +'componere\method::setStatic' => ['Method'], +'componere\patch::apply' => ['void'], +'componere\patch::derive' => ['Componere\Patch', 'instance'=>'object'], +'componere\patch::getClosure' => ['Closure', 'name'=>'string'], +'componere\patch::getClosures' => ['Closure[]'], +'componere\patch::isApplied' => ['bool'], +'componere\patch::revert' => ['void'], +'componere\value::hasDefault' => ['bool'], +'componere\value::isPrivate' => ['bool'], +'componere\value::isProtected' => ['bool'], +'componere\value::isStatic' => ['bool'], +'componere\value::setPrivate' => ['Value'], +'componere\value::setProtected' => ['Value'], +'componere\value::setStatic' => ['Value'], +'Cond::broadcast' => ['bool', 'condition'=>'long'], +'Cond::create' => ['long'], +'Cond::destroy' => ['bool', 'condition'=>'long'], +'Cond::signal' => ['bool', 'condition'=>'long'], +'Cond::wait' => ['bool', 'condition'=>'long', 'mutex'=>'long', 'timeout='=>'long'], +'confirm_pdo_ibm_compiled' => [''], +'connection_aborted' => ['int'], +'connection_status' => ['int'], +'connection_timeout' => ['int'], +'constant' => ['mixed', 'const_name'=>'string'], +'convert_cyr_string' => ['string', 'string'=>'string', 'from'=>'string', 'to'=>'string'], +'convert_uudecode' => ['string', 'data'=>'string'], +'convert_uuencode' => ['string', 'data'=>'string'], +'copy' => ['bool', 'source_file'=>'string', 'destination_file'=>'string', 'context='=>'resource'], +'cos' => ['float', 'number'=>'float'], +'cosh' => ['float', 'number'=>'float'], +'Couchbase\AnalyticsQuery::__construct' => ['void'], +'Couchbase\AnalyticsQuery::fromString' => ['Couchbase\AnalyticsQuery', 'statement'=>'string'], +'Couchbase\basicDecoderV1' => ['mixed', 'bytes'=>'string', 'flags'=>'int', 'datatype'=>'int', 'options'=>'array'], +'Couchbase\basicEncoderV1' => ['array', 'value'=>'mixed', 'options'=>'array'], +'Couchbase\BooleanFieldSearchQuery::__construct' => ['void'], +'Couchbase\BooleanFieldSearchQuery::boost' => ['Couchbase\BooleanFieldSearchQuery', 'boost'=>'float'], +'Couchbase\BooleanFieldSearchQuery::field' => ['Couchbase\BooleanFieldSearchQuery', 'field'=>'string'], +'Couchbase\BooleanFieldSearchQuery::jsonSerialize' => ['array'], +'Couchbase\BooleanSearchQuery::__construct' => ['void'], +'Couchbase\BooleanSearchQuery::boost' => ['Couchbase\BooleanSearchQuery', 'boost'=>'float'], +'Couchbase\BooleanSearchQuery::jsonSerialize' => ['array'], +'Couchbase\BooleanSearchQuery::must' => ['Couchbase\BooleanSearchQuery', '...queries='=>'array'], +'Couchbase\BooleanSearchQuery::mustNot' => ['Couchbase\BooleanSearchQuery', '...queries='=>'array'], +'Couchbase\BooleanSearchQuery::should' => ['Couchbase\BooleanSearchQuery', '...queries='=>'array'], +'Couchbase\Bucket::__construct' => ['void'], +'Couchbase\Bucket::__get' => ['int', 'name'=>'string'], +'Couchbase\Bucket::__set' => ['int', 'name'=>'string', 'value'=>'int'], +'Couchbase\Bucket::append' => ['Couchbase\Document|array', 'ids'=>'array|string', 'value'=>'mixed', 'options='=>'array'], +'Couchbase\Bucket::counter' => ['Couchbase\Document|array', 'ids'=>'array|string', 'delta='=>'int', 'options='=>'array'], +'Couchbase\Bucket::decryptFields' => ['array', 'document'=>'array', 'fieldOptions'=>'', 'prefix='=>'string'], +'Couchbase\Bucket::diag' => ['array', 'reportId='=>'string'], +'Couchbase\Bucket::encryptFields' => ['array', 'document'=>'array', 'fieldOptions'=>'', 'prefix='=>'string'], +'Couchbase\Bucket::get' => ['Couchbase\Document|array', 'ids'=>'array|string', 'options='=>'array'], +'Couchbase\Bucket::getAndLock' => ['Couchbase\Document|array', 'ids'=>'array|string', 'lockTime'=>'int', 'options='=>'array'], +'Couchbase\Bucket::getAndTouch' => ['Couchbase\Document|array', 'ids'=>'array|string', 'expiry'=>'int', 'options='=>'array'], +'Couchbase\Bucket::getFromReplica' => ['Couchbase\Document|array', 'ids'=>'array|string', 'options='=>'array'], +'Couchbase\Bucket::getName' => ['string'], +'Couchbase\Bucket::insert' => ['Couchbase\Document|array', 'ids'=>'array|string', 'value'=>'mixed', 'options='=>'array'], +'Couchbase\Bucket::listExists' => ['bool', 'id'=>'string', 'value'=>'mixed'], +'Couchbase\Bucket::listGet' => ['mixed', 'id'=>'string', 'index'=>'int'], +'Couchbase\Bucket::listPush' => ['', 'id'=>'string', 'value'=>'mixed'], +'Couchbase\Bucket::listRemove' => ['', 'id'=>'string', 'index'=>'int'], +'Couchbase\Bucket::listSet' => ['', 'id'=>'string', 'index'=>'int', 'value'=>'mixed'], +'Couchbase\Bucket::listShift' => ['', 'id'=>'string', 'value'=>'mixed'], +'Couchbase\Bucket::listSize' => ['int', 'id'=>'string'], +'Couchbase\Bucket::lookupIn' => ['Couchbase\LookupInBuilder', 'id'=>'string'], +'Couchbase\Bucket::manager' => ['Couchbase\BucketManager'], +'Couchbase\Bucket::mapAdd' => ['', 'id'=>'string', 'key'=>'string', 'value'=>'mixed'], +'Couchbase\Bucket::mapGet' => ['mixed', 'id'=>'string', 'key'=>'string'], +'Couchbase\Bucket::mapRemove' => ['', 'id'=>'string', 'key'=>'string'], +'Couchbase\Bucket::mapSize' => ['int', 'id'=>'string'], +'Couchbase\Bucket::mutateIn' => ['Couchbase\MutateInBuilder', 'id'=>'string', 'cas'=>'string'], +'Couchbase\Bucket::ping' => ['array', 'services='=>'int', 'reportId='=>'string'], +'Couchbase\Bucket::prepend' => ['Couchbase\Document|array', 'ids'=>'array|string', 'value'=>'mixed', 'options='=>'array'], +'Couchbase\Bucket::query' => ['object', 'query'=>'Couchbase\AnalyticsQuery|Couchbase\N1qlQuery|Couchbase\SearchQuery|Couchbase\SpatialViewQuery|Couchbase\ViewQuery', 'jsonAsArray='=>'bool'], +'Couchbase\Bucket::queueAdd' => ['', 'id'=>'string', 'value'=>'mixed'], +'Couchbase\Bucket::queueExists' => ['bool', 'id'=>'string', 'value'=>'mixed'], +'Couchbase\Bucket::queueRemove' => ['mixed', 'id'=>'string'], +'Couchbase\Bucket::queueSize' => ['int', 'id'=>'string'], +'Couchbase\Bucket::remove' => ['Couchbase\Document|array', 'ids'=>'array|string', 'options='=>'array'], +'Couchbase\Bucket::replace' => ['Couchbase\Document|array', 'ids'=>'array|string', 'value'=>'mixed', 'options='=>'array'], +'Couchbase\Bucket::retrieveIn' => ['Couchbase\DocumentFragment', 'id'=>'string', '...paths='=>'array'], +'Couchbase\Bucket::setAdd' => ['', 'id'=>'string', 'value'=>'bool|float|int|string'], +'Couchbase\Bucket::setExists' => ['bool', 'id'=>'string', 'value'=>'bool|float|int|string'], +'Couchbase\Bucket::setRemove' => ['', 'id'=>'string', 'value'=>'bool|float|int|string'], +'Couchbase\Bucket::setSize' => ['int', 'id'=>'string'], +'Couchbase\Bucket::setTranscoder' => ['', 'encoder'=>'callable', 'decoder'=>'callable'], +'Couchbase\Bucket::touch' => ['Couchbase\Document|array', 'ids'=>'array|string', 'expiry'=>'int', 'options='=>'array'], +'Couchbase\Bucket::unlock' => ['Couchbase\Document|array', 'ids'=>'array|string', 'options='=>'array'], +'Couchbase\Bucket::upsert' => ['Couchbase\Document|array', 'ids'=>'array|string', 'value'=>'mixed', 'options='=>'array'], +'Couchbase\BucketManager::__construct' => ['void'], +'Couchbase\BucketManager::createN1qlIndex' => ['', 'name'=>'string', 'fields'=>'array', 'whereClause='=>'string', 'ignoreIfExist='=>'bool', 'defer='=>'bool'], +'Couchbase\BucketManager::createN1qlPrimaryIndex' => ['', 'customName='=>'string', 'ignoreIfExist='=>'bool', 'defer='=>'bool'], +'Couchbase\BucketManager::dropN1qlIndex' => ['', 'name'=>'string', 'ignoreIfNotExist='=>'bool'], +'Couchbase\BucketManager::dropN1qlPrimaryIndex' => ['', 'customName='=>'string', 'ignoreIfNotExist='=>'bool'], +'Couchbase\BucketManager::flush' => [''], +'Couchbase\BucketManager::getDesignDocument' => ['array', 'name'=>'string'], +'Couchbase\BucketManager::info' => ['array'], +'Couchbase\BucketManager::insertDesignDocument' => ['', 'name'=>'string', 'document'=>'array'], +'Couchbase\BucketManager::listDesignDocuments' => ['array'], +'Couchbase\BucketManager::listN1qlIndexes' => ['array'], +'Couchbase\BucketManager::removeDesignDocument' => ['', 'name'=>'string'], +'Couchbase\BucketManager::upsertDesignDocument' => ['', 'name'=>'string', 'document'=>'array'], +'Couchbase\ClassicAuthenticator::bucket' => ['', 'name'=>'string', 'password'=>'string'], +'Couchbase\ClassicAuthenticator::cluster' => ['', 'username'=>'string', 'password'=>'string'], +'Couchbase\Cluster::__construct' => ['void', 'connstr'=>'string'], +'Couchbase\Cluster::authenticate' => ['null', 'authenticator'=>'Couchbase\Authenticator'], +'Couchbase\Cluster::authenticateAs' => ['null', 'username'=>'string', 'password'=>'string'], +'Couchbase\Cluster::manager' => ['Couchbase\ClusterManager', 'username='=>'string', 'password='=>'string'], +'Couchbase\Cluster::openBucket' => ['Couchbase\Bucket', 'name='=>'string', 'password='=>'string'], +'Couchbase\ClusterManager::__construct' => ['void'], +'Couchbase\ClusterManager::createBucket' => ['', 'name'=>'string', 'options='=>'array'], +'Couchbase\ClusterManager::getUser' => ['array', 'username'=>'string', 'domain='=>'int'], +'Couchbase\ClusterManager::info' => ['array'], +'Couchbase\ClusterManager::listBuckets' => ['array'], +'Couchbase\ClusterManager::listUsers' => ['array', 'domain='=>'int'], +'Couchbase\ClusterManager::removeBucket' => ['', 'name'=>'string'], +'Couchbase\ClusterManager::removeUser' => ['', 'name'=>'string', 'domain='=>'int'], +'Couchbase\ClusterManager::upsertUser' => ['', 'name'=>'string', 'settings'=>'Couchbase\UserSettings', 'domain='=>'int'], +'Couchbase\ConjunctionSearchQuery::__construct' => ['void'], +'Couchbase\ConjunctionSearchQuery::boost' => ['Couchbase\ConjunctionSearchQuery', 'boost'=>'float'], +'Couchbase\ConjunctionSearchQuery::every' => ['Couchbase\ConjunctionSearchQuery', '...queries='=>'array'], +'Couchbase\ConjunctionSearchQuery::jsonSerialize' => ['array'], +'Couchbase\DateRangeSearchFacet::__construct' => ['void'], +'Couchbase\DateRangeSearchFacet::addRange' => ['Couchbase\DateRangeSearchFacet', 'name'=>'string', 'start'=>'int|string', 'end'=>'int|string'], +'Couchbase\DateRangeSearchFacet::jsonSerialize' => ['array'], +'Couchbase\DateRangeSearchQuery::__construct' => ['void'], +'Couchbase\DateRangeSearchQuery::boost' => ['Couchbase\DateRangeSearchQuery', 'boost'=>'float'], +'Couchbase\DateRangeSearchQuery::dateTimeParser' => ['Couchbase\DateRangeSearchQuery', 'dateTimeParser'=>'string'], +'Couchbase\DateRangeSearchQuery::end' => ['Couchbase\DateRangeSearchQuery', 'end'=>'int|string', 'inclusive='=>'bool'], +'Couchbase\DateRangeSearchQuery::field' => ['Couchbase\DateRangeSearchQuery', 'field'=>'string'], +'Couchbase\DateRangeSearchQuery::jsonSerialize' => ['array'], +'Couchbase\DateRangeSearchQuery::start' => ['Couchbase\DateRangeSearchQuery', 'start'=>'int|string', 'inclusive='=>'bool'], +'Couchbase\defaultDecoder' => ['mixed', 'bytes'=>'string', 'flags'=>'int', 'datatype'=>'int'], +'Couchbase\defaultEncoder' => ['array', 'value'=>'mixed'], +'Couchbase\DisjunctionSearchQuery::__construct' => ['void'], +'Couchbase\DisjunctionSearchQuery::boost' => ['Couchbase\DisjunctionSearchQuery', 'boost'=>'float'], +'Couchbase\DisjunctionSearchQuery::either' => ['Couchbase\DisjunctionSearchQuery', '...queries='=>'array'], +'Couchbase\DisjunctionSearchQuery::jsonSerialize' => ['array'], +'Couchbase\DisjunctionSearchQuery::min' => ['Couchbase\DisjunctionSearchQuery', 'min'=>'int'], +'Couchbase\DocIdSearchQuery::__construct' => ['void'], +'Couchbase\DocIdSearchQuery::boost' => ['Couchbase\DocIdSearchQuery', 'boost'=>'float'], +'Couchbase\DocIdSearchQuery::docIds' => ['Couchbase\DocIdSearchQuery', '...documentIds='=>'array'], +'Couchbase\DocIdSearchQuery::field' => ['Couchbase\DocIdSearchQuery', 'field'=>'string'], +'Couchbase\DocIdSearchQuery::jsonSerialize' => ['array'], +'Couchbase\fastlzCompress' => ['string', 'data'=>'string'], +'Couchbase\fastlzDecompress' => ['string', 'data'=>'string'], +'Couchbase\GeoBoundingBoxSearchQuery::__construct' => ['void'], +'Couchbase\GeoBoundingBoxSearchQuery::boost' => ['Couchbase\GeoBoundingBoxSearchQuery', 'boost'=>'float'], +'Couchbase\GeoBoundingBoxSearchQuery::field' => ['Couchbase\GeoBoundingBoxSearchQuery', 'field'=>'string'], +'Couchbase\GeoBoundingBoxSearchQuery::jsonSerialize' => ['array'], +'Couchbase\GeoDistanceSearchQuery::__construct' => ['void'], +'Couchbase\GeoDistanceSearchQuery::boost' => ['Couchbase\GeoDistanceSearchQuery', 'boost'=>'float'], +'Couchbase\GeoDistanceSearchQuery::field' => ['Couchbase\GeoDistanceSearchQuery', 'field'=>'string'], +'Couchbase\GeoDistanceSearchQuery::jsonSerialize' => ['array'], +'Couchbase\LookupInBuilder::__construct' => ['void'], +'Couchbase\LookupInBuilder::execute' => ['Couchbase\DocumentFragment'], +'Couchbase\LookupInBuilder::exists' => ['Couchbase\LookupInBuilder', 'path'=>'string', 'options='=>'array'], +'Couchbase\LookupInBuilder::get' => ['Couchbase\LookupInBuilder', 'path'=>'string', 'options='=>'array'], +'Couchbase\LookupInBuilder::getCount' => ['Couchbase\LookupInBuilder', 'path'=>'string', 'options='=>'array'], +'Couchbase\MatchAllSearchQuery::__construct' => ['void'], +'Couchbase\MatchAllSearchQuery::boost' => ['Couchbase\MatchAllSearchQuery', 'boost'=>'float'], +'Couchbase\MatchAllSearchQuery::jsonSerialize' => ['array'], +'Couchbase\MatchNoneSearchQuery::__construct' => ['void'], +'Couchbase\MatchNoneSearchQuery::boost' => ['Couchbase\MatchNoneSearchQuery', 'boost'=>'float'], +'Couchbase\MatchNoneSearchQuery::jsonSerialize' => ['array'], +'Couchbase\MatchPhraseSearchQuery::__construct' => ['void'], +'Couchbase\MatchPhraseSearchQuery::analyzer' => ['Couchbase\MatchPhraseSearchQuery', 'analyzer'=>'string'], +'Couchbase\MatchPhraseSearchQuery::boost' => ['Couchbase\MatchPhraseSearchQuery', 'boost'=>'float'], +'Couchbase\MatchPhraseSearchQuery::field' => ['Couchbase\MatchPhraseSearchQuery', 'field'=>'string'], +'Couchbase\MatchPhraseSearchQuery::jsonSerialize' => ['array'], +'Couchbase\MatchSearchQuery::__construct' => ['void'], +'Couchbase\MatchSearchQuery::analyzer' => ['Couchbase\MatchSearchQuery', 'analyzer'=>'string'], +'Couchbase\MatchSearchQuery::boost' => ['Couchbase\MatchSearchQuery', 'boost'=>'float'], +'Couchbase\MatchSearchQuery::field' => ['Couchbase\MatchSearchQuery', 'field'=>'string'], +'Couchbase\MatchSearchQuery::fuzziness' => ['Couchbase\MatchSearchQuery', 'fuzziness'=>'int'], +'Couchbase\MatchSearchQuery::jsonSerialize' => ['array'], +'Couchbase\MatchSearchQuery::prefixLength' => ['Couchbase\MatchSearchQuery', 'prefixLength'=>'int'], +'Couchbase\MutateInBuilder::__construct' => ['void'], +'Couchbase\MutateInBuilder::arrayAddUnique' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array|bool'], +'Couchbase\MutateInBuilder::arrayAppend' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array|bool'], +'Couchbase\MutateInBuilder::arrayAppendAll' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'values'=>'array', 'options='=>'array|bool'], +'Couchbase\MutateInBuilder::arrayInsert' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array'], +'Couchbase\MutateInBuilder::arrayInsertAll' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'values'=>'array', 'options='=>'array'], +'Couchbase\MutateInBuilder::arrayPrepend' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array|bool'], +'Couchbase\MutateInBuilder::arrayPrependAll' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'values'=>'array', 'options='=>'array|bool'], +'Couchbase\MutateInBuilder::counter' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'delta'=>'int', 'options='=>'array|bool'], +'Couchbase\MutateInBuilder::execute' => ['Couchbase\DocumentFragment'], +'Couchbase\MutateInBuilder::insert' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array|bool'], +'Couchbase\MutateInBuilder::modeDocument' => ['', 'mode'=>'int'], +'Couchbase\MutateInBuilder::remove' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'options='=>'array'], +'Couchbase\MutateInBuilder::replace' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array'], +'Couchbase\MutateInBuilder::upsert' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array|bool'], +'Couchbase\MutateInBuilder::withExpiry' => ['Couchbase\MutateInBuilder', 'expiry'=>'Couchbase\expiry'], +'Couchbase\MutationState::__construct' => ['void'], +'Couchbase\MutationState::add' => ['', 'source'=>'Couchbase\Document|Couchbase\DocumentFragment|array'], +'Couchbase\MutationState::from' => ['Couchbase\MutationState', 'source'=>'Couchbase\Document|Couchbase\DocumentFragment|array'], +'Couchbase\MutationToken::__construct' => ['void'], +'Couchbase\MutationToken::bucketName' => ['string'], +'Couchbase\MutationToken::from' => ['', 'bucketName'=>'string', 'vbucketId'=>'int', 'vbucketUuid'=>'string', 'sequenceNumber'=>'string'], +'Couchbase\MutationToken::sequenceNumber' => ['string'], +'Couchbase\MutationToken::vbucketId' => ['int'], +'Couchbase\MutationToken::vbucketUuid' => ['string'], +'Couchbase\N1qlIndex::__construct' => ['void'], +'Couchbase\N1qlQuery::__construct' => ['void'], +'Couchbase\N1qlQuery::adhoc' => ['Couchbase\N1qlQuery', 'adhoc'=>'bool'], +'Couchbase\N1qlQuery::consistency' => ['Couchbase\N1qlQuery', 'consistency'=>'int'], +'Couchbase\N1qlQuery::consistentWith' => ['Couchbase\N1qlQuery', 'state'=>'Couchbase\MutationState'], +'Couchbase\N1qlQuery::crossBucket' => ['Couchbase\N1qlQuery', 'crossBucket'=>'bool'], +'Couchbase\N1qlQuery::fromString' => ['Couchbase\N1qlQuery', 'statement'=>'string'], +'Couchbase\N1qlQuery::maxParallelism' => ['Couchbase\N1qlQuery', 'maxParallelism'=>'int'], +'Couchbase\N1qlQuery::namedParams' => ['Couchbase\N1qlQuery', 'params'=>'array'], +'Couchbase\N1qlQuery::pipelineBatch' => ['Couchbase\N1qlQuery', 'pipelineBatch'=>'int'], +'Couchbase\N1qlQuery::pipelineCap' => ['Couchbase\N1qlQuery', 'pipelineCap'=>'int'], +'Couchbase\N1qlQuery::positionalParams' => ['Couchbase\N1qlQuery', 'params'=>'array'], +'Couchbase\N1qlQuery::profile' => ['', 'profileType'=>'string'], +'Couchbase\N1qlQuery::readonly' => ['Couchbase\N1qlQuery', 'readonly'=>'bool'], +'Couchbase\N1qlQuery::scanCap' => ['Couchbase\N1qlQuery', 'scanCap'=>'int'], +'Couchbase\NumericRangeSearchFacet::__construct' => ['void'], +'Couchbase\NumericRangeSearchFacet::addRange' => ['Couchbase\NumericRangeSearchFacet', 'name'=>'string', 'min'=>'float', 'max'=>'float'], +'Couchbase\NumericRangeSearchFacet::jsonSerialize' => ['array'], +'Couchbase\NumericRangeSearchQuery::__construct' => ['void'], +'Couchbase\NumericRangeSearchQuery::boost' => ['Couchbase\NumericRangeSearchQuery', 'boost'=>'float'], +'Couchbase\NumericRangeSearchQuery::field' => ['Couchbase\NumericRangeSearchQuery', 'field'=>'string'], +'Couchbase\NumericRangeSearchQuery::jsonSerialize' => ['array'], +'Couchbase\NumericRangeSearchQuery::max' => ['Couchbase\NumericRangeSearchQuery', 'max'=>'float', 'inclusive='=>'bool'], +'Couchbase\NumericRangeSearchQuery::min' => ['Couchbase\NumericRangeSearchQuery', 'min'=>'float', 'inclusive='=>'bool'], +'Couchbase\passthruDecoder' => ['string', 'bytes'=>'string', 'flags'=>'int', 'datatype'=>'int'], +'Couchbase\passthruEncoder' => ['array', 'value'=>'string'], +'Couchbase\PasswordAuthenticator::password' => ['Couchbase\PasswordAuthenticator', 'password'=>'string'], +'Couchbase\PasswordAuthenticator::username' => ['Couchbase\PasswordAuthenticator', 'username'=>'string'], +'Couchbase\PhraseSearchQuery::__construct' => ['void'], +'Couchbase\PhraseSearchQuery::boost' => ['Couchbase\PhraseSearchQuery', 'boost'=>'float'], +'Couchbase\PhraseSearchQuery::field' => ['Couchbase\PhraseSearchQuery', 'field'=>'string'], +'Couchbase\PhraseSearchQuery::jsonSerialize' => ['array'], +'Couchbase\PrefixSearchQuery::__construct' => ['void'], +'Couchbase\PrefixSearchQuery::boost' => ['Couchbase\PrefixSearchQuery', 'boost'=>'float'], +'Couchbase\PrefixSearchQuery::field' => ['Couchbase\PrefixSearchQuery', 'field'=>'string'], +'Couchbase\PrefixSearchQuery::jsonSerialize' => ['array'], +'Couchbase\QueryStringSearchQuery::__construct' => ['void'], +'Couchbase\QueryStringSearchQuery::boost' => ['Couchbase\QueryStringSearchQuery', 'boost'=>'float'], +'Couchbase\QueryStringSearchQuery::jsonSerialize' => ['array'], +'Couchbase\RegexpSearchQuery::__construct' => ['void'], +'Couchbase\RegexpSearchQuery::boost' => ['Couchbase\RegexpSearchQuery', 'boost'=>'float'], +'Couchbase\RegexpSearchQuery::field' => ['Couchbase\RegexpSearchQuery', 'field'=>'string'], +'Couchbase\RegexpSearchQuery::jsonSerialize' => ['array'], +'Couchbase\SearchQuery::__construct' => ['void', 'indexName'=>'string', 'queryPart'=>'Couchbase\SearchQueryPart'], +'Couchbase\SearchQuery::addFacet' => ['Couchbase\SearchQuery', 'name'=>'string', 'facet'=>'Couchbase\SearchFacet'], +'Couchbase\SearchQuery::boolean' => ['Couchbase\BooleanSearchQuery'], +'Couchbase\SearchQuery::booleanField' => ['Couchbase\BooleanFieldSearchQuery', 'value'=>'bool'], +'Couchbase\SearchQuery::conjuncts' => ['Couchbase\ConjunctionSearchQuery', '...queries='=>'array'], +'Couchbase\SearchQuery::consistentWith' => ['Couchbase\SearchQuery', 'state'=>'Couchbase\MutationState'], +'Couchbase\SearchQuery::dateRange' => ['Couchbase\DateRangeSearchQuery'], +'Couchbase\SearchQuery::dateRangeFacet' => ['Couchbase\DateRangeSearchFacet', 'field'=>'string', 'limit'=>'int'], +'Couchbase\SearchQuery::disjuncts' => ['Couchbase\DisjunctionSearchQuery', '...queries='=>'array'], +'Couchbase\SearchQuery::docId' => ['Couchbase\DocIdSearchQuery', '...documentIds='=>'array'], +'Couchbase\SearchQuery::explain' => ['Couchbase\SearchQuery', 'explain'=>'bool'], +'Couchbase\SearchQuery::fields' => ['Couchbase\SearchQuery', '...fields='=>'array'], +'Couchbase\SearchQuery::geoBoundingBox' => ['Couchbase\GeoBoundingBoxSearchQuery', 'topLeftLongitude'=>'float', 'topLeftLatitude'=>'float', 'bottomRightLongitude'=>'float', 'bottomRightLatitude'=>'float'], +'Couchbase\SearchQuery::geoDistance' => ['Couchbase\GeoDistanceSearchQuery', 'longitude'=>'float', 'latitude'=>'float', 'distance'=>'string'], +'Couchbase\SearchQuery::highlight' => ['Couchbase\SearchQuery', 'style'=>'string', '...fields='=>'array'], +'Couchbase\SearchQuery::jsonSerialize' => ['array'], +'Couchbase\SearchQuery::limit' => ['Couchbase\SearchQuery', 'limit'=>'int'], +'Couchbase\SearchQuery::match' => ['Couchbase\MatchSearchQuery', 'match'=>'string'], +'Couchbase\SearchQuery::matchAll' => ['Couchbase\MatchAllSearchQuery'], +'Couchbase\SearchQuery::matchNone' => ['Couchbase\MatchNoneSearchQuery'], +'Couchbase\SearchQuery::matchPhrase' => ['Couchbase\MatchPhraseSearchQuery', '...terms='=>'array'], +'Couchbase\SearchQuery::numericRange' => ['Couchbase\NumericRangeSearchQuery'], +'Couchbase\SearchQuery::numericRangeFacet' => ['Couchbase\NumericRangeSearchFacet', 'field'=>'string', 'limit'=>'int'], +'Couchbase\SearchQuery::prefix' => ['Couchbase\PrefixSearchQuery', 'prefix'=>'string'], +'Couchbase\SearchQuery::queryString' => ['Couchbase\QueryStringSearchQuery', 'queryString'=>'string'], +'Couchbase\SearchQuery::regexp' => ['Couchbase\RegexpSearchQuery', 'regexp'=>'string'], +'Couchbase\SearchQuery::serverSideTimeout' => ['Couchbase\SearchQuery', 'serverSideTimeout'=>'int'], +'Couchbase\SearchQuery::skip' => ['Couchbase\SearchQuery', 'skip'=>'int'], +'Couchbase\SearchQuery::sort' => ['Couchbase\SearchQuery', '...sort='=>'array'], +'Couchbase\SearchQuery::term' => ['Couchbase\TermSearchQuery', 'term'=>'string'], +'Couchbase\SearchQuery::termFacet' => ['Couchbase\TermSearchFacet', 'field'=>'string', 'limit'=>'int'], +'Couchbase\SearchQuery::termRange' => ['Couchbase\TermRangeSearchQuery'], +'Couchbase\SearchQuery::wildcard' => ['Couchbase\WildcardSearchQuery', 'wildcard'=>'string'], +'Couchbase\SearchSort::__construct' => ['void'], +'Couchbase\SearchSort::field' => ['Couchbase\SearchSortField', 'field'=>'string'], +'Couchbase\SearchSort::geoDistance' => ['Couchbase\SearchSortGeoDistance', 'field'=>'string', 'longitude'=>'float', 'latitude'=>'float'], +'Couchbase\SearchSort::id' => ['Couchbase\SearchSortId'], +'Couchbase\SearchSort::score' => ['Couchbase\SearchSortScore'], +'Couchbase\SearchSortField::__construct' => ['void'], +'Couchbase\SearchSortField::descending' => ['Couchbase\SearchSortField', 'descending'=>'bool'], +'Couchbase\SearchSortField::field' => ['Couchbase\SearchSortField', 'field'=>'string'], +'Couchbase\SearchSortField::geoDistance' => ['Couchbase\SearchSortGeoDistance', 'field'=>'string', 'longitude'=>'float', 'latitude'=>'float'], +'Couchbase\SearchSortField::id' => ['Couchbase\SearchSortId'], +'Couchbase\SearchSortField::jsonSerialize' => ['mixed'], +'Couchbase\SearchSortField::missing' => ['', 'missing'=>'string'], +'Couchbase\SearchSortField::mode' => ['', 'mode'=>'string'], +'Couchbase\SearchSortField::score' => ['Couchbase\SearchSortScore'], +'Couchbase\SearchSortField::type' => ['', 'type'=>'string'], +'Couchbase\SearchSortGeoDistance::__construct' => ['void'], +'Couchbase\SearchSortGeoDistance::descending' => ['Couchbase\SearchSortGeoDistance', 'descending'=>'bool'], +'Couchbase\SearchSortGeoDistance::field' => ['Couchbase\SearchSortField', 'field'=>'string'], +'Couchbase\SearchSortGeoDistance::geoDistance' => ['Couchbase\SearchSortGeoDistance', 'field'=>'string', 'longitude'=>'float', 'latitude'=>'float'], +'Couchbase\SearchSortGeoDistance::id' => ['Couchbase\SearchSortId'], +'Couchbase\SearchSortGeoDistance::jsonSerialize' => ['mixed'], +'Couchbase\SearchSortGeoDistance::score' => ['Couchbase\SearchSortScore'], +'Couchbase\SearchSortGeoDistance::unit' => ['Couchbase\SearchSortGeoDistance', 'unit'=>'string'], +'Couchbase\SearchSortId::__construct' => ['void'], +'Couchbase\SearchSortId::descending' => ['Couchbase\SearchSortId', 'descending'=>'bool'], +'Couchbase\SearchSortId::field' => ['Couchbase\SearchSortField', 'field'=>'string'], +'Couchbase\SearchSortId::geoDistance' => ['Couchbase\SearchSortGeoDistance', 'field'=>'string', 'longitude'=>'float', 'latitude'=>'float'], +'Couchbase\SearchSortId::id' => ['Couchbase\SearchSortId'], +'Couchbase\SearchSortId::jsonSerialize' => ['mixed'], +'Couchbase\SearchSortId::score' => ['Couchbase\SearchSortScore'], +'Couchbase\SearchSortScore::__construct' => ['void'], +'Couchbase\SearchSortScore::descending' => ['Couchbase\SearchSortScore', 'descending'=>'bool'], +'Couchbase\SearchSortScore::field' => ['Couchbase\SearchSortField', 'field'=>'string'], +'Couchbase\SearchSortScore::geoDistance' => ['Couchbase\SearchSortGeoDistance', 'field'=>'string', 'longitude'=>'float', 'latitude'=>'float'], +'Couchbase\SearchSortScore::id' => ['Couchbase\SearchSortId'], +'Couchbase\SearchSortScore::jsonSerialize' => ['mixed'], +'Couchbase\SearchSortScore::score' => ['Couchbase\SearchSortScore'], +'Couchbase\SpatialViewQuery::__construct' => ['void'], +'Couchbase\SpatialViewQuery::bbox' => ['Couchbase\SpatialViewQuery', 'bbox'=>'array'], +'Couchbase\SpatialViewQuery::consistency' => ['Couchbase\SpatialViewQuery', 'consistency'=>'int'], +'Couchbase\SpatialViewQuery::custom' => ['', 'customParameters'=>'array'], +'Couchbase\SpatialViewQuery::encode' => ['array'], +'Couchbase\SpatialViewQuery::endRange' => ['Couchbase\SpatialViewQuery', 'range'=>'array'], +'Couchbase\SpatialViewQuery::limit' => ['Couchbase\SpatialViewQuery', 'limit'=>'int'], +'Couchbase\SpatialViewQuery::order' => ['Couchbase\SpatialViewQuery', 'order'=>'int'], +'Couchbase\SpatialViewQuery::skip' => ['Couchbase\SpatialViewQuery', 'skip'=>'int'], +'Couchbase\SpatialViewQuery::startRange' => ['Couchbase\SpatialViewQuery', 'range'=>'array'], +'Couchbase\TermRangeSearchQuery::__construct' => ['void'], +'Couchbase\TermRangeSearchQuery::boost' => ['Couchbase\TermRangeSearchQuery', 'boost'=>'float'], +'Couchbase\TermRangeSearchQuery::field' => ['Couchbase\TermRangeSearchQuery', 'field'=>'string'], +'Couchbase\TermRangeSearchQuery::jsonSerialize' => ['array'], +'Couchbase\TermRangeSearchQuery::max' => ['Couchbase\TermRangeSearchQuery', 'max'=>'string', 'inclusive='=>'bool'], +'Couchbase\TermRangeSearchQuery::min' => ['Couchbase\TermRangeSearchQuery', 'min'=>'string', 'inclusive='=>'bool'], +'Couchbase\TermSearchFacet::__construct' => ['void'], +'Couchbase\TermSearchFacet::jsonSerialize' => ['array'], +'Couchbase\TermSearchQuery::__construct' => ['void'], +'Couchbase\TermSearchQuery::boost' => ['Couchbase\TermSearchQuery', 'boost'=>'float'], +'Couchbase\TermSearchQuery::field' => ['Couchbase\TermSearchQuery', 'field'=>'string'], +'Couchbase\TermSearchQuery::fuzziness' => ['Couchbase\TermSearchQuery', 'fuzziness'=>'int'], +'Couchbase\TermSearchQuery::jsonSerialize' => ['array'], +'Couchbase\TermSearchQuery::prefixLength' => ['Couchbase\TermSearchQuery', 'prefixLength'=>'int'], +'Couchbase\UserSettings::fullName' => ['Couchbase\UserSettings', 'fullName'=>'string'], +'Couchbase\UserSettings::password' => ['Couchbase\UserSettings', 'password'=>'string'], +'Couchbase\UserSettings::role' => ['Couchbase\UserSettings', 'role'=>'string', 'bucket='=>'string'], +'Couchbase\ViewQuery::__construct' => ['void'], +'Couchbase\ViewQuery::consistency' => ['Couchbase\ViewQuery', 'consistency'=>'int'], +'Couchbase\ViewQuery::custom' => ['Couchbase\ViewQuery', 'customParameters'=>'array'], +'Couchbase\ViewQuery::encode' => ['array'], +'Couchbase\ViewQuery::from' => ['Couchbase\ViewQuery', 'designDocumentName'=>'string', 'viewName'=>'string'], +'Couchbase\ViewQuery::fromSpatial' => ['Couchbase\SpatialViewQuery', 'designDocumentName'=>'string', 'viewName'=>'string'], +'Couchbase\ViewQuery::group' => ['Couchbase\ViewQuery', 'group'=>'bool'], +'Couchbase\ViewQuery::groupLevel' => ['Couchbase\ViewQuery', 'groupLevel'=>'int'], +'Couchbase\ViewQuery::idRange' => ['Couchbase\ViewQuery', 'startKeyDocumentId'=>'string', 'endKeyDocumentId'=>'string'], +'Couchbase\ViewQuery::key' => ['Couchbase\ViewQuery', 'key'=>'mixed'], +'Couchbase\ViewQuery::keys' => ['Couchbase\ViewQuery', 'keys'=>'array'], +'Couchbase\ViewQuery::limit' => ['Couchbase\ViewQuery', 'limit'=>'int'], +'Couchbase\ViewQuery::order' => ['Couchbase\ViewQuery', 'order'=>'int'], +'Couchbase\ViewQuery::range' => ['Couchbase\ViewQuery', 'startKey'=>'mixed', 'endKey'=>'mixed', 'inclusiveEnd='=>'bool'], +'Couchbase\ViewQuery::reduce' => ['Couchbase\ViewQuery', 'reduce'=>'bool'], +'Couchbase\ViewQuery::skip' => ['Couchbase\ViewQuery', 'skip'=>'int'], +'Couchbase\ViewQueryEncodable::encode' => ['array'], +'Couchbase\WildcardSearchQuery::__construct' => ['void'], +'Couchbase\WildcardSearchQuery::boost' => ['Couchbase\WildcardSearchQuery', 'boost'=>'float'], +'Couchbase\WildcardSearchQuery::field' => ['Couchbase\WildcardSearchQuery', 'field'=>'string'], +'Couchbase\WildcardSearchQuery::jsonSerialize' => ['array'], +'Couchbase\zlibCompress' => ['string', 'data'=>'string'], +'Couchbase\zlibDecompress' => ['string', 'data'=>'string'], +'count' => ['int', 'value'=>'Countable|array|SimpleXMLElement|ResourceBundle', 'mode='=>'int'], +'count_chars' => ['mixed', 'input'=>'string', 'mode='=>'int'], +'Countable::count' => ['int'], +'crack_check' => ['bool', 'dictionary'=>'', 'password'=>'string'], +'crack_closedict' => ['bool', 'dictionary='=>'resource'], +'crack_getlastmessage' => ['string'], +'crack_opendict' => ['resource|false', 'dictionary'=>'string'], +'crash' => [''], +'crc32' => ['int', 'string'=>'string'], +'create_function' => ['string', 'args'=>'string', 'code'=>'string'], +'crypt' => ['string', 'string'=>'string', 'salt='=>'string'], +'ctype_alnum' => ['bool', 'text'=>'string|int'], +'ctype_alpha' => ['bool', 'text'=>'string|int'], +'ctype_cntrl' => ['bool', 'text'=>'string|int'], +'ctype_digit' => ['bool', 'text'=>'string|int'], +'ctype_graph' => ['bool', 'text'=>'string|int'], +'ctype_lower' => ['bool', 'text'=>'string|int'], +'ctype_print' => ['bool', 'text'=>'string|int'], +'ctype_punct' => ['bool', 'text'=>'string|int'], +'ctype_space' => ['bool', 'text'=>'string|int'], +'ctype_upper' => ['bool', 'text'=>'string|int'], +'ctype_xdigit' => ['bool', 'text'=>'string|int'], +'cubrid_affected_rows' => ['int', 'req_identifier='=>''], +'cubrid_bind' => ['bool', 'req_identifier'=>'resource', 'bind_param'=>'int', 'bind_value'=>'mixed', 'bind_value_type='=>'string'], +'cubrid_client_encoding' => ['string', 'conn_identifier='=>''], +'cubrid_close' => ['bool', 'conn_identifier='=>''], +'cubrid_close_prepare' => ['bool', 'req_identifier'=>'resource'], +'cubrid_close_request' => ['bool', 'req_identifier'=>'resource'], +'cubrid_col_get' => ['array', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string'], +'cubrid_col_size' => ['int', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string'], +'cubrid_column_names' => ['array', 'req_identifier'=>'resource'], +'cubrid_column_types' => ['array', 'req_identifier'=>'resource'], +'cubrid_commit' => ['bool', 'conn_identifier'=>'resource'], +'cubrid_connect' => ['resource', 'host'=>'string', 'port'=>'int', 'dbname'=>'string', 'userid='=>'string', 'passwd='=>'string'], +'cubrid_connect_with_url' => ['resource', 'conn_url'=>'string', 'userid='=>'string', 'passwd='=>'string'], +'cubrid_current_oid' => ['string', 'req_identifier'=>'resource'], +'cubrid_data_seek' => ['bool', 'req_identifier'=>'', 'row_number'=>'int'], +'cubrid_db_name' => ['string', 'result'=>'array', 'index'=>'int'], +'cubrid_db_parameter' => ['array', 'conn_identifier'=>'resource'], +'cubrid_disconnect' => ['bool', 'conn_identifier'=>'resource'], +'cubrid_drop' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string'], +'cubrid_errno' => ['int', 'conn_identifier='=>''], +'cubrid_error' => ['string', 'connection='=>''], +'cubrid_error_code' => ['int'], +'cubrid_error_code_facility' => ['int'], +'cubrid_error_msg' => ['string'], +'cubrid_execute' => ['bool', 'conn_identifier'=>'', 'sql'=>'string', 'option='=>'int', 'request_identifier='=>''], +'cubrid_fetch' => ['mixed', 'result'=>'resource', 'type='=>'int'], +'cubrid_fetch_array' => ['array', 'result'=>'resource', 'type='=>'int'], +'cubrid_fetch_assoc' => ['array', 'result'=>'resource'], +'cubrid_fetch_field' => ['object', 'result'=>'resource', 'field_offset='=>'int'], +'cubrid_fetch_lengths' => ['array', 'result'=>'resource'], +'cubrid_fetch_object' => ['object', 'result'=>'resource', 'class_name='=>'string', 'params='=>'array'], +'cubrid_fetch_row' => ['array', 'result'=>'resource'], +'cubrid_field_flags' => ['string', 'result'=>'resource', 'field_offset'=>'int'], +'cubrid_field_len' => ['int', 'result'=>'resource', 'field_offset'=>'int'], +'cubrid_field_name' => ['string', 'result'=>'resource', 'field_offset'=>'int'], +'cubrid_field_seek' => ['bool', 'result'=>'resource', 'field_offset='=>'int'], +'cubrid_field_table' => ['string', 'result'=>'resource', 'field_offset'=>'int'], +'cubrid_field_type' => ['string', 'result'=>'resource', 'field_offset'=>'int'], +'cubrid_free_result' => ['bool', 'req_identifier'=>'resource'], +'cubrid_get' => ['mixed', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr='=>'mixed'], +'cubrid_get_autocommit' => ['bool', 'conn_identifier'=>'resource'], +'cubrid_get_charset' => ['string', 'conn_identifier'=>'resource'], +'cubrid_get_class_name' => ['string', 'conn_identifier'=>'resource', 'oid'=>'string'], +'cubrid_get_client_info' => ['string'], +'cubrid_get_db_parameter' => ['array', 'conn_identifier'=>'resource'], +'cubrid_get_query_timeout' => ['int', 'req_identifier'=>'resource'], +'cubrid_get_server_info' => ['string', 'conn_identifier'=>'resource'], +'cubrid_insert_id' => ['string', 'conn_identifier='=>'resource'], +'cubrid_is_instance' => ['int', 'conn_identifier'=>'resource', 'oid'=>'string'], +'cubrid_list_dbs' => ['array', 'conn_identifier'=>'resource'], +'cubrid_load_from_glo' => ['int', 'conn_identifier'=>'', 'oid'=>'string', 'file_name'=>'string'], +'cubrid_lob2_bind' => ['bool', 'req_identifier'=>'resource', 'bind_index'=>'int', 'bind_value'=>'mixed', 'bind_value_type='=>'string'], +'cubrid_lob2_close' => ['bool', 'lob_identifier'=>'resource'], +'cubrid_lob2_export' => ['bool', 'lob_identifier'=>'resource', 'file_name'=>'string'], +'cubrid_lob2_import' => ['bool', 'lob_identifier'=>'resource', 'file_name'=>'string'], +'cubrid_lob2_new' => ['resource', 'conn_identifier='=>'resource', 'type='=>'string'], +'cubrid_lob2_read' => ['string', 'lob_identifier'=>'resource', 'length'=>'int'], +'cubrid_lob2_seek' => ['bool', 'lob_identifier'=>'resource', 'offset'=>'int', 'origin='=>'int'], +'cubrid_lob2_seek64' => ['bool', 'lob_identifier'=>'resource', 'offset'=>'string', 'origin='=>'int'], +'cubrid_lob2_size' => ['int', 'lob_identifier'=>'resource'], +'cubrid_lob2_size64' => ['string', 'lob_identifier'=>'resource'], +'cubrid_lob2_tell' => ['int', 'lob_identifier'=>'resource'], +'cubrid_lob2_tell64' => ['string', 'lob_identifier'=>'resource'], +'cubrid_lob2_write' => ['bool', 'lob_identifier'=>'resource', 'buf'=>'string'], +'cubrid_lob_close' => ['bool', 'lob_identifier_array'=>'array'], +'cubrid_lob_export' => ['bool', 'conn_identifier'=>'resource', 'lob_identifier'=>'resource', 'path_name'=>'string'], +'cubrid_lob_get' => ['array', 'conn_identifier'=>'resource', 'sql'=>'string'], +'cubrid_lob_send' => ['bool', 'conn_identifier'=>'resource', 'lob_identifier'=>'resource'], +'cubrid_lob_size' => ['string', 'lob_identifier'=>'resource'], +'cubrid_lock_read' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string'], +'cubrid_lock_write' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string'], +'cubrid_move_cursor' => ['int', 'req_identifier'=>'resource', 'offset'=>'int', 'origin='=>'int'], +'cubrid_new_glo' => ['string', 'conn_identifier'=>'', 'class_name'=>'string', 'file_name'=>'string'], +'cubrid_next_result' => ['bool', 'result'=>'resource'], +'cubrid_num_cols' => ['int', 'req_identifier'=>'resource'], +'cubrid_num_fields' => ['int', 'result'=>'resource'], +'cubrid_num_rows' => ['int', 'req_identifier'=>'resource'], +'cubrid_pconnect' => ['resource', 'host'=>'string', 'port'=>'int', 'dbname'=>'string', 'userid='=>'string', 'passwd='=>'string'], +'cubrid_pconnect_with_url' => ['resource', 'conn_url'=>'string', 'userid='=>'string', 'passwd='=>'string'], +'cubrid_ping' => ['bool', 'conn_identifier='=>''], +'cubrid_prepare' => ['resource', 'conn_identifier'=>'resource', 'prepare_stmt'=>'string', 'option='=>'int'], +'cubrid_put' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr='=>'string', 'value='=>'mixed'], +'cubrid_query' => ['resource', 'query'=>'string', 'conn_identifier='=>''], +'cubrid_real_escape_string' => ['string', 'unescaped_string'=>'string', 'conn_identifier='=>''], +'cubrid_result' => ['string', 'result'=>'resource', 'row'=>'int', 'field='=>''], +'cubrid_rollback' => ['bool', 'conn_identifier'=>'resource'], +'cubrid_save_to_glo' => ['int', 'conn_identifier'=>'', 'oid'=>'string', 'file_name'=>'string'], +'cubrid_schema' => ['array', 'conn_identifier'=>'resource', 'schema_type'=>'int', 'class_name='=>'string', 'attr_name='=>'string'], +'cubrid_send_glo' => ['int', 'conn_identifier'=>'', 'oid'=>'string'], +'cubrid_seq_add' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string', 'seq_element'=>'string'], +'cubrid_seq_drop' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string', 'index'=>'int'], +'cubrid_seq_insert' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string', 'index'=>'int', 'seq_element'=>'string'], +'cubrid_seq_put' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string', 'index'=>'int', 'seq_element'=>'string'], +'cubrid_set_add' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string', 'set_element'=>'string'], +'cubrid_set_autocommit' => ['bool', 'conn_identifier'=>'resource', 'mode'=>'bool'], +'cubrid_set_db_parameter' => ['bool', 'conn_identifier'=>'resource', 'param_type'=>'int', 'param_value'=>'int'], +'cubrid_set_drop' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string', 'set_element'=>'string'], +'cubrid_set_query_timeout' => ['bool', 'req_identifier'=>'resource', 'timeout'=>'int'], +'cubrid_unbuffered_query' => ['resource', 'query'=>'string', 'conn_identifier='=>''], +'cubrid_version' => ['string'], +'curl_close' => ['void', 'ch'=>'CurlHandle'], +'curl_copy_handle' => ['CurlHandle', 'ch'=>'CurlHandle'], +'curl_errno' => ['int', 'ch'=>'CurlHandle'], +'curl_error' => ['string', 'ch'=>'CurlHandle'], +'curl_escape' => ['string|false', 'ch'=>'CurlHandle', 'string'=>'string'], +'curl_exec' => ['bool|string', 'ch'=>'CurlHandle'], +'curl_file_create' => ['CURLFile', 'filename'=>'string', 'mimetype='=>'string', 'postfilename='=>'string'], +'curl_getinfo' => ['mixed', 'ch'=>'CurlHandle', 'option='=>'int'], +'curl_init' => ['CurlHandle|false', 'url='=>'string'], +'curl_multi_add_handle' => ['int', 'mh'=>'CurlMultiHandle', 'ch'=>'CurlHandle'], +'curl_multi_close' => ['void', 'mh'=>'CurlMultiHandle'], +'curl_multi_errno' => ['int', 'mh'=>'CurlMultiHandle'], +'curl_multi_exec' => ['int', 'mh'=>'CurlMultiHandle', '&w_still_running'=>'int'], +'curl_multi_getcontent' => ['string', 'ch'=>'CurlMultiHandle'], +'curl_multi_info_read' => ['array|false', 'mh'=>'CurlMultiHandle', '&w_msgs_in_queue='=>'int'], +'curl_multi_init' => ['CurlMultiHandle|false'], +'curl_multi_remove_handle' => ['int', 'mh'=>'CurlMultiHandle', 'ch'=>'CurlHandle'], +'curl_multi_select' => ['int', 'mh'=>'CurlMultiHandle', 'timeout='=>'float'], +'curl_multi_setopt' => ['bool', 'mh'=>'CurlMultiHandle', 'option'=>'int', 'value'=>'mixed'], +'curl_multi_strerror' => ['?string', 'code'=>'int'], +'curl_pause' => ['int', 'ch'=>'CurlHandle', 'bitmask'=>'int'], +'curl_reset' => ['void', 'ch'=>'CurlHandle'], +'curl_setopt' => ['bool', 'ch'=>'CurlHandle', 'option'=>'int', 'value'=>'callable|mixed'], +'curl_setopt_array' => ['bool', 'ch'=>'CurlHandle', 'options'=>'array'], +'curl_share_close' => ['void', 'sh'=>'CurlShareHandle'], +'curl_share_errno' => ['int', 'sh'=>'CurlShareHandle'], +'curl_share_init' => ['CurlShareHandle'], +'curl_share_setopt' => ['bool', 'sh'=>'CurlShareHandle', 'option'=>'int', 'value'=>'mixed'], +'curl_share_strerror' => ['string', 'code'=>'int'], +'curl_strerror' => ['?string', 'code'=>'int'], +'curl_unescape' => ['string|false', 'ch'=>'CurlShareHandle', 'string'=>'string'], +'curl_version' => ['array', 'version='=>'int'], +'CURLFile::__construct' => ['void', 'filename'=>'string', 'mimetype='=>'string', 'postfilename='=>'string'], +'CURLFile::__wakeup' => ['void'], +'CURLFile::getFilename' => ['string'], +'CURLFile::getMimeType' => ['string'], +'CURLFile::getPostFilename' => ['string'], +'CURLFile::setMimeType' => ['void', 'mime'=>'string'], +'CURLFile::setPostFilename' => ['void', 'name'=>'string'], +'current' => ['mixed|false', 'array'=>'array|object'], +'cyrus_authenticate' => ['void', 'connection'=>'resource', 'mechlist='=>'string', 'service='=>'string', 'user='=>'string', 'minssf='=>'int', 'maxssf='=>'int', 'authname='=>'string', 'password='=>'string'], +'cyrus_bind' => ['bool', 'connection'=>'resource', 'callbacks'=>'array'], +'cyrus_close' => ['bool', 'connection'=>'resource'], +'cyrus_connect' => ['resource', 'host='=>'string', 'port='=>'string', 'flags='=>'int'], +'cyrus_query' => ['array', 'connection'=>'resource', 'query'=>'string'], +'cyrus_unbind' => ['bool', 'connection'=>'resource', 'trigger_name'=>'string'], +'date' => ['string|false', 'format'=>'string', 'timestamp='=>'int'], +'date_add' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'], +'date_create' => ['DateTime|false', 'time='=>'string', 'timezone='=>'?DateTimeZone'], +'date_create_from_format' => ['DateTime|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'?\DateTimeZone'], +'date_create_immutable' => ['DateTimeImmutable|false', 'time='=>'string', 'timezone='=>'?DateTimeZone'], +'date_create_immutable_from_format' => ['DateTimeImmutable|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'?DateTimeZone'], +'date_date_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'month'=>'int', 'day'=>'int'], +'date_default_timezone_get' => ['string'], +'date_default_timezone_set' => ['bool', 'timezone_identifier'=>'string'], +'date_diff' => ['DateInterval|false', 'obj1'=>'DateTimeInterface', 'obj2'=>'DateTimeInterface', 'absolute='=>'bool'], +'date_format' => ['string|false', 'object'=>'DateTimeInterface', 'format'=>'string'], +'date_get_last_errors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], +'date_interval_create_from_date_string' => ['DateInterval', 'time'=>'string'], +'date_interval_format' => ['string', 'object'=>'DateInterval', 'format'=>'string'], +'date_isodate_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'week'=>'int', 'day='=>'int|mixed'], +'date_modify' => ['DateTime|false', 'object'=>'DateTime', 'modify'=>'string'], +'date_offset_get' => ['int|false', 'object'=>'DateTimeInterface'], +'date_parse' => ['array|false', 'date'=>'string'], +'date_parse_from_format' => ['array', 'format'=>'string', 'date'=>'string'], +'date_sub' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'], +'date_sun_info' => ['array|false', 'time'=>'int', 'latitude'=>'float', 'longitude'=>'float'], +'date_sunrise' => ['mixed', 'time'=>'int', 'format='=>'int', 'latitude='=>'float', 'longitude='=>'float', 'zenith='=>'float', 'gmt_offset='=>'float'], +'date_sunset' => ['mixed', 'time'=>'int', 'format='=>'int', 'latitude='=>'float', 'longitude='=>'float', 'zenith='=>'float', 'gmt_offset='=>'float'], +'date_time_set' => ['DateTime|false', 'object'=>'', 'hour'=>'', 'minute'=>'', 'second='=>'', 'microseconds='=>''], +'date_timestamp_get' => ['int', 'object'=>'DateTimeInterface'], +'date_timestamp_set' => ['DateTime|false', 'object'=>'DateTime', 'unixtimestamp'=>'int'], +'date_timezone_get' => ['DateTimeZone|false', 'object'=>'DateTimeInterface'], +'date_timezone_set' => ['DateTime|false', 'object'=>'DateTime', 'timezone'=>'DateTimeZone'], +'datefmt_create' => ['IntlDateFormatter|false', 'locale'=>'?string', 'datetype'=>'?int', 'timetype'=>'?int', 'timezone='=>'string|DateTimeZone|IntlTimeZone|null', 'calendar='=>'int|IntlCalendar|null', 'pattern='=>'string'], +'datefmt_format' => ['string|false', 'fmt'=>'IntlDateFormatter', 'value'=>'DateTime|IntlCalendar|array|int'], +'datefmt_format_object' => ['string|false', 'object'=>'object', 'format='=>'mixed', 'locale='=>'string'], +'datefmt_get_calendar' => ['int', 'fmt'=>'IntlDateFormatter'], +'datefmt_get_calendar_object' => ['IntlCalendar', 'fmt'=>'IntlDateFormatter'], +'datefmt_get_datetype' => ['int', 'fmt'=>'IntlDateFormatter'], +'datefmt_get_error_code' => ['int', 'fmt'=>'IntlDateFormatter'], +'datefmt_get_error_message' => ['string', 'fmt'=>'IntlDateFormatter'], +'datefmt_get_locale' => ['string|false', 'fmt'=>'IntlDateFormatter', 'which='=>'int'], +'datefmt_get_pattern' => ['string', 'fmt'=>'IntlDateFormatter'], +'datefmt_get_timetype' => ['int', 'fmt'=>'IntlDateFormatter'], +'datefmt_get_timezone' => ['IntlTimeZone|false'], +'datefmt_get_timezone_id' => ['string', 'fmt'=>'IntlDateFormatter'], +'datefmt_is_lenient' => ['bool', 'fmt'=>'IntlDateFormatter'], +'datefmt_localtime' => ['array|false', 'fmt'=>'IntlDateFormatter', 'text_to_parse='=>'string', '&rw_parse_pos='=>'int'], +'datefmt_parse' => ['int|false', 'fmt'=>'IntlDateFormatter', 'text_to_parse='=>'string', '&rw_parse_pos='=>'int'], +'datefmt_set_calendar' => ['bool', 'fmt'=>'IntlDateFormatter', 'which'=>'int'], +'datefmt_set_lenient' => ['?bool', 'fmt'=>'IntlDateFormatter', 'lenient'=>'bool'], +'datefmt_set_pattern' => ['bool', 'fmt'=>'IntlDateFormatter', 'pattern'=>'string'], +'datefmt_set_timezone' => ['bool', 'zone'=>'mixed'], +'datefmt_set_timezone_id' => ['bool', 'fmt'=>'IntlDateFormatter', 'zone'=>'string'], +'DateInterval::__construct' => ['void', 'spec'=>'string'], +'DateInterval::__set_state' => ['DateInterval', 'array'=>'array'], +'DateInterval::__wakeup' => ['void'], +'DateInterval::createFromDateString' => ['DateInterval', 'time'=>'string'], +'DateInterval::format' => ['string', 'format'=>'string'], +'DatePeriod::__construct' => ['void', 'start'=>'DateTimeInterface', 'interval'=>'DateInterval', 'recur'=>'int', 'options='=>'int'], +'DatePeriod::__construct\'1' => ['void', 'start'=>'DateTimeInterface', 'interval'=>'DateInterval', 'end'=>'DateTimeInterface', 'options='=>'int'], +'DatePeriod::__construct\'2' => ['void', 'iso'=>'string', 'options='=>'int'], +'DatePeriod::__wakeup' => ['void'], +'DatePeriod::getDateInterval' => ['DateInterval'], +'DatePeriod::getEndDate' => ['?DateTimeInterface'], +'DatePeriod::getStartDate' => ['DateTimeInterface'], +'DateTime::__construct' => ['void', 'time='=>'string'], +'DateTime::__construct\'1' => ['void', 'time'=>'?string', 'timezone'=>'?DateTimeZone'], +'DateTime::__set_state' => ['static', 'array'=>'array'], +'DateTime::__wakeup' => ['void'], +'DateTime::add' => ['static', 'interval'=>'DateInterval'], +'DateTime::createFromFormat' => ['static|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'?DateTimeZone'], +'DateTime::createFromImmutable' => ['static', 'datetTimeImmutable'=>'DateTimeImmutable'], +'DateTime::diff' => ['DateInterval|false', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], +'DateTime::format' => ['string|false', 'format'=>'string'], +'DateTime::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], +'DateTime::getOffset' => ['int'], +'DateTime::getTimestamp' => ['int|false'], +'DateTime::getTimezone' => ['DateTimeZone'], +'DateTime::modify' => ['static|false', 'modify'=>'string'], +'DateTime::setDate' => ['static', 'year'=>'int', 'month'=>'int', 'day'=>'int'], +'DateTime::setISODate' => ['static', 'year'=>'int', 'week'=>'int', 'day='=>'int'], +'DateTime::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], +'DateTime::setTimestamp' => ['static', 'unixtimestamp'=>'int'], +'DateTime::setTimezone' => ['static', 'timezone'=>'DateTimeZone'], +'DateTime::sub' => ['static', 'interval'=>'DateInterval'], +'DateTimeImmutable::__construct' => ['void', 'time='=>'string'], +'DateTimeImmutable::__construct\'1' => ['void', 'time'=>'?string', 'timezone'=>'?DateTimeZone'], +'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'], +'DateTimeImmutable::__wakeup' => ['void'], +'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'], +'DateTimeImmutable::createFromFormat' => ['static|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'?DateTimeZone'], +'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'], +'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], +'DateTimeImmutable::format' => ['string|false', 'format'=>'string'], +'DateTimeImmutable::getLastErrors' => ['array{warning_count:int,warnings:array,error_count:int,errors:array}'], +'DateTimeImmutable::getOffset' => ['int'], +'DateTimeImmutable::getTimestamp' => ['int|false'], +'DateTimeImmutable::getTimezone' => ['DateTimeZone'], +'DateTimeImmutable::modify' => ['static', 'modify'=>'string'], +'DateTimeImmutable::setDate' => ['static|false', 'year'=>'int', 'month'=>'int', 'day'=>'int'], +'DateTimeImmutable::setISODate' => ['static|false', 'year'=>'int', 'week'=>'int', 'day='=>'int'], +'DateTimeImmutable::setTime' => ['static|false', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], +'DateTimeImmutable::setTimestamp' => ['static|false', 'unixtimestamp'=>'int'], +'DateTimeImmutable::setTimezone' => ['static|false', 'timezone'=>'DateTimeZone'], +'DateTimeImmutable::sub' => ['static|false', 'interval'=>'DateInterval'], +'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'], +'DateTimeInterface::format' => ['string', 'format'=>'string'], +'DateTimeInterface::getOffset' => ['int'], +'DateTimeInterface::getTimestamp' => ['int|false'], +'DateTimeInterface::getTimezone' => ['DateTimeZone'], +'DateTimeZone::__construct' => ['void', 'timezone'=>'string'], +'DateTimeZone::__set_state' => ['DateTimeZone', 'array'=>'array'], +'DateTimeZone::__wakeup' => ['void'], +'DateTimeZone::getLocation' => ['array|false'], +'DateTimeZone::getName' => ['string'], +'DateTimeZone::getOffset' => ['int|false', 'datetime'=>'DateTimeInterface'], +'DateTimeZone::getTransitions' => ['array|false', 'timestamp_begin='=>'int', 'timestamp_end='=>'int'], +'DateTimeZone::listAbbreviations' => ['array|false'], +'DateTimeZone::listIdentifiers' => ['array|false', 'what='=>'int', 'country='=>'string'], +'db2_autocommit' => ['mixed', 'connection'=>'resource', 'value='=>'int'], +'db2_bind_param' => ['bool', 'stmt'=>'resource', 'parameter_number'=>'int', 'variable_name'=>'string', 'parameter_type='=>'int', 'data_type='=>'int', 'precision='=>'int', 'scale='=>'int'], +'db2_client_info' => ['object|false', 'connection'=>'resource'], +'db2_close' => ['bool', 'connection'=>'resource'], +'db2_column_privileges' => ['resource|false', 'connection'=>'resource', 'qualifier='=>'string', 'schema='=>'string', 'table_name='=>'string', 'column_name='=>'string'], +'db2_columns' => ['resource|false', 'connection'=>'resource', 'qualifier='=>'string', 'schema='=>'string', 'table_name='=>'string', 'column_name='=>'string'], +'db2_commit' => ['bool', 'connection'=>'resource'], +'db2_conn_error' => ['string', 'connection='=>'resource'], +'db2_conn_errormsg' => ['string', 'connection='=>'resource'], +'db2_connect' => ['resource|false', 'database'=>'string', 'username'=>'string', 'password'=>'string', 'options='=>'array'], +'db2_cursor_type' => ['int', 'stmt'=>'resource'], +'db2_escape_string' => ['string', 'string_literal'=>'string'], +'db2_exec' => ['resource|false', 'connection'=>'resource', 'statement'=>'string', 'options='=>'array'], +'db2_execute' => ['bool', 'stmt'=>'resource', 'parameters='=>'array'], +'db2_fetch_array' => ['array|false', 'stmt'=>'resource', 'row_number='=>'int'], +'db2_fetch_assoc' => ['array|false', 'stmt'=>'resource', 'row_number='=>'int'], +'db2_fetch_both' => ['array|false', 'stmt'=>'resource', 'row_number='=>'int'], +'db2_fetch_object' => ['object|false', 'stmt'=>'resource', 'row_number='=>'int'], +'db2_fetch_row' => ['bool', 'stmt'=>'resource', 'row_number='=>'int'], +'db2_field_display_size' => ['int|false', 'stmt'=>'resource', 'column'=>'mixed'], +'db2_field_name' => ['string|false', 'stmt'=>'resource', 'column'=>'mixed'], +'db2_field_num' => ['int|false', 'stmt'=>'resource', 'column'=>'mixed'], +'db2_field_precision' => ['int|false', 'stmt'=>'resource', 'column'=>'mixed'], +'db2_field_scale' => ['int|false', 'stmt'=>'resource', 'column'=>'mixed'], +'db2_field_type' => ['string|false', 'stmt'=>'resource', 'column'=>'mixed'], +'db2_field_width' => ['int|false', 'stmt'=>'resource', 'column'=>'mixed'], +'db2_foreign_keys' => ['resource|false', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'table_name'=>'string'], +'db2_free_result' => ['bool', 'stmt'=>'resource'], +'db2_free_stmt' => ['bool', 'stmt'=>'resource'], +'db2_get_option' => ['string|false', 'resource'=>'resource', 'option'=>'string'], +'db2_last_insert_id' => ['string', 'resource'=>'resource'], +'db2_lob_read' => ['string|false', 'stmt'=>'resource', 'colnum'=>'int', 'length'=>'int'], +'db2_next_result' => ['resource|false', 'stmt'=>'resource'], +'db2_num_fields' => ['int|false', 'stmt'=>'resource'], +'db2_num_rows' => ['int', 'stmt'=>'resource'], +'db2_pclose' => ['bool', 'resource'=>'resource'], +'db2_pconnect' => ['resource|false', 'database'=>'string', 'username'=>'string', 'password'=>'string', 'options='=>'array'], +'db2_prepare' => ['resource|false', 'connection'=>'resource', 'statement'=>'string', 'options='=>'array'], +'db2_primary_keys' => ['resource|false', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'table_name'=>'string'], +'db2_primarykeys' => [''], +'db2_procedure_columns' => ['resource|false', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'procedure'=>'string', 'parameter'=>'string'], +'db2_procedurecolumns' => [''], +'db2_procedures' => ['resource|false', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'procedure'=>'string'], +'db2_result' => ['mixed', 'stmt'=>'resource', 'column'=>'mixed'], +'db2_rollback' => ['bool', 'connection'=>'resource'], +'db2_server_info' => ['object|false', 'connection'=>'resource'], +'db2_set_option' => ['bool', 'resource'=>'resource', 'options'=>'array', 'type'=>'int'], +'db2_setoption' => [''], +'db2_special_columns' => ['resource|false', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'table_name'=>'string', 'scope'=>'int'], +'db2_specialcolumns' => [''], +'db2_statistics' => ['resource|false', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'table_name'=>'string', 'unique'=>'bool'], +'db2_stmt_error' => ['string', 'stmt='=>'resource'], +'db2_stmt_errormsg' => ['string', 'stmt='=>'resource'], +'db2_table_privileges' => ['resource|false', 'connection'=>'resource', 'qualifier='=>'string', 'schema='=>'string', 'table_name='=>'string'], +'db2_tableprivileges' => [''], +'db2_tables' => ['resource|false', 'connection'=>'resource', 'qualifier='=>'string', 'schema='=>'string', 'table_name='=>'string', 'table_type='=>'string'], +'dba_close' => ['void', 'handle'=>'resource'], +'dba_delete' => ['bool', 'key'=>'string', 'handle'=>'resource'], +'dba_exists' => ['bool', 'key'=>'string', 'handle'=>'resource'], +'dba_fetch' => ['string|false', 'key'=>'string', 'skip'=>'int', 'handle'=>'resource'], +'dba_fetch\'1' => ['string|false', 'key'=>'string', 'handle'=>'resource'], +'dba_firstkey' => ['string', 'handle'=>'resource'], +'dba_handlers' => ['array', 'full_info='=>'bool'], +'dba_insert' => ['bool', 'key'=>'string', 'value'=>'string', 'handle'=>'resource'], +'dba_key_split' => ['array|false', 'key'=>'string'], +'dba_list' => ['array'], +'dba_nextkey' => ['string', 'handle'=>'resource'], +'dba_open' => ['resource', 'path'=>'string', 'mode'=>'string', 'handlername='=>'string', '...args='=>'string'], +'dba_optimize' => ['bool', 'handle'=>'resource'], +'dba_popen' => ['resource', 'path'=>'string', 'mode'=>'string', 'handlername='=>'string', '...args='=>'string'], +'dba_replace' => ['bool', 'key'=>'string', 'value'=>'string', 'handle'=>'resource'], +'dba_sync' => ['bool', 'handle'=>'resource'], +'dbase_add_record' => ['bool', 'dbase_identifier'=>'resource', 'record'=>'array'], +'dbase_close' => ['bool', 'dbase_identifier'=>'resource'], +'dbase_create' => ['resource|false', 'filename'=>'string', 'fields'=>'array'], +'dbase_delete_record' => ['bool', 'dbase_identifier'=>'resource', 'record_number'=>'int'], +'dbase_get_header_info' => ['array', 'dbase_identifier'=>'resource'], +'dbase_get_record' => ['array', 'dbase_identifier'=>'resource', 'record_number'=>'int'], +'dbase_get_record_with_names' => ['array', 'dbase_identifier'=>'resource', 'record_number'=>'int'], +'dbase_numfields' => ['int', 'dbase_identifier'=>'resource'], +'dbase_numrecords' => ['int', 'dbase_identifier'=>'resource'], +'dbase_open' => ['resource|false', 'filename'=>'string', 'mode'=>'int'], +'dbase_pack' => ['bool', 'dbase_identifier'=>'resource'], +'dbase_replace_record' => ['bool', 'dbase_identifier'=>'resource', 'record'=>'array', 'record_number'=>'int'], +'dbplus_add' => ['int', 'relation'=>'resource', 'tuple'=>'array'], +'dbplus_aql' => ['resource', 'query'=>'string', 'server='=>'string', 'dbpath='=>'string'], +'dbplus_chdir' => ['string', 'newdir='=>'string'], +'dbplus_close' => ['mixed', 'relation'=>'resource'], +'dbplus_curr' => ['int', 'relation'=>'resource', 'tuple'=>'array'], +'dbplus_errcode' => ['string', 'errno='=>'int'], +'dbplus_errno' => ['int'], +'dbplus_find' => ['int', 'relation'=>'resource', 'constraints'=>'array', 'tuple'=>'mixed'], +'dbplus_first' => ['int', 'relation'=>'resource', 'tuple'=>'array'], +'dbplus_flush' => ['int', 'relation'=>'resource'], +'dbplus_freealllocks' => ['int'], +'dbplus_freelock' => ['int', 'relation'=>'resource', 'tuple'=>'string'], +'dbplus_freerlocks' => ['int', 'relation'=>'resource'], +'dbplus_getlock' => ['int', 'relation'=>'resource', 'tuple'=>'string'], +'dbplus_getunique' => ['int', 'relation'=>'resource', 'uniqueid'=>'int'], +'dbplus_info' => ['int', 'relation'=>'resource', 'key'=>'string', 'result'=>'array'], +'dbplus_last' => ['int', 'relation'=>'resource', 'tuple'=>'array'], +'dbplus_lockrel' => ['int', 'relation'=>'resource'], +'dbplus_next' => ['int', 'relation'=>'resource', 'tuple'=>'array'], +'dbplus_open' => ['resource', 'name'=>'string'], +'dbplus_prev' => ['int', 'relation'=>'resource', 'tuple'=>'array'], +'dbplus_rchperm' => ['int', 'relation'=>'resource', 'mask'=>'int', 'user'=>'string', 'group'=>'string'], +'dbplus_rcreate' => ['resource', 'name'=>'string', 'domlist'=>'mixed', 'overwrite='=>'bool'], +'dbplus_rcrtexact' => ['mixed', 'name'=>'string', 'relation'=>'resource', 'overwrite='=>'bool'], +'dbplus_rcrtlike' => ['mixed', 'name'=>'string', 'relation'=>'resource', 'overwrite='=>'int'], +'dbplus_resolve' => ['array', 'relation_name'=>'string'], +'dbplus_restorepos' => ['int', 'relation'=>'resource', 'tuple'=>'array'], +'dbplus_rkeys' => ['mixed', 'relation'=>'resource', 'domlist'=>'mixed'], +'dbplus_ropen' => ['resource', 'name'=>'string'], +'dbplus_rquery' => ['resource', 'query'=>'string', 'dbpath='=>'string'], +'dbplus_rrename' => ['int', 'relation'=>'resource', 'name'=>'string'], +'dbplus_rsecindex' => ['mixed', 'relation'=>'resource', 'domlist'=>'mixed', 'type'=>'int'], +'dbplus_runlink' => ['int', 'relation'=>'resource'], +'dbplus_rzap' => ['int', 'relation'=>'resource'], +'dbplus_savepos' => ['int', 'relation'=>'resource'], +'dbplus_setindex' => ['int', 'relation'=>'resource', 'idx_name'=>'string'], +'dbplus_setindexbynumber' => ['int', 'relation'=>'resource', 'idx_number'=>'int'], +'dbplus_sql' => ['resource', 'query'=>'string', 'server='=>'string', 'dbpath='=>'string'], +'dbplus_tcl' => ['string', 'sid'=>'int', 'script'=>'string'], +'dbplus_tremove' => ['int', 'relation'=>'resource', 'tuple'=>'array', 'current='=>'array'], +'dbplus_undo' => ['int', 'relation'=>'resource'], +'dbplus_undoprepare' => ['int', 'relation'=>'resource'], +'dbplus_unlockrel' => ['int', 'relation'=>'resource'], +'dbplus_unselect' => ['int', 'relation'=>'resource'], +'dbplus_update' => ['int', 'relation'=>'resource', 'old'=>'array', 'new'=>'array'], +'dbplus_xlockrel' => ['int', 'relation'=>'resource'], +'dbplus_xunlockrel' => ['int', 'relation'=>'resource'], +'dbx_close' => ['int', 'link_identifier'=>'object'], +'dbx_compare' => ['int', 'row_a'=>'array', 'row_b'=>'array', 'column_key'=>'string', 'flags='=>'int'], +'dbx_connect' => ['object', 'module'=>'mixed', 'host'=>'string', 'database'=>'string', 'username'=>'string', 'password'=>'string', 'persistent='=>'int'], +'dbx_error' => ['string', 'link_identifier'=>'object'], +'dbx_escape_string' => ['string', 'link_identifier'=>'object', 'text'=>'string'], +'dbx_fetch_row' => ['mixed', 'result_identifier'=>'object'], +'dbx_query' => ['mixed', 'link_identifier'=>'object', 'sql_statement'=>'string', 'flags='=>'int'], +'dbx_sort' => ['bool', 'result'=>'object', 'user_compare_function'=>'string'], +'dcgettext' => ['string', 'domain_name'=>'string', 'msgid'=>'string', 'category'=>'int'], +'dcngettext' => ['string', 'domain'=>'string', 'msgid1'=>'string', 'msgid2'=>'string', 'n'=>'int', 'category'=>'int'], +'deaggregate' => ['', 'object'=>'object', 'class_name='=>'string'], +'debug_backtrace' => ['list', 'options='=>'int|bool', 'limit='=>'int'], +'debug_print_backtrace' => ['void', 'options='=>'int|bool', 'limit='=>'int'], +'debug_zval_dump' => ['void', '...var'=>'mixed'], +'debugger_connect' => [''], +'debugger_connector_pid' => [''], +'debugger_get_server_start_time' => [''], +'debugger_print' => [''], +'debugger_start_debug' => [''], +'decbin' => ['string', 'number'=>'int'], +'dechex' => ['string', 'number'=>'int'], +'decoct' => ['string', 'number'=>'int'], +'define' => ['bool', 'constant_name'=>'string', 'value'=>'mixed', 'case_insensitive='=>'bool'], +'define_syslog_variables' => ['void'], +'defined' => ['bool', 'name'=>'string'], +'deflate_add' => ['string|false', 'context'=>'resource', 'data'=>'string', 'flush_mode='=>'int'], +'deflate_init' => ['resource|false', 'encoding'=>'int', 'options='=>'array'], +'deg2rad' => ['float', 'number'=>'float'], +'dgettext' => ['string', 'domain_name'=>'string', 'msgid'=>'string'], +'dio_close' => ['void', 'fd'=>'resource'], +'dio_fcntl' => ['mixed', 'fd'=>'resource', 'cmd'=>'int', 'args='=>'mixed'], +'dio_open' => ['resource|false', 'filename'=>'string', 'flags'=>'int', 'mode='=>'int'], +'dio_read' => ['string', 'fd'=>'resource', 'length='=>'int'], +'dio_seek' => ['int', 'fd'=>'resource', 'pos'=>'int', 'whence='=>'int'], +'dio_stat' => ['?array', 'fd'=>'resource'], +'dio_tcsetattr' => ['bool', 'fd'=>'resource', 'options'=>'array'], +'dio_truncate' => ['bool', 'fd'=>'resource', 'offset'=>'int'], +'dio_write' => ['int', 'fd'=>'resource', 'data'=>'string', 'length='=>'int'], +'dir' => ['Directory|false|null', 'directory'=>'string', 'context='=>'resource'], +'Directory::close' => ['void', 'dir_handle='=>'resource'], +'Directory::read' => ['string|false', 'dir_handle='=>'resource'], +'Directory::rewind' => ['void', 'dir_handle='=>'resource'], +'DirectoryIterator::__construct' => ['void', 'path'=>'string'], +'DirectoryIterator::__toString' => ['string'], +'DirectoryIterator::current' => ['DirectoryIterator'], +'DirectoryIterator::getATime' => ['int'], +'DirectoryIterator::getBasename' => ['string', 'suffix='=>'string'], +'DirectoryIterator::getCTime' => ['int'], +'DirectoryIterator::getExtension' => ['string'], +'DirectoryIterator::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], +'DirectoryIterator::getFilename' => ['string'], +'DirectoryIterator::getGroup' => ['int'], +'DirectoryIterator::getInode' => ['int'], +'DirectoryIterator::getLinkTarget' => ['string'], +'DirectoryIterator::getMTime' => ['int'], +'DirectoryIterator::getOwner' => ['int'], +'DirectoryIterator::getPath' => ['string'], +'DirectoryIterator::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], +'DirectoryIterator::getPathname' => ['string'], +'DirectoryIterator::getPerms' => ['int'], +'DirectoryIterator::getRealPath' => ['string'], +'DirectoryIterator::getSize' => ['int'], +'DirectoryIterator::getType' => ['string'], +'DirectoryIterator::isDir' => ['bool'], +'DirectoryIterator::isDot' => ['bool'], +'DirectoryIterator::isExecutable' => ['bool'], +'DirectoryIterator::isFile' => ['bool'], +'DirectoryIterator::isLink' => ['bool'], +'DirectoryIterator::isReadable' => ['bool'], +'DirectoryIterator::isWritable' => ['bool'], +'DirectoryIterator::key' => ['string'], +'DirectoryIterator::next' => ['void'], +'DirectoryIterator::openFile' => ['SplFileObject', 'mode='=>'string', 'use_include_path='=>'bool', 'context='=>'resource'], +'DirectoryIterator::rewind' => ['void'], +'DirectoryIterator::seek' => ['void', 'position'=>'int'], +'DirectoryIterator::setFileClass' => ['void', 'class_name='=>'string'], +'DirectoryIterator::setInfoClass' => ['void', 'class_name='=>'string'], +'DirectoryIterator::valid' => ['bool'], +'dirname' => ['string', 'path'=>'string', 'levels='=>'int'], +'disk_free_space' => ['float|false', 'path'=>'string'], +'disk_total_space' => ['float|false', 'path'=>'string'], +'diskfreespace' => ['float|false', 'path'=>'string'], +'display_disabled_function' => [''], +'dl' => ['bool', 'extension_filename'=>'string'], +'dngettext' => ['string', 'domain'=>'string', 'msgid1'=>'string', 'msgid2'=>'string', 'count'=>'int'], +'dns_check_record' => ['bool', 'host'=>'string', 'type='=>'string'], +'dns_get_mx' => ['bool', 'hostname'=>'string', '&w_mxhosts'=>'array', '&w_weight'=>'array'], +'dns_get_record' => ['list|false', 'hostname'=>'string', 'type='=>'int', '&w_authns='=>'array', '&w_addtl='=>'array', 'raw='=>'bool'], +'dom_document_relaxNG_validate_file' => ['bool', 'filename'=>'string'], +'dom_document_relaxNG_validate_xml' => ['bool', 'source'=>'string'], +'dom_document_schema_validate' => ['bool', 'source'=>'string', 'flags'=>'int'], +'dom_document_schema_validate_file' => ['bool', 'filename'=>'string', 'flags'=>'int'], +'dom_document_xinclude' => ['int', 'options'=>'int'], +'dom_import_simplexml' => ['DOMElement|false', 'node'=>'SimpleXMLElement'], +'dom_xpath_evaluate' => ['', 'expr'=>'string', 'context'=>'DOMNode', 'registernodens'=>'bool'], +'dom_xpath_query' => ['DOMNodeList', 'expr'=>'string', 'context'=>'DOMNode', 'registernodens'=>'bool'], +'dom_xpath_register_ns' => ['bool', 'prefix'=>'string', 'uri'=>'string'], +'dom_xpath_register_php_functions' => [''], +'DomainException::__clone' => ['void'], +'DomainException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?DomainException'], +'DomainException::__toString' => ['string'], +'DomainException::__wakeup' => ['void'], +'DomainException::getCode' => ['int'], +'DomainException::getFile' => ['string'], +'DomainException::getLine' => ['int'], +'DomainException::getMessage' => ['string'], +'DomainException::getPrevious' => ['Throwable|DomainException|null'], +'DomainException::getTrace' => ['list>'], +'DomainException::getTraceAsString' => ['string'], +'DOMAttr::__construct' => ['void', 'name'=>'string', 'value='=>'string'], +'DOMAttr::getLineNo' => ['int'], +'DOMAttr::getNodePath' => ['?string'], +'DOMAttr::hasAttributes' => ['bool'], +'DOMAttr::hasChildNodes' => ['bool'], +'DOMAttr::insertBefore' => ['DOMNode', 'newnode'=>'DOMNode', 'refnode='=>'DOMNode'], +'DOMAttr::isDefaultNamespace' => ['bool', 'namespaceuri'=>'string'], +'DOMAttr::isId' => ['bool'], +'DOMAttr::isSameNode' => ['bool', 'node'=>'DOMNode'], +'DOMAttr::isSupported' => ['bool', 'feature'=>'string', 'version'=>'string'], +'DOMAttr::lookupNamespaceUri' => ['string', 'prefix'=>'string'], +'DOMAttr::lookupPrefix' => ['string', 'namespaceuri'=>'string'], +'DOMAttr::normalize' => ['void'], +'DOMAttr::removeChild' => ['DOMNode', 'oldnode'=>'DOMNode'], +'DOMAttr::replaceChild' => ['DOMNode', 'newnode'=>'DOMNode', 'oldnode'=>'DOMNode'], +'DomAttribute::name' => ['string'], +'DomAttribute::set_value' => ['bool', 'content'=>'string'], +'DomAttribute::specified' => ['bool'], +'DomAttribute::value' => ['string'], +'DOMCdataSection::__construct' => ['void', 'value'=>'string'], +'DOMCharacterData::appendData' => ['void', 'data'=>'string'], +'DOMCharacterData::deleteData' => ['void', 'offset'=>'int', 'count'=>'int'], +'DOMCharacterData::insertData' => ['void', 'offset'=>'int', 'data'=>'string'], +'DOMCharacterData::replaceData' => ['void', 'offset'=>'int', 'count'=>'int', 'data'=>'string'], +'DOMCharacterData::substringData' => ['string', 'offset'=>'int', 'count'=>'int'], +'DOMComment::__construct' => ['void', 'value='=>'string'], +'DOMDocument::__construct' => ['void', 'version='=>'string', 'encoding='=>'string'], +'DOMDocument::createAttribute' => ['DOMAttr|false', 'name'=>'string'], +'DOMDocument::createAttributeNS' => ['DOMAttr|false', 'namespaceuri'=>'string', 'qualifiedname'=>'string'], +'DOMDocument::createCDATASection' => ['DOMCDATASection|false', 'data'=>'string'], +'DOMDocument::createComment' => ['DOMComment|false', 'data'=>'string'], +'DOMDocument::createDocumentFragment' => ['DOMDocumentFragment|false'], +'DOMDocument::createElement' => ['DOMElement|false', 'name'=>'string', 'value='=>'string'], +'DOMDocument::createElementNS' => ['DOMElement|false', 'namespaceuri'=>'string', 'qualifiedname'=>'string', 'value='=>'string'], +'DOMDocument::createEntityReference' => ['DOMEntityReference|false', 'name'=>'string'], +'DOMDocument::createProcessingInstruction' => ['DOMProcessingInstruction|false', 'target'=>'string', 'data='=>'string'], +'DOMDocument::createTextNode' => ['DOMText|false', 'content'=>'string'], +'DOMDocument::getElementById' => ['?DOMElement', 'elementid'=>'string'], +'DOMDocument::getElementsByTagName' => ['DOMNodeList', 'name'=>'string'], +'DOMDocument::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMDocument::importNode' => ['DOMNode|false', 'importednode'=>'DOMNode', 'deep='=>'bool'], +'DOMDocument::load' => ['DOMDocument|bool', 'filename'=>'string', 'options='=>'int'], +'DOMDocument::loadHTML' => ['bool', 'source'=>'string', 'options='=>'int'], +'DOMDocument::loadHTMLFile' => ['bool', 'filename'=>'string', 'options='=>'int'], +'DOMDocument::loadXML' => ['DOMDocument|bool', 'source'=>'string', 'options='=>'int'], +'DOMDocument::normalizeDocument' => ['void'], +'DOMDocument::registerNodeClass' => ['bool', 'baseclass'=>'string', 'extendedclass'=>'string'], +'DOMDocument::relaxNGValidate' => ['bool', 'filename'=>'string'], +'DOMDocument::relaxNGValidateSource' => ['bool', 'source'=>'string'], +'DOMDocument::save' => ['int|false', 'filename'=>'string', 'options='=>'int'], +'DOMDocument::saveHTML' => ['string|false', 'node='=>'?DOMNode'], +'DOMDocument::saveHTMLFile' => ['int|false', 'filename'=>'string'], +'DOMDocument::saveXML' => ['string|false', 'node='=>'?DOMNode', 'options='=>'int'], +'DOMDocument::schemaValidate' => ['bool', 'filename'=>'string', 'flags='=>'int'], +'DOMDocument::schemaValidateSource' => ['bool', 'source'=>'string', 'flags='=>'int'], +'DOMDocument::validate' => ['bool'], +'DOMDocument::xinclude' => ['int', 'options='=>'int'], +'DOMDocumentFragment::__construct' => ['void'], +'DOMDocumentFragment::appendXML' => ['bool', 'data'=>'string'], +'DomDocumentType::entities' => ['array'], +'DomDocumentType::internal_subset' => ['bool'], +'DomDocumentType::name' => ['string'], +'DomDocumentType::notations' => ['array'], +'DomDocumentType::public_id' => ['string'], +'DomDocumentType::system_id' => ['string'], +'DOMElement::__construct' => ['void', 'name'=>'string', 'value='=>'string', 'uri='=>'string'], +'DOMElement::get_attribute' => ['string', 'name'=>'string'], +'DOMElement::get_attribute_node' => ['DomAttribute', 'name'=>'string'], +'DOMElement::get_elements_by_tagname' => ['array', 'name'=>'string'], +'DOMElement::getAttribute' => ['string', 'name'=>'string'], +'DOMElement::getAttributeNode' => ['DOMAttr', 'name'=>'string'], +'DOMElement::getAttributeNodeNS' => ['DOMAttr', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMElement::getAttributeNS' => ['string', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMElement::getElementsByTagName' => ['DOMNodeList', 'name'=>'string'], +'DOMElement::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMElement::has_attribute' => ['bool', 'name'=>'string'], +'DOMElement::hasAttribute' => ['bool', 'name'=>'string'], +'DOMElement::hasAttributeNS' => ['bool', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMElement::remove_attribute' => ['bool', 'name'=>'string'], +'DOMElement::removeAttribute' => ['bool', 'name'=>'string'], +'DOMElement::removeAttributeNode' => ['bool', 'oldnode'=>'DOMAttr'], +'DOMElement::removeAttributeNS' => ['bool', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMElement::set_attribute' => ['DomAttribute', 'name'=>'string', 'value'=>'string'], +'DOMElement::set_attribute_node' => ['DomNode', 'attr'=>'DOMNode'], +'DOMElement::setAttribute' => ['DOMAttr|false', 'name'=>'string', 'value'=>'string'], +'DOMElement::setAttributeNode' => ['?DOMAttr', 'attr'=>'DOMAttr'], +'DOMElement::setAttributeNodeNS' => ['DOMAttr', 'attr'=>'DOMAttr'], +'DOMElement::setAttributeNS' => ['void', 'namespaceuri'=>'string', 'qualifiedname'=>'string', 'value'=>'string'], +'DOMElement::setIdAttribute' => ['void', 'name'=>'string', 'isid'=>'bool'], +'DOMElement::setIdAttributeNode' => ['void', 'attr'=>'DOMAttr', 'isid'=>'bool'], +'DOMElement::setIdAttributeNS' => ['void', 'namespaceuri'=>'string', 'localname'=>'string', 'isid'=>'bool'], +'DOMElement::tagname' => ['string'], +'DOMEntityReference::__construct' => ['void', 'name'=>'string'], +'DOMImplementation::__construct' => ['void'], +'DOMImplementation::createDocument' => ['DOMDocument', 'namespaceuri='=>'string', 'qualifiedname='=>'string', 'doctype='=>'DOMDocumentType'], +'DOMImplementation::createDocumentType' => ['DOMDocumentType', 'qualifiedname='=>'string', 'publicid='=>'string', 'systemid='=>'string'], +'DOMImplementation::hasFeature' => ['bool', 'feature'=>'string', 'version'=>'string'], +'DOMNamedNodeMap::count' => ['int'], +'DOMNamedNodeMap::getNamedItem' => ['?DOMNode', 'name'=>'string'], +'DOMNamedNodeMap::getNamedItemNS' => ['?DOMNode', 'namespaceuri'=>'string', 'localname'=>'string'], +'DOMNamedNodeMap::item' => ['?DOMNode', 'index'=>'int'], +'DomNode::add_namespace' => ['bool', 'uri'=>'string', 'prefix'=>'string'], +'DomNode::append_child' => ['DOMNode', 'newnode'=>'DOMNode'], +'DOMNode::appendChild' => ['DOMNode', 'newnode'=>'DOMNode'], +'DOMNode::C14N' => ['string', 'exclusive='=>'bool', 'with_comments='=>'bool', 'xpath='=>'array', 'ns_prefixes='=>'array'], +'DOMNode::C14NFile' => ['int|false', 'uri='=>'string', 'exclusive='=>'bool', 'with_comments='=>'bool', 'xpath='=>'array', 'ns_prefixes='=>'array'], +'DOMNode::cloneNode' => ['DOMNode', 'deep='=>'bool'], +'DOMNode::getLineNo' => ['int'], +'DOMNode::getNodePath' => ['?string'], +'DOMNode::hasAttributes' => ['bool'], +'DOMNode::hasChildNodes' => ['bool'], +'DOMNode::insertBefore' => ['DOMNode', 'newnode'=>'DOMNode', 'refnode='=>'DOMNode|null'], +'DOMNode::isDefaultNamespace' => ['bool', 'namespaceuri'=>'string'], +'DOMNode::isSameNode' => ['bool', 'node'=>'DOMNode'], +'DOMNode::isSupported' => ['bool', 'feature'=>'string', 'version'=>'string'], +'DOMNode::lookupNamespaceURI' => ['string', 'prefix'=>'string'], +'DOMNode::lookupPrefix' => ['string', 'namespaceuri'=>'string'], +'DOMNode::normalize' => ['void'], +'DOMNode::removeChild' => ['DOMNode', 'oldnode'=>'DOMNode'], +'DOMNode::replaceChild' => ['DOMNode|false', 'newnode'=>'DOMNode', 'oldnode'=>'DOMNode'], +'DOMNodeList::count' => ['int'], +'DOMNodeList::item' => ['?DOMNode', 'index'=>'int'], +'DOMProcessingInstruction::__construct' => ['void', 'name'=>'string', 'value'=>'string'], +'DomProcessingInstruction::data' => ['string'], +'DomProcessingInstruction::target' => ['string'], +'DOMText::__construct' => ['void', 'value='=>'string'], +'DOMText::isElementContentWhitespace' => ['bool'], +'DOMText::isWhitespaceInElementContent' => ['bool'], +'DOMText::splitText' => ['DOMText', 'offset'=>'int'], +'domxml_new_doc' => ['DomDocument', 'version'=>'string'], +'domxml_open_file' => ['DomDocument', 'filename'=>'string', 'mode='=>'int', 'error='=>'array'], +'domxml_open_mem' => ['DomDocument', 'string'=>'string', 'mode='=>'int', 'error='=>'array'], +'domxml_version' => ['string'], +'domxml_xmltree' => ['DomDocument', 'string'=>'string'], +'domxml_xslt_stylesheet' => ['DomXsltStylesheet', 'xsl_buf'=>'string'], +'domxml_xslt_stylesheet_doc' => ['DomXsltStylesheet', 'xsl_doc'=>'DOMDocument'], +'domxml_xslt_stylesheet_file' => ['DomXsltStylesheet', 'xsl_file'=>'string'], +'domxml_xslt_version' => ['int'], +'DOMXPath::__construct' => ['void', 'doc'=>'DOMDocument'], +'DOMXPath::evaluate' => ['mixed', 'expression'=>'string', 'contextnode='=>'?DOMNode', 'registernodens='=>'bool'], +'DOMXPath::query' => ['DOMNodeList|false', 'expression'=>'string', 'contextnode='=>'DOMNode|null', 'registernodens='=>'bool'], +'DOMXPath::registerNamespace' => ['bool', 'prefix'=>'string', 'namespaceuri'=>'string'], +'DOMXPath::registerPhpFunctions' => ['void', 'restrict='=>'mixed'], +'DomXsltStylesheet::process' => ['DomDocument', 'xml_doc'=>'DOMDocument', 'xslt_params='=>'array', 'is_xpath_param='=>'bool', 'profile_filename='=>'string'], +'DomXsltStylesheet::result_dump_file' => ['string', 'xmldoc'=>'DOMDocument', 'filename'=>'string'], +'DomXsltStylesheet::result_dump_mem' => ['string', 'xmldoc'=>'DOMDocument'], +'DOTNET::__call' => ['mixed', 'name'=>'string', 'args'=>''], +'DOTNET::__construct' => ['void', 'assembly_name'=>'string', 'class_name'=>'string', 'codepage='=>'int'], +'DOTNET::__get' => ['mixed', 'name'=>'string'], +'DOTNET::__set' => ['void', 'name'=>'string', 'value'=>''], +'dotnet_load' => ['int', 'assembly_name'=>'string', 'datatype_name='=>'string', 'codepage='=>'int'], +'doubleval' => ['float', 'value'=>'mixed'], +'Ds\Collection::clear' => ['void'], +'Ds\Collection::copy' => ['Ds\Collection'], +'Ds\Collection::isEmpty' => ['bool'], +'Ds\Collection::toArray' => ['array'], +'Ds\Deque::__construct' => ['void', 'values='=>'mixed'], +'Ds\Deque::allocate' => ['void', 'capacity'=>'int'], +'Ds\Deque::apply' => ['void', 'callback'=>'callable'], +'Ds\Deque::capacity' => ['int'], +'Ds\Deque::clear' => ['void'], +'Ds\Deque::contains' => ['bool', '...values='=>'mixed'], +'Ds\Deque::copy' => ['Ds\Deque'], +'Ds\Deque::count' => ['int'], +'Ds\Deque::filter' => ['Ds\Deque', 'callback='=>'callable'], +'Ds\Deque::find' => ['mixed', 'value'=>'mixed'], +'Ds\Deque::first' => ['mixed'], +'Ds\Deque::get' => ['void', 'index'=>'int'], +'Ds\Deque::insert' => ['void', 'index'=>'int', '...values='=>'mixed'], +'Ds\Deque::isEmpty' => ['bool'], +'Ds\Deque::join' => ['string', 'glue='=>'string'], +'Ds\Deque::jsonSerialize' => ['array'], +'Ds\Deque::last' => ['mixed'], +'Ds\Deque::map' => ['Ds\Deque', 'callback'=>'callable'], +'Ds\Deque::merge' => ['Ds\Deque', 'values'=>'mixed'], +'Ds\Deque::pop' => ['mixed'], +'Ds\Deque::push' => ['void', '...values='=>'mixed'], +'Ds\Deque::reduce' => ['mixed', 'callback'=>'callable', 'initial='=>'mixed'], +'Ds\Deque::remove' => ['mixed', 'index'=>'int'], +'Ds\Deque::reverse' => ['void'], +'Ds\Deque::reversed' => ['Ds\Deque'], +'Ds\Deque::rotate' => ['void', 'rotations'=>'int'], +'Ds\Deque::set' => ['void', 'index'=>'int', 'value'=>'mixed'], +'Ds\Deque::shift' => ['mixed'], +'Ds\Deque::slice' => ['Ds\Deque', 'index'=>'int', 'length='=>'?int'], +'Ds\Deque::sort' => ['void', 'comparator='=>'callable'], +'Ds\Deque::sorted' => ['Ds\Deque', 'comparator='=>'callable'], +'Ds\Deque::sum' => ['int|float'], +'Ds\Deque::toArray' => ['array'], +'Ds\Deque::unshift' => ['void', '...values='=>'mixed'], +'Ds\Hashable::equals' => ['bool', 'object'=>'mixed'], +'Ds\Hashable::hash' => ['mixed'], +'Ds\Map::__construct' => ['void', 'values='=>'mixed'], +'Ds\Map::allocate' => ['void', 'capacity'=>'int'], +'Ds\Map::apply' => ['void', 'callback'=>'callable'], +'Ds\Map::capacity' => ['int'], +'Ds\Map::clear' => ['void'], +'Ds\Map::copy' => ['Ds\Map'], +'Ds\Map::count' => ['int'], +'Ds\Map::diff' => ['Ds\Map', 'map'=>'Ds\Map'], +'Ds\Map::filter' => ['Ds\Map', 'callback='=>'callable'], +'Ds\Map::first' => ['Ds\Pair'], +'Ds\Map::get' => ['mixed', 'key'=>'mixed', 'default='=>'mixed'], +'Ds\Map::hasKey' => ['bool', 'key'=>'mixed'], +'Ds\Map::hasValue' => ['bool', 'value'=>'mixed'], +'Ds\Map::intersect' => ['Ds\Map', 'map'=>'Ds\Map'], +'Ds\Map::isEmpty' => ['bool'], +'Ds\Map::jsonSerialize' => ['array'], +'Ds\Map::keys' => ['Ds\Set'], +'Ds\Map::ksort' => ['void', 'comparator='=>'callable'], +'Ds\Map::ksorted' => ['Ds\Map', 'comparator='=>'callable'], +'Ds\Map::last' => ['Ds\Pair'], +'Ds\Map::map' => ['Ds\Map', 'callback'=>'callable'], +'Ds\Map::merge' => ['Ds\Map', 'values'=>'mixed'], +'Ds\Map::pairs' => ['Ds\Sequence'], +'Ds\Map::put' => ['void', 'key'=>'mixed', 'value'=>'mixed'], +'Ds\Map::putAll' => ['void', 'values'=>'mixed'], +'Ds\Map::reduce' => ['mixed', 'callback'=>'callable', 'initial='=>'mixed'], +'Ds\Map::remove' => ['mixed', 'key'=>'mixed', 'default='=>'mixed'], +'Ds\Map::reverse' => ['void'], +'Ds\Map::reversed' => ['Ds\Map'], +'Ds\Map::skip' => ['Ds\Pair', 'position'=>'int'], +'Ds\Map::slice' => ['Ds\Map', 'index'=>'int', 'length='=>'?int'], +'Ds\Map::sort' => ['void', 'comparator='=>'callable'], +'Ds\Map::sorted' => ['Ds\Map', 'comparator='=>'callable'], +'Ds\Map::sum' => ['int|float'], +'Ds\Map::toArray' => ['array'], +'Ds\Map::union' => ['Ds\Map', 'map'=>'Ds\Map'], +'Ds\Map::values' => ['Ds\Sequence'], +'Ds\Map::xor' => ['Ds\Map', 'map'=>'Ds\Map'], +'Ds\Pair::__construct' => ['void', 'key='=>'mixed', 'value='=>'mixed'], +'Ds\Pair::clear' => ['void'], +'Ds\Pair::copy' => ['Ds\Pair'], +'Ds\Pair::isEmpty' => ['bool'], +'Ds\Pair::jsonSerialize' => ['array'], +'Ds\Pair::toArray' => ['array'], +'Ds\PriorityQueue::__construct' => ['void'], +'Ds\PriorityQueue::allocate' => ['void', 'capacity'=>'int'], +'Ds\PriorityQueue::capacity' => ['int'], +'Ds\PriorityQueue::clear' => ['void'], +'Ds\PriorityQueue::copy' => ['Ds\PriorityQueue'], +'Ds\PriorityQueue::count' => ['int'], +'Ds\PriorityQueue::isEmpty' => ['bool'], +'Ds\PriorityQueue::jsonSerialize' => ['array'], +'Ds\PriorityQueue::peek' => ['mixed'], +'Ds\PriorityQueue::pop' => ['mixed'], +'Ds\PriorityQueue::push' => ['void', 'value'=>'mixed', 'priority'=>'int'], +'Ds\PriorityQueue::toArray' => ['array'], +'Ds\Queue::__construct' => ['void', 'values='=>'mixed'], +'Ds\Queue::allocate' => ['void', 'capacity'=>'int'], +'Ds\Queue::capacity' => ['int'], +'Ds\Queue::clear' => ['void'], +'Ds\Queue::copy' => ['Ds\Queue'], +'Ds\Queue::count' => ['int'], +'Ds\Queue::isEmpty' => ['bool'], +'Ds\Queue::jsonSerialize' => ['array'], +'Ds\Queue::peek' => ['mixed'], +'Ds\Queue::pop' => ['mixed'], +'Ds\Queue::push' => ['void', '...values='=>'mixed'], +'Ds\Queue::toArray' => ['array'], +'Ds\Sequence::allocate' => ['void', 'capacity'=>'int'], +'Ds\Sequence::apply' => ['void', 'callback'=>'callable'], +'Ds\Sequence::capacity' => ['int'], +'Ds\Sequence::contains' => ['bool', '...values='=>'mixed'], +'Ds\Sequence::filter' => ['Ds\Sequence', 'callback='=>'callable'], +'Ds\Sequence::find' => ['mixed', 'value'=>'mixed'], +'Ds\Sequence::first' => ['mixed'], +'Ds\Sequence::get' => ['mixed', 'index'=>'int'], +'Ds\Sequence::insert' => ['void', 'index'=>'int', '...values='=>'mixed'], +'Ds\Sequence::join' => ['string', 'glue='=>'string'], +'Ds\Sequence::last' => ['void'], +'Ds\Sequence::map' => ['Ds\Sequence', 'callback'=>'callable'], +'Ds\Sequence::merge' => ['Ds\Sequence', 'values'=>'mixed'], +'Ds\Sequence::pop' => ['mixed'], +'Ds\Sequence::push' => ['void', '...values='=>'mixed'], +'Ds\Sequence::reduce' => ['mixed', 'callback'=>'callable', 'initial='=>'mixed'], +'Ds\Sequence::remove' => ['mixed', 'index'=>'int'], +'Ds\Sequence::reverse' => ['void'], +'Ds\Sequence::reversed' => ['Ds\Sequence'], +'Ds\Sequence::rotate' => ['void', 'rotations'=>'int'], +'Ds\Sequence::set' => ['void', 'index'=>'int', 'value'=>'mixed'], +'Ds\Sequence::shift' => ['mixed'], +'Ds\Sequence::slice' => ['Ds\Sequence', 'index'=>'int', 'length='=>'?int'], +'Ds\Sequence::sort' => ['void', 'comparator='=>'callable'], +'Ds\Sequence::sorted' => ['Ds\Sequence', 'comparator='=>'callable'], +'Ds\Sequence::sum' => ['int|float'], +'Ds\Sequence::unshift' => ['void', '...values='=>'mixed'], +'Ds\Set::__construct' => ['void', 'values='=>'mixed'], +'Ds\Set::add' => ['void', '...values='=>'mixed'], +'Ds\Set::allocate' => ['void', 'capacity'=>'int'], +'Ds\Set::capacity' => ['int'], +'Ds\Set::clear' => ['void'], +'Ds\Set::contains' => ['bool', '...values='=>'mixed'], +'Ds\Set::copy' => ['Ds\Set'], +'Ds\Set::count' => ['int'], +'Ds\Set::diff' => ['Ds\Set', 'set'=>'Ds\Set'], +'Ds\Set::filter' => ['Ds\Set', 'callback='=>'callable'], +'Ds\Set::first' => ['mixed'], +'Ds\Set::get' => ['mixed', 'index'=>'int'], +'Ds\Set::intersect' => ['Ds\Set', 'set'=>'Ds\Set'], +'Ds\Set::isEmpty' => ['bool'], +'Ds\Set::join' => ['string', 'glue='=>'string'], +'Ds\Set::jsonSerialize' => ['array'], +'Ds\Set::last' => ['mixed'], +'Ds\Set::merge' => ['Ds\Set', 'values'=>'mixed'], +'Ds\Set::reduce' => ['mixed', 'callback'=>'callable', 'initial='=>'mixed'], +'Ds\Set::remove' => ['void', '...values='=>'mixed'], +'Ds\Set::reverse' => ['void'], +'Ds\Set::reversed' => ['Ds\Set'], +'Ds\Set::slice' => ['Ds\Set', 'index'=>'int', 'length='=>'?int'], +'Ds\Set::sort' => ['void', 'comparator='=>'callable'], +'Ds\Set::sorted' => ['Ds\Set', 'comparator='=>'callable'], +'Ds\Set::sum' => ['int|float'], +'Ds\Set::toArray' => ['array'], +'Ds\Set::union' => ['Ds\Set', 'set'=>'Ds\Set'], +'Ds\Set::xor' => ['Ds\Set', 'set'=>'Ds\Set'], +'Ds\Stack::__construct' => ['void', 'values='=>'mixed'], +'Ds\Stack::allocate' => ['void', 'capacity'=>'int'], +'Ds\Stack::capacity' => ['int'], +'Ds\Stack::clear' => ['void'], +'Ds\Stack::copy' => ['Ds\Stack'], +'Ds\Stack::count' => ['int'], +'Ds\Stack::isEmpty' => ['bool'], +'Ds\Stack::jsonSerialize' => ['array'], +'Ds\Stack::peek' => ['mixed'], +'Ds\Stack::pop' => ['mixed'], +'Ds\Stack::push' => ['void', '...values='=>'mixed'], +'Ds\Stack::toArray' => ['array'], +'Ds\Vector::__construct' => ['void', 'values='=>'mixed'], +'Ds\Vector::allocate' => ['void', 'capacity'=>'int'], +'Ds\Vector::apply' => ['void', 'callback'=>'callable'], +'Ds\Vector::capacity' => ['int'], +'Ds\Vector::clear' => ['void'], +'Ds\Vector::contains' => ['bool', '...values='=>'mixed'], +'Ds\Vector::copy' => ['Ds\Vector'], +'Ds\Vector::count' => ['int'], +'Ds\Vector::filter' => ['Ds\Vector', 'callback='=>'callable'], +'Ds\Vector::find' => ['mixed', 'value'=>'mixed'], +'Ds\Vector::first' => ['mixed'], +'Ds\Vector::get' => ['mixed', 'index'=>'int'], +'Ds\Vector::insert' => ['void', 'index'=>'int', '...values='=>'mixed'], +'Ds\Vector::isEmpty' => ['bool'], +'Ds\Vector::join' => ['string', 'glue='=>'string'], +'Ds\Vector::jsonSerialize' => ['array'], +'Ds\Vector::last' => ['mixed'], +'Ds\Vector::map' => ['Ds\Vector', 'callback'=>'callable'], +'Ds\Vector::merge' => ['Ds\Vector', 'values'=>'mixed'], +'Ds\Vector::pop' => ['mixed'], +'Ds\Vector::push' => ['void', '...values='=>'mixed'], +'Ds\Vector::reduce' => ['mixed', 'callback'=>'callable', 'initial='=>'mixed'], +'Ds\Vector::remove' => ['mixed', 'index'=>'int'], +'Ds\Vector::reverse' => ['void'], +'Ds\Vector::reversed' => ['Ds\Vector'], +'Ds\Vector::rotate' => ['void', 'rotations'=>'int'], +'Ds\Vector::set' => ['void', 'index'=>'int', 'value'=>'mixed'], +'Ds\Vector::shift' => ['mixed'], +'Ds\Vector::slice' => ['Ds\Vector', 'index'=>'int', 'length='=>'?int'], +'Ds\Vector::sort' => ['void', 'comparator='=>'callable'], +'Ds\Vector::sorted' => ['Ds\Vector', 'comparator='=>'callable'], +'Ds\Vector::sum' => ['int|float'], +'Ds\Vector::toArray' => ['array'], +'Ds\Vector::unshift' => ['void', '...values='=>'mixed'], +'each' => ['array{0:int|string,key:int|string,1:mixed,value:mixed}', '&r_arr'=>'array'], +'easter_date' => ['int', 'year='=>'int'], +'easter_days' => ['int', 'year='=>'int', 'method='=>'int'], +'echo' => ['void', 'arg1'=>'string', '...args='=>'string'], +'eio_busy' => ['resource', 'delay'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_cancel' => ['void', 'req'=>'resource'], +'eio_chmod' => ['resource', 'path'=>'string', 'mode'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_chown' => ['resource', 'path'=>'string', 'uid'=>'int', 'gid='=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_close' => ['resource', 'fd'=>'mixed', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_custom' => ['resource', 'execute'=>'callable', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'], +'eio_dup2' => ['resource', 'fd'=>'mixed', 'fd2'=>'mixed', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_event_loop' => ['bool'], +'eio_fallocate' => ['resource', 'fd'=>'mixed', 'mode'=>'int', 'offset'=>'int', 'length'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_fchmod' => ['resource', 'fd'=>'mixed', 'mode'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_fchown' => ['resource', 'fd'=>'mixed', 'uid'=>'int', 'gid='=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_fdatasync' => ['resource', 'fd'=>'mixed', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_fstat' => ['resource', 'fd'=>'mixed', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'], +'eio_fstatvfs' => ['resource', 'fd'=>'mixed', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'], +'eio_fsync' => ['resource', 'fd'=>'mixed', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_ftruncate' => ['resource', 'fd'=>'mixed', 'offset='=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_futime' => ['resource', 'fd'=>'mixed', 'atime'=>'float', 'mtime'=>'float', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_get_event_stream' => ['mixed'], +'eio_get_last_error' => ['string', 'req'=>'resource'], +'eio_grp' => ['resource', 'callback'=>'callable', 'data='=>'string'], +'eio_grp_add' => ['void', 'grp'=>'resource', 'req'=>'resource'], +'eio_grp_cancel' => ['void', 'grp'=>'resource'], +'eio_grp_limit' => ['void', 'grp'=>'resource', 'limit'=>'int'], +'eio_init' => ['void'], +'eio_link' => ['resource', 'path'=>'string', 'new_path'=>'string', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_lstat' => ['resource', 'path'=>'string', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'], +'eio_mkdir' => ['resource', 'path'=>'string', 'mode'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_mknod' => ['resource', 'path'=>'string', 'mode'=>'int', 'dev'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_nop' => ['resource', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_npending' => ['int'], +'eio_nready' => ['int'], +'eio_nreqs' => ['int'], +'eio_nthreads' => ['int'], +'eio_open' => ['resource', 'path'=>'string', 'flags'=>'int', 'mode'=>'int', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'], +'eio_poll' => ['int'], +'eio_read' => ['resource', 'fd'=>'mixed', 'length'=>'int', 'offset'=>'int', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'], +'eio_readahead' => ['resource', 'fd'=>'mixed', 'offset'=>'int', 'length'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_readdir' => ['resource', 'path'=>'string', 'flags'=>'int', 'pri'=>'int', 'callback'=>'callable', 'data='=>'string'], +'eio_readlink' => ['resource', 'path'=>'string', 'pri'=>'int', 'callback'=>'callable', 'data='=>'string'], +'eio_realpath' => ['resource', 'path'=>'string', 'pri'=>'int', 'callback'=>'callable', 'data='=>'string'], +'eio_rename' => ['resource', 'path'=>'string', 'new_path'=>'string', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_rmdir' => ['resource', 'path'=>'string', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_seek' => ['resource', 'fd'=>'mixed', 'offset'=>'int', 'whence'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_sendfile' => ['resource', 'out_fd'=>'mixed', 'in_fd'=>'mixed', 'offset'=>'int', 'length'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'string'], +'eio_set_max_idle' => ['void', 'nthreads'=>'int'], +'eio_set_max_parallel' => ['void', 'nthreads'=>'int'], +'eio_set_max_poll_reqs' => ['void', 'nreqs'=>'int'], +'eio_set_max_poll_time' => ['void', 'nseconds'=>'float'], +'eio_set_min_parallel' => ['void', 'nthreads'=>'string'], +'eio_stat' => ['resource', 'path'=>'string', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'], +'eio_statvfs' => ['resource', 'path'=>'string', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'], +'eio_symlink' => ['resource', 'path'=>'string', 'new_path'=>'string', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_sync' => ['resource', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_sync_file_range' => ['resource', 'fd'=>'mixed', 'offset'=>'int', 'nbytes'=>'int', 'flags'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_syncfs' => ['resource', 'fd'=>'mixed', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_truncate' => ['resource', 'path'=>'string', 'offset='=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_unlink' => ['resource', 'path'=>'string', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_utime' => ['resource', 'path'=>'string', 'atime'=>'float', 'mtime'=>'float', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'eio_write' => ['resource', 'fd'=>'mixed', 'string'=>'string', 'length='=>'int', 'offset='=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], +'empty' => ['bool', 'value'=>'mixed'], +'EmptyIterator::current' => ['mixed'], +'EmptyIterator::key' => ['mixed'], +'EmptyIterator::next' => ['void'], +'EmptyIterator::rewind' => ['void'], +'EmptyIterator::valid' => ['bool'], +'enchant_broker_describe' => ['array', 'broker'=>'resource'], +'enchant_broker_dict_exists' => ['bool', 'broker'=>'resource', 'tag'=>'string'], +'enchant_broker_free' => ['bool', 'broker'=>'resource'], +'enchant_broker_free_dict' => ['bool', 'dict'=>'resource'], +'enchant_broker_get_dict_path' => ['string', 'broker'=>'resource', 'dict_type'=>'int'], +'enchant_broker_get_error' => ['string|false', 'broker'=>'resource'], +'enchant_broker_init' => ['resource|false'], +'enchant_broker_list_dicts' => ['array|false', 'broker'=>'resource'], +'enchant_broker_request_dict' => ['resource|false', 'broker'=>'resource', 'tag'=>'string'], +'enchant_broker_request_pwl_dict' => ['resource|false', 'broker'=>'resource', 'filename'=>'string'], +'enchant_broker_set_dict_path' => ['bool', 'broker'=>'resource', 'dict_type'=>'int', 'value'=>'string'], +'enchant_broker_set_ordering' => ['bool', 'broker'=>'resource', 'tag'=>'string', 'ordering'=>'string'], +'enchant_dict_add_to_personal' => ['void', 'dict'=>'resource', 'word'=>'string'], +'enchant_dict_add_to_session' => ['void', 'dict'=>'resource', 'word'=>'string'], +'enchant_dict_check' => ['bool', 'dict'=>'resource', 'word'=>'string'], +'enchant_dict_describe' => ['array', 'dict'=>'resource'], +'enchant_dict_get_error' => ['string', 'dict'=>'resource'], +'enchant_dict_is_in_session' => ['bool', 'dict'=>'resource', 'word'=>'string'], +'enchant_dict_quick_check' => ['bool', 'dict'=>'resource', 'word'=>'string', '&w_suggestions='=>'array'], +'enchant_dict_store_replacement' => ['void', 'dict'=>'resource', 'mis'=>'string', 'cor'=>'string'], +'enchant_dict_suggest' => ['array', 'dict'=>'resource', 'word'=>'string'], +'end' => ['mixed|false', '&r_array'=>'array|object'], +'Error::__clone' => ['void'], +'Error::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?Error'], +'Error::__toString' => ['string'], +'Error::getCode' => ['int'], +'Error::getFile' => ['string'], +'Error::getLine' => ['int'], +'Error::getMessage' => ['string'], +'Error::getPrevious' => ['Throwable|Error|null'], +'Error::getTrace' => ['list>'], +'Error::getTraceAsString' => ['string'], +'error_clear_last' => ['void'], +'error_get_last' => ['?array{type:int,message:string,file:string,line:int}'], +'error_log' => ['bool', 'message'=>'string', 'message_type='=>'int', 'destination='=>'string', 'extra_headers='=>'string'], +'error_reporting' => ['int', 'new_error_level='=>'int'], +'ErrorException::__clone' => ['void'], +'ErrorException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'severity='=>'int', 'filename='=>'string', 'lineno='=>'int', 'previous='=>'?Throwable|?ErrorException'], +'ErrorException::__toString' => ['string'], +'ErrorException::getCode' => ['int'], +'ErrorException::getFile' => ['string'], +'ErrorException::getLine' => ['int'], +'ErrorException::getMessage' => ['string'], +'ErrorException::getPrevious' => ['Throwable|ErrorException|null'], +'ErrorException::getSeverity' => ['int'], +'ErrorException::getTrace' => ['list>'], +'ErrorException::getTraceAsString' => ['string'], +'escapeshellarg' => ['string', 'arg'=>'string'], +'escapeshellcmd' => ['string', 'command'=>'string'], +'Ev::backend' => ['int'], +'Ev::depth' => ['int'], +'Ev::embeddableBackends' => ['int'], +'Ev::feedSignal' => ['void', 'signum'=>'int'], +'Ev::feedSignalEvent' => ['void', 'signum'=>'int'], +'Ev::iteration' => ['int'], +'Ev::now' => ['float'], +'Ev::nowUpdate' => ['void'], +'Ev::recommendedBackends' => ['int'], +'Ev::resume' => ['void'], +'Ev::run' => ['void', 'flags='=>'int'], +'Ev::sleep' => ['void', 'seconds'=>'float'], +'Ev::stop' => ['void', 'how='=>'int'], +'Ev::supportedBackends' => ['int'], +'Ev::suspend' => ['void'], +'Ev::time' => ['float'], +'Ev::verify' => ['void'], +'eval' => ['mixed', 'code_str'=>'string'], +'EvCheck::__construct' => ['void', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvCheck::clear' => ['int'], +'EvCheck::createStopped' => ['EvCheck', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvCheck::feed' => ['void', 'events'=>'int'], +'EvCheck::getLoop' => ['EvLoop'], +'EvCheck::invoke' => ['void', 'events'=>'int'], +'EvCheck::keepAlive' => ['void', 'value'=>'bool'], +'EvCheck::setCallback' => ['void', 'callback'=>'callable'], +'EvCheck::start' => ['void'], +'EvCheck::stop' => ['void'], +'EvChild::__construct' => ['void', 'pid'=>'int', 'trace'=>'bool', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvChild::clear' => ['int'], +'EvChild::createStopped' => ['EvChild', 'pid'=>'int', 'trace'=>'bool', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvChild::feed' => ['void', 'events'=>'int'], +'EvChild::getLoop' => ['EvLoop'], +'EvChild::invoke' => ['void', 'events'=>'int'], +'EvChild::keepAlive' => ['void', 'value'=>'bool'], +'EvChild::set' => ['void', 'pid'=>'int', 'trace'=>'bool'], +'EvChild::setCallback' => ['void', 'callback'=>'callable'], +'EvChild::start' => ['void'], +'EvChild::stop' => ['void'], +'EvEmbed::__construct' => ['void', 'other'=>'object', 'callback='=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvEmbed::clear' => ['int'], +'EvEmbed::createStopped' => ['EvEmbed', 'other'=>'object', 'callback='=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvEmbed::feed' => ['void', 'events'=>'int'], +'EvEmbed::getLoop' => ['EvLoop'], +'EvEmbed::invoke' => ['void', 'events'=>'int'], +'EvEmbed::keepAlive' => ['void', 'value'=>'bool'], +'EvEmbed::set' => ['void', 'other'=>'object'], +'EvEmbed::setCallback' => ['void', 'callback'=>'callable'], +'EvEmbed::start' => ['void'], +'EvEmbed::stop' => ['void'], +'EvEmbed::sweep' => ['void'], +'Event::__construct' => ['void', 'base'=>'EventBase', 'fd'=>'mixed', 'what'=>'int', 'cb'=>'callable', 'arg='=>'mixed'], +'Event::add' => ['bool', 'timeout='=>'float'], +'Event::addSignal' => ['bool', 'timeout='=>'float'], +'Event::addTimer' => ['bool', 'timeout='=>'float'], +'Event::del' => ['bool'], +'Event::delSignal' => ['bool'], +'Event::delTimer' => ['bool'], +'Event::free' => ['void'], +'Event::getSupportedMethods' => ['array'], +'Event::pending' => ['bool', 'flags'=>'int'], +'Event::set' => ['bool', 'base'=>'EventBase', 'fd'=>'mixed', 'what='=>'int', 'cb='=>'callable', 'arg='=>'mixed'], +'Event::setPriority' => ['bool', 'priority'=>'int'], +'Event::setTimer' => ['bool', 'base'=>'EventBase', 'cb'=>'callable', 'arg='=>'mixed'], +'Event::signal' => ['Event', 'base'=>'EventBase', 'signum'=>'int', 'cb'=>'callable', 'arg='=>'mixed'], +'Event::timer' => ['Event', 'base'=>'EventBase', 'cb'=>'callable', 'arg='=>'mixed'], +'event_add' => ['bool', 'event'=>'resource', 'timeout='=>'int'], +'event_base_free' => ['void', 'event_base'=>'resource'], +'event_base_loop' => ['int', 'event_base'=>'resource', 'flags='=>'int'], +'event_base_loopbreak' => ['bool', 'event_base'=>'resource'], +'event_base_loopexit' => ['bool', 'event_base'=>'resource', 'timeout='=>'int'], +'event_base_new' => ['resource|false'], +'event_base_priority_init' => ['bool', 'event_base'=>'resource', 'npriorities'=>'int'], +'event_base_reinit' => ['bool', 'event_base'=>'resource'], +'event_base_set' => ['bool', 'event'=>'resource', 'event_base'=>'resource'], +'event_buffer_base_set' => ['bool', 'bevent'=>'resource', 'event_base'=>'resource'], +'event_buffer_disable' => ['bool', 'bevent'=>'resource', 'events'=>'int'], +'event_buffer_enable' => ['bool', 'bevent'=>'resource', 'events'=>'int'], +'event_buffer_fd_set' => ['void', 'bevent'=>'resource', 'fd'=>'resource'], +'event_buffer_free' => ['void', 'bevent'=>'resource'], +'event_buffer_new' => ['resource|false', 'stream'=>'resource', 'readcb'=>'callable|null', 'writecb'=>'callable|null', 'errorcb'=>'callable', 'arg='=>'mixed'], +'event_buffer_priority_set' => ['bool', 'bevent'=>'resource', 'priority'=>'int'], +'event_buffer_read' => ['string', 'bevent'=>'resource', 'data_size'=>'int'], +'event_buffer_set_callback' => ['bool', 'event'=>'resource', 'readcb'=>'mixed', 'writecb'=>'mixed', 'errorcb'=>'mixed', 'arg='=>'mixed'], +'event_buffer_timeout_set' => ['void', 'bevent'=>'resource', 'read_timeout'=>'int', 'write_timeout'=>'int'], +'event_buffer_watermark_set' => ['void', 'bevent'=>'resource', 'events'=>'int', 'lowmark'=>'int', 'highmark'=>'int'], +'event_buffer_write' => ['bool', 'bevent'=>'resource', 'data'=>'string', 'data_size='=>'int'], +'event_del' => ['bool', 'event'=>'resource'], +'event_free' => ['void', 'event'=>'resource'], +'event_new' => ['resource|false'], +'event_priority_set' => ['bool', 'event'=>'resource', 'priority'=>'int'], +'event_set' => ['bool', 'event'=>'resource', 'fd'=>'int|resource', 'events'=>'int', 'callback'=>'callable', 'arg='=>'mixed'], +'event_timer_add' => ['bool', 'event'=>'resource', 'timeout='=>'int'], +'event_timer_del' => ['bool', 'event'=>'resource'], +'event_timer_new' => ['resource|false'], +'event_timer_pending' => ['bool', 'event'=>'resource', 'timeout='=>'int'], +'event_timer_set' => ['bool', 'event'=>'resource', 'callback'=>'callable', 'arg='=>'mixed'], +'EventBase::__construct' => ['void', 'cfg='=>'EventConfig'], +'EventBase::dispatch' => ['void'], +'EventBase::exit' => ['bool', 'timeout='=>'float'], +'EventBase::free' => ['void'], +'EventBase::getFeatures' => ['int'], +'EventBase::getMethod' => ['string', 'cfg='=>'EventConfig'], +'EventBase::getTimeOfDayCached' => ['float'], +'EventBase::gotExit' => ['bool'], +'EventBase::gotStop' => ['bool'], +'EventBase::loop' => ['bool', 'flags='=>'int'], +'EventBase::priorityInit' => ['bool', 'n_priorities'=>'int'], +'EventBase::reInit' => ['bool'], +'EventBase::stop' => ['bool'], +'EventBuffer::__construct' => ['void'], +'EventBuffer::add' => ['bool', 'data'=>'string'], +'EventBuffer::addBuffer' => ['bool', 'buf'=>'EventBuffer'], +'EventBuffer::appendFrom' => ['int', 'buf'=>'EventBuffer', 'length'=>'int'], +'EventBuffer::copyout' => ['int', '&w_data'=>'string', 'max_bytes'=>'int'], +'EventBuffer::drain' => ['bool', 'length'=>'int'], +'EventBuffer::enableLocking' => ['void'], +'EventBuffer::expand' => ['bool', 'length'=>'int'], +'EventBuffer::freeze' => ['bool', 'at_front'=>'bool'], +'EventBuffer::lock' => ['void'], +'EventBuffer::prepend' => ['bool', 'data'=>'string'], +'EventBuffer::prependBuffer' => ['bool', 'buf'=>'EventBuffer'], +'EventBuffer::pullup' => ['string', 'size'=>'int'], +'EventBuffer::read' => ['string', 'max_bytes'=>'int'], +'EventBuffer::readFrom' => ['int', 'fd'=>'mixed', 'howmuch'=>'int'], +'EventBuffer::readLine' => ['string', 'eol_style'=>'int'], +'EventBuffer::search' => ['mixed', 'what'=>'string', 'start='=>'int', 'end='=>'int'], +'EventBuffer::searchEol' => ['mixed', 'start='=>'int', 'eol_style='=>'int'], +'EventBuffer::substr' => ['string', 'start'=>'int', 'length='=>'int'], +'EventBuffer::unfreeze' => ['bool', 'at_front'=>'bool'], +'EventBuffer::unlock' => ['bool'], +'EventBuffer::write' => ['int', 'fd'=>'mixed', 'howmuch='=>'int'], +'EventBufferEvent::__construct' => ['void', 'base'=>'EventBase', 'socket='=>'mixed', 'options='=>'int', 'readcb='=>'callable', 'writecb='=>'callable', 'eventcb='=>'callable'], +'EventBufferEvent::close' => ['void'], +'EventBufferEvent::connect' => ['bool', 'addr'=>'string'], +'EventBufferEvent::connectHost' => ['bool', 'dns_base'=>'EventDnsBase', 'hostname'=>'string', 'port'=>'int', 'family='=>'int'], +'EventBufferEvent::createPair' => ['array', 'base'=>'EventBase', 'options='=>'int'], +'EventBufferEvent::disable' => ['bool', 'events'=>'int'], +'EventBufferEvent::enable' => ['bool', 'events'=>'int'], +'EventBufferEvent::free' => ['void'], +'EventBufferEvent::getDnsErrorString' => ['string'], +'EventBufferEvent::getEnabled' => ['int'], +'EventBufferEvent::getInput' => ['EventBuffer'], +'EventBufferEvent::getOutput' => ['EventBuffer'], +'EventBufferEvent::read' => ['string', 'size'=>'int'], +'EventBufferEvent::readBuffer' => ['bool', 'buf'=>'EventBuffer'], +'EventBufferEvent::setCallbacks' => ['void', 'readcb'=>'callable', 'writecb'=>'callable', 'eventcb'=>'callable', 'arg='=>'string'], +'EventBufferEvent::setPriority' => ['bool', 'priority'=>'int'], +'EventBufferEvent::setTimeouts' => ['bool', 'timeout_read'=>'float', 'timeout_write'=>'float'], +'EventBufferEvent::setWatermark' => ['void', 'events'=>'int', 'lowmark'=>'int', 'highmark'=>'int'], +'EventBufferEvent::sslError' => ['string'], +'EventBufferEvent::sslFilter' => ['EventBufferEvent', 'base'=>'EventBase', 'underlying'=>'EventBufferEvent', 'ctx'=>'EventSslContext', 'state'=>'int', 'options='=>'int'], +'EventBufferEvent::sslGetCipherInfo' => ['string'], +'EventBufferEvent::sslGetCipherName' => ['string'], +'EventBufferEvent::sslGetCipherVersion' => ['string'], +'EventBufferEvent::sslGetProtocol' => ['string'], +'EventBufferEvent::sslRenegotiate' => ['void'], +'EventBufferEvent::sslSocket' => ['EventBufferEvent', 'base'=>'EventBase', 'socket'=>'mixed', 'ctx'=>'EventSslContext', 'state'=>'int', 'options='=>'int'], +'EventBufferEvent::write' => ['bool', 'data'=>'string'], +'EventBufferEvent::writeBuffer' => ['bool', 'buf'=>'EventBuffer'], +'EventConfig::__construct' => ['void'], +'EventConfig::avoidMethod' => ['bool', 'method'=>'string'], +'EventConfig::requireFeatures' => ['bool', 'feature'=>'int'], +'EventConfig::setMaxDispatchInterval' => ['void', 'max_interval'=>'int', 'max_callbacks'=>'int', 'min_priority'=>'int'], +'EventDnsBase::__construct' => ['void', 'base'=>'EventBase', 'initialize'=>'bool'], +'EventDnsBase::addNameserverIp' => ['bool', 'ip'=>'string'], +'EventDnsBase::addSearch' => ['void', 'domain'=>'string'], +'EventDnsBase::clearSearch' => ['void'], +'EventDnsBase::countNameservers' => ['int'], +'EventDnsBase::loadHosts' => ['bool', 'hosts'=>'string'], +'EventDnsBase::parseResolvConf' => ['bool', 'flags'=>'int', 'filename'=>'string'], +'EventDnsBase::setOption' => ['bool', 'option'=>'string', 'value'=>'string'], +'EventDnsBase::setSearchNdots' => ['bool', 'ndots'=>'int'], +'EventHttp::__construct' => ['void', 'base'=>'EventBase', 'ctx='=>'EventSslContext'], +'EventHttp::accept' => ['bool', 'socket'=>'mixed'], +'EventHttp::addServerAlias' => ['bool', 'alias'=>'string'], +'EventHttp::bind' => ['void', 'address'=>'string', 'port'=>'int'], +'EventHttp::removeServerAlias' => ['bool', 'alias'=>'string'], +'EventHttp::setAllowedMethods' => ['void', 'methods'=>'int'], +'EventHttp::setCallback' => ['void', 'path'=>'string', 'cb'=>'string', 'arg='=>'string'], +'EventHttp::setDefaultCallback' => ['void', 'cb'=>'string', 'arg='=>'string'], +'EventHttp::setMaxBodySize' => ['void', 'value'=>'int'], +'EventHttp::setMaxHeadersSize' => ['void', 'value'=>'int'], +'EventHttp::setTimeout' => ['void', 'value'=>'int'], +'EventHttpConnection::__construct' => ['void', 'base'=>'EventBase', 'dns_base'=>'EventDnsBase', 'address'=>'string', 'port'=>'int', 'ctx='=>'EventSslContext'], +'EventHttpConnection::getBase' => ['EventBase'], +'EventHttpConnection::getPeer' => ['void', '&w_address'=>'string', '&w_port'=>'int'], +'EventHttpConnection::makeRequest' => ['bool', 'req'=>'EventHttpRequest', 'type'=>'int', 'uri'=>'string'], +'EventHttpConnection::setCloseCallback' => ['void', 'callback'=>'callable', 'data='=>'mixed'], +'EventHttpConnection::setLocalAddress' => ['void', 'address'=>'string'], +'EventHttpConnection::setLocalPort' => ['void', 'port'=>'int'], +'EventHttpConnection::setMaxBodySize' => ['void', 'max_size'=>'string'], +'EventHttpConnection::setMaxHeadersSize' => ['void', 'max_size'=>'string'], +'EventHttpConnection::setRetries' => ['void', 'retries'=>'int'], +'EventHttpConnection::setTimeout' => ['void', 'timeout'=>'int'], +'EventHttpRequest::__construct' => ['void', 'callback'=>'callable', 'data='=>'mixed'], +'EventHttpRequest::addHeader' => ['bool', 'key'=>'string', 'value'=>'string', 'type'=>'int'], +'EventHttpRequest::cancel' => ['void'], +'EventHttpRequest::clearHeaders' => ['void'], +'EventHttpRequest::closeConnection' => ['void'], +'EventHttpRequest::findHeader' => ['void', 'key'=>'string', 'type'=>'string'], +'EventHttpRequest::free' => ['void'], +'EventHttpRequest::getBufferEvent' => ['EventBufferEvent'], +'EventHttpRequest::getCommand' => ['void'], +'EventHttpRequest::getConnection' => ['EventHttpConnection'], +'EventHttpRequest::getHost' => ['string'], +'EventHttpRequest::getInputBuffer' => ['EventBuffer'], +'EventHttpRequest::getInputHeaders' => ['array'], +'EventHttpRequest::getOutputBuffer' => ['EventBuffer'], +'EventHttpRequest::getOutputHeaders' => ['void'], +'EventHttpRequest::getResponseCode' => ['int'], +'EventHttpRequest::getUri' => ['string'], +'EventHttpRequest::removeHeader' => ['void', 'key'=>'string', 'type'=>'string'], +'EventHttpRequest::sendError' => ['void', 'error'=>'int', 'reason='=>'string'], +'EventHttpRequest::sendReply' => ['void', 'code'=>'int', 'reason'=>'string', 'buf='=>'EventBuffer'], +'EventHttpRequest::sendReplyChunk' => ['void', 'buf'=>'EventBuffer'], +'EventHttpRequest::sendReplyEnd' => ['void'], +'EventHttpRequest::sendReplyStart' => ['void', 'code'=>'int', 'reason'=>'string'], +'EventListener::__construct' => ['void', 'base'=>'EventBase', 'cb'=>'callable', 'data'=>'mixed', 'flags'=>'int', 'backlog'=>'int', 'target'=>'mixed'], +'EventListener::disable' => ['bool'], +'EventListener::enable' => ['bool'], +'EventListener::getBase' => ['void'], +'EventListener::getSocketName' => ['bool', '&w_address'=>'string', '&w_port='=>'mixed'], +'EventListener::setCallback' => ['void', 'cb'=>'callable', 'arg='=>'mixed'], +'EventListener::setErrorCallback' => ['void', 'cb'=>'string'], +'EventSslContext::__construct' => ['void', 'method'=>'string', 'options'=>'string'], +'EventUtil::__construct' => ['void'], +'EventUtil::getLastSocketErrno' => ['int', 'socket='=>'mixed'], +'EventUtil::getLastSocketError' => ['string', 'socket='=>'mixed'], +'EventUtil::getSocketFd' => ['int', 'socket'=>'mixed'], +'EventUtil::getSocketName' => ['bool', 'socket'=>'mixed', '&w_address'=>'string', '&w_port='=>'mixed'], +'EventUtil::setSocketOption' => ['bool', 'socket'=>'mixed', 'level'=>'int', 'optname'=>'int', 'optval'=>'mixed'], +'EventUtil::sslRandPoll' => ['void'], +'EvFork::__construct' => ['void', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvFork::clear' => ['int'], +'EvFork::createStopped' => ['EvFork', 'callback'=>'callable', 'data='=>'string', 'priority='=>'string'], +'EvFork::feed' => ['void', 'events'=>'int'], +'EvFork::getLoop' => ['EvLoop'], +'EvFork::invoke' => ['void', 'events'=>'int'], +'EvFork::keepAlive' => ['void', 'value'=>'bool'], +'EvFork::setCallback' => ['void', 'callback'=>'callable'], +'EvFork::start' => ['void'], +'EvFork::stop' => ['void'], +'EvIdle::__construct' => ['void', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvIdle::clear' => ['int'], +'EvIdle::createStopped' => ['EvIdle', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvIdle::feed' => ['void', 'events'=>'int'], +'EvIdle::getLoop' => ['EvLoop'], +'EvIdle::invoke' => ['void', 'events'=>'int'], +'EvIdle::keepAlive' => ['void', 'value'=>'bool'], +'EvIdle::setCallback' => ['void', 'callback'=>'callable'], +'EvIdle::start' => ['void'], +'EvIdle::stop' => ['void'], +'EvIo::__construct' => ['void', 'fd'=>'mixed', 'events'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvIo::clear' => ['int'], +'EvIo::createStopped' => ['EvIo', 'fd'=>'resource', 'events'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvIo::feed' => ['void', 'events'=>'int'], +'EvIo::getLoop' => ['EvLoop'], +'EvIo::invoke' => ['void', 'events'=>'int'], +'EvIo::keepAlive' => ['void', 'value'=>'bool'], +'EvIo::set' => ['void', 'fd'=>'resource', 'events'=>'int'], +'EvIo::setCallback' => ['void', 'callback'=>'callable'], +'EvIo::start' => ['void'], +'EvIo::stop' => ['void'], +'EvLoop::__construct' => ['void', 'flags='=>'int', 'data='=>'mixed', 'io_interval='=>'float', 'timeout_interval='=>'float'], +'EvLoop::backend' => ['int'], +'EvLoop::check' => ['EvCheck', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvLoop::child' => ['EvChild', 'pid'=>'int', 'trace'=>'bool', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvLoop::defaultLoop' => ['EvLoop', 'flags='=>'int', 'data='=>'mixed', 'io_interval='=>'float', 'timeout_interval='=>'float'], +'EvLoop::embed' => ['EvEmbed', 'other'=>'EvLoop', 'callback='=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvLoop::fork' => ['EvFork', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvLoop::idle' => ['EvIdle', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvLoop::invokePending' => ['void'], +'EvLoop::io' => ['EvIo', 'fd'=>'resource', 'events'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvLoop::loopFork' => ['void'], +'EvLoop::now' => ['float'], +'EvLoop::nowUpdate' => ['void'], +'EvLoop::periodic' => ['EvPeriodic', 'offset'=>'float', 'interval'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvLoop::prepare' => ['EvPrepare', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvLoop::resume' => ['void'], +'EvLoop::run' => ['void', 'flags='=>'int'], +'EvLoop::signal' => ['EvSignal', 'signum'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvLoop::stat' => ['EvStat', 'path'=>'string', 'interval'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvLoop::stop' => ['void', 'how='=>'int'], +'EvLoop::suspend' => ['void'], +'EvLoop::timer' => ['EvTimer', 'after'=>'float', 'repeat'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvLoop::verify' => ['void'], +'EvPeriodic::__construct' => ['void', 'offset'=>'float', 'interval'=>'string', 'reschedule_cb'=>'callable', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvPeriodic::again' => ['void'], +'EvPeriodic::at' => ['float'], +'EvPeriodic::clear' => ['int'], +'EvPeriodic::createStopped' => ['EvPeriodic', 'offset'=>'float', 'interval'=>'float', 'reschedule_cb'=>'callable', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvPeriodic::feed' => ['void', 'events'=>'int'], +'EvPeriodic::getLoop' => ['EvLoop'], +'EvPeriodic::invoke' => ['void', 'events'=>'int'], +'EvPeriodic::keepAlive' => ['void', 'value'=>'bool'], +'EvPeriodic::set' => ['void', 'offset'=>'float', 'interval'=>'float'], +'EvPeriodic::setCallback' => ['void', 'callback'=>'callable'], +'EvPeriodic::start' => ['void'], +'EvPeriodic::stop' => ['void'], +'EvPrepare::__construct' => ['void', 'callback'=>'string', 'data='=>'string', 'priority='=>'string'], +'EvPrepare::clear' => ['int'], +'EvPrepare::createStopped' => ['EvPrepare', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvPrepare::feed' => ['void', 'events'=>'int'], +'EvPrepare::getLoop' => ['EvLoop'], +'EvPrepare::invoke' => ['void', 'events'=>'int'], +'EvPrepare::keepAlive' => ['void', 'value'=>'bool'], +'EvPrepare::setCallback' => ['void', 'callback'=>'callable'], +'EvPrepare::start' => ['void'], +'EvPrepare::stop' => ['void'], +'EvSignal::__construct' => ['void', 'signum'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvSignal::clear' => ['int'], +'EvSignal::createStopped' => ['EvSignal', 'signum'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvSignal::feed' => ['void', 'events'=>'int'], +'EvSignal::getLoop' => ['EvLoop'], +'EvSignal::invoke' => ['void', 'events'=>'int'], +'EvSignal::keepAlive' => ['void', 'value'=>'bool'], +'EvSignal::set' => ['void', 'signum'=>'int'], +'EvSignal::setCallback' => ['void', 'callback'=>'callable'], +'EvSignal::start' => ['void'], +'EvSignal::stop' => ['void'], +'EvStat::__construct' => ['void', 'path'=>'string', 'interval'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvStat::attr' => ['array'], +'EvStat::clear' => ['int'], +'EvStat::createStopped' => ['EvStat', 'path'=>'string', 'interval'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvStat::feed' => ['void', 'events'=>'int'], +'EvStat::getLoop' => ['EvLoop'], +'EvStat::invoke' => ['void', 'events'=>'int'], +'EvStat::keepAlive' => ['void', 'value'=>'bool'], +'EvStat::prev' => ['array'], +'EvStat::set' => ['void', 'path'=>'string', 'interval'=>'float'], +'EvStat::setCallback' => ['void', 'callback'=>'callable'], +'EvStat::start' => ['void'], +'EvStat::stat' => ['bool'], +'EvStat::stop' => ['void'], +'EvTimer::__construct' => ['void', 'after'=>'float', 'repeat'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvTimer::again' => ['void'], +'EvTimer::clear' => ['int'], +'EvTimer::createStopped' => ['EvTimer', 'after'=>'float', 'repeat'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], +'EvTimer::feed' => ['void', 'events'=>'int'], +'EvTimer::getLoop' => ['EvLoop'], +'EvTimer::invoke' => ['void', 'events'=>'int'], +'EvTimer::keepAlive' => ['void', 'value'=>'bool'], +'EvTimer::set' => ['void', 'after'=>'float', 'repeat'=>'float'], +'EvTimer::setCallback' => ['void', 'callback'=>'callable'], +'EvTimer::start' => ['void'], +'EvTimer::stop' => ['void'], +'EvWatcher::__construct' => ['void'], +'EvWatcher::clear' => ['int'], +'EvWatcher::feed' => ['void', 'revents'=>'int'], +'EvWatcher::getLoop' => ['EvLoop'], +'EvWatcher::invoke' => ['void', 'revents'=>'int'], +'EvWatcher::keepalive' => ['bool', 'value='=>'bool'], +'EvWatcher::setCallback' => ['void', 'callback'=>'callable'], +'EvWatcher::start' => ['void'], +'EvWatcher::stop' => ['void'], +'Exception::__clone' => ['void'], +'Exception::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?Exception'], +'Exception::__toString' => ['string'], +'Exception::getCode' => ['int|string'], +'Exception::getFile' => ['string'], +'Exception::getLine' => ['int'], +'Exception::getMessage' => ['string'], +'Exception::getPrevious' => ['?Throwable|?Exception'], +'Exception::getTrace' => ['list>'], +'Exception::getTraceAsString' => ['string'], +'exec' => ['string', 'command'=>'string', '&w_output='=>'array', '&w_return_value='=>'int'], +'exif_imagetype' => ['int|false', 'imagefile'=>'string'], +'exif_read_data' => ['array|false', 'filename'=>'string|resource', 'sections_needed='=>'string', 'sub_arrays='=>'bool', 'read_thumbnail='=>'bool'], +'exif_tagname' => ['string|false', 'index'=>'int'], +'exif_thumbnail' => ['string|false', 'filename'=>'string', '&w_width='=>'int', '&w_height='=>'int', '&w_imagetype='=>'int'], +'exit' => ['', 'status'=>'string|int'], +'exp' => ['float', 'number'=>'float'], +'expect_expectl' => ['int', 'expect'=>'resource', 'cases'=>'array', 'match='=>'array'], +'expect_popen' => ['resource|false', 'command'=>'string'], +'explode' => ['list|false', 'separator'=>'string', 'str'=>'string', 'limit='=>'int'], +'expm1' => ['float', 'number'=>'float'], +'extension_loaded' => ['bool', 'extension_name'=>'string'], +'extract' => ['int', '&rw_var_array'=>'array', 'extract_type='=>'int', 'prefix='=>'?string'], +'ezmlm_hash' => ['int', 'addr'=>'string'], +'fam_cancel_monitor' => ['bool', 'fam'=>'resource', 'fam_monitor'=>'resource'], +'fam_close' => ['void', 'fam'=>'resource'], +'fam_monitor_collection' => ['resource', 'fam'=>'resource', 'dirname'=>'string', 'depth'=>'int', 'mask'=>'string'], +'fam_monitor_directory' => ['resource', 'fam'=>'resource', 'dirname'=>'string'], +'fam_monitor_file' => ['resource', 'fam'=>'resource', 'filename'=>'string'], +'fam_next_event' => ['array', 'fam'=>'resource'], +'fam_open' => ['resource|false', 'appname='=>'string'], +'fam_pending' => ['int', 'fam'=>'resource'], +'fam_resume_monitor' => ['bool', 'fam'=>'resource', 'fam_monitor'=>'resource'], +'fam_suspend_monitor' => ['bool', 'fam'=>'resource', 'fam_monitor'=>'resource'], +'fann_cascadetrain_on_data' => ['bool', 'ann'=>'resource', 'data'=>'resource', 'max_neurons'=>'int', 'neurons_between_reports'=>'int', 'desired_error'=>'float'], +'fann_cascadetrain_on_file' => ['bool', 'ann'=>'resource', 'filename'=>'string', 'max_neurons'=>'int', 'neurons_between_reports'=>'int', 'desired_error'=>'float'], +'fann_clear_scaling_params' => ['bool', 'ann'=>'resource'], +'fann_copy' => ['resource|false', 'ann'=>'resource'], +'fann_create_from_file' => ['resource', 'configuration_file'=>'string'], +'fann_create_shortcut' => ['resource|false', 'num_layers'=>'int', 'num_neurons1'=>'int', 'num_neurons2'=>'int', '...args='=>'int'], +'fann_create_shortcut_array' => ['resource|false', 'num_layers'=>'int', 'layers'=>'array'], +'fann_create_sparse' => ['resource|false', 'connection_rate'=>'float', 'num_layers'=>'int', 'num_neurons1'=>'int', 'num_neurons2'=>'int', '...args='=>'int'], +'fann_create_sparse_array' => ['resource|false', 'connection_rate'=>'float', 'num_layers'=>'int', 'layers'=>'array'], +'fann_create_standard' => ['resource|false', 'num_layers'=>'int', 'num_neurons1'=>'int', 'num_neurons2'=>'int', '...args='=>'int'], +'fann_create_standard_array' => ['resource|false', 'num_layers'=>'int', 'layers'=>'array'], +'fann_create_train' => ['resource', 'num_data'=>'int', 'num_input'=>'int', 'num_output'=>'int'], +'fann_create_train_from_callback' => ['resource', 'num_data'=>'int', 'num_input'=>'int', 'num_output'=>'int', 'user_function'=>'callable'], +'fann_descale_input' => ['bool', 'ann'=>'resource', 'input_vector'=>'array'], +'fann_descale_output' => ['bool', 'ann'=>'resource', 'output_vector'=>'array'], +'fann_descale_train' => ['bool', 'ann'=>'resource', 'train_data'=>'resource'], +'fann_destroy' => ['bool', 'ann'=>'resource'], +'fann_destroy_train' => ['bool', 'train_data'=>'resource'], +'fann_duplicate_train_data' => ['resource', 'data'=>'resource'], +'fann_get_activation_function' => ['int|false', 'ann'=>'resource', 'layer'=>'int', 'neuron'=>'int'], +'fann_get_activation_steepness' => ['float|false', 'ann'=>'resource', 'layer'=>'int', 'neuron'=>'int'], +'fann_get_bias_array' => ['array', 'ann'=>'resource'], +'fann_get_bit_fail' => ['int|false', 'ann'=>'resource'], +'fann_get_bit_fail_limit' => ['float|false', 'ann'=>'resource'], +'fann_get_cascade_activation_functions' => ['array|false', 'ann'=>'resource'], +'fann_get_cascade_activation_functions_count' => ['int|false', 'ann'=>'resource'], +'fann_get_cascade_activation_steepnesses' => ['array|false', 'ann'=>'resource'], +'fann_get_cascade_activation_steepnesses_count' => ['int|false', 'ann'=>'resource'], +'fann_get_cascade_candidate_change_fraction' => ['float|false', 'ann'=>'resource'], +'fann_get_cascade_candidate_limit' => ['float|false', 'ann'=>'resource'], +'fann_get_cascade_candidate_stagnation_epochs' => ['float|false', 'ann'=>'resource'], +'fann_get_cascade_max_cand_epochs' => ['int|false', 'ann'=>'resource'], +'fann_get_cascade_max_out_epochs' => ['int|false', 'ann'=>'resource'], +'fann_get_cascade_min_cand_epochs' => ['int|false', 'ann'=>'resource'], +'fann_get_cascade_min_out_epochs' => ['int|false', 'ann'=>'resource'], +'fann_get_cascade_num_candidate_groups' => ['int|false', 'ann'=>'resource'], +'fann_get_cascade_num_candidates' => ['int|false', 'ann'=>'resource'], +'fann_get_cascade_output_change_fraction' => ['float|false', 'ann'=>'resource'], +'fann_get_cascade_output_stagnation_epochs' => ['int|false', 'ann'=>'resource'], +'fann_get_cascade_weight_multiplier' => ['float|false', 'ann'=>'resource'], +'fann_get_connection_array' => ['array', 'ann'=>'resource'], +'fann_get_connection_rate' => ['float|false', 'ann'=>'resource'], +'fann_get_errno' => ['int|false', 'errdat'=>'resource'], +'fann_get_errstr' => ['string|false', 'errdat'=>'resource'], +'fann_get_layer_array' => ['array', 'ann'=>'resource'], +'fann_get_learning_momentum' => ['float|false', 'ann'=>'resource'], +'fann_get_learning_rate' => ['float|false', 'ann'=>'resource'], +'fann_get_MSE' => ['float|false', 'ann'=>'resource'], +'fann_get_network_type' => ['int|false', 'ann'=>'resource'], +'fann_get_num_input' => ['int|false', 'ann'=>'resource'], +'fann_get_num_layers' => ['int|false', 'ann'=>'resource'], +'fann_get_num_output' => ['int|false', 'ann'=>'resource'], +'fann_get_quickprop_decay' => ['float|false', 'ann'=>'resource'], +'fann_get_quickprop_mu' => ['float|false', 'ann'=>'resource'], +'fann_get_rprop_decrease_factor' => ['float|false', 'ann'=>'resource'], +'fann_get_rprop_delta_max' => ['float|false', 'ann'=>'resource'], +'fann_get_rprop_delta_min' => ['float|false', 'ann'=>'resource'], +'fann_get_rprop_delta_zero' => ['float|false', 'ann'=>'resource'], +'fann_get_rprop_increase_factor' => ['float|false', 'ann'=>'resource'], +'fann_get_sarprop_step_error_shift' => ['float|false', 'ann'=>'resource'], +'fann_get_sarprop_step_error_threshold_factor' => ['float|false', 'ann'=>'resource'], +'fann_get_sarprop_temperature' => ['float|false', 'ann'=>'resource'], +'fann_get_sarprop_weight_decay_shift' => ['float|false', 'ann'=>'resource'], +'fann_get_total_connections' => ['int|false', 'ann'=>'resource'], +'fann_get_total_neurons' => ['int|false', 'ann'=>'resource'], +'fann_get_train_error_function' => ['int|false', 'ann'=>'resource'], +'fann_get_train_stop_function' => ['int|false', 'ann'=>'resource'], +'fann_get_training_algorithm' => ['int|false', 'ann'=>'resource'], +'fann_init_weights' => ['bool', 'ann'=>'resource', 'train_data'=>'resource'], +'fann_length_train_data' => ['int|false', 'data'=>'resource'], +'fann_merge_train_data' => ['resource|false', 'data1'=>'resource', 'data2'=>'resource'], +'fann_num_input_train_data' => ['int|false', 'data'=>'resource'], +'fann_num_output_train_data' => ['int|false', 'data'=>'resource'], +'fann_print_error' => ['void', 'errdat'=>'string'], +'fann_randomize_weights' => ['bool', 'ann'=>'resource', 'min_weight'=>'float', 'max_weight'=>'float'], +'fann_read_train_from_file' => ['resource', 'filename'=>'string'], +'fann_reset_errno' => ['void', 'errdat'=>'resource'], +'fann_reset_errstr' => ['void', 'errdat'=>'resource'], +'fann_reset_MSE' => ['bool', 'ann'=>'string'], +'fann_run' => ['array|false', 'ann'=>'resource', 'input'=>'array'], +'fann_save' => ['bool', 'ann'=>'resource', 'configuration_file'=>'string'], +'fann_save_train' => ['bool', 'data'=>'resource', 'file_name'=>'string'], +'fann_scale_input' => ['bool', 'ann'=>'resource', 'input_vector'=>'array'], +'fann_scale_input_train_data' => ['bool', 'train_data'=>'resource', 'new_min'=>'float', 'new_max'=>'float'], +'fann_scale_output' => ['bool', 'ann'=>'resource', 'output_vector'=>'array'], +'fann_scale_output_train_data' => ['bool', 'train_data'=>'resource', 'new_min'=>'float', 'new_max'=>'float'], +'fann_scale_train' => ['bool', 'ann'=>'resource', 'train_data'=>'resource'], +'fann_scale_train_data' => ['bool', 'train_data'=>'resource', 'new_min'=>'float', 'new_max'=>'float'], +'fann_set_activation_function' => ['bool', 'ann'=>'resource', 'activation_function'=>'int', 'layer'=>'int', 'neuron'=>'int'], +'fann_set_activation_function_hidden' => ['bool', 'ann'=>'resource', 'activation_function'=>'int'], +'fann_set_activation_function_layer' => ['bool', 'ann'=>'resource', 'activation_function'=>'int', 'layer'=>'int'], +'fann_set_activation_function_output' => ['bool', 'ann'=>'resource', 'activation_function'=>'int'], +'fann_set_activation_steepness' => ['bool', 'ann'=>'resource', 'activation_steepness'=>'float', 'layer'=>'int', 'neuron'=>'int'], +'fann_set_activation_steepness_hidden' => ['bool', 'ann'=>'resource', 'activation_steepness'=>'float'], +'fann_set_activation_steepness_layer' => ['bool', 'ann'=>'resource', 'activation_steepness'=>'float', 'layer'=>'int'], +'fann_set_activation_steepness_output' => ['bool', 'ann'=>'resource', 'activation_steepness'=>'float'], +'fann_set_bit_fail_limit' => ['bool', 'ann'=>'resource', 'bit_fail_limit'=>'float'], +'fann_set_callback' => ['bool', 'ann'=>'resource', 'callback'=>'callable'], +'fann_set_cascade_activation_functions' => ['bool', 'ann'=>'resource', 'cascade_activation_functions'=>'array'], +'fann_set_cascade_activation_steepnesses' => ['bool', 'ann'=>'resource', 'cascade_activation_steepnesses_count'=>'array'], +'fann_set_cascade_candidate_change_fraction' => ['bool', 'ann'=>'resource', 'cascade_candidate_change_fraction'=>'float'], +'fann_set_cascade_candidate_limit' => ['bool', 'ann'=>'resource', 'cascade_candidate_limit'=>'float'], +'fann_set_cascade_candidate_stagnation_epochs' => ['bool', 'ann'=>'resource', 'cascade_candidate_stagnation_epochs'=>'int'], +'fann_set_cascade_max_cand_epochs' => ['bool', 'ann'=>'resource', 'cascade_max_cand_epochs'=>'int'], +'fann_set_cascade_max_out_epochs' => ['bool', 'ann'=>'resource', 'cascade_max_out_epochs'=>'int'], +'fann_set_cascade_min_cand_epochs' => ['bool', 'ann'=>'resource', 'cascade_min_cand_epochs'=>'int'], +'fann_set_cascade_min_out_epochs' => ['bool', 'ann'=>'resource', 'cascade_min_out_epochs'=>'int'], +'fann_set_cascade_num_candidate_groups' => ['bool', 'ann'=>'resource', 'cascade_num_candidate_groups'=>'int'], +'fann_set_cascade_output_change_fraction' => ['bool', 'ann'=>'resource', 'cascade_output_change_fraction'=>'float'], +'fann_set_cascade_output_stagnation_epochs' => ['bool', 'ann'=>'resource', 'cascade_output_stagnation_epochs'=>'int'], +'fann_set_cascade_weight_multiplier' => ['bool', 'ann'=>'resource', 'cascade_weight_multiplier'=>'float'], +'fann_set_error_log' => ['void', 'errdat'=>'resource', 'log_file'=>'string'], +'fann_set_input_scaling_params' => ['bool', 'ann'=>'resource', 'train_data'=>'resource', 'new_input_min'=>'float', 'new_input_max'=>'float'], +'fann_set_learning_momentum' => ['bool', 'ann'=>'resource', 'learning_momentum'=>'float'], +'fann_set_learning_rate' => ['bool', 'ann'=>'resource', 'learning_rate'=>'float'], +'fann_set_output_scaling_params' => ['bool', 'ann'=>'resource', 'train_data'=>'resource', 'new_output_min'=>'float', 'new_output_max'=>'float'], +'fann_set_quickprop_decay' => ['bool', 'ann'=>'resource', 'quickprop_decay'=>'float'], +'fann_set_quickprop_mu' => ['bool', 'ann'=>'resource', 'quickprop_mu'=>'float'], +'fann_set_rprop_decrease_factor' => ['bool', 'ann'=>'resource', 'rprop_decrease_factor'=>'float'], +'fann_set_rprop_delta_max' => ['bool', 'ann'=>'resource', 'rprop_delta_max'=>'float'], +'fann_set_rprop_delta_min' => ['bool', 'ann'=>'resource', 'rprop_delta_min'=>'float'], +'fann_set_rprop_delta_zero' => ['bool', 'ann'=>'resource', 'rprop_delta_zero'=>'float'], +'fann_set_rprop_increase_factor' => ['bool', 'ann'=>'resource', 'rprop_increase_factor'=>'float'], +'fann_set_sarprop_step_error_shift' => ['bool', 'ann'=>'resource', 'sarprop_step_error_shift'=>'float'], +'fann_set_sarprop_step_error_threshold_factor' => ['bool', 'ann'=>'resource', 'sarprop_step_error_threshold_factor'=>'float'], +'fann_set_sarprop_temperature' => ['bool', 'ann'=>'resource', 'sarprop_temperature'=>'float'], +'fann_set_sarprop_weight_decay_shift' => ['bool', 'ann'=>'resource', 'sarprop_weight_decay_shift'=>'float'], +'fann_set_scaling_params' => ['bool', 'ann'=>'resource', 'train_data'=>'resource', 'new_input_min'=>'float', 'new_input_max'=>'float', 'new_output_min'=>'float', 'new_output_max'=>'float'], +'fann_set_train_error_function' => ['bool', 'ann'=>'resource', 'error_function'=>'int'], +'fann_set_train_stop_function' => ['bool', 'ann'=>'resource', 'stop_function'=>'int'], +'fann_set_training_algorithm' => ['bool', 'ann'=>'resource', 'training_algorithm'=>'int'], +'fann_set_weight' => ['bool', 'ann'=>'resource', 'from_neuron'=>'int', 'to_neuron'=>'int', 'weight'=>'float'], +'fann_set_weight_array' => ['bool', 'ann'=>'resource', 'connections'=>'array'], +'fann_shuffle_train_data' => ['bool', 'train_data'=>'resource'], +'fann_subset_train_data' => ['resource', 'data'=>'resource', 'pos'=>'int', 'length'=>'int'], +'fann_test' => ['bool', 'ann'=>'resource', 'input'=>'array', 'desired_output'=>'array'], +'fann_test_data' => ['float|false', 'ann'=>'resource', 'data'=>'resource'], +'fann_train' => ['bool', 'ann'=>'resource', 'input'=>'array', 'desired_output'=>'array'], +'fann_train_epoch' => ['float|false', 'ann'=>'resource', 'data'=>'resource'], +'fann_train_on_data' => ['bool', 'ann'=>'resource', 'data'=>'resource', 'max_epochs'=>'int', 'epochs_between_reports'=>'int', 'desired_error'=>'float'], +'fann_train_on_file' => ['bool', 'ann'=>'resource', 'filename'=>'string', 'max_epochs'=>'int', 'epochs_between_reports'=>'int', 'desired_error'=>'float'], +'FANNConnection::__construct' => ['void', 'from_neuron'=>'int', 'to_neuron'=>'int', 'weight'=>'float'], +'FANNConnection::getFromNeuron' => ['int'], +'FANNConnection::getToNeuron' => ['int'], +'FANNConnection::getWeight' => ['void'], +'FANNConnection::setWeight' => ['bool', 'weight'=>'float'], +'fastcgi_finish_request' => ['bool'], +'fbsql_affected_rows' => ['int', 'link_identifier='=>'?resource'], +'fbsql_autocommit' => ['bool', 'link_identifier'=>'resource', 'onoff='=>'bool'], +'fbsql_blob_size' => ['int', 'blob_handle'=>'string', 'link_identifier='=>'?resource'], +'fbsql_change_user' => ['bool', 'user'=>'string', 'password'=>'string', 'database='=>'string', 'link_identifier='=>'?resource'], +'fbsql_clob_size' => ['int', 'clob_handle'=>'string', 'link_identifier='=>'?resource'], +'fbsql_close' => ['bool', 'link_identifier='=>'?resource'], +'fbsql_commit' => ['bool', 'link_identifier='=>'?resource'], +'fbsql_connect' => ['resource', 'hostname='=>'string', 'username='=>'string', 'password='=>'string'], +'fbsql_create_blob' => ['string', 'blob_data'=>'string', 'link_identifier='=>'?resource'], +'fbsql_create_clob' => ['string', 'clob_data'=>'string', 'link_identifier='=>'?resource'], +'fbsql_create_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource', 'database_options='=>'string'], +'fbsql_data_seek' => ['bool', 'result'=>'resource', 'row_number'=>'int'], +'fbsql_database' => ['string', 'link_identifier'=>'resource', 'database='=>'string'], +'fbsql_database_password' => ['string', 'link_identifier'=>'resource', 'database_password='=>'string'], +'fbsql_db_query' => ['resource', 'database'=>'string', 'query'=>'string', 'link_identifier='=>'?resource'], +'fbsql_db_status' => ['int', 'database_name'=>'string', 'link_identifier='=>'?resource'], +'fbsql_drop_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource'], +'fbsql_errno' => ['int', 'link_identifier='=>'?resource'], +'fbsql_error' => ['string', 'link_identifier='=>'?resource'], +'fbsql_fetch_array' => ['array', 'result'=>'resource', 'result_type='=>'int'], +'fbsql_fetch_assoc' => ['array', 'result'=>'resource'], +'fbsql_fetch_field' => ['object', 'result'=>'resource', 'field_offset='=>'int'], +'fbsql_fetch_lengths' => ['array', 'result'=>'resource'], +'fbsql_fetch_object' => ['object', 'result'=>'resource'], +'fbsql_fetch_row' => ['array', 'result'=>'resource'], +'fbsql_field_flags' => ['string', 'result'=>'resource', 'field_offset='=>'int'], +'fbsql_field_len' => ['int', 'result'=>'resource', 'field_offset='=>'int'], +'fbsql_field_name' => ['string', 'result'=>'resource', 'field_index='=>'int'], +'fbsql_field_seek' => ['bool', 'result'=>'resource', 'field_offset='=>'int'], +'fbsql_field_table' => ['string', 'result'=>'resource', 'field_offset='=>'int'], +'fbsql_field_type' => ['string', 'result'=>'resource', 'field_offset='=>'int'], +'fbsql_free_result' => ['bool', 'result'=>'resource'], +'fbsql_get_autostart_info' => ['array', 'link_identifier='=>'?resource'], +'fbsql_hostname' => ['string', 'link_identifier'=>'resource', 'host_name='=>'string'], +'fbsql_insert_id' => ['int', 'link_identifier='=>'?resource'], +'fbsql_list_dbs' => ['resource', 'link_identifier='=>'?resource'], +'fbsql_list_fields' => ['resource', 'database_name'=>'string', 'table_name'=>'string', 'link_identifier='=>'?resource'], +'fbsql_list_tables' => ['resource', 'database'=>'string', 'link_identifier='=>'?resource'], +'fbsql_next_result' => ['bool', 'result'=>'resource'], +'fbsql_num_fields' => ['int', 'result'=>'resource'], +'fbsql_num_rows' => ['int', 'result'=>'resource'], +'fbsql_password' => ['string', 'link_identifier'=>'resource', 'password='=>'string'], +'fbsql_pconnect' => ['resource', 'hostname='=>'string', 'username='=>'string', 'password='=>'string'], +'fbsql_query' => ['resource', 'query'=>'string', 'link_identifier='=>'?resource', 'batch_size='=>'int'], +'fbsql_read_blob' => ['string', 'blob_handle'=>'string', 'link_identifier='=>'?resource'], +'fbsql_read_clob' => ['string', 'clob_handle'=>'string', 'link_identifier='=>'?resource'], +'fbsql_result' => ['mixed', 'result'=>'resource', 'row='=>'int', 'field='=>'mixed'], +'fbsql_rollback' => ['bool', 'link_identifier='=>'?resource'], +'fbsql_rows_fetched' => ['int', 'result'=>'resource'], +'fbsql_select_db' => ['bool', 'database_name='=>'string', 'link_identifier='=>'?resource'], +'fbsql_set_characterset' => ['void', 'link_identifier'=>'resource', 'characterset'=>'int', 'in_out_both='=>'int'], +'fbsql_set_lob_mode' => ['bool', 'result'=>'resource', 'lob_mode'=>'int'], +'fbsql_set_password' => ['bool', 'link_identifier'=>'resource', 'user'=>'string', 'password'=>'string', 'old_password'=>'string'], +'fbsql_set_transaction' => ['void', 'link_identifier'=>'resource', 'locking'=>'int', 'isolation'=>'int'], +'fbsql_start_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource', 'database_options='=>'string'], +'fbsql_stop_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource'], +'fbsql_table_name' => ['string', 'result'=>'resource', 'index'=>'int'], +'fbsql_username' => ['string', 'link_identifier'=>'resource', 'username='=>'string'], +'fbsql_warnings' => ['bool', 'onoff='=>'bool'], +'fclose' => ['bool', 'fp'=>'resource'], +'fdf_add_doc_javascript' => ['bool', 'fdf_document'=>'resource', 'script_name'=>'string', 'script_code'=>'string'], +'fdf_add_template' => ['bool', 'fdf_document'=>'resource', 'newpage'=>'int', 'filename'=>'string', 'template'=>'string', 'rename'=>'int'], +'fdf_close' => ['void', 'fdf_document'=>'resource'], +'fdf_create' => ['resource'], +'fdf_enum_values' => ['bool', 'fdf_document'=>'resource', 'function'=>'callable', 'userdata='=>'mixed'], +'fdf_errno' => ['int'], +'fdf_error' => ['string', 'error_code='=>'int'], +'fdf_get_ap' => ['bool', 'fdf_document'=>'resource', 'field'=>'string', 'face'=>'int', 'filename'=>'string'], +'fdf_get_attachment' => ['array', 'fdf_document'=>'resource', 'fieldname'=>'string', 'savepath'=>'string'], +'fdf_get_encoding' => ['string', 'fdf_document'=>'resource'], +'fdf_get_file' => ['string', 'fdf_document'=>'resource'], +'fdf_get_flags' => ['int', 'fdf_document'=>'resource', 'fieldname'=>'string', 'whichflags'=>'int'], +'fdf_get_opt' => ['mixed', 'fdf_document'=>'resource', 'fieldname'=>'string', 'element='=>'int'], +'fdf_get_status' => ['string', 'fdf_document'=>'resource'], +'fdf_get_value' => ['mixed', 'fdf_document'=>'resource', 'fieldname'=>'string', 'which='=>'int'], +'fdf_get_version' => ['string', 'fdf_document='=>'resource'], +'fdf_header' => ['void'], +'fdf_next_field_name' => ['string', 'fdf_document'=>'resource', 'fieldname='=>'string'], +'fdf_open' => ['resource|false', 'filename'=>'string'], +'fdf_open_string' => ['resource', 'fdf_data'=>'string'], +'fdf_remove_item' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'item'=>'int'], +'fdf_save' => ['bool', 'fdf_document'=>'resource', 'filename='=>'string'], +'fdf_save_string' => ['string', 'fdf_document'=>'resource'], +'fdf_set_ap' => ['bool', 'fdf_document'=>'resource', 'field_name'=>'string', 'face'=>'int', 'filename'=>'string', 'page_number'=>'int'], +'fdf_set_encoding' => ['bool', 'fdf_document'=>'resource', 'encoding'=>'string'], +'fdf_set_file' => ['bool', 'fdf_document'=>'resource', 'url'=>'string', 'target_frame='=>'string'], +'fdf_set_flags' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'whichflags'=>'int', 'newflags'=>'int'], +'fdf_set_javascript_action' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'trigger'=>'int', 'script'=>'string'], +'fdf_set_on_import_javascript' => ['bool', 'fdf_document'=>'resource', 'script'=>'string', 'before_data_import'=>'bool'], +'fdf_set_opt' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'element'=>'int', 'string1'=>'string', 'string2'=>'string'], +'fdf_set_status' => ['bool', 'fdf_document'=>'resource', 'status'=>'string'], +'fdf_set_submit_form_action' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'trigger'=>'int', 'script'=>'string', 'flags'=>'int'], +'fdf_set_target_frame' => ['bool', 'fdf_document'=>'resource', 'frame_name'=>'string'], +'fdf_set_value' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'value'=>'mixed', 'isname='=>'int'], +'fdf_set_version' => ['bool', 'fdf_document'=>'resource', 'version'=>'string'], +'feof' => ['bool', 'fp'=>'resource'], +'fflush' => ['bool', 'fp'=>'resource'], +'ffmpeg_animated_gif::__construct' => ['void', 'output_file_path'=>'string', 'width'=>'int', 'height'=>'int', 'frame_rate'=>'int', 'loop_count='=>'int'], +'ffmpeg_animated_gif::addFrame' => ['', 'frame_to_add'=>'ffmpeg_frame'], +'ffmpeg_frame::__construct' => ['void', 'gd_image'=>'resource'], +'ffmpeg_frame::crop' => ['', 'crop_top'=>'int', 'crop_bottom='=>'int', 'crop_left='=>'int', 'crop_right='=>'int'], +'ffmpeg_frame::getHeight' => ['int'], +'ffmpeg_frame::getPresentationTimestamp' => ['int'], +'ffmpeg_frame::getPTS' => ['int'], +'ffmpeg_frame::getWidth' => ['int'], +'ffmpeg_frame::resize' => ['', 'width'=>'int', 'height'=>'int', 'crop_top='=>'int', 'crop_bottom='=>'int', 'crop_left='=>'int', 'crop_right='=>'int'], +'ffmpeg_frame::toGDImage' => ['resource'], +'ffmpeg_movie::__construct' => ['void', 'path_to_media'=>'string', 'persistent'=>'bool'], +'ffmpeg_movie::getArtist' => ['string'], +'ffmpeg_movie::getAudioBitRate' => ['int'], +'ffmpeg_movie::getAudioChannels' => ['int'], +'ffmpeg_movie::getAudioCodec' => ['string'], +'ffmpeg_movie::getAudioSampleRate' => ['int'], +'ffmpeg_movie::getAuthor' => ['string'], +'ffmpeg_movie::getBitRate' => ['int'], +'ffmpeg_movie::getComment' => ['string'], +'ffmpeg_movie::getCopyright' => ['string'], +'ffmpeg_movie::getDuration' => ['int'], +'ffmpeg_movie::getFilename' => ['string'], +'ffmpeg_movie::getFrame' => ['ffmpeg_frame|false', 'framenumber'=>'int'], +'ffmpeg_movie::getFrameCount' => ['int'], +'ffmpeg_movie::getFrameHeight' => ['int'], +'ffmpeg_movie::getFrameNumber' => ['int'], +'ffmpeg_movie::getFrameRate' => ['int'], +'ffmpeg_movie::getFrameWidth' => ['int'], +'ffmpeg_movie::getGenre' => ['string'], +'ffmpeg_movie::getNextKeyFrame' => ['ffmpeg_frame|false'], +'ffmpeg_movie::getPixelFormat' => [''], +'ffmpeg_movie::getTitle' => ['string'], +'ffmpeg_movie::getTrackNumber' => ['int|string'], +'ffmpeg_movie::getVideoBitRate' => ['int'], +'ffmpeg_movie::getVideoCodec' => ['string'], +'ffmpeg_movie::getYear' => ['int|string'], +'ffmpeg_movie::hasAudio' => ['bool'], +'ffmpeg_movie::hasVideo' => ['bool'], +'fgetc' => ['string|false', 'fp'=>'resource'], +'fgetcsv' => ['list|array{0: null}|false|null', 'fp'=>'resource', 'length='=>'int', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], +'fgets' => ['string|false', 'fp'=>'resource', 'length='=>'int'], +'fgetss' => ['string|false', 'fp'=>'resource', 'length='=>'int', 'allowable_tags='=>'string'], +'file' => ['list|false', 'filename'=>'string', 'flags='=>'int', 'context='=>'resource'], +'file_exists' => ['bool', 'filename'=>'string'], +'file_get_contents' => ['string|false', 'filename'=>'string', 'use_include_path='=>'bool', 'context='=>'?resource', 'offset='=>'int', 'maxlen='=>'int'], +'file_put_contents' => ['int|false', 'file'=>'string', 'data'=>'mixed', 'flags='=>'int', 'context='=>'resource'], +'fileatime' => ['int|false', 'filename'=>'string'], +'filectime' => ['int|false', 'filename'=>'string'], +'filegroup' => ['int|false', 'filename'=>'string'], +'fileinode' => ['int|false', 'filename'=>'string'], +'filemtime' => ['int|false', 'filename'=>'string'], +'fileowner' => ['int|false', 'filename'=>'string'], +'fileperms' => ['int|false', 'filename'=>'string'], +'filepro' => ['bool', 'directory'=>'string'], +'filepro_fieldcount' => ['int'], +'filepro_fieldname' => ['string', 'field_number'=>'int'], +'filepro_fieldtype' => ['string', 'field_number'=>'int'], +'filepro_fieldwidth' => ['int', 'field_number'=>'int'], +'filepro_retrieve' => ['string', 'row_number'=>'int', 'field_number'=>'int'], +'filepro_rowcount' => ['int'], +'filesize' => ['int|false', 'filename'=>'string'], +'FilesystemIterator::__construct' => ['void', 'path'=>'string', 'flags='=>'int'], +'FilesystemIterator::__toString' => ['string'], +'FilesystemIterator::_bad_state_ex' => [''], +'FilesystemIterator::current' => ['mixed'], +'FilesystemIterator::getATime' => ['int'], +'FilesystemIterator::getBasename' => ['string', 'suffix='=>'string'], +'FilesystemIterator::getCTime' => ['int'], +'FilesystemIterator::getExtension' => ['string'], +'FilesystemIterator::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], +'FilesystemIterator::getFilename' => ['string'], +'FilesystemIterator::getFlags' => ['int'], +'FilesystemIterator::getGroup' => ['int'], +'FilesystemIterator::getInode' => ['int'], +'FilesystemIterator::getLinkTarget' => ['string'], +'FilesystemIterator::getMTime' => ['int'], +'FilesystemIterator::getOwner' => ['int'], +'FilesystemIterator::getPath' => ['string'], +'FilesystemIterator::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], +'FilesystemIterator::getPathname' => ['string'], +'FilesystemIterator::getPerms' => ['int'], +'FilesystemIterator::getRealPath' => ['string'], +'FilesystemIterator::getSize' => ['int'], +'FilesystemIterator::getType' => ['string'], +'FilesystemIterator::isDir' => ['bool'], +'FilesystemIterator::isDot' => ['bool'], +'FilesystemIterator::isExecutable' => ['bool'], +'FilesystemIterator::isFile' => ['bool'], +'FilesystemIterator::isLink' => ['bool'], +'FilesystemIterator::isReadable' => ['bool'], +'FilesystemIterator::isWritable' => ['bool'], +'FilesystemIterator::key' => ['string'], +'FilesystemIterator::next' => ['void'], +'FilesystemIterator::openFile' => ['SplFileObject', 'mode='=>'string', 'use_include_path='=>'bool', 'context='=>'resource'], +'FilesystemIterator::rewind' => ['void'], +'FilesystemIterator::seek' => ['void', 'position'=>'int'], +'FilesystemIterator::setFileClass' => ['void', 'class_name='=>'string'], +'FilesystemIterator::setFlags' => ['void', 'flags='=>'int'], +'FilesystemIterator::setInfoClass' => ['void', 'class_name='=>'string'], +'FilesystemIterator::valid' => ['bool'], +'filetype' => ['string|false', 'filename'=>'string'], +'filter_has_var' => ['bool', 'type'=>'int', 'variable_name'=>'string'], +'filter_id' => ['int|false', 'filtername'=>'string'], +'filter_input' => ['mixed|false', 'type'=>'int', 'variable_name'=>'string', 'filter='=>'int', 'options='=>'array|int'], +'filter_input_array' => ['mixed|false', 'type'=>'int', 'definition='=>'int|array', 'add_empty='=>'bool'], +'filter_list' => ['array'], +'filter_var' => ['mixed|false', 'variable'=>'mixed', 'filter='=>'int', 'options='=>'mixed'], +'filter_var_array' => ['mixed|false', 'data'=>'array', 'definition='=>'mixed', 'add_empty='=>'bool'], +'FilterIterator::__construct' => ['void', 'iterator'=>'Iterator'], +'FilterIterator::accept' => ['bool'], +'FilterIterator::current' => ['mixed'], +'FilterIterator::getInnerIterator' => ['Iterator'], +'FilterIterator::key' => ['mixed'], +'FilterIterator::next' => ['void'], +'FilterIterator::rewind' => ['void'], +'FilterIterator::valid' => ['bool'], +'finfo::__construct' => ['void', 'options='=>'int', 'magic_file='=>'string'], +'finfo::buffer' => ['string|false', 'string'=>'string', 'options='=>'int', 'context='=>'resource'], +'finfo::file' => ['string|false', 'file_name'=>'string', 'options='=>'int', 'context='=>'resource'], +'finfo::finfo' => ['void', 'options='=>'int', 'magic_file='=>'string'], +'finfo::set_flags' => ['bool', 'options'=>'int'], +'finfo_buffer' => ['string|false', 'finfo'=>'resource', 'string'=>'string', 'options='=>'int', 'context='=>'resource'], +'finfo_close' => ['bool', 'finfo'=>'resource'], +'finfo_file' => ['string|false', 'finfo'=>'resource', 'file_name'=>'string', 'options='=>'int', 'context='=>'resource'], +'finfo_open' => ['resource|false', 'options='=>'int', 'arg='=>'string'], +'finfo_set_flags' => ['bool', 'finfo'=>'resource', 'options'=>'int'], +'floatval' => ['float', 'value'=>'mixed'], +'flock' => ['bool', 'fp'=>'resource', 'operation'=>'int', '&w_wouldblock='=>'int'], +'floor' => ['float', 'number'=>'float'], +'flush' => ['void'], +'fmod' => ['float', 'x'=>'float', 'y'=>'float'], +'fnmatch' => ['bool', 'pattern'=>'string', 'filename'=>'string', 'flags='=>'int'], +'fopen' => ['resource|false', 'filename'=>'string', 'mode'=>'string', 'use_include_path='=>'bool', 'context='=>'resource'], +'forward_static_call' => ['mixed|false', 'function'=>'callable', '...parameters='=>'mixed'], +'forward_static_call_array' => ['mixed|false', 'function'=>'callable', 'parameters'=>'list'], +'fpassthru' => ['int|false', 'fp'=>'resource'], +'fpm_get_status' => ['array|false'], +'fprintf' => ['int', 'stream'=>'resource', 'format'=>'string', '...args='=>'string|int|float'], +'fputcsv' => ['int|false', 'fp'=>'resource', 'fields'=>'array', 'delimiter='=>'string', 'enclosure='=>'string', 'escape_char='=>'string'], +'fputs' => ['int|false', 'fp'=>'resource', 'string'=>'string', 'length='=>'int'], +'fread' => ['string|false', 'fp'=>'resource', 'length'=>'int'], +'frenchtojd' => ['int', 'month'=>'int', 'day'=>'int', 'year'=>'int'], +'fribidi_log2vis' => ['string', 'string'=>'string', 'direction'=>'string', 'charset'=>'int'], +'fscanf' => ['list', 'stream'=>'resource', 'format'=>'string'], +'fscanf\'1' => ['int', 'stream'=>'resource', 'format'=>'string', '&...w_vars='=>'string|int|float'], +'fseek' => ['int', 'fp'=>'resource', 'offset'=>'int', 'whence='=>'int'], +'fsockopen' => ['resource|false', 'hostname'=>'string', 'port='=>'int', '&w_errno='=>'int', '&w_errstr='=>'string', 'timeout='=>'float'], +'fstat' => ['array|false', 'fp'=>'resource'], +'ftell' => ['int|false', 'fp'=>'resource'], +'ftok' => ['int', 'pathname'=>'string', 'proj'=>'string'], +'ftp_alloc' => ['bool', 'stream'=>'resource', 'size'=>'int', '&w_response='=>'string'], +'ftp_append' => ['bool', 'ftp'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'int'], +'ftp_cdup' => ['bool', 'stream'=>'resource'], +'ftp_chdir' => ['bool', 'stream'=>'resource', 'directory'=>'string'], +'ftp_chmod' => ['int|false', 'stream'=>'resource', 'mode'=>'int', 'filename'=>'string'], +'ftp_close' => ['bool', 'stream'=>'resource'], +'ftp_connect' => ['resource|false', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'], +'ftp_delete' => ['bool', 'stream'=>'resource', 'file'=>'string'], +'ftp_exec' => ['bool', 'stream'=>'resource', 'command'=>'string'], +'ftp_fget' => ['bool', 'stream'=>'resource', 'fp'=>'resource', 'remote_file'=>'string', 'mode='=>'int', 'resumepos='=>'int'], +'ftp_fput' => ['bool', 'stream'=>'resource', 'remote_file'=>'string', 'fp'=>'resource', 'mode='=>'int', 'startpos='=>'int'], +'ftp_get' => ['bool', 'stream'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'mode='=>'int', 'resume_pos='=>'int'], +'ftp_get_option' => ['mixed|false', 'stream'=>'resource', 'option'=>'int'], +'ftp_login' => ['bool', 'stream'=>'resource', 'username'=>'string', 'password'=>'string'], +'ftp_mdtm' => ['int', 'stream'=>'resource', 'filename'=>'string'], +'ftp_mkdir' => ['string|false', 'stream'=>'resource', 'directory'=>'string'], +'ftp_mlsd' => ['array', 'ftp_stream'=>'resource', 'directory'=>'string'], +'ftp_nb_continue' => ['int', 'stream'=>'resource'], +'ftp_nb_fget' => ['int', 'stream'=>'resource', 'fp'=>'resource', 'remote_file'=>'string', 'mode='=>'int', 'resumepos='=>'int'], +'ftp_nb_fput' => ['int', 'stream'=>'resource', 'remote_file'=>'string', 'fp'=>'resource', 'mode='=>'int', 'startpos='=>'int'], +'ftp_nb_get' => ['int', 'stream'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'mode='=>'int', 'resume_pos='=>'int'], +'ftp_nb_put' => ['int', 'stream'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'int', 'startpos='=>'int'], +'ftp_nlist' => ['array|false', 'stream'=>'resource', 'directory'=>'string'], +'ftp_pasv' => ['bool', 'stream'=>'resource', 'pasv'=>'bool'], +'ftp_put' => ['bool', 'stream'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'int', 'startpos='=>'int'], +'ftp_pwd' => ['string|false', 'stream'=>'resource'], +'ftp_quit' => ['bool', 'stream'=>'resource'], +'ftp_raw' => ['array', 'stream'=>'resource', 'command'=>'string'], +'ftp_rawlist' => ['array|false', 'stream'=>'resource', 'directory'=>'string', 'recursive='=>'bool'], +'ftp_rename' => ['bool', 'stream'=>'resource', 'src'=>'string', 'dest'=>'string'], +'ftp_rmdir' => ['bool', 'stream'=>'resource', 'directory'=>'string'], +'ftp_set_option' => ['bool', 'stream'=>'resource', 'option'=>'int', 'value'=>'mixed'], +'ftp_site' => ['bool', 'stream'=>'resource', 'cmd'=>'string'], +'ftp_size' => ['int', 'stream'=>'resource', 'filename'=>'string'], +'ftp_ssl_connect' => ['resource|false', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'], +'ftp_systype' => ['string|false', 'stream'=>'resource'], +'ftruncate' => ['bool', 'fp'=>'resource', 'size'=>'int'], +'func_get_arg' => ['mixed|false', 'arg_num'=>'int'], +'func_get_args' => ['list'], +'func_num_args' => ['int'], +'function_exists' => ['bool', 'function_name'=>'string'], +'fwrite' => ['int|false', 'fp'=>'resource', 'string'=>'string', 'length='=>'int'], +'gc_collect_cycles' => ['int'], +'gc_disable' => ['void'], +'gc_enable' => ['void'], +'gc_enabled' => ['bool'], +'gc_mem_caches' => ['int'], +'gc_status' => ['array{runs:int,collected:int,threshold:int,roots:int}'], +'gd_info' => ['array'], +'gearman_bugreport' => [''], +'gearman_client_add_options' => ['', 'client_object'=>'', 'option'=>''], +'gearman_client_add_server' => ['', 'client_object'=>'', 'host'=>'', 'port'=>''], +'gearman_client_add_servers' => ['', 'client_object'=>'', 'servers'=>''], +'gearman_client_add_task' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''], +'gearman_client_add_task_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''], +'gearman_client_add_task_high' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''], +'gearman_client_add_task_high_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''], +'gearman_client_add_task_low' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''], +'gearman_client_add_task_low_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''], +'gearman_client_add_task_status' => ['', 'client_object'=>'', 'job_handle'=>'', 'context'=>''], +'gearman_client_clear_fn' => ['', 'client_object'=>''], +'gearman_client_clone' => ['', 'client_object'=>''], +'gearman_client_context' => ['', 'client_object'=>''], +'gearman_client_create' => ['', 'client_object'=>''], +'gearman_client_do' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''], +'gearman_client_do_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''], +'gearman_client_do_high' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''], +'gearman_client_do_high_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''], +'gearman_client_do_job_handle' => ['', 'client_object'=>''], +'gearman_client_do_low' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''], +'gearman_client_do_low_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''], +'gearman_client_do_normal' => ['', 'client_object'=>'', 'function_name'=>'string', 'workload'=>'string', 'unique'=>'string'], +'gearman_client_do_status' => ['', 'client_object'=>''], +'gearman_client_echo' => ['', 'client_object'=>'', 'workload'=>''], +'gearman_client_errno' => ['', 'client_object'=>''], +'gearman_client_error' => ['', 'client_object'=>''], +'gearman_client_job_status' => ['', 'client_object'=>'', 'job_handle'=>''], +'gearman_client_options' => ['', 'client_object'=>''], +'gearman_client_remove_options' => ['', 'client_object'=>'', 'option'=>''], +'gearman_client_return_code' => ['', 'client_object'=>''], +'gearman_client_run_tasks' => ['', 'data'=>''], +'gearman_client_set_complete_fn' => ['', 'client_object'=>'', 'callback'=>''], +'gearman_client_set_context' => ['', 'client_object'=>'', 'context'=>''], +'gearman_client_set_created_fn' => ['', 'client_object'=>'', 'callback'=>''], +'gearman_client_set_data_fn' => ['', 'client_object'=>'', 'callback'=>''], +'gearman_client_set_exception_fn' => ['', 'client_object'=>'', 'callback'=>''], +'gearman_client_set_fail_fn' => ['', 'client_object'=>'', 'callback'=>''], +'gearman_client_set_options' => ['', 'client_object'=>'', 'option'=>''], +'gearman_client_set_status_fn' => ['', 'client_object'=>'', 'callback'=>''], +'gearman_client_set_timeout' => ['', 'client_object'=>'', 'timeout'=>''], +'gearman_client_set_warning_fn' => ['', 'client_object'=>'', 'callback'=>''], +'gearman_client_set_workload_fn' => ['', 'client_object'=>'', 'callback'=>''], +'gearman_client_timeout' => ['', 'client_object'=>''], +'gearman_client_wait' => ['', 'client_object'=>''], +'gearman_job_function_name' => ['', 'job_object'=>''], +'gearman_job_handle' => ['string'], +'gearman_job_return_code' => ['', 'job_object'=>''], +'gearman_job_send_complete' => ['', 'job_object'=>'', 'result'=>''], +'gearman_job_send_data' => ['', 'job_object'=>'', 'data'=>''], +'gearman_job_send_exception' => ['', 'job_object'=>'', 'exception'=>''], +'gearman_job_send_fail' => ['', 'job_object'=>''], +'gearman_job_send_status' => ['', 'job_object'=>'', 'numerator'=>'', 'denominator'=>''], +'gearman_job_send_warning' => ['', 'job_object'=>'', 'warning'=>''], +'gearman_job_status' => ['array', 'job_handle'=>'string'], +'gearman_job_unique' => ['', 'job_object'=>''], +'gearman_job_workload' => ['', 'job_object'=>''], +'gearman_job_workload_size' => ['', 'job_object'=>''], +'gearman_task_data' => ['', 'task_object'=>''], +'gearman_task_data_size' => ['', 'task_object'=>''], +'gearman_task_denominator' => ['', 'task_object'=>''], +'gearman_task_function_name' => ['', 'task_object'=>''], +'gearman_task_is_known' => ['', 'task_object'=>''], +'gearman_task_is_running' => ['', 'task_object'=>''], +'gearman_task_job_handle' => ['', 'task_object'=>''], +'gearman_task_numerator' => ['', 'task_object'=>''], +'gearman_task_recv_data' => ['', 'task_object'=>'', 'data_len'=>''], +'gearman_task_return_code' => ['', 'task_object'=>''], +'gearman_task_send_workload' => ['', 'task_object'=>'', 'data'=>''], +'gearman_task_unique' => ['', 'task_object'=>''], +'gearman_verbose_name' => ['', 'verbose'=>''], +'gearman_version' => [''], +'gearman_worker_add_function' => ['', 'worker_object'=>'', 'function_name'=>'', 'function'=>'', 'data'=>'', 'timeout'=>''], +'gearman_worker_add_options' => ['', 'worker_object'=>'', 'option'=>''], +'gearman_worker_add_server' => ['', 'worker_object'=>'', 'host'=>'', 'port'=>''], +'gearman_worker_add_servers' => ['', 'worker_object'=>'', 'servers'=>''], +'gearman_worker_clone' => ['', 'worker_object'=>''], +'gearman_worker_create' => [''], +'gearman_worker_echo' => ['', 'worker_object'=>'', 'workload'=>''], +'gearman_worker_errno' => ['', 'worker_object'=>''], +'gearman_worker_error' => ['', 'worker_object'=>''], +'gearman_worker_grab_job' => ['', 'worker_object'=>''], +'gearman_worker_options' => ['', 'worker_object'=>''], +'gearman_worker_register' => ['', 'worker_object'=>'', 'function_name'=>'', 'timeout'=>''], +'gearman_worker_remove_options' => ['', 'worker_object'=>'', 'option'=>''], +'gearman_worker_return_code' => ['', 'worker_object'=>''], +'gearman_worker_set_options' => ['', 'worker_object'=>'', 'option'=>''], +'gearman_worker_set_timeout' => ['', 'worker_object'=>'', 'timeout'=>''], +'gearman_worker_timeout' => ['', 'worker_object'=>''], +'gearman_worker_unregister' => ['', 'worker_object'=>'', 'function_name'=>''], +'gearman_worker_unregister_all' => ['', 'worker_object'=>''], +'gearman_worker_wait' => ['', 'worker_object'=>''], +'gearman_worker_work' => ['', 'worker_object'=>''], +'GearmanClient::__construct' => ['void'], +'GearmanClient::addOptions' => ['bool', 'options'=>'int'], +'GearmanClient::addServer' => ['bool', 'host='=>'string', 'port='=>'int'], +'GearmanClient::addServers' => ['bool', 'servers='=>'string'], +'GearmanClient::addTask' => ['GearmanTask|false', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'], +'GearmanClient::addTaskBackground' => ['GearmanTask|false', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'], +'GearmanClient::addTaskHigh' => ['GearmanTask|false', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'], +'GearmanClient::addTaskHighBackground' => ['GearmanTask|false', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'], +'GearmanClient::addTaskLow' => ['GearmanTask|false', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'], +'GearmanClient::addTaskLowBackground' => ['GearmanTask|false', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'], +'GearmanClient::addTaskStatus' => ['GearmanTask', 'job_handle'=>'string', 'context='=>'string'], +'GearmanClient::clearCallbacks' => ['bool'], +'GearmanClient::clone' => ['GearmanClient'], +'GearmanClient::context' => ['string'], +'GearmanClient::data' => ['string'], +'GearmanClient::do' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'], +'GearmanClient::doBackground' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'], +'GearmanClient::doHigh' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'], +'GearmanClient::doHighBackground' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'], +'GearmanClient::doJobHandle' => ['string'], +'GearmanClient::doLow' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'], +'GearmanClient::doLowBackground' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'], +'GearmanClient::doNormal' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'], +'GearmanClient::doStatus' => ['array'], +'GearmanClient::echo' => ['bool', 'workload'=>'string'], +'GearmanClient::error' => ['string'], +'GearmanClient::getErrno' => ['int'], +'GearmanClient::jobStatus' => ['array', 'job_handle'=>'string'], +'GearmanClient::options' => [''], +'GearmanClient::ping' => ['bool', 'workload'=>'string'], +'GearmanClient::removeOptions' => ['bool', 'options'=>'int'], +'GearmanClient::returnCode' => ['int'], +'GearmanClient::runTasks' => ['bool'], +'GearmanClient::setClientCallback' => ['void', 'callback'=>'callable'], +'GearmanClient::setCompleteCallback' => ['bool', 'callback'=>'callable'], +'GearmanClient::setContext' => ['bool', 'context'=>'string'], +'GearmanClient::setCreatedCallback' => ['bool', 'callback'=>'string'], +'GearmanClient::setData' => ['bool', 'data'=>'string'], +'GearmanClient::setDataCallback' => ['bool', 'callback'=>'callable'], +'GearmanClient::setExceptionCallback' => ['bool', 'callback'=>'callable'], +'GearmanClient::setFailCallback' => ['bool', 'callback'=>'callable'], +'GearmanClient::setOptions' => ['bool', 'options'=>'int'], +'GearmanClient::setStatusCallback' => ['bool', 'callback'=>'callable'], +'GearmanClient::setTimeout' => ['bool', 'timeout'=>'int'], +'GearmanClient::setWarningCallback' => ['bool', 'callback'=>'callable'], +'GearmanClient::setWorkloadCallback' => ['bool', 'callback'=>'callable'], +'GearmanClient::timeout' => ['int'], +'GearmanClient::wait' => [''], +'GearmanJob::__construct' => ['void'], +'GearmanJob::complete' => ['bool', 'result'=>'string'], +'GearmanJob::data' => ['bool', 'data'=>'string'], +'GearmanJob::exception' => ['bool', 'exception'=>'string'], +'GearmanJob::fail' => ['bool'], +'GearmanJob::functionName' => ['string'], +'GearmanJob::handle' => ['string'], +'GearmanJob::returnCode' => ['int'], +'GearmanJob::sendComplete' => ['bool', 'result'=>'string'], +'GearmanJob::sendData' => ['bool', 'data'=>'string'], +'GearmanJob::sendException' => ['bool', 'exception'=>'string'], +'GearmanJob::sendFail' => ['bool'], +'GearmanJob::sendStatus' => ['bool', 'numerator'=>'int', 'denominator'=>'int'], +'GearmanJob::sendWarning' => ['bool', 'warning'=>'string'], +'GearmanJob::setReturn' => ['bool', 'gearman_return_t'=>'string'], +'GearmanJob::status' => ['bool', 'numerator'=>'int', 'denominator'=>'int'], +'GearmanJob::unique' => ['string'], +'GearmanJob::warning' => ['bool', 'warning'=>'string'], +'GearmanJob::workload' => ['string'], +'GearmanJob::workloadSize' => ['int'], +'GearmanTask::__construct' => ['void'], +'GearmanTask::create' => ['GearmanTask'], +'GearmanTask::data' => ['string|false'], +'GearmanTask::dataSize' => ['int|false'], +'GearmanTask::function' => ['string'], +'GearmanTask::functionName' => ['string'], +'GearmanTask::isKnown' => ['bool'], +'GearmanTask::isRunning' => ['bool'], +'GearmanTask::jobHandle' => ['string'], +'GearmanTask::recvData' => ['array|false', 'data_len'=>'int'], +'GearmanTask::returnCode' => ['int'], +'GearmanTask::sendData' => ['int', 'data'=>'string'], +'GearmanTask::sendWorkload' => ['int|false', 'data'=>'string'], +'GearmanTask::taskDenominator' => ['int|false'], +'GearmanTask::taskNumerator' => ['int|false'], +'GearmanTask::unique' => ['string|false'], +'GearmanTask::uuid' => ['string'], +'GearmanWorker::__construct' => ['void'], +'GearmanWorker::addFunction' => ['bool', 'function_name'=>'string', 'function'=>'callable', 'context='=>'mixed', 'timeout='=>'int'], +'GearmanWorker::addOptions' => ['bool', 'option'=>'int'], +'GearmanWorker::addServer' => ['bool', 'host='=>'string', 'port='=>'int'], +'GearmanWorker::addServers' => ['bool', 'servers'=>'string'], +'GearmanWorker::clone' => ['void'], +'GearmanWorker::echo' => ['bool', 'workload'=>'string'], +'GearmanWorker::error' => ['string'], +'GearmanWorker::getErrno' => ['int'], +'GearmanWorker::grabJob' => [''], +'GearmanWorker::options' => ['int'], +'GearmanWorker::register' => ['bool', 'function_name'=>'string', 'timeout='=>'int'], +'GearmanWorker::removeOptions' => ['bool', 'option'=>'int'], +'GearmanWorker::returnCode' => ['int'], +'GearmanWorker::setId' => ['bool', 'id'=>'string'], +'GearmanWorker::setOptions' => ['bool', 'option'=>'int'], +'GearmanWorker::setTimeout' => ['bool', 'timeout'=>'int'], +'GearmanWorker::timeout' => ['int'], +'GearmanWorker::unregister' => ['bool', 'function_name'=>'string'], +'GearmanWorker::unregisterAll' => ['bool'], +'GearmanWorker::wait' => ['bool'], +'GearmanWorker::work' => ['bool'], +'Gender\Gender::__construct' => ['void', 'dsn='=>'string'], +'Gender\Gender::connect' => ['bool', 'dsn'=>'string'], +'Gender\Gender::country' => ['array', 'country'=>'int'], +'Gender\Gender::get' => ['int', 'name'=>'string', 'country='=>'int'], +'Gender\Gender::isNick' => ['array', 'name0'=>'string', 'name1'=>'string', 'country='=>'int'], +'Gender\Gender::similarNames' => ['array', 'name'=>'string', 'country='=>'int'], +'Generator::__wakeup' => ['void'], +'Generator::current' => ['mixed'], +'Generator::getReturn' => ['mixed'], +'Generator::key' => ['mixed'], +'Generator::next' => ['void'], +'Generator::rewind' => ['void'], +'Generator::send' => ['mixed', 'value'=>'mixed'], +'Generator::throw' => ['mixed', 'exception'=>'Exception|Throwable'], +'Generator::valid' => ['bool'], +'geoip_asnum_by_name' => ['string|false', 'hostname'=>'string'], +'geoip_continent_code_by_name' => ['string|false', 'hostname'=>'string'], +'geoip_country_code3_by_name' => ['string|false', 'hostname'=>'string'], +'geoip_country_code_by_name' => ['string|false', 'hostname'=>'string'], +'geoip_country_name_by_name' => ['string|false', 'hostname'=>'string'], +'geoip_database_info' => ['string', 'database='=>'int'], +'geoip_db_avail' => ['bool', 'database'=>'int'], +'geoip_db_filename' => ['string', 'database'=>'int'], +'geoip_db_get_all_info' => ['array'], +'geoip_domain_by_name' => ['string', 'hostname'=>'string'], +'geoip_id_by_name' => ['int', 'hostname'=>'string'], +'geoip_isp_by_name' => ['string|false', 'hostname'=>'string'], +'geoip_netspeedcell_by_name' => ['string|false', 'hostname'=>'string'], +'geoip_org_by_name' => ['string|false', 'hostname'=>'string'], +'geoip_record_by_name' => ['array|false', 'hostname'=>'string'], +'geoip_region_by_name' => ['array|false', 'hostname'=>'string'], +'geoip_region_name_by_code' => ['string|false', 'country_code'=>'string', 'region_code'=>'string'], +'geoip_setup_custom_directory' => ['void', 'path'=>'string'], +'geoip_time_zone_by_country_and_region' => ['string|false', 'country_code'=>'string', 'region_code='=>'string'], +'get_browser' => ['array|object|false', 'browser_name='=>'string', 'return_array='=>'bool'], +'get_call_stack' => [''], +'get_called_class' => ['class-string'], +'get_cfg_var' => ['string|false', 'option_name'=>'string'], +'get_class' => ['class-string|false', 'object='=>'object'], +'get_class_methods' => ['list|null', 'class'=>'mixed'], +'get_class_vars' => ['array', 'class_name'=>'string'], +'get_current_user' => ['string'], +'get_debug_type' => ['string', 'data'=>'mixed'], +'get_declared_classes' => ['list'], +'get_declared_interfaces' => ['list'], +'get_declared_traits' => ['list|null'], +'get_defined_constants' => ['array', 'categorize='=>'bool'], +'get_defined_functions' => ['array>', 'exclude_disabled='=>'bool'], +'get_defined_vars' => ['array'], +'get_extension_funcs' => ['list|false', 'extension_name'=>'string'], +'get_headers' => ['array|false', 'url'=>'string', 'format='=>'int', 'context='=>'resource'], +'get_html_translation_table' => ['array', 'table='=>'int', 'flags='=>'int', 'encoding='=>'string'], +'get_include_path' => ['string'], +'get_included_files' => ['list'], +'get_loaded_extensions' => ['list', 'zend_extensions='=>'bool'], +'get_magic_quotes_gpc' => ['int|false'], +'get_magic_quotes_runtime' => ['int|false'], +'get_meta_tags' => ['array', 'filename'=>'string', 'use_include_path='=>'bool'], +'get_object_vars' => ['array', 'object'=>'object'], +'get_parent_class' => ['class-string|false', 'object='=>'mixed'], +'get_required_files' => ['list'], +'get_resource_id' => ['int', 'res'=>'resource'], +'get_resource_type' => ['string', 'res'=>'resource'], +'get_resources' => ['array', 'resource_type='=>'string'], +'getallheaders' => ['array|false'], +'getcwd' => ['string|false'], +'getdate' => ['array', 'timestamp='=>'int'], +'getenv' => ['string|false', 'varname'=>'string', 'local_only='=>'bool'], +'getenv\'1' => ['array'], +'gethostbyaddr' => ['string|false', 'ip_address'=>'string'], +'gethostbyname' => ['string', 'hostname'=>'string'], +'gethostbynamel' => ['list|false', 'hostname'=>'string'], +'gethostname' => ['string|false'], +'getimagesize' => ['array|false', 'imagefile'=>'string', '&w_info='=>'array'], +'getimagesizefromstring' => ['array|false', 'data'=>'string', '&w_info='=>'array'], +'getlastmod' => ['int|false'], +'getmxrr' => ['bool', 'hostname'=>'string', '&w_mxhosts'=>'array', '&w_weight='=>'array'], +'getmygid' => ['int|false'], +'getmyinode' => ['int|false'], +'getmypid' => ['int|false'], +'getmyuid' => ['int|false'], +'getopt' => ['array|array|array>|false', 'options'=>'string', 'longopts='=>'array', '&w_optind='=>'int'], +'getprotobyname' => ['int|false', 'name'=>'string'], +'getprotobynumber' => ['string', 'proto'=>'int'], +'getrandmax' => ['int'], +'getrusage' => ['array', 'who='=>'int'], +'getservbyname' => ['int|false', 'service'=>'string', 'protocol'=>'string'], +'getservbyport' => ['string|false', 'port'=>'int', 'protocol'=>'string'], +'gettext' => ['string', 'msgid'=>'string'], +'gettimeofday' => ['array'], +'gettimeofday\'1' => ['float', 'get_as_float='=>'true'], +'gettype' => ['string', 'var'=>'mixed'], +'glob' => ['list|false', 'pattern'=>'string', 'flags='=>'int'], +'GlobIterator::__construct' => ['void', 'path'=>'string', 'flags='=>'int'], +'GlobIterator::count' => ['int'], +'GlobIterator::current' => ['FilesystemIterator|SplFileInfo|string'], +'GlobIterator::getATime' => [''], +'GlobIterator::getBasename' => ['', 'suffix='=>'string'], +'GlobIterator::getCTime' => [''], +'GlobIterator::getExtension' => [''], +'GlobIterator::getFileInfo' => [''], +'GlobIterator::getFilename' => [''], +'GlobIterator::getFlags' => ['int'], +'GlobIterator::getGroup' => [''], +'GlobIterator::getInode' => [''], +'GlobIterator::getLinkTarget' => [''], +'GlobIterator::getMTime' => [''], +'GlobIterator::getOwner' => [''], +'GlobIterator::getPath' => [''], +'GlobIterator::getPathInfo' => [''], +'GlobIterator::getPathname' => [''], +'GlobIterator::getPerms' => [''], +'GlobIterator::getRealPath' => [''], +'GlobIterator::getSize' => [''], +'GlobIterator::getType' => [''], +'GlobIterator::isDir' => [''], +'GlobIterator::isDot' => [''], +'GlobIterator::isExecutable' => [''], +'GlobIterator::isFile' => [''], +'GlobIterator::isLink' => [''], +'GlobIterator::isReadable' => [''], +'GlobIterator::isWritable' => [''], +'GlobIterator::key' => ['string'], +'GlobIterator::next' => ['void'], +'GlobIterator::openFile' => [''], +'GlobIterator::rewind' => ['void'], +'GlobIterator::seek' => ['void', 'position'=>'int'], +'GlobIterator::setFileClass' => [''], +'GlobIterator::setFlags' => ['void', 'flags='=>'int'], +'GlobIterator::setInfoClass' => [''], +'GlobIterator::valid' => [''], +'Gmagick::__construct' => ['void', 'filename='=>'string'], +'Gmagick::addimage' => ['Gmagick', 'gmagick'=>'gmagick'], +'Gmagick::addnoiseimage' => ['Gmagick', 'noise'=>'int'], +'Gmagick::annotateimage' => ['Gmagick', 'gmagickdraw'=>'gmagickdraw', 'x'=>'float', 'y'=>'float', 'angle'=>'float', 'text'=>'string'], +'Gmagick::blurimage' => ['Gmagick', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'], +'Gmagick::borderimage' => ['Gmagick', 'color'=>'gmagickpixel', 'width'=>'int', 'height'=>'int'], +'Gmagick::charcoalimage' => ['Gmagick', 'radius'=>'float', 'sigma'=>'float'], +'Gmagick::chopimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], +'Gmagick::clear' => ['Gmagick'], +'Gmagick::commentimage' => ['Gmagick', 'comment'=>'string'], +'Gmagick::compositeimage' => ['Gmagick', 'source'=>'gmagick', 'compose'=>'int', 'x'=>'int', 'y'=>'int'], +'Gmagick::cropimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], +'Gmagick::cropthumbnailimage' => ['Gmagick', 'width'=>'int', 'height'=>'int'], +'Gmagick::current' => ['Gmagick'], +'Gmagick::cyclecolormapimage' => ['Gmagick', 'displace'=>'int'], +'Gmagick::deconstructimages' => ['Gmagick'], +'Gmagick::despeckleimage' => ['Gmagick'], +'Gmagick::destroy' => ['bool'], +'Gmagick::drawimage' => ['Gmagick', 'gmagickdraw'=>'gmagickdraw'], +'Gmagick::edgeimage' => ['Gmagick', 'radius'=>'float'], +'Gmagick::embossimage' => ['Gmagick', 'radius'=>'float', 'sigma'=>'float'], +'Gmagick::enhanceimage' => ['Gmagick'], +'Gmagick::equalizeimage' => ['Gmagick'], +'Gmagick::flipimage' => ['Gmagick'], +'Gmagick::flopimage' => ['Gmagick'], +'Gmagick::frameimage' => ['Gmagick', 'color'=>'gmagickpixel', 'width'=>'int', 'height'=>'int', 'inner_bevel'=>'int', 'outer_bevel'=>'int'], +'Gmagick::gammaimage' => ['Gmagick', 'gamma'=>'float'], +'Gmagick::getcopyright' => ['string'], +'Gmagick::getfilename' => ['string'], +'Gmagick::getimagebackgroundcolor' => ['GmagickPixel'], +'Gmagick::getimageblueprimary' => ['array'], +'Gmagick::getimagebordercolor' => ['GmagickPixel'], +'Gmagick::getimagechanneldepth' => ['int', 'channel_type'=>'int'], +'Gmagick::getimagecolors' => ['int'], +'Gmagick::getimagecolorspace' => ['int'], +'Gmagick::getimagecompose' => ['int'], +'Gmagick::getimagedelay' => ['int'], +'Gmagick::getimagedepth' => ['int'], +'Gmagick::getimagedispose' => ['int'], +'Gmagick::getimageextrema' => ['array'], +'Gmagick::getimagefilename' => ['string'], +'Gmagick::getimageformat' => ['string'], +'Gmagick::getimagegamma' => ['float'], +'Gmagick::getimagegreenprimary' => ['array'], +'Gmagick::getimageheight' => ['int'], +'Gmagick::getimagehistogram' => ['array'], +'Gmagick::getimageindex' => ['int'], +'Gmagick::getimageinterlacescheme' => ['int'], +'Gmagick::getimageiterations' => ['int'], +'Gmagick::getimagematte' => ['int'], +'Gmagick::getimagemattecolor' => ['GmagickPixel'], +'Gmagick::getimageprofile' => ['string', 'name'=>'string'], +'Gmagick::getimageredprimary' => ['array'], +'Gmagick::getimagerenderingintent' => ['int'], +'Gmagick::getimageresolution' => ['array'], +'Gmagick::getimagescene' => ['int'], +'Gmagick::getimagesignature' => ['string'], +'Gmagick::getimagetype' => ['int'], +'Gmagick::getimageunits' => ['int'], +'Gmagick::getimagewhitepoint' => ['array'], +'Gmagick::getimagewidth' => ['int'], +'Gmagick::getpackagename' => ['string'], +'Gmagick::getquantumdepth' => ['array'], +'Gmagick::getreleasedate' => ['string'], +'Gmagick::getsamplingfactors' => ['array'], +'Gmagick::getsize' => ['array'], +'Gmagick::getversion' => ['array'], +'Gmagick::hasnextimage' => ['bool'], +'Gmagick::haspreviousimage' => ['bool'], +'Gmagick::implodeimage' => ['mixed', 'radius'=>'float'], +'Gmagick::labelimage' => ['mixed', 'label'=>'string'], +'Gmagick::levelimage' => ['mixed', 'blackpoint'=>'float', 'gamma'=>'float', 'whitepoint'=>'float', 'channel='=>'int'], +'Gmagick::magnifyimage' => ['mixed'], +'Gmagick::mapimage' => ['Gmagick', 'gmagick'=>'gmagick', 'dither'=>'bool'], +'Gmagick::medianfilterimage' => ['void', 'radius'=>'float'], +'Gmagick::minifyimage' => ['Gmagick'], +'Gmagick::modulateimage' => ['Gmagick', 'brightness'=>'float', 'saturation'=>'float', 'hue'=>'float'], +'Gmagick::motionblurimage' => ['Gmagick', 'radius'=>'float', 'sigma'=>'float', 'angle'=>'float'], +'Gmagick::newimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'background'=>'string', 'format='=>'string'], +'Gmagick::nextimage' => ['bool'], +'Gmagick::normalizeimage' => ['Gmagick', 'channel='=>'int'], +'Gmagick::oilpaintimage' => ['Gmagick', 'radius'=>'float'], +'Gmagick::previousimage' => ['bool'], +'Gmagick::profileimage' => ['Gmagick', 'name'=>'string', 'profile'=>'string'], +'Gmagick::quantizeimage' => ['Gmagick', 'numcolors'=>'int', 'colorspace'=>'int', 'treedepth'=>'int', 'dither'=>'bool', 'measureerror'=>'bool'], +'Gmagick::quantizeimages' => ['Gmagick', 'numcolors'=>'int', 'colorspace'=>'int', 'treedepth'=>'int', 'dither'=>'bool', 'measureerror'=>'bool'], +'Gmagick::queryfontmetrics' => ['array', 'draw'=>'gmagickdraw', 'text'=>'string'], +'Gmagick::queryfonts' => ['array', 'pattern='=>'string'], +'Gmagick::queryformats' => ['array', 'pattern='=>'string'], +'Gmagick::radialblurimage' => ['Gmagick', 'angle'=>'float', 'channel='=>'int'], +'Gmagick::raiseimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int', 'raise'=>'bool'], +'Gmagick::read' => ['Gmagick', 'filename'=>'string'], +'Gmagick::readimage' => ['Gmagick', 'filename'=>'string'], +'Gmagick::readimageblob' => ['Gmagick', 'imagecontents'=>'string', 'filename='=>'string'], +'Gmagick::readimagefile' => ['Gmagick', 'fp'=>'resource', 'filename='=>'string'], +'Gmagick::reducenoiseimage' => ['Gmagick', 'radius'=>'float'], +'Gmagick::removeimage' => ['Gmagick'], +'Gmagick::removeimageprofile' => ['string', 'name'=>'string'], +'Gmagick::resampleimage' => ['Gmagick', 'xresolution'=>'float', 'yresolution'=>'float', 'filter'=>'int', 'blur'=>'float'], +'Gmagick::resizeimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'filter'=>'int', 'blur'=>'float', 'fit='=>'bool'], +'Gmagick::rollimage' => ['Gmagick', 'x'=>'int', 'y'=>'int'], +'Gmagick::rotateimage' => ['Gmagick', 'color'=>'mixed', 'degrees'=>'float'], +'Gmagick::scaleimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'fit='=>'bool'], +'Gmagick::separateimagechannel' => ['Gmagick', 'channel'=>'int'], +'Gmagick::setCompressionQuality' => ['Gmagick', 'quality'=>'int'], +'Gmagick::setfilename' => ['Gmagick', 'filename'=>'string'], +'Gmagick::setimagebackgroundcolor' => ['Gmagick', 'color'=>'gmagickpixel'], +'Gmagick::setimageblueprimary' => ['Gmagick', 'x'=>'float', 'y'=>'float'], +'Gmagick::setimagebordercolor' => ['Gmagick', 'color'=>'gmagickpixel'], +'Gmagick::setimagechanneldepth' => ['Gmagick', 'channel'=>'int', 'depth'=>'int'], +'Gmagick::setimagecolorspace' => ['Gmagick', 'colorspace'=>'int'], +'Gmagick::setimagecompose' => ['Gmagick', 'composite'=>'int'], +'Gmagick::setimagedelay' => ['Gmagick', 'delay'=>'int'], +'Gmagick::setimagedepth' => ['Gmagick', 'depth'=>'int'], +'Gmagick::setimagedispose' => ['Gmagick', 'disposetype'=>'int'], +'Gmagick::setimagefilename' => ['Gmagick', 'filename'=>'string'], +'Gmagick::setimageformat' => ['Gmagick', 'imageformat'=>'string'], +'Gmagick::setimagegamma' => ['Gmagick', 'gamma'=>'float'], +'Gmagick::setimagegreenprimary' => ['Gmagick', 'x'=>'float', 'y'=>'float'], +'Gmagick::setimageindex' => ['Gmagick', 'index'=>'int'], +'Gmagick::setimageinterlacescheme' => ['Gmagick', 'interlace'=>'int'], +'Gmagick::setimageiterations' => ['Gmagick', 'iterations'=>'int'], +'Gmagick::setimageprofile' => ['Gmagick', 'name'=>'string', 'profile'=>'string'], +'Gmagick::setimageredprimary' => ['Gmagick', 'x'=>'float', 'y'=>'float'], +'Gmagick::setimagerenderingintent' => ['Gmagick', 'rendering_intent'=>'int'], +'Gmagick::setimageresolution' => ['Gmagick', 'xresolution'=>'float', 'yresolution'=>'float'], +'Gmagick::setimagescene' => ['Gmagick', 'scene'=>'int'], +'Gmagick::setimagetype' => ['Gmagick', 'imgtype'=>'int'], +'Gmagick::setimageunits' => ['Gmagick', 'resolution'=>'int'], +'Gmagick::setimagewhitepoint' => ['Gmagick', 'x'=>'float', 'y'=>'float'], +'Gmagick::setsamplingfactors' => ['Gmagick', 'factors'=>'array'], +'Gmagick::setsize' => ['Gmagick', 'columns'=>'int', 'rows'=>'int'], +'Gmagick::shearimage' => ['Gmagick', 'color'=>'mixed', 'xshear'=>'float', 'yshear'=>'float'], +'Gmagick::solarizeimage' => ['Gmagick', 'threshold'=>'int'], +'Gmagick::spreadimage' => ['Gmagick', 'radius'=>'float'], +'Gmagick::stripimage' => ['Gmagick'], +'Gmagick::swirlimage' => ['Gmagick', 'degrees'=>'float'], +'Gmagick::thumbnailimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'fit='=>'bool'], +'Gmagick::trimimage' => ['Gmagick', 'fuzz'=>'float'], +'Gmagick::write' => ['Gmagick', 'filename'=>'string'], +'Gmagick::writeimage' => ['Gmagick', 'filename'=>'string', 'all_frames='=>'bool'], +'GmagickDraw::annotate' => ['GmagickDraw', 'x'=>'float', 'y'=>'float', 'text'=>'string'], +'GmagickDraw::arc' => ['GmagickDraw', 'sx'=>'float', 'sy'=>'float', 'ex'=>'float', 'ey'=>'float', 'sd'=>'float', 'ed'=>'float'], +'GmagickDraw::bezier' => ['GmagickDraw', 'coordinate_array'=>'array'], +'GmagickDraw::ellipse' => ['GmagickDraw', 'ox'=>'float', 'oy'=>'float', 'rx'=>'float', 'ry'=>'float', 'start'=>'float', 'end'=>'float'], +'GmagickDraw::getfillcolor' => ['GmagickPixel'], +'GmagickDraw::getfillopacity' => ['float'], +'GmagickDraw::getfont' => ['string|false'], +'GmagickDraw::getfontsize' => ['float'], +'GmagickDraw::getfontstyle' => ['int'], +'GmagickDraw::getfontweight' => ['int'], +'GmagickDraw::getstrokecolor' => ['GmagickPixel'], +'GmagickDraw::getstrokeopacity' => ['float'], +'GmagickDraw::getstrokewidth' => ['float'], +'GmagickDraw::gettextdecoration' => ['int'], +'GmagickDraw::gettextencoding' => ['string|false'], +'GmagickDraw::line' => ['GmagickDraw', 'sx'=>'float', 'sy'=>'float', 'ex'=>'float', 'ey'=>'float'], +'GmagickDraw::point' => ['GmagickDraw', 'x'=>'float', 'y'=>'float'], +'GmagickDraw::polygon' => ['GmagickDraw', 'coordinates'=>'array'], +'GmagickDraw::polyline' => ['GmagickDraw', 'coordinate_array'=>'array'], +'GmagickDraw::rectangle' => ['GmagickDraw', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float'], +'GmagickDraw::rotate' => ['GmagickDraw', 'degrees'=>'float'], +'GmagickDraw::roundrectangle' => ['GmagickDraw', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'rx'=>'float', 'ry'=>'float'], +'GmagickDraw::scale' => ['GmagickDraw', 'x'=>'float', 'y'=>'float'], +'GmagickDraw::setfillcolor' => ['GmagickDraw', 'color'=>'string'], +'GmagickDraw::setfillopacity' => ['GmagickDraw', 'fill_opacity'=>'float'], +'GmagickDraw::setfont' => ['GmagickDraw', 'font'=>'string'], +'GmagickDraw::setfontsize' => ['GmagickDraw', 'pointsize'=>'float'], +'GmagickDraw::setfontstyle' => ['GmagickDraw', 'style'=>'int'], +'GmagickDraw::setfontweight' => ['GmagickDraw', 'weight'=>'int'], +'GmagickDraw::setstrokecolor' => ['GmagickDraw', 'color'=>'gmagickpixel'], +'GmagickDraw::setstrokeopacity' => ['GmagickDraw', 'stroke_opacity'=>'float'], +'GmagickDraw::setstrokewidth' => ['GmagickDraw', 'width'=>'float'], +'GmagickDraw::settextdecoration' => ['GmagickDraw', 'decoration'=>'int'], +'GmagickDraw::settextencoding' => ['GmagickDraw', 'encoding'=>'string'], +'GmagickPixel::__construct' => ['void', 'color='=>'string'], +'GmagickPixel::getcolor' => ['mixed', 'as_array='=>'bool', 'normalize_array='=>'bool'], +'GmagickPixel::getcolorcount' => ['int'], +'GmagickPixel::getcolorvalue' => ['float', 'color'=>'int'], +'GmagickPixel::setcolor' => ['GmagickPixel', 'color'=>'string'], +'GmagickPixel::setcolorvalue' => ['GmagickPixel', 'color'=>'int', 'value'=>'float'], +'gmdate' => ['string|false', 'format'=>'string', 'timestamp='=>'int'], +'gmmktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], +'GMP::__construct' => ['void'], +'GMP::__toString' => ['numeric-string'], +'GMP::serialize' => ['string'], +'GMP::unserialize' => ['void', 'serialized'=>'string'], +'gmp_abs' => ['GMP', 'a'=>'GMP|string|int'], +'gmp_add' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_and' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_binomial' => ['GMP|false', 'n'=>'GMP|string|int', 'k'=>'int'], +'gmp_clrbit' => ['void', 'a'=>'GMP|string|int', 'index'=>'int'], +'gmp_cmp' => ['int', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_com' => ['GMP', 'a'=>'GMP|string|int'], +'gmp_div' => ['GMP', 'a'=>'GMP|resource|string', 'b'=>'GMP|resource|string', 'round='=>'int'], +'gmp_div_q' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int', 'round='=>'int'], +'gmp_div_qr' => ['array{0: GMP, 1: GMP}', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int', 'round='=>'int'], +'gmp_div_r' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int', 'round='=>'int'], +'gmp_divexact' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_export' => ['string|false', 'gmpnumber'=>'GMP|string|int', 'word_size='=>'int', 'options='=>'int'], +'gmp_fact' => ['GMP', 'a'=>'int'], +'gmp_gcd' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_gcdext' => ['array', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_hamdist' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_import' => ['GMP|false', 'data'=>'string', 'word_size='=>'int', 'options='=>'int'], +'gmp_init' => ['GMP', 'number'=>'int|string', 'base='=>'int'], +'gmp_intval' => ['int', 'gmpnumber'=>'GMP|string|int'], +'gmp_invert' => ['GMP|false', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_jacobi' => ['int', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_kronecker' => ['int', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_lcm' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_legendre' => ['int', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_mod' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_mul' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_neg' => ['GMP', 'a'=>'GMP|string|int'], +'gmp_nextprime' => ['GMP', 'a'=>'GMP|string|int'], +'gmp_or' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_perfect_power' => ['bool', 'a'=>'GMP|string|int'], +'gmp_perfect_square' => ['bool', 'a'=>'GMP|string|int'], +'gmp_popcount' => ['int', 'a'=>'GMP|string|int'], +'gmp_pow' => ['GMP', 'base'=>'GMP|string|int', 'exp'=>'int'], +'gmp_powm' => ['GMP', 'base'=>'GMP|string|int', 'exp'=>'GMP|string|int', 'mod'=>'GMP|string|int'], +'gmp_prob_prime' => ['int', 'a'=>'GMP|string|int', 'reps='=>'int'], +'gmp_random' => ['GMP', 'limiter='=>'int'], +'gmp_random_bits' => ['GMP', 'bits'=>'int'], +'gmp_random_range' => ['GMP', 'min'=>'GMP|string|int', 'max'=>'GMP|string|int'], +'gmp_random_seed' => ['void', 'seed'=>'GMP|string|int'], +'gmp_root' => ['GMP', 'a'=>'GMP|string|int', 'nth'=>'int'], +'gmp_rootrem' => ['array{0: GMP, 1: GMP}', 'a'=>'GMP|string|int', 'nth'=>'int'], +'gmp_scan0' => ['int', 'a'=>'GMP|string|int', 'start'=>'int'], +'gmp_scan1' => ['int', 'a'=>'GMP|string|int', 'start'=>'int'], +'gmp_setbit' => ['void', 'a'=>'GMP|string|int', 'index'=>'int', 'set_clear='=>'bool'], +'gmp_sign' => ['int', 'a'=>'GMP|string|int'], +'gmp_sqrt' => ['GMP', 'a'=>'GMP|string|int'], +'gmp_sqrtrem' => ['array{0: GMP, 1: GMP}', 'a'=>'GMP|string|int'], +'gmp_strval' => ['numeric-string', 'gmpnumber'=>'GMP|string|int', 'base='=>'int'], +'gmp_sub' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmp_testbit' => ['bool', 'a'=>'GMP|string|int', 'index'=>'int'], +'gmp_xor' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], +'gmstrftime' => ['string', 'format'=>'string', 'timestamp='=>'int'], +'gnupg::adddecryptkey' => ['bool', 'fingerprint'=>'string', 'passphrase'=>'string'], +'gnupg::addencryptkey' => ['bool', 'fingerprint'=>'string'], +'gnupg::addsignkey' => ['bool', 'fingerprint'=>'string', 'passphrase='=>'string'], +'gnupg::cleardecryptkeys' => ['bool'], +'gnupg::clearencryptkeys' => ['bool'], +'gnupg::clearsignkeys' => ['bool'], +'gnupg::decrypt' => ['string|false', 'text'=>'string'], +'gnupg::decryptverify' => ['array|false', 'text'=>'string', '&plaintext'=>'string'], +'gnupg::encrypt' => ['string|false', 'plaintext'=>'string'], +'gnupg::encryptsign' => ['string|false', 'plaintext'=>'string'], +'gnupg::export' => ['string|false', 'fingerprint'=>'string'], +'gnupg::geterror' => ['string|false'], +'gnupg::getprotocol' => ['int'], +'gnupg::import' => ['array|false', 'keydata'=>'string'], +'gnupg::init' => ['resource'], +'gnupg::keyinfo' => ['array', 'pattern'=>'string'], +'gnupg::setarmor' => ['bool', 'armor'=>'int'], +'gnupg::seterrormode' => ['void', 'errormode'=>'int'], +'gnupg::setsignmode' => ['bool', 'signmode'=>'int'], +'gnupg::sign' => ['string|false', 'plaintext'=>'string'], +'gnupg::verify' => ['array|false', 'signed_text'=>'string', 'signature'=>'string', '&plaintext='=>'string'], +'gnupg_adddecryptkey' => ['bool', 'identifier'=>'resource', 'fingerprint'=>'string', 'passphrase'=>'string'], +'gnupg_addencryptkey' => ['bool', 'identifier'=>'resource', 'fingerprint'=>'string'], +'gnupg_addsignkey' => ['bool', 'identifier'=>'resource', 'fingerprint'=>'string', 'passphrase='=>'string'], +'gnupg_cleardecryptkeys' => ['bool', 'identifier'=>'resource'], +'gnupg_clearencryptkeys' => ['bool', 'identifier'=>'resource'], +'gnupg_clearsignkeys' => ['bool', 'identifier'=>'resource'], +'gnupg_decrypt' => ['string', 'identifier'=>'resource', 'text'=>'string'], +'gnupg_decryptverify' => ['array', 'identifier'=>'resource', 'text'=>'string', 'plaintext'=>'string'], +'gnupg_encrypt' => ['string', 'identifier'=>'resource', 'plaintext'=>'string'], +'gnupg_encryptsign' => ['string', 'identifier'=>'resource', 'plaintext'=>'string'], +'gnupg_export' => ['string', 'identifier'=>'resource', 'fingerprint'=>'string'], +'gnupg_geterror' => ['string', 'identifier'=>'resource'], +'gnupg_getprotocol' => ['int', 'identifier'=>'resource'], +'gnupg_import' => ['array', 'identifier'=>'resource', 'keydata'=>'string'], +'gnupg_init' => ['resource'], +'gnupg_keyinfo' => ['array', 'identifier'=>'resource', 'pattern'=>'string'], +'gnupg_setarmor' => ['bool', 'identifier'=>'resource', 'armor'=>'int'], +'gnupg_seterrormode' => ['void', 'identifier'=>'resource', 'errormode'=>'int'], +'gnupg_setsignmode' => ['bool', 'identifier'=>'resource', 'signmode'=>'int'], +'gnupg_sign' => ['string', 'identifier'=>'resource', 'plaintext'=>'string'], +'gnupg_verify' => ['array', 'identifier'=>'resource', 'signed_text'=>'string', 'signature'=>'string', 'plaintext='=>'string'], +'gopher_parsedir' => ['array', 'dirent'=>'string'], +'grapheme_extract' => ['string|false', 'string'=>'string', 'size'=>'int', 'extract_type='=>'int', 'start='=>'int', '&w_next='=>'int'], +'grapheme_stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], +'grapheme_stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool'], +'grapheme_strlen' => ['int|false|null', 'string'=>'string'], +'grapheme_strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], +'grapheme_strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], +'grapheme_strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], +'grapheme_strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool'], +'grapheme_substr' => ['string|false', 'string'=>'string', 'start'=>'int', 'length='=>'int'], +'gregoriantojd' => ['int', 'month'=>'int', 'day'=>'int', 'year'=>'int'], +'gridObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'Grpc\Call::__construct' => ['void', 'channel'=>'Grpc\Channel', 'method'=>'string', 'absolute_deadline'=>'Grpc\Timeval', 'host_override='=>'mixed'], +'Grpc\Call::cancel' => [''], +'Grpc\Call::getPeer' => ['string'], +'Grpc\Call::setCredentials' => ['int', 'creds_obj'=>'Grpc\CallCredentials'], +'Grpc\Call::startBatch' => ['object', 'batch'=>'array'], +'Grpc\CallCredentials::createComposite' => ['Grpc\CallCredentials', 'cred1'=>'Grpc\CallCredentials', 'cred2'=>'Grpc\CallCredentials'], +'Grpc\CallCredentials::createFromPlugin' => ['Grpc\CallCredentials', 'callback'=>'Closure'], +'Grpc\Channel::__construct' => ['void', 'target'=>'string', 'args='=>'array'], +'Grpc\Channel::close' => [''], +'Grpc\Channel::getConnectivityState' => ['int', 'try_to_connect='=>'bool'], +'Grpc\Channel::getTarget' => ['string'], +'Grpc\Channel::watchConnectivityState' => ['bool', 'last_state'=>'int', 'deadline_obj'=>'Grpc\Timeval'], +'Grpc\ChannelCredentials::createComposite' => ['Grpc\ChannelCredentials', 'cred1'=>'Grpc\ChannelCredentials', 'cred2'=>'Grpc\CallCredentials'], +'Grpc\ChannelCredentials::createDefault' => ['Grpc\ChannelCredentials'], +'Grpc\ChannelCredentials::createInsecure' => ['null'], +'Grpc\ChannelCredentials::createSsl' => ['Grpc\ChannelCredentials', 'pem_root_certs'=>'string', 'pem_private_key='=>'string', 'pem_cert_chain='=>'string'], +'Grpc\ChannelCredentials::setDefaultRootsPem' => ['', 'pem_roots'=>'string'], +'Grpc\Server::__construct' => ['void', 'args'=>'array'], +'Grpc\Server::addHttp2Port' => ['bool', 'addr'=>'string'], +'Grpc\Server::addSecureHttp2Port' => ['bool', 'addr'=>'string', 'creds_obj'=>'Grpc\ServerCredentials'], +'Grpc\Server::requestCall' => ['', 'tag_new'=>'int', 'tag_cancel'=>'int'], +'Grpc\Server::start' => [''], +'Grpc\ServerCredentials::createSsl' => ['object', 'pem_root_certs'=>'string', 'pem_private_key'=>'string', 'pem_cert_chain'=>'string'], +'Grpc\Timeval::__construct' => ['void', 'usec'=>'int'], +'Grpc\Timeval::add' => ['Grpc\Timeval', 'other'=>'Grpc\Timeval'], +'Grpc\Timeval::compare' => ['int', 'a'=>'Grpc\Timeval', 'b'=>'Grpc\Timeval'], +'Grpc\Timeval::infFuture' => ['Grpc\Timeval'], +'Grpc\Timeval::infPast' => ['Grpc\Timeval'], +'Grpc\Timeval::now' => ['Grpc\Timeval'], +'Grpc\Timeval::similar' => ['bool', 'a'=>'Grpc\Timeval', 'b'=>'Grpc\Timeval', 'threshold'=>'Grpc\Timeval'], +'Grpc\Timeval::sleepUntil' => [''], +'Grpc\Timeval::subtract' => ['Grpc\Timeval', 'other'=>'Grpc\Timeval'], +'Grpc\Timeval::zero' => ['Grpc\Timeval'], +'gupnp_context_get_host_ip' => ['string', 'context'=>'resource'], +'gupnp_context_get_port' => ['int', 'context'=>'resource'], +'gupnp_context_get_subscription_timeout' => ['int', 'context'=>'resource'], +'gupnp_context_host_path' => ['bool', 'context'=>'resource', 'local_path'=>'string', 'server_path'=>'string'], +'gupnp_context_new' => ['resource', 'host_ip='=>'string', 'port='=>'int'], +'gupnp_context_set_subscription_timeout' => ['void', 'context'=>'resource', 'timeout'=>'int'], +'gupnp_context_timeout_add' => ['bool', 'context'=>'resource', 'timeout'=>'int', 'callback'=>'mixed', 'arg='=>'mixed'], +'gupnp_context_unhost_path' => ['bool', 'context'=>'resource', 'server_path'=>'string'], +'gupnp_control_point_browse_start' => ['bool', 'cpoint'=>'resource'], +'gupnp_control_point_browse_stop' => ['bool', 'cpoint'=>'resource'], +'gupnp_control_point_callback_set' => ['bool', 'cpoint'=>'resource', 'signal'=>'int', 'callback'=>'mixed', 'arg='=>'mixed'], +'gupnp_control_point_new' => ['resource', 'context'=>'resource', 'target'=>'string'], +'gupnp_device_action_callback_set' => ['bool', 'root_device'=>'resource', 'signal'=>'int', 'action_name'=>'string', 'callback'=>'mixed', 'arg='=>'mixed'], +'gupnp_device_info_get' => ['array', 'root_device'=>'resource'], +'gupnp_device_info_get_service' => ['resource', 'root_device'=>'resource', 'type'=>'string'], +'gupnp_root_device_get_available' => ['bool', 'root_device'=>'resource'], +'gupnp_root_device_get_relative_location' => ['string', 'root_device'=>'resource'], +'gupnp_root_device_new' => ['resource', 'context'=>'resource', 'location'=>'string', 'description_dir'=>'string'], +'gupnp_root_device_set_available' => ['bool', 'root_device'=>'resource', 'available'=>'bool'], +'gupnp_root_device_start' => ['bool', 'root_device'=>'resource'], +'gupnp_root_device_stop' => ['bool', 'root_device'=>'resource'], +'gupnp_service_action_get' => ['mixed', 'action'=>'resource', 'name'=>'string', 'type'=>'int'], +'gupnp_service_action_return' => ['bool', 'action'=>'resource'], +'gupnp_service_action_return_error' => ['bool', 'action'=>'resource', 'error_code'=>'int', 'error_description='=>'string'], +'gupnp_service_action_set' => ['bool', 'action'=>'resource', 'name'=>'string', 'type'=>'int', 'value'=>'mixed'], +'gupnp_service_freeze_notify' => ['bool', 'service'=>'resource'], +'gupnp_service_info_get' => ['array', 'proxy'=>'resource'], +'gupnp_service_info_get_introspection' => ['mixed', 'proxy'=>'resource', 'callback='=>'mixed', 'arg='=>'mixed'], +'gupnp_service_introspection_get_state_variable' => ['array', 'introspection'=>'resource', 'variable_name'=>'string'], +'gupnp_service_notify' => ['bool', 'service'=>'resource', 'name'=>'string', 'type'=>'int', 'value'=>'mixed'], +'gupnp_service_proxy_action_get' => ['mixed', 'proxy'=>'resource', 'action'=>'string', 'name'=>'string', 'type'=>'int'], +'gupnp_service_proxy_action_set' => ['bool', 'proxy'=>'resource', 'action'=>'string', 'name'=>'string', 'value'=>'mixed', 'type'=>'int'], +'gupnp_service_proxy_add_notify' => ['bool', 'proxy'=>'resource', 'value'=>'string', 'type'=>'int', 'callback'=>'mixed', 'arg='=>'mixed'], +'gupnp_service_proxy_callback_set' => ['bool', 'proxy'=>'resource', 'signal'=>'int', 'callback'=>'mixed', 'arg='=>'mixed'], +'gupnp_service_proxy_get_subscribed' => ['bool', 'proxy'=>'resource'], +'gupnp_service_proxy_remove_notify' => ['bool', 'proxy'=>'resource', 'value'=>'string'], +'gupnp_service_proxy_send_action' => ['array', 'proxy'=>'resource', 'action'=>'string', 'in_params'=>'array', 'out_params'=>'array'], +'gupnp_service_proxy_set_subscribed' => ['bool', 'proxy'=>'resource', 'subscribed'=>'bool'], +'gupnp_service_thaw_notify' => ['bool', 'service'=>'resource'], +'gzclose' => ['bool', 'zp'=>'resource'], +'gzcompress' => ['string|false', 'data'=>'string', 'level='=>'int', 'encoding='=>'int'], +'gzdecode' => ['string|false', 'data'=>'string', 'length='=>'int'], +'gzdeflate' => ['string|false', 'data'=>'string', 'level='=>'int', 'encoding='=>'int'], +'gzencode' => ['string|false', 'data'=>'string', 'level='=>'int', 'encoding_mode='=>'int'], +'gzeof' => ['bool|int', 'zp'=>'resource'], +'gzfile' => ['list', 'filename'=>'string', 'use_include_path='=>'int'], +'gzgetc' => ['string|false', 'zp'=>'resource'], +'gzgets' => ['string|false', 'zp'=>'resource', 'length='=>'int'], +'gzgetss' => ['string|false', 'zp'=>'resource', 'length'=>'int', 'allowable_tags='=>'string'], +'gzinflate' => ['string|false', 'data'=>'string', 'length='=>'int'], +'gzopen' => ['resource|false', 'filename'=>'string', 'mode'=>'string', 'use_include_path='=>'int'], +'gzpassthru' => ['int|false', 'zp'=>'resource'], +'gzputs' => ['int', 'zp'=>'resource', 'string'=>'string', 'length='=>'int'], +'gzread' => ['string', 'zp'=>'resource', 'length'=>'int'], +'gzrewind' => ['bool', 'zp'=>'resource'], +'gzseek' => ['int', 'zp'=>'resource', 'offset'=>'int', 'whence='=>'int'], +'gztell' => ['int|false', 'zp'=>'resource'], +'gzuncompress' => ['string|false', 'data'=>'string', 'length='=>'int'], +'gzwrite' => ['int', 'zp'=>'resource', 'string'=>'string', 'length='=>'int'], +'HaruAnnotation::setBorderStyle' => ['bool', 'width'=>'float', 'dash_on'=>'int', 'dash_off'=>'int'], +'HaruAnnotation::setHighlightMode' => ['bool', 'mode'=>'int'], +'HaruAnnotation::setIcon' => ['bool', 'icon'=>'int'], +'HaruAnnotation::setOpened' => ['bool', 'opened'=>'bool'], +'HaruDestination::setFit' => ['bool'], +'HaruDestination::setFitB' => ['bool'], +'HaruDestination::setFitBH' => ['bool', 'top'=>'float'], +'HaruDestination::setFitBV' => ['bool', 'left'=>'float'], +'HaruDestination::setFitH' => ['bool', 'top'=>'float'], +'HaruDestination::setFitR' => ['bool', 'left'=>'float', 'bottom'=>'float', 'right'=>'float', 'top'=>'float'], +'HaruDestination::setFitV' => ['bool', 'left'=>'float'], +'HaruDestination::setXYZ' => ['bool', 'left'=>'float', 'top'=>'float', 'zoom'=>'float'], +'HaruDoc::__construct' => ['void'], +'HaruDoc::addPage' => ['object'], +'HaruDoc::addPageLabel' => ['bool', 'first_page'=>'int', 'style'=>'int', 'first_num'=>'int', 'prefix='=>'string'], +'HaruDoc::createOutline' => ['object', 'title'=>'string', 'parent_outline='=>'object', 'encoder='=>'object'], +'HaruDoc::getCurrentEncoder' => ['object'], +'HaruDoc::getCurrentPage' => ['object'], +'HaruDoc::getEncoder' => ['object', 'encoding'=>'string'], +'HaruDoc::getFont' => ['object', 'fontname'=>'string', 'encoding='=>'string'], +'HaruDoc::getInfoAttr' => ['string', 'type'=>'int'], +'HaruDoc::getPageLayout' => ['int'], +'HaruDoc::getPageMode' => ['int'], +'HaruDoc::getStreamSize' => ['int'], +'HaruDoc::insertPage' => ['object', 'page'=>'object'], +'HaruDoc::loadJPEG' => ['object', 'filename'=>'string'], +'HaruDoc::loadPNG' => ['object', 'filename'=>'string', 'deferred='=>'bool'], +'HaruDoc::loadRaw' => ['object', 'filename'=>'string', 'width'=>'int', 'height'=>'int', 'color_space'=>'int'], +'HaruDoc::loadTTC' => ['string', 'fontfile'=>'string', 'index'=>'int', 'embed='=>'bool'], +'HaruDoc::loadTTF' => ['string', 'fontfile'=>'string', 'embed='=>'bool'], +'HaruDoc::loadType1' => ['string', 'afmfile'=>'string', 'pfmfile='=>'string'], +'HaruDoc::output' => ['bool'], +'HaruDoc::readFromStream' => ['string', 'bytes'=>'int'], +'HaruDoc::resetError' => ['bool'], +'HaruDoc::resetStream' => ['bool'], +'HaruDoc::save' => ['bool', 'file'=>'string'], +'HaruDoc::saveToStream' => ['bool'], +'HaruDoc::setCompressionMode' => ['bool', 'mode'=>'int'], +'HaruDoc::setCurrentEncoder' => ['bool', 'encoding'=>'string'], +'HaruDoc::setEncryptionMode' => ['bool', 'mode'=>'int', 'key_len='=>'int'], +'HaruDoc::setInfoAttr' => ['bool', 'type'=>'int', 'info'=>'string'], +'HaruDoc::setInfoDateAttr' => ['bool', 'type'=>'int', 'year'=>'int', 'month'=>'int', 'day'=>'int', 'hour'=>'int', 'min'=>'int', 'sec'=>'int', 'ind'=>'string', 'off_hour'=>'int', 'off_min'=>'int'], +'HaruDoc::setOpenAction' => ['bool', 'destination'=>'object'], +'HaruDoc::setPageLayout' => ['bool', 'layout'=>'int'], +'HaruDoc::setPageMode' => ['bool', 'mode'=>'int'], +'HaruDoc::setPagesConfiguration' => ['bool', 'page_per_pages'=>'int'], +'HaruDoc::setPassword' => ['bool', 'owner_password'=>'string', 'user_password'=>'string'], +'HaruDoc::setPermission' => ['bool', 'permission'=>'int'], +'HaruDoc::useCNSEncodings' => ['bool'], +'HaruDoc::useCNSFonts' => ['bool'], +'HaruDoc::useCNTEncodings' => ['bool'], +'HaruDoc::useCNTFonts' => ['bool'], +'HaruDoc::useJPEncodings' => ['bool'], +'HaruDoc::useJPFonts' => ['bool'], +'HaruDoc::useKREncodings' => ['bool'], +'HaruDoc::useKRFonts' => ['bool'], +'HaruEncoder::getByteType' => ['int', 'text'=>'string', 'index'=>'int'], +'HaruEncoder::getType' => ['int'], +'HaruEncoder::getUnicode' => ['int', 'character'=>'int'], +'HaruEncoder::getWritingMode' => ['int'], +'HaruFont::getAscent' => ['int'], +'HaruFont::getCapHeight' => ['int'], +'HaruFont::getDescent' => ['int'], +'HaruFont::getEncodingName' => ['string'], +'HaruFont::getFontName' => ['string'], +'HaruFont::getTextWidth' => ['array', 'text'=>'string'], +'HaruFont::getUnicodeWidth' => ['int', 'character'=>'int'], +'HaruFont::getXHeight' => ['int'], +'HaruFont::measureText' => ['int', 'text'=>'string', 'width'=>'float', 'font_size'=>'float', 'char_space'=>'float', 'word_space'=>'float', 'word_wrap='=>'bool'], +'HaruImage::getBitsPerComponent' => ['int'], +'HaruImage::getColorSpace' => ['string'], +'HaruImage::getHeight' => ['int'], +'HaruImage::getSize' => ['array'], +'HaruImage::getWidth' => ['int'], +'HaruImage::setColorMask' => ['bool', 'rmin'=>'int', 'rmax'=>'int', 'gmin'=>'int', 'gmax'=>'int', 'bmin'=>'int', 'bmax'=>'int'], +'HaruImage::setMaskImage' => ['bool', 'mask_image'=>'object'], +'HaruOutline::setDestination' => ['bool', 'destination'=>'object'], +'HaruOutline::setOpened' => ['bool', 'opened'=>'bool'], +'HaruPage::arc' => ['bool', 'x'=>'float', 'y'=>'float', 'ray'=>'float', 'ang1'=>'float', 'ang2'=>'float'], +'HaruPage::beginText' => ['bool'], +'HaruPage::circle' => ['bool', 'x'=>'float', 'y'=>'float', 'ray'=>'float'], +'HaruPage::closePath' => ['bool'], +'HaruPage::concat' => ['bool', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'x'=>'float', 'y'=>'float'], +'HaruPage::createDestination' => ['object'], +'HaruPage::createLinkAnnotation' => ['object', 'rectangle'=>'array', 'destination'=>'object'], +'HaruPage::createTextAnnotation' => ['object', 'rectangle'=>'array', 'text'=>'string', 'encoder='=>'object'], +'HaruPage::createURLAnnotation' => ['object', 'rectangle'=>'array', 'url'=>'string'], +'HaruPage::curveTo' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float'], +'HaruPage::curveTo2' => ['bool', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float'], +'HaruPage::curveTo3' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x3'=>'float', 'y3'=>'float'], +'HaruPage::drawImage' => ['bool', 'image'=>'object', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'], +'HaruPage::ellipse' => ['bool', 'x'=>'float', 'y'=>'float', 'xray'=>'float', 'yray'=>'float'], +'HaruPage::endPath' => ['bool'], +'HaruPage::endText' => ['bool'], +'HaruPage::eofill' => ['bool'], +'HaruPage::eoFillStroke' => ['bool', 'close_path='=>'bool'], +'HaruPage::fill' => ['bool'], +'HaruPage::fillStroke' => ['bool', 'close_path='=>'bool'], +'HaruPage::getCharSpace' => ['float'], +'HaruPage::getCMYKFill' => ['array'], +'HaruPage::getCMYKStroke' => ['array'], +'HaruPage::getCurrentFont' => ['object'], +'HaruPage::getCurrentFontSize' => ['float'], +'HaruPage::getCurrentPos' => ['array'], +'HaruPage::getCurrentTextPos' => ['array'], +'HaruPage::getDash' => ['array'], +'HaruPage::getFillingColorSpace' => ['int'], +'HaruPage::getFlatness' => ['float'], +'HaruPage::getGMode' => ['int'], +'HaruPage::getGrayFill' => ['float'], +'HaruPage::getGrayStroke' => ['float'], +'HaruPage::getHeight' => ['float'], +'HaruPage::getHorizontalScaling' => ['float'], +'HaruPage::getLineCap' => ['int'], +'HaruPage::getLineJoin' => ['int'], +'HaruPage::getLineWidth' => ['float'], +'HaruPage::getMiterLimit' => ['float'], +'HaruPage::getRGBFill' => ['array'], +'HaruPage::getRGBStroke' => ['array'], +'HaruPage::getStrokingColorSpace' => ['int'], +'HaruPage::getTextLeading' => ['float'], +'HaruPage::getTextMatrix' => ['array'], +'HaruPage::getTextRenderingMode' => ['int'], +'HaruPage::getTextRise' => ['float'], +'HaruPage::getTextWidth' => ['float', 'text'=>'string'], +'HaruPage::getTransMatrix' => ['array'], +'HaruPage::getWidth' => ['float'], +'HaruPage::getWordSpace' => ['float'], +'HaruPage::lineTo' => ['bool', 'x'=>'float', 'y'=>'float'], +'HaruPage::measureText' => ['int', 'text'=>'string', 'width'=>'float', 'wordwrap='=>'bool'], +'HaruPage::moveTextPos' => ['bool', 'x'=>'float', 'y'=>'float', 'set_leading='=>'bool'], +'HaruPage::moveTo' => ['bool', 'x'=>'float', 'y'=>'float'], +'HaruPage::moveToNextLine' => ['bool'], +'HaruPage::rectangle' => ['bool', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'], +'HaruPage::setCharSpace' => ['bool', 'char_space'=>'float'], +'HaruPage::setCMYKFill' => ['bool', 'c'=>'float', 'm'=>'float', 'y'=>'float', 'k'=>'float'], +'HaruPage::setCMYKStroke' => ['bool', 'c'=>'float', 'm'=>'float', 'y'=>'float', 'k'=>'float'], +'HaruPage::setDash' => ['bool', 'pattern'=>'array', 'phase'=>'int'], +'HaruPage::setFlatness' => ['bool', 'flatness'=>'float'], +'HaruPage::setFontAndSize' => ['bool', 'font'=>'object', 'size'=>'float'], +'HaruPage::setGrayFill' => ['bool', 'value'=>'float'], +'HaruPage::setGrayStroke' => ['bool', 'value'=>'float'], +'HaruPage::setHeight' => ['bool', 'height'=>'float'], +'HaruPage::setHorizontalScaling' => ['bool', 'scaling'=>'float'], +'HaruPage::setLineCap' => ['bool', 'cap'=>'int'], +'HaruPage::setLineJoin' => ['bool', 'join'=>'int'], +'HaruPage::setLineWidth' => ['bool', 'width'=>'float'], +'HaruPage::setMiterLimit' => ['bool', 'limit'=>'float'], +'HaruPage::setRGBFill' => ['bool', 'r'=>'float', 'g'=>'float', 'b'=>'float'], +'HaruPage::setRGBStroke' => ['bool', 'r'=>'float', 'g'=>'float', 'b'=>'float'], +'HaruPage::setRotate' => ['bool', 'angle'=>'int'], +'HaruPage::setSize' => ['bool', 'size'=>'int', 'direction'=>'int'], +'HaruPage::setSlideShow' => ['bool', 'type'=>'int', 'disp_time'=>'float', 'trans_time'=>'float'], +'HaruPage::setTextLeading' => ['bool', 'text_leading'=>'float'], +'HaruPage::setTextMatrix' => ['bool', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'x'=>'float', 'y'=>'float'], +'HaruPage::setTextRenderingMode' => ['bool', 'mode'=>'int'], +'HaruPage::setTextRise' => ['bool', 'rise'=>'float'], +'HaruPage::setWidth' => ['bool', 'width'=>'float'], +'HaruPage::setWordSpace' => ['bool', 'word_space'=>'float'], +'HaruPage::showText' => ['bool', 'text'=>'string'], +'HaruPage::showTextNextLine' => ['bool', 'text'=>'string', 'word_space='=>'float', 'char_space='=>'float'], +'HaruPage::stroke' => ['bool', 'close_path='=>'bool'], +'HaruPage::textOut' => ['bool', 'x'=>'float', 'y'=>'float', 'text'=>'string'], +'HaruPage::textRect' => ['bool', 'left'=>'float', 'top'=>'float', 'right'=>'float', 'bottom'=>'float', 'text'=>'string', 'align='=>'int'], +'hash' => ['string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'], +'hash_algos' => ['list'], +'hash_copy' => ['HashContext', 'context'=>'HashContext|resource'], +'hash_equals' => ['bool', 'known_string'=>'string', 'user_string'=>'string'], +'hash_file' => ['string|false', 'algo'=>'string', 'filename'=>'string', 'raw_output='=>'bool'], +'hash_final' => ['string', 'context'=>'HashContext|resource', 'raw_output='=>'bool'], +'hash_hkdf' => ['string|false', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], +'hash_hmac' => ['string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'], +'hash_hmac_algos' => ['list'], +'hash_hmac_file' => ['string|false', 'algo'=>'string', 'filename'=>'string', 'key'=>'string', 'raw_output='=>'bool'], +'hash_init' => ['HashContext|false', 'algo'=>'string', 'options='=>'int', 'key='=>'string'], +'hash_pbkdf2' => ['string|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'], +'hash_update' => ['bool', 'context'=>'HashContext', 'data'=>'string'], +'hash_update_file' => ['bool', 'context'=>'HashContext', 'filename'=>'string', 'scontext='=>'?HashContext'], +'hash_update_stream' => ['int', 'context'=>'HashContext', 'handle'=>'resource', 'length='=>'int'], +'hashTableObj::clear' => ['void'], +'hashTableObj::get' => ['string', 'key'=>'string'], +'hashTableObj::nextkey' => ['string', 'previousKey'=>'string'], +'hashTableObj::remove' => ['int', 'key'=>'string'], +'hashTableObj::set' => ['int', 'key'=>'string', 'value'=>'string'], +'header' => ['void', 'header'=>'string', 'replace='=>'bool', 'http_response_code='=>'int'], +'header_register_callback' => ['bool', 'callback'=>'callable():void'], +'header_remove' => ['void', 'name='=>'string'], +'headers_list' => ['list'], +'headers_sent' => ['bool', '&w_file='=>'string', '&w_line='=>'int'], +'hebrev' => ['string', 'string'=>'string', 'max_chars_per_line='=>'int'], +'hebrevc' => ['string', 'string'=>'string', 'max_chars_per_line='=>'int'], +'hex2bin' => ['string|false', 'data'=>'string'], +'hexdec' => ['int|float', 'hexadecimal_number'=>'string'], +'highlight_file' => ['string|bool', 'file_name'=>'string', 'return='=>'bool'], +'highlight_string' => ['string|bool', 'string'=>'string', 'return='=>'bool'], +'hrtime' => ['array{0:int,1:int}|false', 'get_as_number='=>'false'], +'hrtime\'1' => ['int|float|false', 'get_as_number='=>'true'], +'HRTime\PerformanceCounter::getElapsedTicks' => ['int'], +'HRTime\PerformanceCounter::getFrequency' => ['int'], +'HRTime\PerformanceCounter::getLastElapsedTicks' => ['int'], +'HRTime\PerformanceCounter::getTicks' => ['int'], +'HRTime\PerformanceCounter::getTicksSince' => ['int', 'start'=>'int'], +'HRTime\PerformanceCounter::isRunning' => ['bool'], +'HRTime\PerformanceCounter::start' => ['void'], +'HRTime\PerformanceCounter::stop' => ['void'], +'HRTime\StopWatch::getElapsedTicks' => ['int'], +'HRTime\StopWatch::getElapsedTime' => ['float', 'unit='=>'int'], +'HRTime\StopWatch::getLastElapsedTicks' => ['int'], +'HRTime\StopWatch::getLastElapsedTime' => ['float', 'unit='=>'int'], +'HRTime\StopWatch::isRunning' => ['bool'], +'HRTime\StopWatch::start' => ['void'], +'HRTime\StopWatch::stop' => ['void'], +'html_entity_decode' => ['string', 'string'=>'string', 'quote_style='=>'int', 'encoding='=>'string'], +'htmlentities' => ['string', 'string'=>'string', 'quote_style='=>'int', 'encoding='=>'string', 'double_encode='=>'bool'], +'htmlspecialchars' => ['string', 'string'=>'string', 'quote_style='=>'int', 'encoding='=>'string', 'double_encode='=>'bool'], +'htmlspecialchars_decode' => ['string', 'string'=>'string', 'quote_style='=>'int'], +'http\Client::__construct' => ['void', 'driver='=>'string', 'persistent_handle_id='=>'string'], +'http\Client::addCookies' => ['http\Client', 'cookies='=>'?array'], +'http\Client::addSslOptions' => ['http\Client', 'ssl_options='=>'?array'], +'http\Client::attach' => ['void', 'observer'=>'SplObserver'], +'http\Client::configure' => ['http\Client', 'settings'=>'array'], +'http\Client::count' => ['int'], +'http\Client::dequeue' => ['http\Client', 'request'=>'http\Client\Request'], +'http\Client::detach' => ['void', 'observer'=>'SplObserver'], +'http\Client::enableEvents' => ['http\Client', 'enable='=>'mixed'], +'http\Client::enablePipelining' => ['http\Client', 'enable='=>'mixed'], +'http\Client::enqueue' => ['http\Client', 'request'=>'http\Client\Request', 'callable='=>'mixed'], +'http\Client::getAvailableConfiguration' => ['array'], +'http\Client::getAvailableDrivers' => ['array'], +'http\Client::getAvailableOptions' => ['array'], +'http\Client::getCookies' => ['array'], +'http\Client::getHistory' => ['http\Message'], +'http\Client::getObservers' => ['SplObjectStorage'], +'http\Client::getOptions' => ['array'], +'http\Client::getProgressInfo' => ['null|object', 'request'=>'http\Client\Request'], +'http\Client::getResponse' => ['http\Client\Response|null', 'request='=>'?http\Client\Request'], +'http\Client::getSslOptions' => ['array'], +'http\Client::getTransferInfo' => ['object', 'request'=>'http\Client\Request'], +'http\Client::notify' => ['void', 'request='=>'?http\Client\Request'], +'http\Client::once' => ['bool'], +'http\Client::requeue' => ['http\Client', 'request'=>'http\Client\Request', 'callable='=>'mixed'], +'http\Client::reset' => ['http\Client'], +'http\Client::send' => ['http\Client'], +'http\Client::setCookies' => ['http\Client', 'cookies='=>'?array'], +'http\Client::setDebug' => ['http\Client', 'callback'=>'callable'], +'http\Client::setOptions' => ['http\Client', 'options='=>'?array'], +'http\Client::setSslOptions' => ['http\Client', 'ssl_option='=>'?array'], +'http\Client::wait' => ['bool', 'timeout='=>'mixed'], +'http\Client\Curl\User::init' => ['', 'run'=>'callable'], +'http\Client\Curl\User::once' => [''], +'http\Client\Curl\User::send' => [''], +'http\Client\Curl\User::socket' => ['', 'socket'=>'resource', 'action'=>'int'], +'http\Client\Curl\User::timer' => ['', 'timeout_ms'=>'int'], +'http\Client\Curl\User::wait' => ['', 'timeout_ms='=>'mixed'], +'http\Client\Request::__construct' => ['void', 'method='=>'mixed', 'url='=>'mixed', 'headers='=>'?array', 'body='=>'?http\Message\Body'], +'http\Client\Request::__toString' => ['string'], +'http\Client\Request::addBody' => ['http\Message', 'body'=>'http\Message\Body'], +'http\Client\Request::addHeader' => ['http\Message', 'header'=>'string', 'value'=>'mixed'], +'http\Client\Request::addHeaders' => ['http\Message', 'headers'=>'array', 'append='=>'mixed'], +'http\Client\Request::addQuery' => ['http\Client\Request', 'query_data'=>'mixed'], +'http\Client\Request::addSslOptions' => ['http\Client\Request', 'ssl_options='=>'?array'], +'http\Client\Request::count' => ['int'], +'http\Client\Request::current' => ['mixed'], +'http\Client\Request::detach' => ['http\Message'], +'http\Client\Request::getBody' => ['http\Message\Body'], +'http\Client\Request::getContentType' => ['null|string'], +'http\Client\Request::getHeader' => ['http\Header|mixed', 'header'=>'string', 'into_class='=>'mixed'], +'http\Client\Request::getHeaders' => ['array'], +'http\Client\Request::getHttpVersion' => ['string'], +'http\Client\Request::getInfo' => ['null|string'], +'http\Client\Request::getOptions' => ['array'], +'http\Client\Request::getParentMessage' => ['http\Message'], +'http\Client\Request::getQuery' => ['null|string'], +'http\Client\Request::getRequestMethod' => ['false|string'], +'http\Client\Request::getRequestUrl' => ['false|string'], +'http\Client\Request::getResponseCode' => ['false|int'], +'http\Client\Request::getResponseStatus' => ['false|string'], +'http\Client\Request::getSslOptions' => ['array'], +'http\Client\Request::getType' => ['int'], +'http\Client\Request::isMultipart' => ['bool', '&boundary='=>'mixed'], +'http\Client\Request::key' => ['int|string'], +'http\Client\Request::next' => ['void'], +'http\Client\Request::prepend' => ['http\Message', 'message'=>'http\Message', 'top='=>'mixed'], +'http\Client\Request::reverse' => ['http\Message'], +'http\Client\Request::rewind' => ['void'], +'http\Client\Request::serialize' => ['string'], +'http\Client\Request::setBody' => ['http\Message', 'body'=>'http\Message\Body'], +'http\Client\Request::setContentType' => ['http\Client\Request', 'content_type'=>'string'], +'http\Client\Request::setHeader' => ['http\Message', 'header'=>'string', 'value='=>'mixed'], +'http\Client\Request::setHeaders' => ['http\Message', 'headers'=>'array'], +'http\Client\Request::setHttpVersion' => ['http\Message', 'http_version'=>'string'], +'http\Client\Request::setInfo' => ['http\Message', 'http_info'=>'string'], +'http\Client\Request::setOptions' => ['http\Client\Request', 'options='=>'?array'], +'http\Client\Request::setQuery' => ['http\Client\Request', 'query_data='=>'mixed'], +'http\Client\Request::setRequestMethod' => ['http\Message', 'request_method'=>'string'], +'http\Client\Request::setRequestUrl' => ['http\Message', 'url'=>'string'], +'http\Client\Request::setResponseCode' => ['http\Message', 'response_code'=>'int', 'strict='=>'mixed'], +'http\Client\Request::setResponseStatus' => ['http\Message', 'response_status'=>'string'], +'http\Client\Request::setSslOptions' => ['http\Client\Request', 'ssl_options='=>'?array'], +'http\Client\Request::setType' => ['http\Message', 'type'=>'int'], +'http\Client\Request::splitMultipartBody' => ['http\Message'], +'http\Client\Request::toCallback' => ['http\Message', 'callback'=>'callable'], +'http\Client\Request::toStream' => ['http\Message', 'stream'=>'resource'], +'http\Client\Request::toString' => ['string', 'include_parent='=>'mixed'], +'http\Client\Request::unserialize' => ['void', 'serialized'=>'string'], +'http\Client\Request::valid' => ['bool'], +'http\Client\Response::__construct' => ['Iterator'], +'http\Client\Response::__toString' => ['string'], +'http\Client\Response::addBody' => ['http\Message', 'body'=>'http\Message\Body'], +'http\Client\Response::addHeader' => ['http\Message', 'header'=>'string', 'value'=>'mixed'], +'http\Client\Response::addHeaders' => ['http\Message', 'headers'=>'array', 'append='=>'mixed'], +'http\Client\Response::count' => ['int'], +'http\Client\Response::current' => ['mixed'], +'http\Client\Response::detach' => ['http\Message'], +'http\Client\Response::getBody' => ['http\Message\Body'], +'http\Client\Response::getCookies' => ['array', 'flags='=>'mixed', 'allowed_extras='=>'mixed'], +'http\Client\Response::getHeader' => ['http\Header|mixed', 'header'=>'string', 'into_class='=>'mixed'], +'http\Client\Response::getHeaders' => ['array'], +'http\Client\Response::getHttpVersion' => ['string'], +'http\Client\Response::getInfo' => ['null|string'], +'http\Client\Response::getParentMessage' => ['http\Message'], +'http\Client\Response::getRequestMethod' => ['false|string'], +'http\Client\Response::getRequestUrl' => ['false|string'], +'http\Client\Response::getResponseCode' => ['false|int'], +'http\Client\Response::getResponseStatus' => ['false|string'], +'http\Client\Response::getTransferInfo' => ['mixed|object', 'element='=>'mixed'], +'http\Client\Response::getType' => ['int'], +'http\Client\Response::isMultipart' => ['bool', '&boundary='=>'mixed'], +'http\Client\Response::key' => ['int|string'], +'http\Client\Response::next' => ['void'], +'http\Client\Response::prepend' => ['http\Message', 'message'=>'http\Message', 'top='=>'mixed'], +'http\Client\Response::reverse' => ['http\Message'], +'http\Client\Response::rewind' => ['void'], +'http\Client\Response::serialize' => ['string'], +'http\Client\Response::setBody' => ['http\Message', 'body'=>'http\Message\Body'], +'http\Client\Response::setHeader' => ['http\Message', 'header'=>'string', 'value='=>'mixed'], +'http\Client\Response::setHeaders' => ['http\Message', 'headers'=>'array'], +'http\Client\Response::setHttpVersion' => ['http\Message', 'http_version'=>'string'], +'http\Client\Response::setInfo' => ['http\Message', 'http_info'=>'string'], +'http\Client\Response::setRequestMethod' => ['http\Message', 'request_method'=>'string'], +'http\Client\Response::setRequestUrl' => ['http\Message', 'url'=>'string'], +'http\Client\Response::setResponseCode' => ['http\Message', 'response_code'=>'int', 'strict='=>'mixed'], +'http\Client\Response::setResponseStatus' => ['http\Message', 'response_status'=>'string'], +'http\Client\Response::setType' => ['http\Message', 'type'=>'int'], +'http\Client\Response::splitMultipartBody' => ['http\Message'], +'http\Client\Response::toCallback' => ['http\Message', 'callback'=>'callable'], +'http\Client\Response::toStream' => ['http\Message', 'stream'=>'resource'], +'http\Client\Response::toString' => ['string', 'include_parent='=>'mixed'], +'http\Client\Response::unserialize' => ['void', 'serialized'=>'string'], +'http\Client\Response::valid' => ['bool'], +'http\Cookie::__construct' => ['void', 'cookie_string='=>'mixed', 'parser_flags='=>'int', 'allowed_extras='=>'array'], +'http\Cookie::__toString' => ['string'], +'http\Cookie::addCookie' => ['http\Cookie', 'cookie_name'=>'string', 'cookie_value'=>'string'], +'http\Cookie::addCookies' => ['http\Cookie', 'cookies'=>'array'], +'http\Cookie::addExtra' => ['http\Cookie', 'extra_name'=>'string', 'extra_value'=>'string'], +'http\Cookie::addExtras' => ['http\Cookie', 'extras'=>'array'], +'http\Cookie::getCookie' => ['null|string', 'name'=>'string'], +'http\Cookie::getCookies' => ['array'], +'http\Cookie::getDomain' => ['string'], +'http\Cookie::getExpires' => ['int'], +'http\Cookie::getExtra' => ['string', 'name'=>'string'], +'http\Cookie::getExtras' => ['array'], +'http\Cookie::getFlags' => ['int'], +'http\Cookie::getMaxAge' => ['int'], +'http\Cookie::getPath' => ['string'], +'http\Cookie::setCookie' => ['http\Cookie', 'cookie_name'=>'string', 'cookie_value='=>'mixed'], +'http\Cookie::setCookies' => ['http\Cookie', 'cookies='=>'mixed'], +'http\Cookie::setDomain' => ['http\Cookie', 'value='=>'mixed'], +'http\Cookie::setExpires' => ['http\Cookie', 'value='=>'mixed'], +'http\Cookie::setExtra' => ['http\Cookie', 'extra_name'=>'string', 'extra_value='=>'mixed'], +'http\Cookie::setExtras' => ['http\Cookie', 'extras='=>'mixed'], +'http\Cookie::setFlags' => ['http\Cookie', 'value='=>'mixed'], +'http\Cookie::setMaxAge' => ['http\Cookie', 'value='=>'mixed'], +'http\Cookie::setPath' => ['http\Cookie', 'value='=>'mixed'], +'http\Cookie::toArray' => ['array'], +'http\Cookie::toString' => ['string'], +'http\Encoding\Stream::__construct' => ['void', 'flags='=>'mixed'], +'http\Encoding\Stream::done' => ['bool'], +'http\Encoding\Stream::finish' => ['string'], +'http\Encoding\Stream::flush' => ['string'], +'http\Encoding\Stream::update' => ['string', 'data'=>'string'], +'http\Encoding\Stream\Debrotli::__construct' => ['void', 'flags='=>'int'], +'http\Encoding\Stream\Debrotli::decode' => ['string', 'data'=>'string'], +'http\Encoding\Stream\Debrotli::done' => ['bool'], +'http\Encoding\Stream\Debrotli::finish' => ['string'], +'http\Encoding\Stream\Debrotli::flush' => ['string'], +'http\Encoding\Stream\Debrotli::update' => ['string', 'data'=>'string'], +'http\Encoding\Stream\Dechunk::__construct' => ['void', 'flags='=>'mixed'], +'http\Encoding\Stream\Dechunk::decode' => ['false|string', 'data'=>'string', '&decoded_len='=>'mixed'], +'http\Encoding\Stream\Dechunk::done' => ['bool'], +'http\Encoding\Stream\Dechunk::finish' => ['string'], +'http\Encoding\Stream\Dechunk::flush' => ['string'], +'http\Encoding\Stream\Dechunk::update' => ['string', 'data'=>'string'], +'http\Encoding\Stream\Deflate::__construct' => ['void', 'flags='=>'mixed'], +'http\Encoding\Stream\Deflate::done' => ['bool'], +'http\Encoding\Stream\Deflate::encode' => ['string', 'data'=>'string', 'flags='=>'mixed'], +'http\Encoding\Stream\Deflate::finish' => ['string'], +'http\Encoding\Stream\Deflate::flush' => ['string'], +'http\Encoding\Stream\Deflate::update' => ['string', 'data'=>'string'], +'http\Encoding\Stream\Enbrotli::__construct' => ['void', 'flags='=>'int'], +'http\Encoding\Stream\Enbrotli::done' => ['bool'], +'http\Encoding\Stream\Enbrotli::encode' => ['string', 'data'=>'string', 'flags='=>'int'], +'http\Encoding\Stream\Enbrotli::finish' => ['string'], +'http\Encoding\Stream\Enbrotli::flush' => ['string'], +'http\Encoding\Stream\Enbrotli::update' => ['string', 'data'=>'string'], +'http\Encoding\Stream\Inflate::__construct' => ['void', 'flags='=>'mixed'], +'http\Encoding\Stream\Inflate::decode' => ['string', 'data'=>'string'], +'http\Encoding\Stream\Inflate::done' => ['bool'], +'http\Encoding\Stream\Inflate::finish' => ['string'], +'http\Encoding\Stream\Inflate::flush' => ['string'], +'http\Encoding\Stream\Inflate::update' => ['string', 'data'=>'string'], +'http\Env::getRequestBody' => ['http\Message\Body', 'body_class_name='=>'mixed'], +'http\Env::getRequestHeader' => ['array|null|string', 'header_name='=>'mixed'], +'http\Env::getResponseCode' => ['int'], +'http\Env::getResponseHeader' => ['array|null|string', 'header_name='=>'mixed'], +'http\Env::getResponseStatusForAllCodes' => ['array'], +'http\Env::getResponseStatusForCode' => ['string', 'code'=>'int'], +'http\Env::negotiate' => ['null|string', 'params'=>'string', 'supported'=>'array', 'primary_type_separator='=>'mixed', '&result_array='=>'mixed'], +'http\Env::negotiateCharset' => ['null|string', 'supported'=>'array', '&result_array='=>'mixed'], +'http\Env::negotiateContentType' => ['null|string', 'supported'=>'array', '&result_array='=>'mixed'], +'http\Env::negotiateEncoding' => ['null|string', 'supported'=>'array', '&result_array='=>'mixed'], +'http\Env::negotiateLanguage' => ['null|string', 'supported'=>'array', '&result_array='=>'mixed'], +'http\Env::setResponseCode' => ['bool', 'code'=>'int'], +'http\Env::setResponseHeader' => ['bool', 'header_name'=>'string', 'header_value='=>'mixed', 'response_code='=>'mixed', 'replace_header='=>'mixed'], +'http\Env\Request::__construct' => ['void'], +'http\Env\Request::__toString' => ['string'], +'http\Env\Request::addBody' => ['http\Message', 'body'=>'http\Message\Body'], +'http\Env\Request::addHeader' => ['http\Message', 'header'=>'string', 'value'=>'mixed'], +'http\Env\Request::addHeaders' => ['http\Message', 'headers'=>'array', 'append='=>'mixed'], +'http\Env\Request::count' => ['int'], +'http\Env\Request::current' => ['mixed'], +'http\Env\Request::detach' => ['http\Message'], +'http\Env\Request::getBody' => ['http\Message\Body'], +'http\Env\Request::getCookie' => ['mixed', 'name='=>'string', 'type='=>'mixed', 'defval='=>'mixed', 'delete='=>'bool'], +'http\Env\Request::getFiles' => ['array'], +'http\Env\Request::getForm' => ['mixed', 'name='=>'string', 'type='=>'mixed', 'defval='=>'mixed', 'delete='=>'bool'], +'http\Env\Request::getHeader' => ['http\Header|mixed', 'header'=>'string', 'into_class='=>'mixed'], +'http\Env\Request::getHeaders' => ['array'], +'http\Env\Request::getHttpVersion' => ['string'], +'http\Env\Request::getInfo' => ['null|string'], +'http\Env\Request::getParentMessage' => ['http\Message'], +'http\Env\Request::getQuery' => ['mixed', 'name='=>'string', 'type='=>'mixed', 'defval='=>'mixed', 'delete='=>'bool'], +'http\Env\Request::getRequestMethod' => ['false|string'], +'http\Env\Request::getRequestUrl' => ['false|string'], +'http\Env\Request::getResponseCode' => ['false|int'], +'http\Env\Request::getResponseStatus' => ['false|string'], +'http\Env\Request::getType' => ['int'], +'http\Env\Request::isMultipart' => ['bool', '&boundary='=>'mixed'], +'http\Env\Request::key' => ['int|string'], +'http\Env\Request::next' => ['void'], +'http\Env\Request::prepend' => ['http\Message', 'message'=>'http\Message', 'top='=>'mixed'], +'http\Env\Request::reverse' => ['http\Message'], +'http\Env\Request::rewind' => ['void'], +'http\Env\Request::serialize' => ['string'], +'http\Env\Request::setBody' => ['http\Message', 'body'=>'http\Message\Body'], +'http\Env\Request::setHeader' => ['http\Message', 'header'=>'string', 'value='=>'mixed'], +'http\Env\Request::setHeaders' => ['http\Message', 'headers'=>'array'], +'http\Env\Request::setHttpVersion' => ['http\Message', 'http_version'=>'string'], +'http\Env\Request::setInfo' => ['http\Message', 'http_info'=>'string'], +'http\Env\Request::setRequestMethod' => ['http\Message', 'request_method'=>'string'], +'http\Env\Request::setRequestUrl' => ['http\Message', 'url'=>'string'], +'http\Env\Request::setResponseCode' => ['http\Message', 'response_code'=>'int', 'strict='=>'mixed'], +'http\Env\Request::setResponseStatus' => ['http\Message', 'response_status'=>'string'], +'http\Env\Request::setType' => ['http\Message', 'type'=>'int'], +'http\Env\Request::splitMultipartBody' => ['http\Message'], +'http\Env\Request::toCallback' => ['http\Message', 'callback'=>'callable'], +'http\Env\Request::toStream' => ['http\Message', 'stream'=>'resource'], +'http\Env\Request::toString' => ['string', 'include_parent='=>'mixed'], +'http\Env\Request::unserialize' => ['void', 'serialized'=>'string'], +'http\Env\Request::valid' => ['bool'], +'http\Env\Response::__construct' => ['void'], +'http\Env\Response::__invoke' => ['bool', 'data'=>'string', 'ob_flags='=>'int'], +'http\Env\Response::__toString' => ['string'], +'http\Env\Response::addBody' => ['http\Message', 'body'=>'http\Message\Body'], +'http\Env\Response::addHeader' => ['http\Message', 'header'=>'string', 'value'=>'mixed'], +'http\Env\Response::addHeaders' => ['http\Message', 'headers'=>'array', 'append='=>'mixed'], +'http\Env\Response::count' => ['int'], +'http\Env\Response::current' => ['mixed'], +'http\Env\Response::detach' => ['http\Message'], +'http\Env\Response::getBody' => ['http\Message\Body'], +'http\Env\Response::getHeader' => ['http\Header|mixed', 'header'=>'string', 'into_class='=>'mixed'], +'http\Env\Response::getHeaders' => ['array'], +'http\Env\Response::getHttpVersion' => ['string'], +'http\Env\Response::getInfo' => ['?string'], +'http\Env\Response::getParentMessage' => ['http\Message'], +'http\Env\Response::getRequestMethod' => ['false|string'], +'http\Env\Response::getRequestUrl' => ['false|string'], +'http\Env\Response::getResponseCode' => ['false|int'], +'http\Env\Response::getResponseStatus' => ['false|string'], +'http\Env\Response::getType' => ['int'], +'http\Env\Response::isCachedByETag' => ['int', 'header_name='=>'string'], +'http\Env\Response::isCachedByLastModified' => ['int', 'header_name='=>'string'], +'http\Env\Response::isMultipart' => ['bool', '&boundary='=>'mixed'], +'http\Env\Response::key' => ['int|string'], +'http\Env\Response::next' => ['void'], +'http\Env\Response::prepend' => ['http\Message', 'message'=>'http\Message', 'top='=>'mixed'], +'http\Env\Response::reverse' => ['http\Message'], +'http\Env\Response::rewind' => ['void'], +'http\Env\Response::send' => ['bool', 'stream='=>'resource'], +'http\Env\Response::serialize' => ['string'], +'http\Env\Response::setBody' => ['http\Message', 'body'=>'http\Message\Body'], +'http\Env\Response::setCacheControl' => ['http\Env\Response', 'cache_control'=>'string'], +'http\Env\Response::setContentDisposition' => ['http\Env\Response', 'disposition_params'=>'array'], +'http\Env\Response::setContentEncoding' => ['http\Env\Response', 'content_encoding'=>'int'], +'http\Env\Response::setContentType' => ['http\Env\Response', 'content_type'=>'string'], +'http\Env\Response::setCookie' => ['http\Env\Response', 'cookie'=>'mixed'], +'http\Env\Response::setEnvRequest' => ['http\Env\Response', 'env_request'=>'http\Message'], +'http\Env\Response::setEtag' => ['http\Env\Response', 'etag'=>'string'], +'http\Env\Response::setHeader' => ['http\Message', 'header'=>'string', 'value='=>'mixed'], +'http\Env\Response::setHeaders' => ['http\Message', 'headers'=>'array'], +'http\Env\Response::setHttpVersion' => ['http\Message', 'http_version'=>'string'], +'http\Env\Response::setInfo' => ['http\Message', 'http_info'=>'string'], +'http\Env\Response::setLastModified' => ['http\Env\Response', 'last_modified'=>'int'], +'http\Env\Response::setRequestMethod' => ['http\Message', 'request_method'=>'string'], +'http\Env\Response::setRequestUrl' => ['http\Message', 'url'=>'string'], +'http\Env\Response::setResponseCode' => ['http\Message', 'response_code'=>'int', 'strict='=>'mixed'], +'http\Env\Response::setResponseStatus' => ['http\Message', 'response_status'=>'string'], +'http\Env\Response::setThrottleRate' => ['http\Env\Response', 'chunk_size'=>'int', 'delay='=>'float|int'], +'http\Env\Response::setType' => ['http\Message', 'type'=>'int'], +'http\Env\Response::splitMultipartBody' => ['http\Message'], +'http\Env\Response::toCallback' => ['http\Message', 'callback'=>'callable'], +'http\Env\Response::toStream' => ['http\Message', 'stream'=>'resource'], +'http\Env\Response::toString' => ['string', 'include_parent='=>'mixed'], +'http\Env\Response::unserialize' => ['void', 'serialized'=>'string'], +'http\Env\Response::valid' => ['bool'], +'http\Header::__construct' => ['void', 'name='=>'mixed', 'value='=>'mixed'], +'http\Header::__toString' => ['string'], +'http\Header::getParams' => ['http\Params', 'param_sep='=>'mixed', 'arg_sep='=>'mixed', 'val_sep='=>'mixed', 'flags='=>'mixed'], +'http\Header::match' => ['bool', 'value'=>'string', 'flags='=>'mixed'], +'http\Header::negotiate' => ['null|string', 'supported'=>'array', '&result='=>'mixed'], +'http\Header::parse' => ['array|false', 'string'=>'string', 'header_class='=>'mixed'], +'http\Header::serialize' => ['string'], +'http\Header::toString' => ['string'], +'http\Header::unserialize' => ['void', 'serialized'=>'string'], +'http\Header\Parser::getState' => ['int'], +'http\Header\Parser::parse' => ['int', 'data'=>'string', 'flags'=>'int', '&headers'=>'array'], +'http\Header\Parser::stream' => ['int', 'stream'=>'resource', 'flags'=>'int', '&headers'=>'array'], +'http\Message::__construct' => ['void', 'message='=>'mixed', 'greedy='=>'bool'], +'http\Message::__toString' => ['string'], +'http\Message::addBody' => ['http\Message', 'body'=>'http\Message\Body'], +'http\Message::addHeader' => ['http\Message', 'header'=>'string', 'value'=>'mixed'], +'http\Message::addHeaders' => ['http\Message', 'headers'=>'array', 'append='=>'mixed'], +'http\Message::count' => ['int'], +'http\Message::current' => ['mixed'], +'http\Message::detach' => ['http\Message'], +'http\Message::getBody' => ['http\Message\Body'], +'http\Message::getHeader' => ['http\Header|mixed', 'header'=>'string', 'into_class='=>'mixed'], +'http\Message::getHeaders' => ['array'], +'http\Message::getHttpVersion' => ['string'], +'http\Message::getInfo' => ['null|string'], +'http\Message::getParentMessage' => ['http\Message'], +'http\Message::getRequestMethod' => ['false|string'], +'http\Message::getRequestUrl' => ['false|string'], +'http\Message::getResponseCode' => ['false|int'], +'http\Message::getResponseStatus' => ['false|string'], +'http\Message::getType' => ['int'], +'http\Message::isMultipart' => ['bool', '&boundary='=>'mixed'], +'http\Message::key' => ['int|string'], +'http\Message::next' => ['void'], +'http\Message::prepend' => ['http\Message', 'message'=>'http\Message', 'top='=>'mixed'], +'http\Message::reverse' => ['http\Message'], +'http\Message::rewind' => ['void'], +'http\Message::serialize' => ['string'], +'http\Message::setBody' => ['http\Message', 'body'=>'http\Message\Body'], +'http\Message::setHeader' => ['http\Message', 'header'=>'string', 'value='=>'mixed'], +'http\Message::setHeaders' => ['http\Message', 'headers'=>'array'], +'http\Message::setHttpVersion' => ['http\Message', 'http_version'=>'string'], +'http\Message::setInfo' => ['http\Message', 'http_info'=>'string'], +'http\Message::setRequestMethod' => ['http\Message', 'request_method'=>'string'], +'http\Message::setRequestUrl' => ['http\Message', 'url'=>'string'], +'http\Message::setResponseCode' => ['http\Message', 'response_code'=>'int', 'strict='=>'mixed'], +'http\Message::setResponseStatus' => ['http\Message', 'response_status'=>'string'], +'http\Message::setType' => ['http\Message', 'type'=>'int'], +'http\Message::splitMultipartBody' => ['http\Message'], +'http\Message::toCallback' => ['http\Message', 'callback'=>'callable'], +'http\Message::toStream' => ['http\Message', 'stream'=>'resource'], +'http\Message::toString' => ['string', 'include_parent='=>'mixed'], +'http\Message::unserialize' => ['void', 'serialized'=>'string'], +'http\Message::valid' => ['bool'], +'http\Message\Body::__construct' => ['void', 'stream='=>'resource'], +'http\Message\Body::__toString' => ['string'], +'http\Message\Body::addForm' => ['http\Message\Body', 'fields='=>'?array', 'files='=>'?array'], +'http\Message\Body::addPart' => ['http\Message\Body', 'message'=>'http\Message'], +'http\Message\Body::append' => ['http\Message\Body', 'string'=>'string'], +'http\Message\Body::etag' => ['false|string'], +'http\Message\Body::getBoundary' => ['null|string'], +'http\Message\Body::getResource' => ['resource'], +'http\Message\Body::serialize' => ['string'], +'http\Message\Body::stat' => ['int|object', 'field='=>'mixed'], +'http\Message\Body::toCallback' => ['http\Message\Body', 'callback'=>'callable', 'offset='=>'mixed', 'maxlen='=>'mixed'], +'http\Message\Body::toStream' => ['http\Message\Body', 'stream'=>'resource', 'offset='=>'mixed', 'maxlen='=>'mixed'], +'http\Message\Body::toString' => ['string'], +'http\Message\Body::unserialize' => ['void', 'serialized'=>'string'], +'http\Message\Parser::getState' => ['int'], +'http\Message\Parser::parse' => ['int', 'data'=>'string', 'flags'=>'int', '&message'=>'http\Message'], +'http\Message\Parser::stream' => ['int', 'stream'=>'resource', 'flags'=>'int', '&message'=>'http\Message'], +'http\Params::__construct' => ['void', 'params='=>'mixed', 'param_sep='=>'mixed', 'arg_sep='=>'mixed', 'val_sep='=>'mixed', 'flags='=>'mixed'], +'http\Params::__toString' => ['string'], +'http\Params::offsetExists' => ['bool', 'name'=>'mixed'], +'http\Params::offsetGet' => ['mixed', 'name'=>'mixed'], +'http\Params::offsetSet' => ['void', 'name'=>'mixed', 'value'=>'mixed'], +'http\Params::offsetUnset' => ['void', 'name'=>'mixed'], +'http\Params::toArray' => ['array'], +'http\Params::toString' => ['string'], +'http\QueryString::__construct' => ['void', 'querystring'=>'string'], +'http\QueryString::__toString' => ['string'], +'http\QueryString::get' => ['http\QueryString|string|mixed', 'name='=>'string', 'type='=>'mixed', 'defval='=>'mixed', 'delete='=>'bool|false'], +'http\QueryString::getArray' => ['array|mixed', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'], +'http\QueryString::getBool' => ['bool|mixed', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'], +'http\QueryString::getFloat' => ['float|mixed', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'], +'http\QueryString::getGlobalInstance' => ['http\QueryString'], +'http\QueryString::getInt' => ['int|mixed', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'], +'http\QueryString::getIterator' => ['IteratorAggregate'], +'http\QueryString::getObject' => ['object|mixed', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'], +'http\QueryString::getString' => ['string|mixed', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'], +'http\QueryString::mod' => ['http\QueryString', 'params='=>'mixed'], +'http\QueryString::offsetExists' => ['bool', 'offset'=>'mixed'], +'http\QueryString::offsetGet' => ['mixed|null', 'offset'=>'mixed'], +'http\QueryString::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'], +'http\QueryString::offsetUnset' => ['void', 'offset'=>'mixed'], +'http\QueryString::serialize' => ['string'], +'http\QueryString::set' => ['http\QueryString', 'params'=>'mixed'], +'http\QueryString::toArray' => ['array'], +'http\QueryString::toString' => ['string'], +'http\QueryString::unserialize' => ['void', 'serialized'=>'string'], +'http\QueryString::xlate' => ['http\QueryString'], +'http\Url::__construct' => ['void', 'old_url='=>'mixed', 'new_url='=>'mixed', 'flags='=>'int'], +'http\Url::__toString' => ['string'], +'http\Url::mod' => ['http\Url', 'parts'=>'mixed', 'flags='=>'float|int|mixed'], +'http\Url::toArray' => ['string[]'], +'http\Url::toString' => ['string'], +'http_build_cookie' => ['string', 'cookie'=>'array'], +'http_build_query' => ['string', 'querydata'=>'array|object', 'prefix='=>'string', 'arg_separator='=>'string', 'enc_type='=>'int'], +'http_build_str' => ['string', 'query'=>'array', 'prefix='=>'?string', 'arg_separator='=>'string'], +'http_build_url' => ['string', 'url='=>'string|array', 'parts='=>'string|array', 'flags='=>'int', 'new_url='=>'array'], +'http_cache_etag' => ['bool', 'etag='=>'string'], +'http_cache_last_modified' => ['bool', 'timestamp_or_expires='=>'int'], +'http_chunked_decode' => ['string|false', 'encoded'=>'string'], +'http_date' => ['string', 'timestamp='=>'int'], +'http_deflate' => ['?string', 'data'=>'string', 'flags='=>'int'], +'http_get' => ['string', 'url'=>'string', 'options='=>'array', 'info='=>'array'], +'http_get_request_body' => ['?string'], +'http_get_request_body_stream' => ['?resource'], +'http_get_request_headers' => ['array'], +'http_head' => ['string', 'url'=>'string', 'options='=>'array', 'info='=>'array'], +'http_inflate' => ['?string', 'data'=>'string'], +'http_match_etag' => ['bool', 'etag'=>'string', 'for_range='=>'bool'], +'http_match_modified' => ['bool', 'timestamp='=>'int', 'for_range='=>'bool'], +'http_match_request_header' => ['bool', 'header'=>'string', 'value'=>'string', 'match_case='=>'bool'], +'http_negotiate_charset' => ['string', 'supported'=>'array', 'result='=>'array'], +'http_negotiate_content_type' => ['string', 'supported'=>'array', 'result='=>'array'], +'http_negotiate_language' => ['string', 'supported'=>'array', 'result='=>'array'], +'http_parse_cookie' => ['stdClass|false', 'cookie'=>'string', 'flags='=>'int', 'allowed_extras='=>'array'], +'http_parse_headers' => ['array|false', 'header'=>'string'], +'http_parse_message' => ['object', 'message'=>'string'], +'http_parse_params' => ['stdClass', 'param'=>'string', 'flags='=>'int'], +'http_persistent_handles_clean' => ['string', 'ident='=>'string'], +'http_persistent_handles_count' => ['stdClass|false'], +'http_persistent_handles_ident' => ['string|false', 'ident='=>'string'], +'http_post_data' => ['string', 'url'=>'string', 'data'=>'string', 'options='=>'array', 'info='=>'array'], +'http_post_fields' => ['string', 'url'=>'string', 'data'=>'array', 'files='=>'array', 'options='=>'array', 'info='=>'array'], +'http_put_data' => ['string', 'url'=>'string', 'data'=>'string', 'options='=>'array', 'info='=>'array'], +'http_put_file' => ['string', 'url'=>'string', 'file'=>'string', 'options='=>'array', 'info='=>'array'], +'http_put_stream' => ['string', 'url'=>'string', 'stream'=>'resource', 'options='=>'array', 'info='=>'array'], +'http_redirect' => ['int|false', 'url='=>'string', 'params='=>'array', 'session='=>'bool', 'status='=>'int'], +'http_request' => ['string', 'method'=>'int', 'url'=>'string', 'body='=>'string', 'options='=>'array', 'info='=>'array'], +'http_request_body_encode' => ['string|false', 'fields'=>'array', 'files'=>'array'], +'http_request_method_exists' => ['bool', 'method'=>'mixed'], +'http_request_method_name' => ['string|false', 'method'=>'int'], +'http_request_method_register' => ['int|false', 'method'=>'string'], +'http_request_method_unregister' => ['bool', 'method'=>'mixed'], +'http_response_code' => ['int|bool', 'response_code='=>'int'], +'http_send_content_disposition' => ['bool', 'filename'=>'string', 'inline='=>'bool'], +'http_send_content_type' => ['bool', 'content_type='=>'string'], +'http_send_data' => ['bool', 'data'=>'string'], +'http_send_file' => ['bool', 'file'=>'string'], +'http_send_last_modified' => ['bool', 'timestamp='=>'int'], +'http_send_status' => ['bool', 'status'=>'int'], +'http_send_stream' => ['bool', 'stream'=>'resource'], +'http_support' => ['int', 'feature='=>'int'], +'http_throttle' => ['void', 'sec'=>'float', 'bytes='=>'int'], +'HttpDeflateStream::__construct' => ['void', 'flags='=>'int'], +'HttpDeflateStream::factory' => ['HttpDeflateStream', 'flags='=>'int', 'class_name='=>'string'], +'HttpDeflateStream::finish' => ['string', 'data='=>'string'], +'HttpDeflateStream::flush' => ['string|false', 'data='=>'string'], +'HttpDeflateStream::update' => ['string|false', 'data'=>'string'], +'HttpInflateStream::__construct' => ['void', 'flags='=>'int'], +'HttpInflateStream::factory' => ['HttpInflateStream', 'flags='=>'int', 'class_name='=>'string'], +'HttpInflateStream::finish' => ['string', 'data='=>'string'], +'HttpInflateStream::flush' => ['string|false', 'data='=>'string'], +'HttpInflateStream::update' => ['string|false', 'data'=>'string'], +'HttpMessage::__construct' => ['void', 'message='=>'string'], +'HttpMessage::__toString' => ['string'], +'HttpMessage::addHeaders' => ['void', 'headers'=>'array', 'append='=>'bool'], +'HttpMessage::count' => ['int'], +'HttpMessage::current' => ['mixed'], +'HttpMessage::detach' => ['HttpMessage'], +'HttpMessage::factory' => ['?HttpMessage', 'raw_message='=>'string', 'class_name='=>'string'], +'HttpMessage::fromEnv' => ['?HttpMessage', 'message_type'=>'int', 'class_name='=>'string'], +'HttpMessage::fromString' => ['?HttpMessage', 'raw_message='=>'string', 'class_name='=>'string'], +'HttpMessage::getBody' => ['string'], +'HttpMessage::getHeader' => ['?string', 'header'=>'string'], +'HttpMessage::getHeaders' => ['array'], +'HttpMessage::getHttpVersion' => ['string'], +'HttpMessage::getInfo' => [''], +'HttpMessage::getParentMessage' => ['HttpMessage'], +'HttpMessage::getRequestMethod' => ['string|false'], +'HttpMessage::getRequestUrl' => ['string|false'], +'HttpMessage::getResponseCode' => ['int'], +'HttpMessage::getResponseStatus' => ['string'], +'HttpMessage::getType' => ['int'], +'HttpMessage::guessContentType' => ['string|false', 'magic_file'=>'string', 'magic_mode='=>'int'], +'HttpMessage::key' => ['int|string'], +'HttpMessage::next' => ['void'], +'HttpMessage::prepend' => ['void', 'message'=>'HttpMessage', 'top='=>'bool'], +'HttpMessage::reverse' => ['HttpMessage'], +'HttpMessage::rewind' => ['void'], +'HttpMessage::send' => ['bool'], +'HttpMessage::serialize' => ['string'], +'HttpMessage::setBody' => ['void', 'body'=>'string'], +'HttpMessage::setHeaders' => ['void', 'headers'=>'array'], +'HttpMessage::setHttpVersion' => ['bool', 'version'=>'string'], +'HttpMessage::setInfo' => ['', 'http_info'=>''], +'HttpMessage::setRequestMethod' => ['bool', 'method'=>'string'], +'HttpMessage::setRequestUrl' => ['bool', 'url'=>'string'], +'HttpMessage::setResponseCode' => ['bool', 'code'=>'int'], +'HttpMessage::setResponseStatus' => ['bool', 'status'=>'string'], +'HttpMessage::setType' => ['void', 'type'=>'int'], +'HttpMessage::toMessageTypeObject' => ['HttpRequest|HttpResponse|null'], +'HttpMessage::toString' => ['string', 'include_parent='=>'bool'], +'HttpMessage::unserialize' => ['void', 'serialized'=>'string'], +'HttpMessage::valid' => ['bool'], +'HttpQueryString::__construct' => ['void', 'global='=>'bool', 'add='=>'mixed'], +'HttpQueryString::__toString' => ['string'], +'HttpQueryString::factory' => ['', 'global'=>'', 'params'=>'', 'class_name'=>''], +'HttpQueryString::get' => ['mixed', 'key='=>'string', 'type='=>'mixed', 'defval='=>'mixed', 'delete='=>'bool'], +'HttpQueryString::getArray' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''], +'HttpQueryString::getBool' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''], +'HttpQueryString::getFloat' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''], +'HttpQueryString::getInt' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''], +'HttpQueryString::getObject' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''], +'HttpQueryString::getString' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''], +'HttpQueryString::mod' => ['HttpQueryString', 'params'=>'mixed'], +'HttpQueryString::offsetExists' => ['bool', 'offset'=>'mixed'], +'HttpQueryString::offsetGet' => ['mixed', 'offset'=>'mixed'], +'HttpQueryString::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'], +'HttpQueryString::offsetUnset' => ['void', 'offset'=>'mixed'], +'HttpQueryString::serialize' => ['string'], +'HttpQueryString::set' => ['string', 'params'=>'mixed'], +'HttpQueryString::singleton' => ['HttpQueryString', 'global='=>'bool'], +'HttpQueryString::toArray' => ['array'], +'HttpQueryString::toString' => ['string'], +'HttpQueryString::unserialize' => ['void', 'serialized'=>'string'], +'HttpQueryString::xlate' => ['bool', 'ie'=>'string', 'oe'=>'string'], +'HttpRequest::__construct' => ['void', 'url='=>'string', 'request_method='=>'int', 'options='=>'array'], +'HttpRequest::addBody' => ['', 'request_body_data'=>''], +'HttpRequest::addCookies' => ['bool', 'cookies'=>'array'], +'HttpRequest::addHeaders' => ['bool', 'headers'=>'array'], +'HttpRequest::addPostFields' => ['bool', 'post_data'=>'array'], +'HttpRequest::addPostFile' => ['bool', 'name'=>'string', 'file'=>'string', 'content_type='=>'string'], +'HttpRequest::addPutData' => ['bool', 'put_data'=>'string'], +'HttpRequest::addQueryData' => ['bool', 'query_params'=>'array'], +'HttpRequest::addRawPostData' => ['bool', 'raw_post_data'=>'string'], +'HttpRequest::addSslOptions' => ['bool', 'options'=>'array'], +'HttpRequest::clearHistory' => ['void'], +'HttpRequest::enableCookies' => ['bool'], +'HttpRequest::encodeBody' => ['', 'fields'=>'', 'files'=>''], +'HttpRequest::factory' => ['', 'url'=>'', 'method'=>'', 'options'=>'', 'class_name'=>''], +'HttpRequest::flushCookies' => [''], +'HttpRequest::get' => ['', 'url'=>'', 'options'=>'', '&info'=>''], +'HttpRequest::getBody' => [''], +'HttpRequest::getContentType' => ['string'], +'HttpRequest::getCookies' => ['array'], +'HttpRequest::getHeaders' => ['array'], +'HttpRequest::getHistory' => ['HttpMessage'], +'HttpRequest::getMethod' => ['int'], +'HttpRequest::getOptions' => ['array'], +'HttpRequest::getPostFields' => ['array'], +'HttpRequest::getPostFiles' => ['array'], +'HttpRequest::getPutData' => ['string'], +'HttpRequest::getPutFile' => ['string'], +'HttpRequest::getQueryData' => ['string'], +'HttpRequest::getRawPostData' => ['string'], +'HttpRequest::getRawRequestMessage' => ['string'], +'HttpRequest::getRawResponseMessage' => ['string'], +'HttpRequest::getRequestMessage' => ['HttpMessage'], +'HttpRequest::getResponseBody' => ['string'], +'HttpRequest::getResponseCode' => ['int'], +'HttpRequest::getResponseCookies' => ['stdClass[]', 'flags='=>'int', 'allowed_extras='=>'array'], +'HttpRequest::getResponseData' => ['array'], +'HttpRequest::getResponseHeader' => ['mixed', 'name='=>'string'], +'HttpRequest::getResponseInfo' => ['mixed', 'name='=>'string'], +'HttpRequest::getResponseMessage' => ['HttpMessage'], +'HttpRequest::getResponseStatus' => ['string'], +'HttpRequest::getSslOptions' => ['array'], +'HttpRequest::getUrl' => ['string'], +'HttpRequest::head' => ['', 'url'=>'', 'options'=>'', '&info'=>''], +'HttpRequest::methodExists' => ['', 'method'=>''], +'HttpRequest::methodName' => ['', 'method_id'=>''], +'HttpRequest::methodRegister' => ['', 'method_name'=>''], +'HttpRequest::methodUnregister' => ['', 'method'=>''], +'HttpRequest::postData' => ['', 'url'=>'', 'data'=>'', 'options'=>'', '&info'=>''], +'HttpRequest::postFields' => ['', 'url'=>'', 'data'=>'', 'options'=>'', '&info'=>''], +'HttpRequest::putData' => ['', 'url'=>'', 'data'=>'', 'options'=>'', '&info'=>''], +'HttpRequest::putFile' => ['', 'url'=>'', 'file'=>'', 'options'=>'', '&info'=>''], +'HttpRequest::putStream' => ['', 'url'=>'', 'stream'=>'', 'options'=>'', '&info'=>''], +'HttpRequest::resetCookies' => ['bool', 'session_only='=>'bool'], +'HttpRequest::send' => ['HttpMessage'], +'HttpRequest::setBody' => ['bool', 'request_body_data='=>'string'], +'HttpRequest::setContentType' => ['bool', 'content_type'=>'string'], +'HttpRequest::setCookies' => ['bool', 'cookies='=>'array'], +'HttpRequest::setHeaders' => ['bool', 'headers='=>'array'], +'HttpRequest::setMethod' => ['bool', 'request_method'=>'int'], +'HttpRequest::setOptions' => ['bool', 'options='=>'array'], +'HttpRequest::setPostFields' => ['bool', 'post_data'=>'array'], +'HttpRequest::setPostFiles' => ['bool', 'post_files'=>'array'], +'HttpRequest::setPutData' => ['bool', 'put_data='=>'string'], +'HttpRequest::setPutFile' => ['bool', 'file='=>'string'], +'HttpRequest::setQueryData' => ['bool', 'query_data'=>'mixed'], +'HttpRequest::setRawPostData' => ['bool', 'raw_post_data='=>'string'], +'HttpRequest::setSslOptions' => ['bool', 'options='=>'array'], +'HttpRequest::setUrl' => ['bool', 'url'=>'string'], +'HttpRequestDataShare::__construct' => ['void'], +'HttpRequestDataShare::__destruct' => ['void'], +'HttpRequestDataShare::attach' => ['', 'request'=>'HttpRequest'], +'HttpRequestDataShare::count' => ['int'], +'HttpRequestDataShare::detach' => ['', 'request'=>'HttpRequest'], +'HttpRequestDataShare::factory' => ['', 'global'=>'', 'class_name'=>''], +'HttpRequestDataShare::reset' => [''], +'HttpRequestDataShare::singleton' => ['', 'global'=>''], +'HttpRequestPool::__construct' => ['void', 'request='=>'HttpRequest'], +'HttpRequestPool::__destruct' => ['void'], +'HttpRequestPool::attach' => ['bool', 'request'=>'HttpRequest'], +'HttpRequestPool::count' => ['int'], +'HttpRequestPool::current' => ['mixed'], +'HttpRequestPool::detach' => ['bool', 'request'=>'HttpRequest'], +'HttpRequestPool::enableEvents' => ['', 'enable'=>''], +'HttpRequestPool::enablePipelining' => ['', 'enable'=>''], +'HttpRequestPool::getAttachedRequests' => ['array'], +'HttpRequestPool::getFinishedRequests' => ['array'], +'HttpRequestPool::key' => ['int|string'], +'HttpRequestPool::next' => ['void'], +'HttpRequestPool::reset' => ['void'], +'HttpRequestPool::rewind' => ['void'], +'HttpRequestPool::send' => ['bool'], +'HttpRequestPool::socketPerform' => ['bool'], +'HttpRequestPool::socketSelect' => ['bool', 'timeout='=>'float'], +'HttpRequestPool::valid' => ['bool'], +'HttpResponse::capture' => ['void'], +'HttpResponse::getBufferSize' => ['int'], +'HttpResponse::getCache' => ['bool'], +'HttpResponse::getCacheControl' => ['string'], +'HttpResponse::getContentDisposition' => ['string'], +'HttpResponse::getContentType' => ['string'], +'HttpResponse::getData' => ['string'], +'HttpResponse::getETag' => ['string'], +'HttpResponse::getFile' => ['string'], +'HttpResponse::getGzip' => ['bool'], +'HttpResponse::getHeader' => ['mixed', 'name='=>'string'], +'HttpResponse::getLastModified' => ['int'], +'HttpResponse::getRequestBody' => ['string'], +'HttpResponse::getRequestBodyStream' => ['resource'], +'HttpResponse::getRequestHeaders' => ['array'], +'HttpResponse::getStream' => ['resource'], +'HttpResponse::getThrottleDelay' => ['float'], +'HttpResponse::guessContentType' => ['string|false', 'magic_file'=>'string', 'magic_mode='=>'int'], +'HttpResponse::redirect' => ['void', 'url='=>'string', 'params='=>'array', 'session='=>'bool', 'status='=>'int'], +'HttpResponse::send' => ['bool', 'clean_ob='=>'bool'], +'HttpResponse::setBufferSize' => ['bool', 'bytes'=>'int'], +'HttpResponse::setCache' => ['bool', 'cache'=>'bool'], +'HttpResponse::setCacheControl' => ['bool', 'control'=>'string', 'max_age='=>'int', 'must_revalidate='=>'bool'], +'HttpResponse::setContentDisposition' => ['bool', 'filename'=>'string', 'inline='=>'bool'], +'HttpResponse::setContentType' => ['bool', 'content_type'=>'string'], +'HttpResponse::setData' => ['bool', 'data'=>'mixed'], +'HttpResponse::setETag' => ['bool', 'etag'=>'string'], +'HttpResponse::setFile' => ['bool', 'file'=>'string'], +'HttpResponse::setGzip' => ['bool', 'gzip'=>'bool'], +'HttpResponse::setHeader' => ['bool', 'name'=>'string', 'value='=>'mixed', 'replace='=>'bool'], +'HttpResponse::setLastModified' => ['bool', 'timestamp'=>'int'], +'HttpResponse::setStream' => ['bool', 'stream'=>'resource'], +'HttpResponse::setThrottleDelay' => ['bool', 'seconds'=>'float'], +'HttpResponse::status' => ['bool', 'status'=>'int'], +'HttpUtil::buildCookie' => ['', 'cookie_array'=>''], +'HttpUtil::buildStr' => ['', 'query'=>'', 'prefix'=>'', 'arg_sep'=>''], +'HttpUtil::buildUrl' => ['', 'url'=>'', 'parts'=>'', 'flags'=>'', '&composed'=>''], +'HttpUtil::chunkedDecode' => ['', 'encoded_string'=>''], +'HttpUtil::date' => ['', 'timestamp'=>''], +'HttpUtil::deflate' => ['', 'plain'=>'', 'flags'=>''], +'HttpUtil::inflate' => ['', 'encoded'=>''], +'HttpUtil::matchEtag' => ['', 'plain_etag'=>'', 'for_range'=>''], +'HttpUtil::matchModified' => ['', 'last_modified'=>'', 'for_range'=>''], +'HttpUtil::matchRequestHeader' => ['', 'header_name'=>'', 'header_value'=>'', 'case_sensitive'=>''], +'HttpUtil::negotiateCharset' => ['', 'supported'=>'', '&result'=>''], +'HttpUtil::negotiateContentType' => ['', 'supported'=>'', '&result'=>''], +'HttpUtil::negotiateLanguage' => ['', 'supported'=>'', '&result'=>''], +'HttpUtil::parseCookie' => ['', 'cookie_string'=>''], +'HttpUtil::parseHeaders' => ['', 'headers_string'=>''], +'HttpUtil::parseMessage' => ['', 'message_string'=>''], +'HttpUtil::parseParams' => ['', 'param_string'=>'', 'flags'=>''], +'HttpUtil::support' => ['', 'feature'=>''], +'hw_api::checkin' => ['bool', 'parameter'=>'array'], +'hw_api::checkout' => ['bool', 'parameter'=>'array'], +'hw_api::children' => ['array', 'parameter'=>'array'], +'hw_api::content' => ['HW_API_Content', 'parameter'=>'array'], +'hw_api::copy' => ['hw_api_content', 'parameter'=>'array'], +'hw_api::dbstat' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::dcstat' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::dstanchors' => ['array', 'parameter'=>'array'], +'hw_api::dstofsrcanchor' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::find' => ['array', 'parameter'=>'array'], +'hw_api::ftstat' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::hwstat' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::identify' => ['bool', 'parameter'=>'array'], +'hw_api::info' => ['array', 'parameter'=>'array'], +'hw_api::insert' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::insertanchor' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::insertcollection' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::insertdocument' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::link' => ['bool', 'parameter'=>'array'], +'hw_api::lock' => ['bool', 'parameter'=>'array'], +'hw_api::move' => ['bool', 'parameter'=>'array'], +'hw_api::object' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::objectbyanchor' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::parents' => ['array', 'parameter'=>'array'], +'hw_api::remove' => ['bool', 'parameter'=>'array'], +'hw_api::replace' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::setcommittedversion' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::srcanchors' => ['array', 'parameter'=>'array'], +'hw_api::srcsofdst' => ['array', 'parameter'=>'array'], +'hw_api::unlock' => ['bool', 'parameter'=>'array'], +'hw_api::user' => ['hw_api_object', 'parameter'=>'array'], +'hw_api::userlist' => ['array', 'parameter'=>'array'], +'hw_api_attribute' => ['HW_API_Attribute', 'name='=>'string', 'value='=>'string'], +'hw_api_attribute::key' => ['string'], +'hw_api_attribute::langdepvalue' => ['string', 'language'=>'string'], +'hw_api_attribute::value' => ['string'], +'hw_api_attribute::values' => ['array'], +'hw_api_content' => ['HW_API_Content', 'content'=>'string', 'mimetype'=>'string'], +'hw_api_content::mimetype' => ['string'], +'hw_api_content::read' => ['string', 'buffer'=>'string', 'length'=>'int'], +'hw_api_error::count' => ['int'], +'hw_api_error::reason' => ['HW_API_Reason'], +'hw_api_object' => ['hw_api_object', 'parameter'=>'array'], +'hw_api_object::assign' => ['bool', 'parameter'=>'array'], +'hw_api_object::attreditable' => ['bool', 'parameter'=>'array'], +'hw_api_object::count' => ['int', 'parameter'=>'array'], +'hw_api_object::insert' => ['bool', 'attribute'=>'hw_api_attribute'], +'hw_api_object::remove' => ['bool', 'name'=>'string'], +'hw_api_object::title' => ['string', 'parameter'=>'array'], +'hw_api_object::value' => ['string', 'name'=>'string'], +'hw_api_reason::description' => ['string'], +'hw_api_reason::type' => ['HW_API_Reason'], +'hw_Array2Objrec' => ['string', 'object_array'=>'array'], +'hw_changeobject' => ['bool', 'link'=>'int', 'objid'=>'int', 'attributes'=>'array'], +'hw_Children' => ['array', 'connection'=>'int', 'objectid'=>'int'], +'hw_ChildrenObj' => ['array', 'connection'=>'int', 'objectid'=>'int'], +'hw_Close' => ['bool', 'connection'=>'int'], +'hw_Connect' => ['int', 'host'=>'string', 'port'=>'int', 'username='=>'string', 'password='=>'string'], +'hw_connection_info' => ['', 'link'=>'int'], +'hw_cp' => ['int', 'connection'=>'int', 'object_id_array'=>'array', 'destination_id'=>'int'], +'hw_Deleteobject' => ['bool', 'connection'=>'int', 'object_to_delete'=>'int'], +'hw_DocByAnchor' => ['int', 'connection'=>'int', 'anchorid'=>'int'], +'hw_DocByAnchorObj' => ['string', 'connection'=>'int', 'anchorid'=>'int'], +'hw_Document_Attributes' => ['string', 'hw_document'=>'int'], +'hw_Document_BodyTag' => ['string', 'hw_document'=>'int', 'prefix='=>'string'], +'hw_Document_Content' => ['string', 'hw_document'=>'int'], +'hw_Document_SetContent' => ['bool', 'hw_document'=>'int', 'content'=>'string'], +'hw_Document_Size' => ['int', 'hw_document'=>'int'], +'hw_dummy' => ['string', 'link'=>'int', 'id'=>'int', 'msgid'=>'int'], +'hw_EditText' => ['bool', 'connection'=>'int', 'hw_document'=>'int'], +'hw_Error' => ['int', 'connection'=>'int'], +'hw_ErrorMsg' => ['string', 'connection'=>'int'], +'hw_Free_Document' => ['bool', 'hw_document'=>'int'], +'hw_GetAnchors' => ['array', 'connection'=>'int', 'objectid'=>'int'], +'hw_GetAnchorsObj' => ['array', 'connection'=>'int', 'objectid'=>'int'], +'hw_GetAndLock' => ['string', 'connection'=>'int', 'objectid'=>'int'], +'hw_GetChildColl' => ['array', 'connection'=>'int', 'objectid'=>'int'], +'hw_GetChildCollObj' => ['array', 'connection'=>'int', 'objectid'=>'int'], +'hw_GetChildDocColl' => ['array', 'connection'=>'int', 'objectid'=>'int'], +'hw_GetChildDocCollObj' => ['array', 'connection'=>'int', 'objectid'=>'int'], +'hw_GetObject' => ['', 'connection'=>'int', 'objectid'=>'', 'query='=>'string'], +'hw_GetObjectByQuery' => ['array', 'connection'=>'int', 'query'=>'string', 'max_hits'=>'int'], +'hw_GetObjectByQueryColl' => ['array', 'connection'=>'int', 'objectid'=>'int', 'query'=>'string', 'max_hits'=>'int'], +'hw_GetObjectByQueryCollObj' => ['array', 'connection'=>'int', 'objectid'=>'int', 'query'=>'string', 'max_hits'=>'int'], +'hw_GetObjectByQueryObj' => ['array', 'connection'=>'int', 'query'=>'string', 'max_hits'=>'int'], +'hw_GetParents' => ['array', 'connection'=>'int', 'objectid'=>'int'], +'hw_GetParentsObj' => ['array', 'connection'=>'int', 'objectid'=>'int'], +'hw_getrellink' => ['string', 'link'=>'int', 'rootid'=>'int', 'sourceid'=>'int', 'destid'=>'int'], +'hw_GetRemote' => ['int', 'connection'=>'int', 'objectid'=>'int'], +'hw_getremotechildren' => ['', 'connection'=>'int', 'object_record'=>'string'], +'hw_GetSrcByDestObj' => ['array', 'connection'=>'int', 'objectid'=>'int'], +'hw_GetText' => ['int', 'connection'=>'int', 'objectid'=>'int', 'prefix='=>''], +'hw_getusername' => ['string', 'connection'=>'int'], +'hw_Identify' => ['string', 'link'=>'int', 'username'=>'string', 'password'=>'string'], +'hw_InCollections' => ['array', 'connection'=>'int', 'object_id_array'=>'array', 'collection_id_array'=>'array', 'return_collections'=>'int'], +'hw_Info' => ['string', 'connection'=>'int'], +'hw_InsColl' => ['int', 'connection'=>'int', 'objectid'=>'int', 'object_array'=>'array'], +'hw_InsDoc' => ['int', 'connection'=>'', 'parentid'=>'int', 'object_record'=>'string', 'text='=>'string'], +'hw_insertanchors' => ['bool', 'hwdoc'=>'int', 'anchorecs'=>'array', 'dest'=>'array', 'urlprefixes='=>'array'], +'hw_InsertDocument' => ['int', 'connection'=>'int', 'parent_id'=>'int', 'hw_document'=>'int'], +'hw_InsertObject' => ['int', 'connection'=>'int', 'object_rec'=>'string', 'parameter'=>'string'], +'hw_mapid' => ['int', 'connection'=>'int', 'server_id'=>'int', 'object_id'=>'int'], +'hw_Modifyobject' => ['bool', 'connection'=>'int', 'object_to_change'=>'int', 'remove'=>'array', 'add'=>'array', 'mode='=>'int'], +'hw_mv' => ['int', 'connection'=>'int', 'object_id_array'=>'array', 'source_id'=>'int', 'destination_id'=>'int'], +'hw_New_Document' => ['int', 'object_record'=>'string', 'document_data'=>'string', 'document_size'=>'int'], +'hw_objrec2array' => ['array', 'object_record'=>'string', 'format='=>'array'], +'hw_Output_Document' => ['bool', 'hw_document'=>'int'], +'hw_pConnect' => ['int', 'host'=>'string', 'port'=>'int', 'username='=>'string', 'password='=>'string'], +'hw_PipeDocument' => ['int', 'connection'=>'int', 'objectid'=>'int', 'url_prefixes='=>'array'], +'hw_Root' => ['int'], +'hw_setlinkroot' => ['int', 'link'=>'int', 'rootid'=>'int'], +'hw_stat' => ['string', 'link'=>'int'], +'hw_Unlock' => ['bool', 'connection'=>'int', 'objectid'=>'int'], +'hw_Who' => ['array', 'connection'=>'int'], +'hwapi_attribute_new' => ['HW_API_Attribute', 'name='=>'string', 'value='=>'string'], +'hwapi_content_new' => ['HW_API_Content', 'content'=>'string', 'mimetype'=>'string'], +'hwapi_hgcsp' => ['HW_API', 'hostname'=>'string', 'port='=>'int'], +'hwapi_object_new' => ['hw_api_object', 'parameter'=>'array'], +'hypot' => ['float', 'num1'=>'float', 'num2'=>'float'], +'ibase_add_user' => ['bool', 'service_handle'=>'resource', 'user_name'=>'string', 'password'=>'string', 'first_name='=>'string', 'middle_name='=>'string', 'last_name='=>'string'], +'ibase_affected_rows' => ['int', 'link_identifier='=>'resource'], +'ibase_backup' => ['mixed', 'service_handle'=>'resource', 'source_db'=>'string', 'dest_file'=>'string', 'options='=>'int', 'verbose='=>'bool'], +'ibase_blob_add' => ['void', 'blob_handle'=>'resource', 'data'=>'string'], +'ibase_blob_cancel' => ['bool', 'blob_handle'=>'resource'], +'ibase_blob_close' => ['string|bool', 'blob_handle'=>'resource'], +'ibase_blob_create' => ['resource', 'link_identifier='=>'resource'], +'ibase_blob_echo' => ['bool', 'link_identifier'=>'', 'blob_id'=>'string'], +'ibase_blob_echo\'1' => ['bool', 'blob_id'=>'string'], +'ibase_blob_get' => ['string|false', 'blob_handle'=>'resource', 'length'=>'int'], +'ibase_blob_import' => ['string|false', 'link_identifier'=>'resource', 'file_handle'=>'resource'], +'ibase_blob_info' => ['array', 'link_identifier'=>'resource', 'blob_id'=>'string'], +'ibase_blob_info\'1' => ['array', 'blob_id'=>'string'], +'ibase_blob_open' => ['resource|false', 'link_identifier'=>'', 'blob_id'=>'string'], +'ibase_blob_open\'1' => ['resource', 'blob_id'=>'string'], +'ibase_close' => ['bool', 'link_identifier='=>'resource'], +'ibase_commit' => ['bool', 'link_identifier='=>'resource'], +'ibase_commit_ret' => ['bool', 'link_identifier='=>'resource'], +'ibase_connect' => ['resource|false', 'database='=>'string', 'username='=>'string', 'password='=>'string', 'charset='=>'string', 'buffers='=>'int', 'dialect='=>'int', 'role='=>'string'], +'ibase_db_info' => ['string', 'service_handle'=>'resource', 'db'=>'string', 'action'=>'int', 'argument='=>'int'], +'ibase_delete_user' => ['bool', 'service_handle'=>'resource', 'user_name'=>'string', 'password='=>'string', 'first_name='=>'string', 'middle_name='=>'string', 'last_name='=>'string'], +'ibase_drop_db' => ['bool', 'link_identifier='=>'resource'], +'ibase_errcode' => ['int|false'], +'ibase_errmsg' => ['string|false'], +'ibase_execute' => ['resource|false', 'query'=>'resource', 'bind_arg='=>'mixed', '...args='=>'mixed'], +'ibase_fetch_assoc' => ['array|false', 'result'=>'resource', 'fetch_flags='=>'int'], +'ibase_fetch_object' => ['object|false', 'result'=>'resource', 'fetch_flags='=>'int'], +'ibase_fetch_row' => ['array|false', 'result'=>'resource', 'fetch_flags='=>'int'], +'ibase_field_info' => ['array', 'query_result'=>'resource', 'field_number'=>'int'], +'ibase_free_event_handler' => ['bool', 'event'=>'resource'], +'ibase_free_query' => ['bool', 'query'=>'resource'], +'ibase_free_result' => ['bool', 'result'=>'resource'], +'ibase_gen_id' => ['int|string', 'generator'=>'string', 'increment='=>'int', 'link_identifier='=>'resource'], +'ibase_maintain_db' => ['bool', 'service_handle'=>'resource', 'db'=>'string', 'action'=>'int', 'argument='=>'int'], +'ibase_modify_user' => ['bool', 'service_handle'=>'resource', 'user_name'=>'string', 'password'=>'string', 'first_name='=>'string', 'middle_name='=>'string', 'last_name='=>'string'], +'ibase_name_result' => ['bool', 'result'=>'resource', 'name'=>'string'], +'ibase_num_fields' => ['int', 'query_result'=>'resource'], +'ibase_num_params' => ['int', 'query'=>'resource'], +'ibase_num_rows' => ['int', 'result_identifier'=>''], +'ibase_param_info' => ['array', 'query'=>'resource', 'field_number'=>'int'], +'ibase_pconnect' => ['resource|false', 'database='=>'string', 'username='=>'string', 'password='=>'string', 'charset='=>'string', 'buffers='=>'int', 'dialect='=>'int', 'role='=>'string'], +'ibase_prepare' => ['resource|false', 'link_identifier'=>'', 'query'=>'string', 'trans_identifier'=>''], +'ibase_query' => ['resource|false', 'link_identifier='=>'resource', 'string='=>'string', 'bind_arg='=>'int', '...args='=>''], +'ibase_restore' => ['mixed', 'service_handle'=>'resource', 'source_file'=>'string', 'dest_db'=>'string', 'options='=>'int', 'verbose='=>'bool'], +'ibase_rollback' => ['bool', 'link_identifier='=>'resource'], +'ibase_rollback_ret' => ['bool', 'link_identifier='=>'resource'], +'ibase_server_info' => ['string', 'service_handle'=>'resource', 'action'=>'int'], +'ibase_service_attach' => ['resource', 'host'=>'string', 'dba_username'=>'string', 'dba_password'=>'string'], +'ibase_service_detach' => ['bool', 'service_handle'=>'resource'], +'ibase_set_event_handler' => ['resource', 'link_identifier'=>'', 'callback'=>'callable', 'event='=>'string', '...args='=>''], +'ibase_set_event_handler\'1' => ['resource', 'callback'=>'callable', 'event'=>'string', '...args'=>''], +'ibase_timefmt' => ['bool', 'format'=>'string', 'columntype='=>'int'], +'ibase_trans' => ['resource|false', 'trans_args='=>'int', 'link_identifier='=>'', '...args='=>''], +'ibase_wait_event' => ['string', 'link_identifier'=>'', 'event='=>'string', '...args='=>''], +'ibase_wait_event\'1' => ['string', 'event'=>'string', '...args'=>''], +'iconv' => ['string|false', 'in_charset'=>'string', 'out_charset'=>'string', 'string'=>'string'], +'iconv_get_encoding' => ['mixed', 'type='=>'string'], +'iconv_mime_decode' => ['string|false', 'encoded_string'=>'string', 'mode='=>'int', 'charset='=>'string'], +'iconv_mime_decode_headers' => ['array|false', 'headers'=>'string', 'mode='=>'int', 'charset='=>'string'], +'iconv_mime_encode' => ['string|false', 'field_name'=>'string', 'field_value'=>'string', 'preference='=>'array'], +'iconv_set_encoding' => ['bool', 'type'=>'string', 'charset'=>'string'], +'iconv_strlen' => ['int|false', 'string'=>'string', 'charset='=>'string'], +'iconv_strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'charset='=>'string'], +'iconv_strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'charset='=>'string'], +'iconv_substr' => ['string|false', 'string'=>'string', 'offset'=>'int', 'length='=>'int', 'charset='=>'string'], +'id3_get_frame_long_name' => ['string', 'frameid'=>'string'], +'id3_get_frame_short_name' => ['string', 'frameid'=>'string'], +'id3_get_genre_id' => ['int', 'genre'=>'string'], +'id3_get_genre_list' => ['array'], +'id3_get_genre_name' => ['string', 'genre_id'=>'int'], +'id3_get_tag' => ['array', 'filename'=>'string', 'version='=>'int'], +'id3_get_version' => ['int', 'filename'=>'string'], +'id3_remove_tag' => ['bool', 'filename'=>'string', 'version='=>'int'], +'id3_set_tag' => ['bool', 'filename'=>'string', 'tag'=>'array', 'version='=>'int'], +'idate' => ['int', 'format'=>'string', 'timestamp='=>'int'], +'idn_strerror' => ['string', 'errorcode'=>'int'], +'idn_to_ascii' => ['string|false', 'domain'=>'string', 'options='=>'int', 'variant='=>'int', '&w_idna_info='=>'array'], +'idn_to_utf8' => ['string|false', 'domain'=>'string', 'options='=>'int', 'variant='=>'int', '&w_idna_info='=>'array'], +'ifx_affected_rows' => ['int', 'result_id'=>'resource'], +'ifx_blobinfile_mode' => ['bool', 'mode'=>'int'], +'ifx_byteasvarchar' => ['bool', 'mode'=>'int'], +'ifx_close' => ['bool', 'link_identifier='=>'resource'], +'ifx_connect' => ['resource', 'database='=>'string', 'userid='=>'string', 'password='=>'string'], +'ifx_copy_blob' => ['int', 'bid'=>'int'], +'ifx_create_blob' => ['int', 'type'=>'int', 'mode'=>'int', 'param'=>'string'], +'ifx_create_char' => ['int', 'param'=>'string'], +'ifx_do' => ['bool', 'result_id'=>'resource'], +'ifx_error' => ['string', 'link_identifier='=>'resource'], +'ifx_errormsg' => ['string', 'errorcode='=>'int'], +'ifx_fetch_row' => ['array', 'result_id'=>'resource', 'position='=>'mixed'], +'ifx_fieldproperties' => ['array', 'result_id'=>'resource'], +'ifx_fieldtypes' => ['array', 'result_id'=>'resource'], +'ifx_free_blob' => ['bool', 'bid'=>'int'], +'ifx_free_char' => ['bool', 'bid'=>'int'], +'ifx_free_result' => ['bool', 'result_id'=>'resource'], +'ifx_get_blob' => ['string', 'bid'=>'int'], +'ifx_get_char' => ['string', 'bid'=>'int'], +'ifx_getsqlca' => ['array', 'result_id'=>'resource'], +'ifx_htmltbl_result' => ['int', 'result_id'=>'resource', 'html_table_options='=>'string'], +'ifx_nullformat' => ['bool', 'mode'=>'int'], +'ifx_num_fields' => ['int', 'result_id'=>'resource'], +'ifx_num_rows' => ['int', 'result_id'=>'resource'], +'ifx_pconnect' => ['resource', 'database='=>'string', 'userid='=>'string', 'password='=>'string'], +'ifx_prepare' => ['resource', 'query'=>'string', 'link_identifier'=>'resource', 'cursor_def='=>'int', 'blobidarray='=>'mixed'], +'ifx_query' => ['resource', 'query'=>'string', 'link_identifier'=>'resource', 'cursor_type='=>'int', 'blobidarray='=>'mixed'], +'ifx_textasvarchar' => ['bool', 'mode'=>'int'], +'ifx_update_blob' => ['bool', 'bid'=>'int', 'content'=>'string'], +'ifx_update_char' => ['bool', 'bid'=>'int', 'content'=>'string'], +'ifxus_close_slob' => ['bool', 'bid'=>'int'], +'ifxus_create_slob' => ['int', 'mode'=>'int'], +'ifxus_free_slob' => ['bool', 'bid'=>'int'], +'ifxus_open_slob' => ['int', 'bid'=>'int', 'mode'=>'int'], +'ifxus_read_slob' => ['string', 'bid'=>'int', 'nbytes'=>'int'], +'ifxus_seek_slob' => ['int', 'bid'=>'int', 'mode'=>'int', 'offset'=>'int'], +'ifxus_tell_slob' => ['int', 'bid'=>'int'], +'ifxus_write_slob' => ['int', 'bid'=>'int', 'content'=>'string'], +'igbinary_serialize' => ['string|false', 'value'=>'mixed'], +'igbinary_unserialize' => ['mixed', 'string'=>'string'], +'ignore_user_abort' => ['int', 'value='=>'bool'], +'iis_add_server' => ['int', 'path'=>'string', 'comment'=>'string', 'server_ip'=>'string', 'port'=>'int', 'host_name'=>'string', 'rights'=>'int', 'start_server'=>'int'], +'iis_get_dir_security' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string'], +'iis_get_script_map' => ['string', 'server_instance'=>'int', 'virtual_path'=>'string', 'script_extension'=>'string'], +'iis_get_server_by_comment' => ['int', 'comment'=>'string'], +'iis_get_server_by_path' => ['int', 'path'=>'string'], +'iis_get_server_rights' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string'], +'iis_get_service_state' => ['int', 'service_id'=>'string'], +'iis_remove_server' => ['int', 'server_instance'=>'int'], +'iis_set_app_settings' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string', 'application_scope'=>'string'], +'iis_set_dir_security' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string', 'directory_flags'=>'int'], +'iis_set_script_map' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string', 'script_extension'=>'string', 'engine_path'=>'string', 'allow_scripting'=>'int'], +'iis_set_server_rights' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string', 'directory_flags'=>'int'], +'iis_start_server' => ['int', 'server_instance'=>'int'], +'iis_start_service' => ['int', 'service_id'=>'string'], +'iis_stop_server' => ['int', 'server_instance'=>'int'], +'iis_stop_service' => ['int', 'service_id'=>'string'], +'image2wbmp' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'threshold='=>'int'], +'image_type_to_extension' => ['string', 'imagetype'=>'int', 'include_dot='=>'bool'], +'image_type_to_mime_type' => ['string', 'imagetype'=>'int'], +'imageaffine' => ['resource|false', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'], +'imageaffineconcat' => ['array|false', 'm1'=>'array', 'm2'=>'array'], +'imageaffinematrixconcat' => ['array{0:float,1:float,2:float,3:float,4:float,5:float}|false', 'm1'=>'array', 'm2'=>'array'], +'imageaffinematrixget' => ['array{0:float,1:float,2:float,3:float,4:float,5:float}|false', 'type'=>'int', 'options'=>'array|float'], +'imagealphablending' => ['bool', 'im'=>'resource', 'on'=>'bool'], +'imageantialias' => ['bool', 'im'=>'resource', 'on'=>'bool'], +'imagearc' => ['bool', 'im'=>'resource', 'cx'=>'int', 'cy'=>'int', 'w'=>'int', 'h'=>'int', 's'=>'int', 'e'=>'int', 'col'=>'int'], +'imagebmp' => ['bool', 'image'=>'resource', 'to='=>'string|resource|null', 'compressed='=>'bool'], +'imagechar' => ['bool', 'im'=>'resource', 'font'=>'int', 'x'=>'int', 'y'=>'int', 'c'=>'string', 'col'=>'int'], +'imagecharup' => ['bool', 'im'=>'resource', 'font'=>'int', 'x'=>'int', 'y'=>'int', 'c'=>'string', 'col'=>'int'], +'imagecolorallocate' => ['int|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], +'imagecolorallocatealpha' => ['int|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'], +'imagecolorat' => ['int|false', 'im'=>'resource', 'x'=>'int', 'y'=>'int'], +'imagecolorclosest' => ['int|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], +'imagecolorclosestalpha' => ['int|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'], +'imagecolorclosesthwb' => ['int|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], +'imagecolordeallocate' => ['bool', 'im'=>'resource', 'index'=>'int'], +'imagecolorexact' => ['int|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], +'imagecolorexactalpha' => ['int|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'], +'imagecolormatch' => ['bool', 'im1'=>'resource', 'im2'=>'resource'], +'imagecolorresolve' => ['int|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], +'imagecolorresolvealpha' => ['int|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'], +'imagecolorset' => ['void', 'im'=>'resource', 'col'=>'int', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha='=>'int'], +'imagecolorsforindex' => ['array|false', 'im'=>'resource', 'col'=>'int'], +'imagecolorstotal' => ['int|false', 'im'=>'resource'], +'imagecolortransparent' => ['int|false', 'im'=>'resource', 'col='=>'int'], +'imageconvolution' => ['bool', 'src_im'=>'resource', 'matrix3x3'=>'array', 'div'=>'float', 'offset'=>'float'], +'imagecopy' => ['bool', 'dst_im'=>'resource', 'src_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'src_w'=>'int', 'src_h'=>'int'], +'imagecopymerge' => ['bool', 'src_im'=>'resource', 'dst_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'src_w'=>'int', 'src_h'=>'int', 'pct'=>'int'], +'imagecopymergegray' => ['bool', 'src_im'=>'resource', 'dst_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'src_w'=>'int', 'src_h'=>'int', 'pct'=>'int'], +'imagecopyresampled' => ['bool', 'dst_im'=>'resource', 'src_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'dst_w'=>'int', 'dst_h'=>'int', 'src_w'=>'int', 'src_h'=>'int'], +'imagecopyresized' => ['bool', 'dst_im'=>'resource', 'src_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'dst_w'=>'int', 'dst_h'=>'int', 'src_w'=>'int', 'src_h'=>'int'], +'imagecreate' => ['resource|false', 'x_size'=>'int', 'y_size'=>'int'], +'imagecreatefrombmp' => ['resource|false', 'filename'=>'string'], +'imagecreatefromgd' => ['resource|false', 'filename'=>'string'], +'imagecreatefromgd2' => ['resource|false', 'filename'=>'string'], +'imagecreatefromgd2part' => ['resource|false', 'filename'=>'string', 'srcx'=>'int', 'srcy'=>'int', 'width'=>'int', 'height'=>'int'], +'imagecreatefromgif' => ['resource|false', 'filename'=>'string'], +'imagecreatefromjpeg' => ['resource|false', 'filename'=>'string'], +'imagecreatefrompng' => ['resource|false', 'filename'=>'string'], +'imagecreatefromstring' => ['resource|false', 'image'=>'string'], +'imagecreatefromwbmp' => ['resource|false', 'filename'=>'string'], +'imagecreatefromwebp' => ['resource|false', 'filename'=>'string'], +'imagecreatefromxbm' => ['resource|false', 'filename'=>'string'], +'imagecreatefromxpm' => ['resource|false', 'filename'=>'string'], +'imagecreatetruecolor' => ['resource|false', 'x_size'=>'int', 'y_size'=>'int'], +'imagecrop' => ['resource|false', 'im'=>'resource', 'rect'=>'array'], +'imagecropauto' => ['resource|false', 'im'=>'resource', 'mode'=>'int', 'threshold'=>'float', 'color'=>'int'], +'imagedashedline' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int', 'col'=>'int'], +'imagedestroy' => ['bool', 'im'=>'resource'], +'imageellipse' => ['bool', 'im'=>'resource', 'cx'=>'int', 'cy'=>'int', 'w'=>'int', 'h'=>'int', 'color'=>'int'], +'imagefill' => ['bool', 'im'=>'resource', 'x'=>'int', 'y'=>'int', 'col'=>'int'], +'imagefilledarc' => ['bool', 'im'=>'resource', 'cx'=>'int', 'cy'=>'int', 'w'=>'int', 'h'=>'int', 's'=>'int', 'e'=>'int', 'col'=>'int', 'style'=>'int'], +'imagefilledellipse' => ['bool', 'im'=>'resource', 'cx'=>'int', 'cy'=>'int', 'w'=>'int', 'h'=>'int', 'color'=>'int'], +'imagefilledpolygon' => ['bool', 'im'=>'resource', 'point'=>'array', 'num_points'=>'int', 'col'=>'int'], +'imagefilledrectangle' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int', 'col'=>'int'], +'imagefilltoborder' => ['bool', 'im'=>'resource', 'x'=>'int', 'y'=>'int', 'border'=>'int', 'col'=>'int'], +'imagefilter' => ['bool', 'src_im'=>'resource', 'filtertype'=>'int', 'arg1='=>'int', 'arg2='=>'int', 'arg3='=>'int', 'arg4='=>'int'], +'imageflip' => ['bool', 'im'=>'resource', 'mode'=>'int'], +'imagefontheight' => ['int', 'font'=>'int'], +'imagefontwidth' => ['int', 'font'=>'int'], +'imageftbbox' => ['array|false', 'size'=>'float', 'angle'=>'float', 'font_file'=>'string', 'text'=>'string', 'extrainfo='=>'array'], +'imagefttext' => ['array|false', 'im'=>'resource', 'size'=>'float', 'angle'=>'float', 'x'=>'int', 'y'=>'int', 'col'=>'int', 'font_file'=>'string', 'text'=>'string', 'extrainfo='=>'array'], +'imagegammacorrect' => ['bool', 'im'=>'resource', 'inputgamma'=>'float', 'outputgamma'=>'float'], +'imagegd' => ['bool', 'im'=>'resource', 'to='=>'string|resource|null'], +'imagegd2' => ['bool', 'im'=>'resource', 'to='=>'string|resource|null', 'chunk_size='=>'int', 'type='=>'int'], +'imagegetclip' => ['array|false', 'im'=>'resource'], +'imagegif' => ['bool', 'im'=>'resource', 'to='=>'string|resource|null'], +'imagegrabscreen' => ['false|resource'], +'imagegrabwindow' => ['false|resource', 'window_handle'=>'int', 'client_area='=>'int'], +'imageinterlace' => ['int|false', 'im'=>'resource', 'interlace='=>'int'], +'imageistruecolor' => ['bool', 'im'=>'resource'], +'imagejpeg' => ['bool', 'im'=>'resource', 'to='=>'string|resource|null', 'quality='=>'int'], +'imagejpeg\'1' => ['string|false', 'im'=>'resource', 'filename='=>'null', 'quality='=>'int'], +'imagelayereffect' => ['bool', 'im'=>'resource', 'effect'=>'int'], +'imageline' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int', 'col'=>'int'], +'imageloadfont' => ['int|false', 'filename'=>'string'], +'imageObj::pasteImage' => ['void', 'srcImg'=>'imageObj', 'transparentColorHex'=>'int', 'dstX'=>'int', 'dstY'=>'int', 'angle'=>'int'], +'imageObj::saveImage' => ['int', 'filename'=>'string', 'oMap'=>'mapObj'], +'imageObj::saveWebImage' => ['string'], +'imageopenpolygon' => ['bool', 'image'=>'resource', 'points'=>'array', 'num_points'=>'int', 'color'=>'int'], +'imagepalettecopy' => ['void', 'dst'=>'resource', 'src'=>'resource'], +'imagepalettetotruecolor' => ['bool', 'src'=>'resource'], +'imagepng' => ['bool', 'im'=>'resource', 'to='=>'string|resource|null', 'quality='=>'int', 'filters='=>'int'], +'imagepolygon' => ['bool', 'im'=>'resource', 'point'=>'array', 'num_points'=>'int', 'col'=>'int'], +'imagerectangle' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int', 'col'=>'int'], +'imageresolution' => ['array|bool', 'image'=>'resource', 'res_x='=>'int', 'res_y='=>'int'], +'imagerotate' => ['resource|false', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], +'imagesavealpha' => ['bool', 'im'=>'resource', 'on'=>'bool'], +'imagescale' => ['resource|false', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], +'imagesetbrush' => ['bool', 'image'=>'resource', 'brush'=>'resource'], +'imagesetclip' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int'], +'imagesetinterpolation' => ['bool', 'im'=>'resource', 'method'=>'int'], +'imagesetpixel' => ['bool', 'im'=>'resource', 'x'=>'int', 'y'=>'int', 'col'=>'int'], +'imagesetstyle' => ['bool', 'im'=>'resource', 'styles'=>'non-empty-array'], +'imagesetthickness' => ['bool', 'im'=>'resource', 'thickness'=>'int'], +'imagesettile' => ['bool', 'image'=>'resource', 'tile'=>'resource'], +'imagestring' => ['bool', 'im'=>'resource', 'font'=>'int', 'x'=>'int', 'y'=>'int', 'string'=>'string', 'col'=>'int'], +'imagestringup' => ['bool', 'im'=>'resource', 'font'=>'int', 'x'=>'int', 'y'=>'int', 'string'=>'string', 'col'=>'int'], +'imagesx' => ['int|false', 'im'=>'resource'], +'imagesy' => ['int|false', 'im'=>'resource'], +'imagetruecolortopalette' => ['bool', 'im'=>'resource', 'ditherflag'=>'bool', 'colorswanted'=>'int'], +'imagettfbbox' => ['false|array', 'size'=>'float', 'angle'=>'float', 'font_file'=>'string', 'text'=>'string'], +'imagettftext' => ['false|array', 'im'=>'resource', 'size'=>'float', 'angle'=>'float', 'x'=>'int', 'y'=>'int', 'col'=>'int', 'font_file'=>'string', 'text'=>'string'], +'imagetypes' => ['int'], +'imagewbmp' => ['bool', 'im'=>'resource', 'to='=>'string|resource|null', 'foreground='=>'int'], +'imagewebp' => ['bool', 'im'=>'resource', 'to='=>'string|resource|null', 'quality='=>'int'], +'imagexbm' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'foreground='=>'int'], +'Imagick::__construct' => ['void', 'files='=>'string|string[]'], +'Imagick::__toString' => ['string'], +'Imagick::adaptiveBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'], +'Imagick::adaptiveResizeImage' => ['bool', 'columns'=>'int', 'rows'=>'int', 'bestfit='=>'bool'], +'Imagick::adaptiveSharpenImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'], +'Imagick::adaptiveThresholdImage' => ['bool', 'width'=>'int', 'height'=>'int', 'offset'=>'int'], +'Imagick::addImage' => ['bool', 'source'=>'imagick'], +'Imagick::addNoiseImage' => ['bool', 'noise_type'=>'int', 'channel='=>'int'], +'Imagick::affineTransformImage' => ['bool', 'matrix'=>'imagickdraw'], +'Imagick::animateImages' => ['bool', 'x_server'=>'string'], +'Imagick::annotateImage' => ['bool', 'draw_settings'=>'imagickdraw', 'x'=>'float', 'y'=>'float', 'angle'=>'float', 'text'=>'string'], +'Imagick::appendImages' => ['Imagick', 'stack'=>'bool'], +'Imagick::autoGammaImage' => ['bool', 'channel='=>'int'], +'Imagick::autoLevelImage' => ['void', 'CHANNEL='=>'string'], +'Imagick::autoOrient' => ['bool'], +'Imagick::averageImages' => ['Imagick'], +'Imagick::blackThresholdImage' => ['bool', 'threshold'=>'mixed'], +'Imagick::blueShiftImage' => ['void', 'factor='=>'float'], +'Imagick::blurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'], +'Imagick::borderImage' => ['bool', 'bordercolor'=>'mixed', 'width'=>'int', 'height'=>'int'], +'Imagick::brightnessContrastImage' => ['void', 'brightness'=>'string', 'contrast'=>'string', 'CHANNEL='=>'string'], +'Imagick::charcoalImage' => ['bool', 'radius'=>'float', 'sigma'=>'float'], +'Imagick::chopImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], +'Imagick::clampImage' => ['void', 'CHANNEL='=>'string'], +'Imagick::clear' => ['bool'], +'Imagick::clipImage' => ['bool'], +'Imagick::clipImagePath' => ['void', 'pathname'=>'string', 'inside'=>'string'], +'Imagick::clipPathImage' => ['bool', 'pathname'=>'string', 'inside'=>'bool'], +'Imagick::clone' => ['Imagick'], +'Imagick::clutImage' => ['bool', 'lookup_table'=>'imagick', 'channel='=>'float'], +'Imagick::coalesceImages' => ['Imagick'], +'Imagick::colorFloodfillImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'bordercolor'=>'mixed', 'x'=>'int', 'y'=>'int'], +'Imagick::colorizeImage' => ['bool', 'colorize'=>'mixed', 'opacity'=>'mixed'], +'Imagick::colorMatrixImage' => ['void', 'color_matrix'=>'string'], +'Imagick::combineImages' => ['Imagick', 'channeltype'=>'int'], +'Imagick::commentImage' => ['bool', 'comment'=>'string'], +'Imagick::compareImageChannels' => ['array', 'image'=>'imagick', 'channeltype'=>'int', 'metrictype'=>'int'], +'Imagick::compareImageLayers' => ['Imagick', 'method'=>'int'], +'Imagick::compareImages' => ['array', 'compare'=>'imagick', 'metric'=>'int'], +'Imagick::compositeImage' => ['bool', 'composite_object'=>'imagick', 'composite'=>'int', 'x'=>'int', 'y'=>'int', 'channel='=>'int'], +'Imagick::compositeImageGravity' => ['bool', 'imagick'=>'Imagick', 'COMPOSITE_CONSTANT'=>'int', 'GRAVITY_CONSTANT'=>'int'], +'Imagick::contrastImage' => ['bool', 'sharpen'=>'bool'], +'Imagick::contrastStretchImage' => ['bool', 'black_point'=>'float', 'white_point'=>'float', 'channel='=>'int'], +'Imagick::convolveImage' => ['bool', 'kernel'=>'array', 'channel='=>'int'], +'Imagick::count' => ['void', 'mode='=>'string'], +'Imagick::cropImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], +'Imagick::cropThumbnailImage' => ['bool', 'width'=>'int', 'height'=>'int', 'legacy='=>'bool'], +'Imagick::current' => ['Imagick'], +'Imagick::cycleColormapImage' => ['bool', 'displace'=>'int'], +'Imagick::decipherImage' => ['bool', 'passphrase'=>'string'], +'Imagick::deconstructImages' => ['Imagick'], +'Imagick::deleteImageArtifact' => ['bool', 'artifact'=>'string'], +'Imagick::deleteImageProperty' => ['void', 'name'=>'string'], +'Imagick::deskewImage' => ['bool', 'threshold'=>'float'], +'Imagick::despeckleImage' => ['bool'], +'Imagick::destroy' => ['bool'], +'Imagick::displayImage' => ['bool', 'servername'=>'string'], +'Imagick::displayImages' => ['bool', 'servername'=>'string'], +'Imagick::distortImage' => ['bool', 'method'=>'int', 'arguments'=>'array', 'bestfit'=>'bool'], +'Imagick::drawImage' => ['bool', 'draw'=>'imagickdraw'], +'Imagick::edgeImage' => ['bool', 'radius'=>'float'], +'Imagick::embossImage' => ['bool', 'radius'=>'float', 'sigma'=>'float'], +'Imagick::encipherImage' => ['bool', 'passphrase'=>'string'], +'Imagick::enhanceImage' => ['bool'], +'Imagick::equalizeImage' => ['bool'], +'Imagick::evaluateImage' => ['bool', 'op'=>'int', 'constant'=>'float', 'channel='=>'int'], +'Imagick::evaluateImages' => ['bool', 'EVALUATE_CONSTANT'=>'int'], +'Imagick::exportImagePixels' => ['array', 'x'=>'int', 'y'=>'int', 'width'=>'int', 'height'=>'int', 'map'=>'string', 'storage'=>'int'], +'Imagick::extentImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], +'Imagick::filter' => ['void', 'ImagickKernel'=>'ImagickKernel', 'CHANNEL='=>'int'], +'Imagick::flattenImages' => ['Imagick'], +'Imagick::flipImage' => ['bool'], +'Imagick::floodFillPaintImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'target'=>'mixed', 'x'=>'int', 'y'=>'int', 'invert'=>'bool', 'channel='=>'int'], +'Imagick::flopImage' => ['bool'], +'Imagick::forwardFourierTransformimage' => ['void', 'magnitude'=>'bool'], +'Imagick::frameImage' => ['bool', 'matte_color'=>'mixed', 'width'=>'int', 'height'=>'int', 'inner_bevel'=>'int', 'outer_bevel'=>'int'], +'Imagick::functionImage' => ['bool', 'function'=>'int', 'arguments'=>'array', 'channel='=>'int'], +'Imagick::fxImage' => ['Imagick', 'expression'=>'string', 'channel='=>'int'], +'Imagick::gammaImage' => ['bool', 'gamma'=>'float', 'channel='=>'int'], +'Imagick::gaussianBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'], +'Imagick::getColorspace' => ['int'], +'Imagick::getCompression' => ['int'], +'Imagick::getCompressionQuality' => ['int'], +'Imagick::getConfigureOptions' => ['string'], +'Imagick::getCopyright' => ['string'], +'Imagick::getFeatures' => ['string'], +'Imagick::getFilename' => ['string'], +'Imagick::getFont' => ['string|false'], +'Imagick::getFormat' => ['string'], +'Imagick::getGravity' => ['int'], +'Imagick::getHDRIEnabled' => ['int'], +'Imagick::getHomeURL' => ['string'], +'Imagick::getImage' => ['Imagick'], +'Imagick::getImageAlphaChannel' => ['int'], +'Imagick::getImageArtifact' => ['string', 'artifact'=>'string'], +'Imagick::getImageAttribute' => ['string', 'key'=>'string'], +'Imagick::getImageBackgroundColor' => ['ImagickPixel'], +'Imagick::getImageBlob' => ['string'], +'Imagick::getImageBluePrimary' => ['array'], +'Imagick::getImageBorderColor' => ['ImagickPixel'], +'Imagick::getImageChannelDepth' => ['int', 'channel'=>'int'], +'Imagick::getImageChannelDistortion' => ['float', 'reference'=>'imagick', 'channel'=>'int', 'metric'=>'int'], +'Imagick::getImageChannelDistortions' => ['float', 'reference'=>'imagick', 'metric'=>'int', 'channel='=>'int'], +'Imagick::getImageChannelExtrema' => ['array', 'channel'=>'int'], +'Imagick::getImageChannelKurtosis' => ['array', 'channel='=>'int'], +'Imagick::getImageChannelMean' => ['array', 'channel'=>'int'], +'Imagick::getImageChannelRange' => ['array', 'channel'=>'int'], +'Imagick::getImageChannelStatistics' => ['array'], +'Imagick::getImageClipMask' => ['Imagick'], +'Imagick::getImageColormapColor' => ['ImagickPixel', 'index'=>'int'], +'Imagick::getImageColors' => ['int'], +'Imagick::getImageColorspace' => ['int'], +'Imagick::getImageCompose' => ['int'], +'Imagick::getImageCompression' => ['int'], +'Imagick::getImageCompressionQuality' => ['int'], +'Imagick::getImageDelay' => ['int'], +'Imagick::getImageDepth' => ['int'], +'Imagick::getImageDispose' => ['int'], +'Imagick::getImageDistortion' => ['float', 'reference'=>'magickwand', 'metric'=>'int'], +'Imagick::getImageExtrema' => ['array'], +'Imagick::getImageFilename' => ['string'], +'Imagick::getImageFormat' => ['string'], +'Imagick::getImageGamma' => ['float'], +'Imagick::getImageGeometry' => ['array'], +'Imagick::getImageGravity' => ['int'], +'Imagick::getImageGreenPrimary' => ['array'], +'Imagick::getImageHeight' => ['int'], +'Imagick::getImageHistogram' => ['array'], +'Imagick::getImageIndex' => ['int'], +'Imagick::getImageInterlaceScheme' => ['int'], +'Imagick::getImageInterpolateMethod' => ['int'], +'Imagick::getImageIterations' => ['int'], +'Imagick::getImageLength' => ['int'], +'Imagick::getImageMagickLicense' => ['string'], +'Imagick::getImageMatte' => ['bool'], +'Imagick::getImageMatteColor' => ['ImagickPixel'], +'Imagick::getImageMimeType' => ['string'], +'Imagick::getImageOrientation' => ['int'], +'Imagick::getImagePage' => ['array'], +'Imagick::getImagePixelColor' => ['ImagickPixel', 'x'=>'int', 'y'=>'int'], +'Imagick::getImageProfile' => ['string', 'name'=>'string'], +'Imagick::getImageProfiles' => ['array', 'pattern='=>'string', 'only_names='=>'bool'], +'Imagick::getImageProperties' => ['array', 'pattern='=>'string', 'only_names='=>'bool'], +'Imagick::getImageProperty' => ['string|false', 'name'=>'string'], +'Imagick::getImageRedPrimary' => ['array'], +'Imagick::getImageRegion' => ['Imagick', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], +'Imagick::getImageRenderingIntent' => ['int'], +'Imagick::getImageResolution' => ['array'], +'Imagick::getImagesBlob' => ['string'], +'Imagick::getImageScene' => ['int'], +'Imagick::getImageSignature' => ['string'], +'Imagick::getImageSize' => ['int'], +'Imagick::getImageTicksPerSecond' => ['int'], +'Imagick::getImageTotalInkDensity' => ['float'], +'Imagick::getImageType' => ['int'], +'Imagick::getImageUnits' => ['int'], +'Imagick::getImageVirtualPixelMethod' => ['int'], +'Imagick::getImageWhitePoint' => ['array'], +'Imagick::getImageWidth' => ['int'], +'Imagick::getInterlaceScheme' => ['int'], +'Imagick::getIteratorIndex' => ['int'], +'Imagick::getNumberImages' => ['int'], +'Imagick::getOption' => ['string', 'key'=>'string'], +'Imagick::getPackageName' => ['string'], +'Imagick::getPage' => ['array'], +'Imagick::getPixelIterator' => ['ImagickPixelIterator'], +'Imagick::getPixelRegionIterator' => ['ImagickPixelIterator', 'x'=>'int', 'y'=>'int', 'columns'=>'int', 'rows'=>'int'], +'Imagick::getPointSize' => ['float'], +'Imagick::getQuantum' => ['int'], +'Imagick::getQuantumDepth' => ['array'], +'Imagick::getQuantumRange' => ['array'], +'Imagick::getRegistry' => ['string|false', 'key'=>'string'], +'Imagick::getReleaseDate' => ['string'], +'Imagick::getResource' => ['int', 'type'=>'int'], +'Imagick::getResourceLimit' => ['int', 'type'=>'int'], +'Imagick::getSamplingFactors' => ['array'], +'Imagick::getSize' => ['array'], +'Imagick::getSizeOffset' => ['int'], +'Imagick::getVersion' => ['array'], +'Imagick::haldClutImage' => ['bool', 'clut'=>'imagick', 'channel='=>'int'], +'Imagick::hasNextImage' => ['bool'], +'Imagick::hasPreviousImage' => ['bool'], +'Imagick::identifyFormat' => ['string|false', 'embedText'=>'string'], +'Imagick::identifyImage' => ['array', 'appendrawoutput='=>'bool'], +'Imagick::identifyImageType' => ['int'], +'Imagick::implodeImage' => ['bool', 'radius'=>'float'], +'Imagick::importImagePixels' => ['bool', 'x'=>'int', 'y'=>'int', 'width'=>'int', 'height'=>'int', 'map'=>'string', 'storage'=>'int', 'pixels'=>'array'], +'Imagick::inverseFourierTransformImage' => ['void', 'complement'=>'string', 'magnitude'=>'string'], +'Imagick::key' => ['int|string'], +'Imagick::labelImage' => ['bool', 'label'=>'string'], +'Imagick::levelImage' => ['bool', 'blackpoint'=>'float', 'gamma'=>'float', 'whitepoint'=>'float', 'channel='=>'int'], +'Imagick::linearStretchImage' => ['bool', 'blackpoint'=>'float', 'whitepoint'=>'float'], +'Imagick::liquidRescaleImage' => ['bool', 'width'=>'int', 'height'=>'int', 'delta_x'=>'float', 'rigidity'=>'float'], +'Imagick::listRegistry' => ['array'], +'Imagick::localContrastImage' => ['bool', 'radius'=>'float', 'strength'=>'float'], +'Imagick::magnifyImage' => ['bool'], +'Imagick::mapImage' => ['bool', 'map'=>'imagick', 'dither'=>'bool'], +'Imagick::matteFloodfillImage' => ['bool', 'alpha'=>'float', 'fuzz'=>'float', 'bordercolor'=>'mixed', 'x'=>'int', 'y'=>'int'], +'Imagick::medianFilterImage' => ['bool', 'radius'=>'float'], +'Imagick::mergeImageLayers' => ['Imagick', 'layer_method'=>'int'], +'Imagick::minifyImage' => ['bool'], +'Imagick::modulateImage' => ['bool', 'brightness'=>'float', 'saturation'=>'float', 'hue'=>'float'], +'Imagick::montageImage' => ['Imagick', 'draw'=>'imagickdraw', 'tile_geometry'=>'string', 'thumbnail_geometry'=>'string', 'mode'=>'int', 'frame'=>'string'], +'Imagick::morphImages' => ['Imagick', 'number_frames'=>'int'], +'Imagick::morphology' => ['void', 'morphologyMethod'=>'int', 'iterations'=>'int', 'ImagickKernel'=>'ImagickKernel', 'CHANNEL='=>'string'], +'Imagick::mosaicImages' => ['Imagick'], +'Imagick::motionBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'angle'=>'float', 'channel='=>'int'], +'Imagick::negateImage' => ['bool', 'gray'=>'bool', 'channel='=>'int'], +'Imagick::newImage' => ['bool', 'cols'=>'int', 'rows'=>'int', 'background'=>'mixed', 'format='=>'string'], +'Imagick::newPseudoImage' => ['bool', 'columns'=>'int', 'rows'=>'int', 'pseudostring'=>'string'], +'Imagick::next' => ['void'], +'Imagick::nextImage' => ['bool'], +'Imagick::normalizeImage' => ['bool', 'channel='=>'int'], +'Imagick::oilPaintImage' => ['bool', 'radius'=>'float'], +'Imagick::opaquePaintImage' => ['bool', 'target'=>'mixed', 'fill'=>'mixed', 'fuzz'=>'float', 'invert'=>'bool', 'channel='=>'int'], +'Imagick::optimizeImageLayers' => ['bool'], +'Imagick::orderedPosterizeImage' => ['bool', 'threshold_map'=>'string', 'channel='=>'int'], +'Imagick::paintFloodfillImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'bordercolor'=>'mixed', 'x'=>'int', 'y'=>'int', 'channel='=>'int'], +'Imagick::paintOpaqueImage' => ['bool', 'target'=>'mixed', 'fill'=>'mixed', 'fuzz'=>'float', 'channel='=>'int'], +'Imagick::paintTransparentImage' => ['bool', 'target'=>'mixed', 'alpha'=>'float', 'fuzz'=>'float'], +'Imagick::pingImage' => ['bool', 'filename'=>'string'], +'Imagick::pingImageBlob' => ['bool', 'image'=>'string'], +'Imagick::pingImageFile' => ['bool', 'filehandle'=>'resource', 'filename='=>'string'], +'Imagick::polaroidImage' => ['bool', 'properties'=>'imagickdraw', 'angle'=>'float'], +'Imagick::posterizeImage' => ['bool', 'levels'=>'int', 'dither'=>'bool'], +'Imagick::previewImages' => ['bool', 'preview'=>'int'], +'Imagick::previousImage' => ['bool'], +'Imagick::profileImage' => ['bool', 'name'=>'string', 'profile'=>'string'], +'Imagick::quantizeImage' => ['bool', 'numbercolors'=>'int', 'colorspace'=>'int', 'treedepth'=>'int', 'dither'=>'bool', 'measureerror'=>'bool'], +'Imagick::quantizeImages' => ['bool', 'numbercolors'=>'int', 'colorspace'=>'int', 'treedepth'=>'int', 'dither'=>'bool', 'measureerror'=>'bool'], +'Imagick::queryFontMetrics' => ['array', 'properties'=>'imagickdraw', 'text'=>'string', 'multiline='=>'bool'], +'Imagick::queryFonts' => ['array', 'pattern='=>'string'], +'Imagick::queryFormats' => ['array', 'pattern='=>'string'], +'Imagick::radialBlurImage' => ['bool', 'angle'=>'float', 'channel='=>'int'], +'Imagick::raiseImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int', 'raise'=>'bool'], +'Imagick::randomThresholdImage' => ['bool', 'low'=>'float', 'high'=>'float', 'channel='=>'int'], +'Imagick::readImage' => ['bool', 'filename'=>'string'], +'Imagick::readImageBlob' => ['bool', 'image'=>'string', 'filename='=>'string'], +'Imagick::readImageFile' => ['bool', 'filehandle'=>'resource', 'filename='=>'string'], +'Imagick::readImages' => ['Imagick', 'filenames'=>'string'], +'Imagick::recolorImage' => ['bool', 'matrix'=>'array'], +'Imagick::reduceNoiseImage' => ['bool', 'radius'=>'float'], +'Imagick::remapImage' => ['bool', 'replacement'=>'imagick', 'dither'=>'int'], +'Imagick::removeImage' => ['bool'], +'Imagick::removeImageProfile' => ['string', 'name'=>'string'], +'Imagick::render' => ['bool'], +'Imagick::resampleImage' => ['bool', 'x_resolution'=>'float', 'y_resolution'=>'float', 'filter'=>'int', 'blur'=>'float'], +'Imagick::resetImagePage' => ['bool', 'page'=>'string'], +'Imagick::resetIterator' => [''], +'Imagick::resizeImage' => ['bool', 'columns'=>'int', 'rows'=>'int', 'filter'=>'int', 'blur'=>'float', 'bestfit='=>'bool'], +'Imagick::rewind' => ['void'], +'Imagick::rollImage' => ['bool', 'x'=>'int', 'y'=>'int'], +'Imagick::rotateImage' => ['bool', 'background'=>'mixed', 'degrees'=>'float'], +'Imagick::rotationalBlurImage' => ['void', 'angle'=>'string', 'CHANNEL='=>'string'], +'Imagick::roundCorners' => ['bool', 'x_rounding'=>'float', 'y_rounding'=>'float', 'stroke_width='=>'float', 'displace='=>'float', 'size_correction='=>'float'], +'Imagick::roundCornersImage' => ['', 'xRounding'=>'', 'yRounding'=>'', 'strokeWidth'=>'', 'displace'=>'', 'sizeCorrection'=>''], +'Imagick::sampleImage' => ['bool', 'columns'=>'int', 'rows'=>'int'], +'Imagick::scaleImage' => ['bool', 'cols'=>'int', 'rows'=>'int', 'bestfit='=>'bool'], +'Imagick::segmentImage' => ['bool', 'colorspace'=>'int', 'cluster_threshold'=>'float', 'smooth_threshold'=>'float', 'verbose='=>'bool'], +'Imagick::selectiveBlurImage' => ['void', 'radius'=>'float', 'sigma'=>'float', 'threshold'=>'float', 'CHANNEL'=>'int'], +'Imagick::separateImageChannel' => ['bool', 'channel'=>'int'], +'Imagick::sepiaToneImage' => ['bool', 'threshold'=>'float'], +'Imagick::setAntiAlias' => ['int', 'antialias'=>'bool'], +'Imagick::setBackgroundColor' => ['bool', 'background'=>'mixed'], +'Imagick::setColorspace' => ['bool', 'colorspace'=>'int'], +'Imagick::setCompression' => ['bool', 'compression'=>'int'], +'Imagick::setCompressionQuality' => ['bool', 'quality'=>'int'], +'Imagick::setFilename' => ['bool', 'filename'=>'string'], +'Imagick::setFirstIterator' => ['bool'], +'Imagick::setFont' => ['bool', 'font'=>'string'], +'Imagick::setFormat' => ['bool', 'format'=>'string'], +'Imagick::setGravity' => ['bool', 'gravity'=>'int'], +'Imagick::setImage' => ['bool', 'replace'=>'imagick'], +'Imagick::setImageAlpha' => ['bool', 'alpha'=>'float'], +'Imagick::setImageAlphaChannel' => ['bool', 'mode'=>'int'], +'Imagick::setImageArtifact' => ['bool', 'artifact'=>'string', 'value'=>'string'], +'Imagick::setImageAttribute' => ['void', 'key'=>'string', 'value'=>'string'], +'Imagick::setImageBackgroundColor' => ['bool', 'background'=>'mixed'], +'Imagick::setImageBias' => ['bool', 'bias'=>'float'], +'Imagick::setImageBiasQuantum' => ['void', 'bias'=>'string'], +'Imagick::setImageBluePrimary' => ['bool', 'x'=>'float', 'y'=>'float'], +'Imagick::setImageBorderColor' => ['bool', 'border'=>'mixed'], +'Imagick::setImageChannelDepth' => ['bool', 'channel'=>'int', 'depth'=>'int'], +'Imagick::setImageChannelMask' => ['', 'channel'=>'int'], +'Imagick::setImageClipMask' => ['bool', 'clip_mask'=>'imagick'], +'Imagick::setImageColormapColor' => ['bool', 'index'=>'int', 'color'=>'imagickpixel'], +'Imagick::setImageColorspace' => ['bool', 'colorspace'=>'int'], +'Imagick::setImageCompose' => ['bool', 'compose'=>'int'], +'Imagick::setImageCompression' => ['bool', 'compression'=>'int'], +'Imagick::setImageCompressionQuality' => ['bool', 'quality'=>'int'], +'Imagick::setImageDelay' => ['bool', 'delay'=>'int'], +'Imagick::setImageDepth' => ['bool', 'depth'=>'int'], +'Imagick::setImageDispose' => ['bool', 'dispose'=>'int'], +'Imagick::setImageExtent' => ['bool', 'columns'=>'int', 'rows'=>'int'], +'Imagick::setImageFilename' => ['bool', 'filename'=>'string'], +'Imagick::setImageFormat' => ['bool', 'format'=>'string'], +'Imagick::setImageGamma' => ['bool', 'gamma'=>'float'], +'Imagick::setImageGravity' => ['bool', 'gravity'=>'int'], +'Imagick::setImageGreenPrimary' => ['bool', 'x'=>'float', 'y'=>'float'], +'Imagick::setImageIndex' => ['bool', 'index'=>'int'], +'Imagick::setImageInterlaceScheme' => ['bool', 'interlace_scheme'=>'int'], +'Imagick::setImageInterpolateMethod' => ['bool', 'method'=>'int'], +'Imagick::setImageIterations' => ['bool', 'iterations'=>'int'], +'Imagick::setImageMatte' => ['bool', 'matte'=>'bool'], +'Imagick::setImageMatteColor' => ['bool', 'matte'=>'mixed'], +'Imagick::setImageOpacity' => ['bool', 'opacity'=>'float'], +'Imagick::setImageOrientation' => ['bool', 'orientation'=>'int'], +'Imagick::setImagePage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], +'Imagick::setImageProfile' => ['bool', 'name'=>'string', 'profile'=>'string'], +'Imagick::setImageProgressMonitor' => ['', 'filename'=>''], +'Imagick::setImageProperty' => ['bool', 'name'=>'string', 'value'=>'string'], +'Imagick::setImageRedPrimary' => ['bool', 'x'=>'float', 'y'=>'float'], +'Imagick::setImageRenderingIntent' => ['bool', 'rendering_intent'=>'int'], +'Imagick::setImageResolution' => ['bool', 'x_resolution'=>'float', 'y_resolution'=>'float'], +'Imagick::setImageScene' => ['bool', 'scene'=>'int'], +'Imagick::setImageTicksPerSecond' => ['bool', 'ticks_per_second'=>'int'], +'Imagick::setImageType' => ['bool', 'image_type'=>'int'], +'Imagick::setImageUnits' => ['bool', 'units'=>'int'], +'Imagick::setImageVirtualPixelMethod' => ['bool', 'method'=>'int'], +'Imagick::setImageWhitePoint' => ['bool', 'x'=>'float', 'y'=>'float'], +'Imagick::setInterlaceScheme' => ['bool', 'interlace_scheme'=>'int'], +'Imagick::setIteratorIndex' => ['bool', 'index'=>'int'], +'Imagick::setLastIterator' => ['bool'], +'Imagick::setOption' => ['bool', 'key'=>'string', 'value'=>'string'], +'Imagick::setPage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], +'Imagick::setPointSize' => ['bool', 'point_size'=>'float'], +'Imagick::setProgressMonitor' => ['void', 'callback'=>'callable'], +'Imagick::setRegistry' => ['void', 'key'=>'string', 'value'=>'string'], +'Imagick::setResolution' => ['bool', 'x_resolution'=>'float', 'y_resolution'=>'float'], +'Imagick::setResourceLimit' => ['bool', 'type'=>'int', 'limit'=>'int'], +'Imagick::setSamplingFactors' => ['bool', 'factors'=>'array'], +'Imagick::setSize' => ['bool', 'columns'=>'int', 'rows'=>'int'], +'Imagick::setSizeOffset' => ['bool', 'columns'=>'int', 'rows'=>'int', 'offset'=>'int'], +'Imagick::setType' => ['bool', 'image_type'=>'int'], +'Imagick::shadeImage' => ['bool', 'gray'=>'bool', 'azimuth'=>'float', 'elevation'=>'float'], +'Imagick::shadowImage' => ['bool', 'opacity'=>'float', 'sigma'=>'float', 'x'=>'int', 'y'=>'int'], +'Imagick::sharpenImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'], +'Imagick::shaveImage' => ['bool', 'columns'=>'int', 'rows'=>'int'], +'Imagick::shearImage' => ['bool', 'background'=>'mixed', 'x_shear'=>'float', 'y_shear'=>'float'], +'Imagick::sigmoidalContrastImage' => ['bool', 'sharpen'=>'bool', 'alpha'=>'float', 'beta'=>'float', 'channel='=>'int'], +'Imagick::similarityImage' => ['Imagick', 'imagick'=>'Imagick', '&bestMatch'=>'array', '&similarity'=>'float', 'similarity_threshold'=>'float', 'metric'=>'int'], +'Imagick::sketchImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'angle'=>'float'], +'Imagick::smushImages' => ['Imagick', 'stack'=>'string', 'offset'=>'string'], +'Imagick::solarizeImage' => ['bool', 'threshold'=>'int'], +'Imagick::sparseColorImage' => ['bool', 'sparse_method'=>'int', 'arguments'=>'array', 'channel='=>'int'], +'Imagick::spliceImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], +'Imagick::spreadImage' => ['bool', 'radius'=>'float'], +'Imagick::statisticImage' => ['void', 'type'=>'int', 'width'=>'int', 'height'=>'int', 'CHANNEL='=>'string'], +'Imagick::steganoImage' => ['Imagick', 'watermark_wand'=>'imagick', 'offset'=>'int'], +'Imagick::stereoImage' => ['bool', 'offset_wand'=>'imagick'], +'Imagick::stripImage' => ['bool'], +'Imagick::subImageMatch' => ['Imagick', 'Imagick'=>'Imagick', '&w_offset='=>'array', '&w_similarity='=>'float'], +'Imagick::swirlImage' => ['bool', 'degrees'=>'float'], +'Imagick::textureImage' => ['bool', 'texture_wand'=>'imagick'], +'Imagick::thresholdImage' => ['bool', 'threshold'=>'float', 'channel='=>'int'], +'Imagick::thumbnailImage' => ['bool', 'columns'=>'int', 'rows'=>'int', 'bestfit='=>'bool', 'fill='=>'bool', 'legacy='=>'bool'], +'Imagick::tintImage' => ['bool', 'tint'=>'mixed', 'opacity'=>'mixed'], +'Imagick::transformImage' => ['Imagick', 'crop'=>'string', 'geometry'=>'string'], +'Imagick::transformImageColorspace' => ['bool', 'colorspace'=>'int'], +'Imagick::transparentPaintImage' => ['bool', 'target'=>'mixed', 'alpha'=>'float', 'fuzz'=>'float', 'invert'=>'bool'], +'Imagick::transposeImage' => ['bool'], +'Imagick::transverseImage' => ['bool'], +'Imagick::trimImage' => ['bool', 'fuzz'=>'float'], +'Imagick::uniqueImageColors' => ['bool'], +'Imagick::unsharpMaskImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'amount'=>'float', 'threshold'=>'float', 'channel='=>'int'], +'Imagick::valid' => ['bool'], +'Imagick::vignetteImage' => ['bool', 'blackpoint'=>'float', 'whitepoint'=>'float', 'x'=>'int', 'y'=>'int'], +'Imagick::waveImage' => ['bool', 'amplitude'=>'float', 'length'=>'float'], +'Imagick::whiteThresholdImage' => ['bool', 'threshold'=>'mixed'], +'Imagick::writeImage' => ['bool', 'filename='=>'string'], +'Imagick::writeImageFile' => ['bool', 'filehandle'=>'resource'], +'Imagick::writeImages' => ['bool', 'filename'=>'string', 'adjoin'=>'bool'], +'Imagick::writeImagesFile' => ['bool', 'filehandle'=>'resource'], +'ImagickDraw::__construct' => ['void'], +'ImagickDraw::affine' => ['bool', 'affine'=>'array'], +'ImagickDraw::annotation' => ['bool', 'x'=>'float', 'y'=>'float', 'text'=>'string'], +'ImagickDraw::arc' => ['bool', 'sx'=>'float', 'sy'=>'float', 'ex'=>'float', 'ey'=>'float', 'sd'=>'float', 'ed'=>'float'], +'ImagickDraw::bezier' => ['bool', 'coordinates'=>'array'], +'ImagickDraw::circle' => ['bool', 'ox'=>'float', 'oy'=>'float', 'px'=>'float', 'py'=>'float'], +'ImagickDraw::clear' => ['bool'], +'ImagickDraw::clone' => ['ImagickDraw'], +'ImagickDraw::color' => ['bool', 'x'=>'float', 'y'=>'float', 'paintmethod'=>'int'], +'ImagickDraw::comment' => ['bool', 'comment'=>'string'], +'ImagickDraw::composite' => ['bool', 'compose'=>'int', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float', 'compositewand'=>'imagick'], +'ImagickDraw::destroy' => ['bool'], +'ImagickDraw::ellipse' => ['bool', 'ox'=>'float', 'oy'=>'float', 'rx'=>'float', 'ry'=>'float', 'start'=>'float', 'end'=>'float'], +'ImagickDraw::getBorderColor' => ['ImagickPixel'], +'ImagickDraw::getClipPath' => ['string|false'], +'ImagickDraw::getClipRule' => ['int'], +'ImagickDraw::getClipUnits' => ['int'], +'ImagickDraw::getDensity' => ['?string'], +'ImagickDraw::getFillColor' => ['ImagickPixel'], +'ImagickDraw::getFillOpacity' => ['float'], +'ImagickDraw::getFillRule' => ['int'], +'ImagickDraw::getFont' => ['string|false'], +'ImagickDraw::getFontFamily' => ['string|false'], +'ImagickDraw::getFontResolution' => ['array'], +'ImagickDraw::getFontSize' => ['float'], +'ImagickDraw::getFontStretch' => ['int'], +'ImagickDraw::getFontStyle' => ['int'], +'ImagickDraw::getFontWeight' => ['int'], +'ImagickDraw::getGravity' => ['int'], +'ImagickDraw::getOpacity' => ['float'], +'ImagickDraw::getStrokeAntialias' => ['bool'], +'ImagickDraw::getStrokeColor' => ['ImagickPixel'], +'ImagickDraw::getStrokeDashArray' => ['array'], +'ImagickDraw::getStrokeDashOffset' => ['float'], +'ImagickDraw::getStrokeLineCap' => ['int'], +'ImagickDraw::getStrokeLineJoin' => ['int'], +'ImagickDraw::getStrokeMiterLimit' => ['int'], +'ImagickDraw::getStrokeOpacity' => ['float'], +'ImagickDraw::getStrokeWidth' => ['float'], +'ImagickDraw::getTextAlignment' => ['int'], +'ImagickDraw::getTextAntialias' => ['bool'], +'ImagickDraw::getTextDecoration' => ['int'], +'ImagickDraw::getTextDirection' => ['bool'], +'ImagickDraw::getTextEncoding' => ['string'], +'ImagickDraw::getTextInterlineSpacing' => ['float'], +'ImagickDraw::getTextInterwordSpacing' => ['float'], +'ImagickDraw::getTextKerning' => ['float'], +'ImagickDraw::getTextUnderColor' => ['ImagickPixel'], +'ImagickDraw::getVectorGraphics' => ['string'], +'ImagickDraw::line' => ['bool', 'sx'=>'float', 'sy'=>'float', 'ex'=>'float', 'ey'=>'float'], +'ImagickDraw::matte' => ['bool', 'x'=>'float', 'y'=>'float', 'paintmethod'=>'int'], +'ImagickDraw::pathClose' => ['bool'], +'ImagickDraw::pathCurveToAbsolute' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathCurveToQuadraticBezierAbsolute' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathCurveToQuadraticBezierRelative' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathCurveToQuadraticBezierSmoothAbsolute' => ['bool', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathCurveToQuadraticBezierSmoothRelative' => ['bool', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathCurveToRelative' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathCurveToSmoothAbsolute' => ['bool', 'x2'=>'float', 'y2'=>'float', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathCurveToSmoothRelative' => ['bool', 'x2'=>'float', 'y2'=>'float', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathEllipticArcAbsolute' => ['bool', 'rx'=>'float', 'ry'=>'float', 'x_axis_rotation'=>'float', 'large_arc_flag'=>'bool', 'sweep_flag'=>'bool', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathEllipticArcRelative' => ['bool', 'rx'=>'float', 'ry'=>'float', 'x_axis_rotation'=>'float', 'large_arc_flag'=>'bool', 'sweep_flag'=>'bool', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathFinish' => ['bool'], +'ImagickDraw::pathLineToAbsolute' => ['bool', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathLineToHorizontalAbsolute' => ['bool', 'x'=>'float'], +'ImagickDraw::pathLineToHorizontalRelative' => ['bool', 'x'=>'float'], +'ImagickDraw::pathLineToRelative' => ['bool', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathLineToVerticalAbsolute' => ['bool', 'y'=>'float'], +'ImagickDraw::pathLineToVerticalRelative' => ['bool', 'y'=>'float'], +'ImagickDraw::pathMoveToAbsolute' => ['bool', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathMoveToRelative' => ['bool', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::pathStart' => ['bool'], +'ImagickDraw::point' => ['bool', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::polygon' => ['bool', 'coordinates'=>'array'], +'ImagickDraw::polyline' => ['bool', 'coordinates'=>'array'], +'ImagickDraw::pop' => ['bool'], +'ImagickDraw::popClipPath' => ['bool'], +'ImagickDraw::popDefs' => ['bool'], +'ImagickDraw::popPattern' => ['bool'], +'ImagickDraw::push' => ['bool'], +'ImagickDraw::pushClipPath' => ['bool', 'clip_mask_id'=>'string'], +'ImagickDraw::pushDefs' => ['bool'], +'ImagickDraw::pushPattern' => ['bool', 'pattern_id'=>'string', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'], +'ImagickDraw::rectangle' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float'], +'ImagickDraw::render' => ['bool'], +'ImagickDraw::resetVectorGraphics' => ['void'], +'ImagickDraw::rotate' => ['bool', 'degrees'=>'float'], +'ImagickDraw::roundRectangle' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'rx'=>'float', 'ry'=>'float'], +'ImagickDraw::scale' => ['bool', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::setBorderColor' => ['bool', 'color'=>'ImagickPixel|string'], +'ImagickDraw::setClipPath' => ['bool', 'clip_mask'=>'string'], +'ImagickDraw::setClipRule' => ['bool', 'fill_rule'=>'int'], +'ImagickDraw::setClipUnits' => ['bool', 'clip_units'=>'int'], +'ImagickDraw::setDensity' => ['bool', 'density_string'=>'string'], +'ImagickDraw::setFillAlpha' => ['bool', 'opacity'=>'float'], +'ImagickDraw::setFillColor' => ['bool', 'fill_pixel'=>'ImagickPixel|string'], +'ImagickDraw::setFillOpacity' => ['bool', 'fillopacity'=>'float'], +'ImagickDraw::setFillPatternURL' => ['bool', 'fill_url'=>'string'], +'ImagickDraw::setFillRule' => ['bool', 'fill_rule'=>'int'], +'ImagickDraw::setFont' => ['bool', 'font_name'=>'string'], +'ImagickDraw::setFontFamily' => ['bool', 'font_family'=>'string'], +'ImagickDraw::setFontResolution' => ['bool', 'x'=>'float', 'y'=>'float'], +'ImagickDraw::setFontSize' => ['bool', 'pointsize'=>'float'], +'ImagickDraw::setFontStretch' => ['bool', 'fontstretch'=>'int'], +'ImagickDraw::setFontStyle' => ['bool', 'style'=>'int'], +'ImagickDraw::setFontWeight' => ['bool', 'font_weight'=>'int'], +'ImagickDraw::setGravity' => ['bool', 'gravity'=>'int'], +'ImagickDraw::setOpacity' => ['void', 'opacity'=>'float'], +'ImagickDraw::setResolution' => ['void', 'x_resolution'=>'float', 'y_resolution'=>'float'], +'ImagickDraw::setStrokeAlpha' => ['bool', 'opacity'=>'float'], +'ImagickDraw::setStrokeAntialias' => ['bool', 'stroke_antialias'=>'bool'], +'ImagickDraw::setStrokeColor' => ['bool', 'stroke_pixel'=>'ImagickPixel|string'], +'ImagickDraw::setStrokeDashArray' => ['bool', 'dasharray'=>'array'], +'ImagickDraw::setStrokeDashOffset' => ['bool', 'dash_offset'=>'float'], +'ImagickDraw::setStrokeLineCap' => ['bool', 'linecap'=>'int'], +'ImagickDraw::setStrokeLineJoin' => ['bool', 'linejoin'=>'int'], +'ImagickDraw::setStrokeMiterLimit' => ['bool', 'miterlimit'=>'int'], +'ImagickDraw::setStrokeOpacity' => ['bool', 'stroke_opacity'=>'float'], +'ImagickDraw::setStrokePatternURL' => ['bool', 'stroke_url'=>'string'], +'ImagickDraw::setStrokeWidth' => ['bool', 'stroke_width'=>'float'], +'ImagickDraw::setTextAlignment' => ['bool', 'alignment'=>'int'], +'ImagickDraw::setTextAntialias' => ['bool', 'antialias'=>'bool'], +'ImagickDraw::setTextDecoration' => ['bool', 'decoration'=>'int'], +'ImagickDraw::setTextDirection' => ['bool', 'direction'=>'int'], +'ImagickDraw::setTextEncoding' => ['bool', 'encoding'=>'string'], +'ImagickDraw::setTextInterlineSpacing' => ['void', 'spacing'=>'float'], +'ImagickDraw::setTextInterwordSpacing' => ['void', 'spacing'=>'float'], +'ImagickDraw::setTextKerning' => ['void', 'kerning'=>'float'], +'ImagickDraw::setTextUnderColor' => ['bool', 'under_color'=>'ImagickPixel|string'], +'ImagickDraw::setVectorGraphics' => ['bool', 'xml'=>'string'], +'ImagickDraw::setViewbox' => ['bool', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int'], +'ImagickDraw::skewX' => ['bool', 'degrees'=>'float'], +'ImagickDraw::skewY' => ['bool', 'degrees'=>'float'], +'ImagickDraw::translate' => ['bool', 'x'=>'float', 'y'=>'float'], +'ImagickKernel::addKernel' => ['void', 'ImagickKernel'=>'ImagickKernel'], +'ImagickKernel::addUnityKernel' => ['void'], +'ImagickKernel::fromBuiltin' => ['ImagickKernel', 'kernelType'=>'string', 'kernelString'=>'string'], +'ImagickKernel::fromMatrix' => ['ImagickKernel', 'matrix'=>'array', 'origin='=>'array'], +'ImagickKernel::getMatrix' => ['array'], +'ImagickKernel::scale' => ['void'], +'ImagickKernel::separate' => ['array'], +'ImagickKernel::seperate' => ['void'], +'ImagickPixel::__construct' => ['void', 'color='=>'string'], +'ImagickPixel::clear' => ['bool'], +'ImagickPixel::clone' => ['void'], +'ImagickPixel::destroy' => ['bool'], +'ImagickPixel::getColor' => ['array', 'normalized='=>'bool'], +'ImagickPixel::getColorAsString' => ['string'], +'ImagickPixel::getColorCount' => ['int'], +'ImagickPixel::getColorQuantum' => ['mixed'], +'ImagickPixel::getColorValue' => ['float', 'color'=>'int'], +'ImagickPixel::getColorValueQuantum' => ['mixed'], +'ImagickPixel::getHSL' => ['array'], +'ImagickPixel::getIndex' => ['int'], +'ImagickPixel::isPixelSimilar' => ['bool', 'color'=>'ImagickPixel', 'fuzz'=>'float'], +'ImagickPixel::isPixelSimilarQuantum' => ['bool', 'color'=>'string', 'fuzz='=>'string'], +'ImagickPixel::isSimilar' => ['bool', 'color'=>'imagickpixel', 'fuzz'=>'float'], +'ImagickPixel::setColor' => ['bool', 'color'=>'string'], +'ImagickPixel::setcolorcount' => ['void', 'colorCount'=>'string'], +'ImagickPixel::setColorFromPixel' => ['bool', 'srcPixel'=>'ImagickPixel'], +'ImagickPixel::setColorValue' => ['bool', 'color'=>'int', 'value'=>'float'], +'ImagickPixel::setColorValueQuantum' => ['void', 'color'=>'int', 'value'=>'mixed'], +'ImagickPixel::setHSL' => ['bool', 'hue'=>'float', 'saturation'=>'float', 'luminosity'=>'float'], +'ImagickPixel::setIndex' => ['void', 'index'=>'int'], +'ImagickPixelIterator::__construct' => ['void', 'wand'=>'imagick'], +'ImagickPixelIterator::clear' => ['bool'], +'ImagickPixelIterator::current' => ['mixed'], +'ImagickPixelIterator::destroy' => ['bool'], +'ImagickPixelIterator::getCurrentIteratorRow' => ['array'], +'ImagickPixelIterator::getIteratorRow' => ['int'], +'ImagickPixelIterator::getNextIteratorRow' => ['array'], +'ImagickPixelIterator::getpixeliterator' => ['', 'Imagick'=>'Imagick'], +'ImagickPixelIterator::getpixelregioniterator' => ['', 'Imagick'=>'Imagick', 'x'=>'', 'y'=>'', 'columns'=>'', 'rows'=>''], +'ImagickPixelIterator::getPreviousIteratorRow' => ['array'], +'ImagickPixelIterator::key' => ['int|string'], +'ImagickPixelIterator::newPixelIterator' => ['bool', 'wand'=>'imagick'], +'ImagickPixelIterator::newPixelRegionIterator' => ['bool', 'wand'=>'imagick', 'x'=>'int', 'y'=>'int', 'columns'=>'int', 'rows'=>'int'], +'ImagickPixelIterator::next' => ['void'], +'ImagickPixelIterator::resetIterator' => ['bool'], +'ImagickPixelIterator::rewind' => ['void'], +'ImagickPixelIterator::setIteratorFirstRow' => ['bool'], +'ImagickPixelIterator::setIteratorLastRow' => ['bool'], +'ImagickPixelIterator::setIteratorRow' => ['bool', 'row'=>'int'], +'ImagickPixelIterator::syncIterator' => ['bool'], +'ImagickPixelIterator::valid' => ['bool'], +'imap_8bit' => ['string|false', 'text'=>'string'], +'imap_alerts' => ['array|false'], +'imap_append' => ['bool', 'stream_id'=>'resource', 'folder'=>'string', 'message'=>'string', 'options='=>'string', 'internal_date='=>'string'], +'imap_base64' => ['string|false', 'text'=>'string'], +'imap_binary' => ['string|false', 'text'=>'string'], +'imap_body' => ['string|false', 'stream_id'=>'resource', 'msg_no'=>'int', 'options='=>'int'], +'imap_bodystruct' => ['stdClass|false', 'stream_id'=>'resource', 'msg_no'=>'int', 'section'=>'string'], +'imap_check' => ['stdClass|false', 'stream_id'=>'resource'], +'imap_clearflag_full' => ['bool', 'stream_id'=>'resource', 'sequence'=>'string', 'flag'=>'string', 'options='=>'int'], +'imap_close' => ['bool', 'stream_id'=>'resource', 'options='=>'int'], +'imap_create' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'], +'imap_createmailbox' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'], +'imap_delete' => ['bool', 'stream_id'=>'resource', 'msg_no'=>'int', 'options='=>'int'], +'imap_deletemailbox' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'], +'imap_errors' => ['array|false'], +'imap_expunge' => ['bool', 'stream_id'=>'resource'], +'imap_fetch_overview' => ['array|false', 'stream_id'=>'resource', 'sequence'=>'string', 'options='=>'int'], +'imap_fetchbody' => ['string|false', 'stream_id'=>'resource', 'msg_no'=>'int', 'section'=>'string', 'options='=>'int'], +'imap_fetchheader' => ['string|false', 'stream_id'=>'resource', 'msg_no'=>'int', 'options='=>'int'], +'imap_fetchmime' => ['string|false', 'stream_id'=>'resource', 'msg_no'=>'int', 'section'=>'string', 'options='=>'int'], +'imap_fetchstructure' => ['stdClass|false', 'stream_id'=>'resource', 'msg_no'=>'int', 'options='=>'int'], +'imap_fetchtext' => ['string|false', 'stream_id'=>'resource', 'msg_no'=>'int', 'options='=>'int'], +'imap_gc' => ['bool', 'stream_id'=>'resource', 'flags'=>'int'], +'imap_get_quota' => ['array|false', 'stream_id'=>'resource', 'qroot'=>'string'], +'imap_get_quotaroot' => ['array|false', 'stream_id'=>'resource', 'mbox'=>'string'], +'imap_getacl' => ['array|false', 'stream_id'=>'resource', 'mailbox'=>'string'], +'imap_getmailboxes' => ['array|false', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'], +'imap_getsubscribed' => ['array|false', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'], +'imap_header' => ['stdClass|false', 'stream_id'=>'resource', 'msg_no'=>'int', 'from_length='=>'int', 'subject_length='=>'int', 'default_host='=>'string'], +'imap_headerinfo' => ['stdClass|false', 'stream_id'=>'resource', 'msg_no'=>'int', 'from_length='=>'int', 'subject_length='=>'int', 'default_host='=>'string|null'], +'imap_headers' => ['array|false', 'stream_id'=>'resource'], +'imap_last_error' => ['string|false'], +'imap_list' => ['array|false', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'], +'imap_listmailbox' => ['array|false', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'], +'imap_listscan' => ['array|false', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string', 'content'=>'string'], +'imap_listsubscribed' => ['array|false', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'], +'imap_lsub' => ['array|false', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'], +'imap_mail' => ['bool', 'to'=>'string', 'subject'=>'string', 'message'=>'string', 'additional_headers='=>'string', 'cc='=>'string', 'bcc='=>'string', 'rpath='=>'string'], +'imap_mail_compose' => ['string|false', 'envelope'=>'array', 'body'=>'array'], +'imap_mail_copy' => ['bool', 'stream_id'=>'resource', 'msglist'=>'string', 'mailbox'=>'string', 'options='=>'int'], +'imap_mail_move' => ['bool', 'stream_id'=>'resource', 'sequence'=>'string', 'mailbox'=>'string', 'options='=>'int'], +'imap_mailboxmsginfo' => ['stdClass|false', 'stream_id'=>'resource'], +'imap_mime_header_decode' => ['array|false', 'string'=>'string'], +'imap_msgno' => ['int|false', 'stream_id'=>'resource', 'unique_msg_id'=>'int'], +'imap_mutf7_to_utf8' => ['string|false', 'in'=>'string'], +'imap_num_msg' => ['int|false', 'stream_id'=>'resource'], +'imap_num_recent' => ['int|false', 'stream_id'=>'resource'], +'imap_open' => ['resource|false', 'mailbox'=>'string', 'user'=>'string', 'password'=>'string', 'options='=>'int', 'n_retries='=>'int', 'params='=>'?array'], +'imap_ping' => ['bool', 'stream_id'=>'resource'], +'imap_qprint' => ['string|false', 'text'=>'string'], +'imap_rename' => ['bool', 'stream_id'=>'resource', 'old_name'=>'string', 'new_name'=>'string'], +'imap_renamemailbox' => ['bool', 'stream_id'=>'resource', 'old_name'=>'string', 'new_name'=>'string'], +'imap_reopen' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string', 'options='=>'int', 'n_retries='=>'int'], +'imap_rfc822_parse_adrlist' => ['array', 'address_string'=>'string', 'default_host'=>'string'], +'imap_rfc822_parse_headers' => ['stdClass', 'headers'=>'string', 'default_host='=>'string'], +'imap_rfc822_write_address' => ['string|false', 'mailbox'=>'?string', 'host'=>'?string', 'personal'=>'?string'], +'imap_savebody' => ['bool', 'stream_id'=>'resource', 'file'=>'string|resource', 'msg_no'=>'int', 'section='=>'string', 'options='=>'int'], +'imap_scan' => ['array|false', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string', 'content'=>'string'], +'imap_scanmailbox' => ['array|false', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string', 'content'=>'string'], +'imap_search' => ['array|false', 'stream_id'=>'resource', 'criteria'=>'string', 'options='=>'int', 'charset='=>'string'], +'imap_set_quota' => ['bool', 'stream_id'=>'resource', 'qroot'=>'string', 'mailbox_size'=>'int'], +'imap_setacl' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string', 'id'=>'string', 'rights'=>'string'], +'imap_setflag_full' => ['bool', 'stream_id'=>'resource', 'sequence'=>'string', 'flag'=>'string', 'options='=>'int'], +'imap_sort' => ['array|false', 'stream_id'=>'resource', 'criteria'=>'int', 'reverse'=>'int', 'options='=>'int', 'search_criteria='=>'string', 'charset='=>'string'], +'imap_status' => ['stdClass|false', 'stream_id'=>'resource', 'mailbox'=>'string', 'options'=>'int'], +'imap_subscribe' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'], +'imap_thread' => ['array|false', 'stream_id'=>'resource', 'options='=>'int'], +'imap_timeout' => ['int|bool', 'timeout_type'=>'int', 'timeout='=>'int'], +'imap_uid' => ['int|false', 'stream_id'=>'resource', 'msg_no'=>'int'], +'imap_undelete' => ['bool', 'stream_id'=>'resource', 'msg_no'=>'int', 'flags='=>'int'], +'imap_unsubscribe' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'], +'imap_utf7_decode' => ['string|false', 'buf'=>'string'], +'imap_utf7_encode' => ['string', 'buf'=>'string'], +'imap_utf8' => ['string', 'mime_encoded_text'=>'string'], +'imap_utf8_to_mutf7' => ['string|false', 'in'=>'string'], +'implode' => ['string', 'glue'=>'string', 'pieces'=>'array'], +'implode\'1' => ['string', 'pieces'=>'array'], +'import_request_variables' => ['bool', 'types'=>'string', 'prefix='=>'string'], +'in_array' => ['bool', 'needle'=>'mixed', 'haystack'=>'array', 'strict='=>'bool'], +'inclued_get_data' => ['array'], +'inet_ntop' => ['string|false', 'in_addr'=>'string'], +'inet_pton' => ['string|false', 'ip_address'=>'string'], +'InfiniteIterator::__construct' => ['void', 'iterator'=>'Iterator'], +'InfiniteIterator::current' => ['mixed'], +'InfiniteIterator::getInnerIterator' => ['Traversable'], +'InfiniteIterator::key' => ['bool|float|int|string'], +'InfiniteIterator::next' => ['void'], +'InfiniteIterator::rewind' => ['void'], +'InfiniteIterator::valid' => ['bool'], +'inflate_add' => ['string|false', 'context'=>'resource', 'encoded_data'=>'string', 'flush_mode='=>'int'], +'inflate_get_read_len' => ['int|false', 'resource'=>'resource'], +'inflate_get_status' => ['int|false', 'resource'=>'resource'], +'inflate_init' => ['resource|false', 'encoding'=>'int', 'options='=>'array'], +'ingres_autocommit' => ['bool', 'link'=>'resource'], +'ingres_autocommit_state' => ['bool', 'link'=>'resource'], +'ingres_charset' => ['string', 'link'=>'resource'], +'ingres_close' => ['bool', 'link'=>'resource'], +'ingres_commit' => ['bool', 'link'=>'resource'], +'ingres_connect' => ['resource', 'database='=>'string', 'username='=>'string', 'password='=>'string', 'options='=>'array'], +'ingres_cursor' => ['string', 'result'=>'resource'], +'ingres_errno' => ['int', 'link='=>'resource'], +'ingres_error' => ['string', 'link='=>'resource'], +'ingres_errsqlstate' => ['string', 'link='=>'resource'], +'ingres_escape_string' => ['string', 'link'=>'resource', 'source_string'=>'string'], +'ingres_execute' => ['bool', 'result'=>'resource', 'params='=>'array', 'types='=>'string'], +'ingres_fetch_array' => ['array', 'result'=>'resource', 'result_type='=>'int'], +'ingres_fetch_assoc' => ['array', 'result'=>'resource'], +'ingres_fetch_object' => ['object', 'result'=>'resource', 'result_type='=>'int'], +'ingres_fetch_proc_return' => ['int', 'result'=>'resource'], +'ingres_fetch_row' => ['array', 'result'=>'resource'], +'ingres_field_length' => ['int', 'result'=>'resource', 'index'=>'int'], +'ingres_field_name' => ['string', 'result'=>'resource', 'index'=>'int'], +'ingres_field_nullable' => ['bool', 'result'=>'resource', 'index'=>'int'], +'ingres_field_precision' => ['int', 'result'=>'resource', 'index'=>'int'], +'ingres_field_scale' => ['int', 'result'=>'resource', 'index'=>'int'], +'ingres_field_type' => ['string', 'result'=>'resource', 'index'=>'int'], +'ingres_free_result' => ['bool', 'result'=>'resource'], +'ingres_next_error' => ['bool', 'link='=>'resource'], +'ingres_num_fields' => ['int', 'result'=>'resource'], +'ingres_num_rows' => ['int', 'result'=>'resource'], +'ingres_pconnect' => ['resource', 'database='=>'string', 'username='=>'string', 'password='=>'string', 'options='=>'array'], +'ingres_prepare' => ['mixed', 'link'=>'resource', 'query'=>'string'], +'ingres_query' => ['mixed', 'link'=>'resource', 'query'=>'string', 'params='=>'array', 'types='=>'string'], +'ingres_result_seek' => ['bool', 'result'=>'resource', 'position'=>'int'], +'ingres_rollback' => ['bool', 'link'=>'resource'], +'ingres_set_environment' => ['bool', 'link'=>'resource', 'options'=>'array'], +'ingres_unbuffered_query' => ['mixed', 'link'=>'resource', 'query'=>'string', 'params='=>'array', 'types='=>'string'], +'ini_alter' => ['string|false', 'varname'=>'string', 'newvalue'=>'string'], +'ini_get' => ['string|false', 'varname'=>'string'], +'ini_get_all' => ['array|false', 'extension='=>'?string', 'details='=>'bool'], +'ini_restore' => ['void', 'varname'=>'string'], +'ini_set' => ['string|false', 'varname'=>'string', 'newvalue'=>'string'], +'inotify_add_watch' => ['int', 'inotify_instance'=>'resource', 'pathname'=>'string', 'mask'=>'int'], +'inotify_init' => ['resource|false'], +'inotify_queue_len' => ['int', 'inotify_instance'=>'resource'], +'inotify_read' => ['array|false', 'inotify_instance'=>'resource'], +'inotify_rm_watch' => ['bool', 'inotify_instance'=>'resource', 'watch_descriptor'=>'int'], +'intdiv' => ['int', 'numerator'=>'int', 'divisor'=>'int'], +'interface_exists' => ['bool', 'classname'=>'string', 'autoload='=>'bool'], +'intl_error_name' => ['string', 'error_code'=>'int'], +'intl_get_error_code' => ['int'], +'intl_get_error_message' => ['string'], +'intl_is_failure' => ['bool', 'error_code'=>'int'], +'IntlBreakIterator::__construct' => ['void'], +'IntlBreakIterator::createCharacterInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlBreakIterator::createCodePointInstance' => ['IntlCodePointBreakIterator'], +'IntlBreakIterator::createLineInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlBreakIterator::createSentenceInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlBreakIterator::createTitleInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlBreakIterator::createWordInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlBreakIterator::current' => ['int'], +'IntlBreakIterator::first' => ['int'], +'IntlBreakIterator::following' => ['int', 'offset'=>'int'], +'IntlBreakIterator::getErrorCode' => ['int'], +'IntlBreakIterator::getErrorMessage' => ['string'], +'IntlBreakIterator::getLocale' => ['string', 'locale_type'=>'string'], +'IntlBreakIterator::getPartsIterator' => ['IntlPartsIterator', 'key_type='=>'int'], +'IntlBreakIterator::getText' => ['string'], +'IntlBreakIterator::isBoundary' => ['bool', 'offset'=>'int'], +'IntlBreakIterator::last' => ['int'], +'IntlBreakIterator::next' => ['int', 'offset='=>'int'], +'IntlBreakIterator::preceding' => ['int', 'offset'=>'int'], +'IntlBreakIterator::previous' => ['int'], +'IntlBreakIterator::setText' => ['bool', 'text'=>'string'], +'intlcal_add' => ['bool', 'calendar'=>'IntlCalendar', 'field'=>'int', 'amount'=>'int'], +'intlcal_after' => ['bool', 'calendar'=>'IntlCalendar', 'other'=>'IntlCalendar'], +'intlcal_before' => ['bool', 'calendar'=>'IntlCalendar', 'other'=>'IntlCalendar'], +'intlcal_clear' => ['bool', 'calendar'=>'IntlCalendar', 'field='=>'int'], +'intlcal_create_instance' => ['IntlCalendar', 'timeZone='=>'mixed', 'locale='=>'string'], +'intlcal_equals' => ['bool', 'calendar'=>'IntlCalendar', 'other'=>'IntlCalendar'], +'intlcal_field_difference' => ['int', 'calendar'=>'IntlCalendar', 'when'=>'float', 'field'=>'int'], +'intlcal_from_date_time' => ['IntlCalendar', 'dateTime'=>'DateTime|string'], +'intlcal_get' => ['mixed', 'calendar'=>'IntlCalendar', 'field'=>'int'], +'intlcal_get_actual_maximum' => ['int', 'calendar'=>'IntlCalendar', 'field'=>'int'], +'intlcal_get_actual_minimum' => ['int', 'calendar'=>'IntlCalendar', 'field'=>'int'], +'intlcal_get_available_locales' => ['array'], +'intlcal_get_day_of_week_type' => ['int', 'calendar'=>'IntlCalendar', 'dayOfWeek'=>'int'], +'intlcal_get_first_day_of_week' => ['int', 'calendar'=>'IntlCalendar'], +'intlcal_get_greatest_minimum' => ['int', 'calendar'=>'IntlCalendar', 'field'=>'int'], +'intlcal_get_keyword_values_for_locale' => ['Iterator|false', 'key'=>'string', 'locale'=>'string', 'commonlyUsed'=>'bool'], +'intlcal_get_least_maximum' => ['int', 'calendar'=>'IntlCalendar', 'field'=>'int'], +'intlcal_get_locale' => ['string', 'calendar'=>'IntlCalendar', 'localeType'=>'int'], +'intlcal_get_maximum' => ['int|false', 'calendar'=>'IntlCalendar', 'field'=>'int'], +'intlcal_get_minimal_days_in_first_week' => ['int', 'calendar'=>'IntlCalendar'], +'intlcal_get_minimum' => ['int', 'calendar'=>'IntlCalendar', 'field'=>'int'], +'intlcal_get_now' => ['float'], +'intlcal_get_repeated_wall_time_option' => ['int', 'calendar'=>'IntlCalendar'], +'intlcal_get_skipped_wall_time_option' => ['int', 'calendar'=>'IntlCalendar'], +'intlcal_get_time' => ['float', 'calendar'=>'IntlCalendar'], +'intlcal_get_time_zone' => ['IntlTimeZone', 'calendar'=>'IntlCalendar'], +'intlcal_get_type' => ['string', 'calendar'=>'IntlCalendar'], +'intlcal_get_weekend_transition' => ['int', 'calendar'=>'IntlCalendar', 'dayOfWeek'=>'string'], +'intlcal_in_daylight_time' => ['bool', 'calendar'=>'IntlCalendar'], +'intlcal_is_equivalent_to' => ['bool', 'calendar'=>'IntlCalendar', 'other'=>'IntlCalendar'], +'intlcal_is_lenient' => ['bool', 'calendar'=>'IntlCalendar'], +'intlcal_is_set' => ['bool', 'calendar'=>'IntlCalendar', 'field'=>'int'], +'intlcal_is_weekend' => ['bool', 'calendar'=>'IntlCalendar', 'date='=>'float'], +'intlcal_roll' => ['bool', 'calendar'=>'IntlCalendar', 'field'=>'int', 'amountOrUpOrDown'=>'mixed'], +'intlcal_set' => ['bool', 'calendar'=>'IntlCalendar', 'field'=>'int', 'value'=>'int'], +'intlcal_set\'1' => ['bool', 'calendar'=>'IntlCalendar', 'year'=>'int', 'month'=>'int', 'dayOfMonth='=>'int', 'hour='=>'int', 'minute='=>'int', 'second='=>'int'], +'intlcal_set_first_day_of_week' => ['bool', 'calendar'=>'IntlCalendar', 'dayOfWeek'=>'int'], +'intlcal_set_lenient' => ['bool', 'calendar'=>'IntlCalendar', 'isLenient'=>'bool'], +'intlcal_set_repeated_wall_time_option' => ['bool', 'calendar'=>'IntlCalendar', 'wallTimeOption'=>'int'], +'intlcal_set_skipped_wall_time_option' => ['bool', 'calendar'=>'IntlCalendar', 'wallTimeOption'=>'int'], +'intlcal_set_time' => ['bool', 'calendar'=>'IntlCalendar', 'date'=>'float'], +'intlcal_set_time_zone' => ['bool', 'calendar'=>'IntlCalendar', 'timeZone'=>'mixed'], +'intlcal_to_date_time' => ['DateTime|false', 'calendar'=>'IntlCalendar'], +'IntlCalendar::__construct' => ['void'], +'IntlCalendar::add' => ['bool', 'field'=>'int', 'amount'=>'int'], +'IntlCalendar::after' => ['bool', 'other'=>'IntlCalendar'], +'IntlCalendar::before' => ['bool', 'other'=>'IntlCalendar'], +'IntlCalendar::clear' => ['bool', 'field='=>'int'], +'IntlCalendar::createInstance' => ['IntlCalendar', 'timeZone='=>'mixed', 'locale='=>'string'], +'IntlCalendar::equals' => ['bool', 'other'=>'IntlCalendar'], +'IntlCalendar::fieldDifference' => ['int', 'when'=>'float', 'field'=>'int'], +'IntlCalendar::fromDateTime' => ['IntlCalendar', 'dateTime'=>'DateTime|string'], +'IntlCalendar::get' => ['int', 'field'=>'int'], +'IntlCalendar::getActualMaximum' => ['int', 'field'=>'int'], +'IntlCalendar::getActualMinimum' => ['int', 'field'=>'int'], +'IntlCalendar::getAvailableLocales' => ['array'], +'IntlCalendar::getDayOfWeekType' => ['int', 'dayOfWeek'=>'int'], +'IntlCalendar::getErrorCode' => ['int'], +'IntlCalendar::getErrorMessage' => ['string'], +'IntlCalendar::getFirstDayOfWeek' => ['int'], +'IntlCalendar::getGreatestMinimum' => ['int', 'field'=>'int'], +'IntlCalendar::getKeywordValuesForLocale' => ['Iterator|false', 'key'=>'string', 'locale'=>'string', 'commonlyUsed'=>'bool'], +'IntlCalendar::getLeastMaximum' => ['int', 'field'=>'int'], +'IntlCalendar::getLocale' => ['string', 'localeType'=>'int'], +'IntlCalendar::getMaximum' => ['int|false', 'field'=>'int'], +'IntlCalendar::getMinimalDaysInFirstWeek' => ['int'], +'IntlCalendar::getMinimum' => ['int', 'field'=>'int'], +'IntlCalendar::getNow' => ['float'], +'IntlCalendar::getRepeatedWallTimeOption' => ['int'], +'IntlCalendar::getSkippedWallTimeOption' => ['int'], +'IntlCalendar::getTime' => ['float'], +'IntlCalendar::getTimeZone' => ['IntlTimeZone'], +'IntlCalendar::getType' => ['string'], +'IntlCalendar::getWeekendTransition' => ['int', 'dayOfWeek'=>'string'], +'IntlCalendar::inDaylightTime' => ['bool'], +'IntlCalendar::isEquivalentTo' => ['bool', 'other'=>'IntlCalendar'], +'IntlCalendar::isLenient' => ['bool'], +'IntlCalendar::isSet' => ['bool', 'field'=>'int'], +'IntlCalendar::isWeekend' => ['bool', 'date='=>'float'], +'IntlCalendar::roll' => ['bool', 'field'=>'int', 'amountOrUpOrDown'=>'mixed'], +'IntlCalendar::set' => ['bool', 'field'=>'int', 'value'=>'int'], +'IntlCalendar::set\'1' => ['bool', 'year'=>'int', 'month'=>'int', 'dayOfMonth='=>'int', 'hour='=>'int', 'minute='=>'int', 'second='=>'int'], +'IntlCalendar::setFirstDayOfWeek' => ['bool', 'dayOfWeek'=>'int'], +'IntlCalendar::setLenient' => ['bool', 'isLenient'=>'string'], +'IntlCalendar::setMinimalDaysInFirstWeek' => ['bool', 'minimalDays'=>'int'], +'IntlCalendar::setRepeatedWallTimeOption' => ['bool', 'wallTimeOption'=>'int'], +'IntlCalendar::setSkippedWallTimeOption' => ['bool', 'wallTimeOption'=>'int'], +'IntlCalendar::setTime' => ['bool', 'date'=>'float'], +'IntlCalendar::setTimeZone' => ['bool', 'timeZone'=>'mixed'], +'IntlCalendar::toDateTime' => ['DateTime|false'], +'IntlChar::charAge' => ['array', 'char'=>'int|string'], +'IntlChar::charDigitValue' => ['int', 'codepoint'=>'mixed'], +'IntlChar::charDirection' => ['int', 'codepoint'=>'mixed'], +'IntlChar::charFromName' => ['?int', 'name'=>'string', 'namechoice='=>'int'], +'IntlChar::charMirror' => ['mixed', 'codepoint'=>'mixed'], +'IntlChar::charName' => ['string', 'char'=>'int|string', 'namechoice='=>'int'], +'IntlChar::charType' => ['int', 'codepoint'=>'mixed'], +'IntlChar::chr' => ['string', 'codepoint'=>'mixed'], +'IntlChar::digit' => ['int|false', 'char'=>'int|string', 'radix='=>'int'], +'IntlChar::enumCharNames' => ['void', 'start'=>'mixed', 'limit'=>'mixed', 'callback'=>'callable', 'nameChoice='=>'int'], +'IntlChar::enumCharTypes' => ['void', 'cb='=>'callable'], +'IntlChar::foldCase' => ['int|string', 'char'=>'int|string', 'options='=>'int'], +'IntlChar::forDigit' => ['int', 'digit'=>'int', 'radix'=>'int'], +'IntlChar::getBidiPairedBracket' => ['mixed', 'codepoint'=>'mixed'], +'IntlChar::getBlockCode' => ['int', 'char'=>'int|string'], +'IntlChar::getCombiningClass' => ['int', 'codepoint'=>'mixed'], +'IntlChar::getFC_NFKC_Closure' => ['string', 'char'=>'int|string'], +'IntlChar::getIntPropertyMaxValue' => ['int', 'property'=>'int'], +'IntlChar::getIntPropertyMinValue' => ['int', 'property'=>'int'], +'IntlChar::getIntPropertyMxValue' => ['int', 'property'=>'int'], +'IntlChar::getIntPropertyValue' => ['int', 'char'=>'int|string', 'property'=>'int'], +'IntlChar::getNumericValue' => ['float', 'char'=>'int|string'], +'IntlChar::getPropertyEnum' => ['int', 'alias'=>'string'], +'IntlChar::getPropertyName' => ['string|false', 'property'=>'int', 'namechoice='=>'int'], +'IntlChar::getPropertyValueEnum' => ['int', 'property'=>'int', 'name'=>'string'], +'IntlChar::getPropertyValueName' => ['string|false', 'prop'=>'int', 'value'=>'int', 'namechoice='=>'int'], +'IntlChar::getUnicodeVersion' => ['array'], +'IntlChar::hasBinaryProperty' => ['bool', 'char'=>'int|string', 'property'=>'int'], +'IntlChar::isalnum' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isalpha' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isbase' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isblank' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::iscntrl' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isdefined' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isdigit' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isgraph' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isIDIgnorable' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isIDPart' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isIDStart' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isISOControl' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isJavaIDPart' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isJavaIDStart' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isJavaSpaceChar' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::islower' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isMirrored' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isprint' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::ispunct' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isspace' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::istitle' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isUAlphabetic' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isULowercase' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isupper' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isUUppercase' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isUWhiteSpace' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isWhitespace' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::isxdigit' => ['bool', 'codepoint'=>'mixed'], +'IntlChar::ord' => ['int', 'character'=>'mixed'], +'IntlChar::tolower' => ['mixed', 'codepoint'=>'mixed'], +'IntlChar::totitle' => ['mixed', 'codepoint'=>'mixed'], +'IntlChar::toupper' => ['mixed', 'codepoint'=>'mixed'], +'IntlCodePointBreakIterator::__construct' => ['void'], +'IntlCodePointBreakIterator::createCharacterInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlCodePointBreakIterator::createCodePointInstance' => ['IntlCodePointBreakIterator'], +'IntlCodePointBreakIterator::createLineInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlCodePointBreakIterator::createSentenceInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlCodePointBreakIterator::createTitleInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlCodePointBreakIterator::createWordInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlCodePointBreakIterator::current' => ['int'], +'IntlCodePointBreakIterator::first' => ['int'], +'IntlCodePointBreakIterator::following' => ['int', 'offset'=>'string'], +'IntlCodePointBreakIterator::getErrorCode' => ['int'], +'IntlCodePointBreakIterator::getErrorMessage' => ['string'], +'IntlCodePointBreakIterator::getLastCodePoint' => ['int'], +'IntlCodePointBreakIterator::getLocale' => ['string', 'locale_type'=>'string'], +'IntlCodePointBreakIterator::getPartsIterator' => ['IntlPartsIterator', 'key_type='=>'string'], +'IntlCodePointBreakIterator::getText' => ['string'], +'IntlCodePointBreakIterator::isBoundary' => ['bool', 'offset'=>'string'], +'IntlCodePointBreakIterator::last' => ['int'], +'IntlCodePointBreakIterator::next' => ['int', 'offset='=>'string'], +'IntlCodePointBreakIterator::preceding' => ['int', 'offset'=>'string'], +'IntlCodePointBreakIterator::previous' => ['int'], +'IntlCodePointBreakIterator::setText' => ['bool', 'text'=>'string'], +'IntlDateFormatter::__construct' => ['void', 'locale'=>'?string', 'datetype'=>'?int', 'timetype'=>'?int', 'timezone='=>'null|string|IntlTimeZone|DateTimeZone', 'calendar='=>'null|int|IntlCalendar', 'pattern='=>'string'], +'IntlDateFormatter::create' => ['IntlDateFormatter|false', 'locale'=>'?string', 'datetype'=>'?int', 'timetype'=>'?int', 'timezone='=>'null|string|IntlTimeZone|DateTimeZone', 'calendar='=>'int|IntlCalendar', 'pattern='=>'string'], +'IntlDateFormatter::format' => ['string|false', 'args'=>''], +'IntlDateFormatter::formatObject' => ['string|false', 'object'=>'object', 'format='=>'mixed', 'locale='=>'string'], +'IntlDateFormatter::getCalendar' => ['int'], +'IntlDateFormatter::getCalendarObject' => ['IntlCalendar'], +'IntlDateFormatter::getDateType' => ['int'], +'IntlDateFormatter::getErrorCode' => ['int'], +'IntlDateFormatter::getErrorMessage' => ['string'], +'IntlDateFormatter::getLocale' => ['string|false'], +'IntlDateFormatter::getPattern' => ['string'], +'IntlDateFormatter::getTimeType' => ['int'], +'IntlDateFormatter::getTimeZone' => ['IntlTimeZone|false'], +'IntlDateFormatter::getTimeZoneId' => ['string'], +'IntlDateFormatter::isLenient' => ['bool'], +'IntlDateFormatter::localtime' => ['array', 'text_to_parse'=>'string', '&w_parse_pos='=>'int'], +'IntlDateFormatter::parse' => ['int|false', 'text_to_parse'=>'string', '&rw_parse_pos='=>'int'], +'IntlDateFormatter::setCalendar' => ['bool', 'calendar'=>''], +'IntlDateFormatter::setLenient' => ['bool', 'lenient'=>'bool'], +'IntlDateFormatter::setPattern' => ['bool', 'pattern'=>'string'], +'IntlDateFormatter::setTimeZone' => ['bool', 'timezone'=>''], +'IntlDateFormatter::setTimeZoneId' => ['bool', 'zone'=>'string', 'fmt='=>'IntlDateFormatter'], +'IntlException::__clone' => ['void'], +'IntlException::__construct' => ['void'], +'IntlException::__toString' => ['string'], +'IntlException::__wakeup' => ['void'], +'IntlException::getCode' => ['int'], +'IntlException::getFile' => ['string'], +'IntlException::getLine' => ['int'], +'IntlException::getMessage' => ['string'], +'IntlException::getPrevious' => ['?Throwable'], +'IntlException::getTrace' => ['list>'], +'IntlException::getTraceAsString' => ['string'], +'intlgregcal_create_instance' => ['IntlGregorianCalendar', 'timeZone='=>'mixed', 'locale='=>'string'], +'intlgregcal_get_gregorian_change' => ['float', 'object'=>'IntlGregorianCalendar'], +'intlgregcal_is_leap_year' => ['bool', 'year'=>'int'], +'intlgregcal_set_gregorian_change' => ['void', 'object'=>'IntlGregorianCalendar', 'change'=>'float'], +'IntlGregorianCalendar::__construct' => ['void'], +'IntlGregorianCalendar::add' => ['bool', 'field'=>'int', 'amount'=>'int'], +'IntlGregorianCalendar::after' => ['bool', 'other'=>'IntlCalendar'], +'IntlGregorianCalendar::before' => ['bool', 'other'=>'IntlCalendar'], +'IntlGregorianCalendar::clear' => ['bool', 'field='=>'int'], +'IntlGregorianCalendar::createInstance' => ['IntlGregorianCalendar', 'timeZone='=>'mixed', 'locale='=>'string'], +'IntlGregorianCalendar::equals' => ['bool', 'other'=>'IntlCalendar'], +'IntlGregorianCalendar::fieldDifference' => ['int', 'when'=>'float', 'field'=>'int'], +'IntlGregorianCalendar::fromDateTime' => ['IntlCalendar', 'dateTime'=>'DateTime|string'], +'IntlGregorianCalendar::get' => ['int', 'field'=>'int'], +'IntlGregorianCalendar::getActualMaximum' => ['int', 'field'=>'int'], +'IntlGregorianCalendar::getActualMinimum' => ['int', 'field'=>'int'], +'IntlGregorianCalendar::getAvailableLocales' => ['array'], +'IntlGregorianCalendar::getDayOfWeekType' => ['int', 'dayOfWeek'=>'int'], +'IntlGregorianCalendar::getErrorCode' => ['int'], +'IntlGregorianCalendar::getErrorMessage' => ['string'], +'IntlGregorianCalendar::getFirstDayOfWeek' => ['int'], +'IntlGregorianCalendar::getGreatestMinimum' => ['int', 'field'=>'int'], +'IntlGregorianCalendar::getGregorianChange' => ['float'], +'IntlGregorianCalendar::getKeywordValuesForLocale' => ['Iterator', 'key'=>'string', 'locale'=>'string', 'commonlyUsed'=>'bool'], +'IntlGregorianCalendar::getLeastMaximum' => ['int', 'field'=>'int'], +'IntlGregorianCalendar::getLocale' => ['string', 'localeType'=>'int'], +'IntlGregorianCalendar::getMaximum' => ['int', 'field'=>'int'], +'IntlGregorianCalendar::getMinimalDaysInFirstWeek' => ['int'], +'IntlGregorianCalendar::getMinimum' => ['int', 'field'=>'int'], +'IntlGregorianCalendar::getNow' => ['float'], +'IntlGregorianCalendar::getRepeatedWallTimeOption' => ['int'], +'IntlGregorianCalendar::getSkippedWallTimeOption' => ['int'], +'IntlGregorianCalendar::getTime' => ['float'], +'IntlGregorianCalendar::getTimeZone' => ['IntlTimeZone'], +'IntlGregorianCalendar::getType' => ['string'], +'IntlGregorianCalendar::getWeekendTransition' => ['int', 'dayOfWeek'=>'string'], +'IntlGregorianCalendar::inDaylightTime' => ['bool'], +'IntlGregorianCalendar::isEquivalentTo' => ['bool', 'other'=>'IntlCalendar'], +'IntlGregorianCalendar::isLeapYear' => ['bool', 'year'=>'int'], +'IntlGregorianCalendar::isLenient' => ['bool'], +'IntlGregorianCalendar::isSet' => ['bool', 'field'=>'int'], +'IntlGregorianCalendar::isWeekend' => ['bool', 'date='=>'float'], +'IntlGregorianCalendar::roll' => ['bool', 'field'=>'int', 'amountOrUpOrDown'=>'mixed'], +'IntlGregorianCalendar::set' => ['bool', 'field'=>'int', 'value'=>'int'], +'IntlGregorianCalendar::set\'1' => ['bool', 'year'=>'int', 'month'=>'int', 'dayOfMonth='=>'int', 'hour='=>'int', 'minute='=>'int', 'second='=>'int'], +'IntlGregorianCalendar::setFirstDayOfWeek' => ['bool', 'dayOfWeek'=>'int'], +'IntlGregorianCalendar::setGregorianChange' => ['bool', 'date'=>'float'], +'IntlGregorianCalendar::setLenient' => ['bool', 'isLenient'=>'string'], +'IntlGregorianCalendar::setMinimalDaysInFirstWeek' => ['bool', 'minimalDays'=>'int'], +'IntlGregorianCalendar::setRepeatedWallTimeOption' => ['bool', 'wallTimeOption'=>'int'], +'IntlGregorianCalendar::setSkippedWallTimeOption' => ['bool', 'wallTimeOption'=>'int'], +'IntlGregorianCalendar::setTime' => ['bool', 'date'=>'float'], +'IntlGregorianCalendar::setTimeZone' => ['bool', 'timeZone'=>'mixed'], +'IntlGregorianCalendar::toDateTime' => ['DateTime'], +'IntlIterator::__construct' => ['void'], +'IntlIterator::current' => ['mixed'], +'IntlIterator::key' => ['string'], +'IntlIterator::next' => ['void'], +'IntlIterator::rewind' => ['void'], +'IntlIterator::valid' => ['bool'], +'IntlPartsIterator::getBreakIterator' => ['IntlBreakIterator'], +'IntlRuleBasedBreakIterator::__construct' => ['void', 'rules'=>'string', 'areCompiled='=>'string'], +'IntlRuleBasedBreakIterator::createCharacterInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlRuleBasedBreakIterator::createCodePointInstance' => ['IntlCodePointBreakIterator'], +'IntlRuleBasedBreakIterator::createLineInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlRuleBasedBreakIterator::createSentenceInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlRuleBasedBreakIterator::createTitleInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlRuleBasedBreakIterator::createWordInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'], +'IntlRuleBasedBreakIterator::current' => ['int'], +'IntlRuleBasedBreakIterator::first' => ['int'], +'IntlRuleBasedBreakIterator::following' => ['int', 'offset'=>'int'], +'IntlRuleBasedBreakIterator::getBinaryRules' => ['string'], +'IntlRuleBasedBreakIterator::getErrorCode' => ['int'], +'IntlRuleBasedBreakIterator::getErrorMessage' => ['string'], +'IntlRuleBasedBreakIterator::getLocale' => ['string', 'locale_type'=>'string'], +'IntlRuleBasedBreakIterator::getPartsIterator' => ['IntlPartsIterator', 'key_type='=>'int'], +'IntlRuleBasedBreakIterator::getRules' => ['string'], +'IntlRuleBasedBreakIterator::getRuleStatus' => ['int'], +'IntlRuleBasedBreakIterator::getRuleStatusVec' => ['array'], +'IntlRuleBasedBreakIterator::getText' => ['string'], +'IntlRuleBasedBreakIterator::isBoundary' => ['bool', 'offset'=>'int'], +'IntlRuleBasedBreakIterator::last' => ['int'], +'IntlRuleBasedBreakIterator::next' => ['int', 'offset='=>'int'], +'IntlRuleBasedBreakIterator::preceding' => ['int', 'offset'=>'int'], +'IntlRuleBasedBreakIterator::previous' => ['int'], +'IntlRuleBasedBreakIterator::setText' => ['bool', 'text'=>'string'], +'IntlTimeZone::countEquivalentIDs' => ['int|false', 'zoneId'=>'string'], +'IntlTimeZone::createDefault' => ['IntlTimeZone'], +'IntlTimeZone::createEnumeration' => ['IntlIterator|false', 'countryOrRawOffset='=>'mixed'], +'IntlTimeZone::createTimeZone' => ['IntlTimeZone|false', 'zoneId'=>'string'], +'IntlTimeZone::createTimeZoneIDEnumeration' => ['IntlIterator|false', 'zoneType'=>'int', 'region='=>'string', 'rawOffset='=>'int'], +'IntlTimeZone::fromDateTimeZone' => ['?IntlTimeZone', 'zoneId'=>'DateTimeZone'], +'IntlTimeZone::getCanonicalID' => ['string|false', 'zoneId'=>'string', '&w_isSystemID='=>'bool'], +'IntlTimeZone::getDisplayName' => ['string|false', 'isDaylight='=>'bool', 'style='=>'int', 'locale='=>'string'], +'IntlTimeZone::getDSTSavings' => ['int'], +'IntlTimeZone::getEquivalentID' => ['string|false', 'zoneId'=>'string', 'index'=>'int'], +'IntlTimeZone::getErrorCode' => ['int'], +'IntlTimeZone::getErrorMessage' => ['string'], +'IntlTimeZone::getGMT' => ['IntlTimeZone'], +'IntlTimeZone::getID' => ['string'], +'IntlTimeZone::getIDForWindowsID' => ['string', 'timezone'=>'string', 'region='=>'string'], +'IntlTimeZone::getOffset' => ['int', 'date'=>'float', 'local'=>'bool', '&w_rawOffset'=>'int', '&w_dstOffset'=>'int'], +'IntlTimeZone::getRawOffset' => ['int'], +'IntlTimeZone::getRegion' => ['string|false', 'zoneId'=>'string'], +'IntlTimeZone::getTZDataVersion' => ['string'], +'IntlTimeZone::getUnknown' => ['IntlTimeZone'], +'IntlTimeZone::getWindowsID' => ['string|false', 'timezone'=>'string'], +'IntlTimeZone::hasSameRules' => ['bool', 'otherTimeZone'=>'IntlTimeZone'], +'IntlTimeZone::toDateTimeZone' => ['DateTimeZone|false'], +'IntlTimeZone::useDaylightTime' => ['bool'], +'intltz_count_equivalent_ids' => ['int', 'zoneId'=>'string'], +'intltz_create_enumeration' => ['IntlIterator', 'countryOrRawOffset'=>'mixed'], +'intltz_create_time_zone' => ['IntlTimeZone', 'zoneId'=>'string'], +'intltz_from_date_time_zone' => ['IntlTimeZone', 'zoneId'=>'DateTimeZone'], +'intltz_get_canonical_id' => ['string', 'zoneId'=>'string', '&isSystemID'=>'bool'], +'intltz_get_display_name' => ['string', 'object'=>'IntlTimeZone', 'isDaylight'=>'bool', 'style'=>'int', 'locale'=>'string'], +'intltz_get_dst_savings' => ['int', 'object'=>'IntlTimeZone'], +'intltz_get_equivalent_id' => ['string', 'zoneId'=>'string', 'index'=>'int'], +'intltz_get_error_code' => ['int', 'object'=>'IntlTimeZone'], +'intltz_get_error_message' => ['string', 'object'=>'IntlTimeZone'], +'intltz_get_id' => ['string', 'object'=>'IntlTimeZone'], +'intltz_get_offset' => ['int', 'object'=>'IntlTimeZone', 'date'=>'float', 'local'=>'bool', '&rawOffset'=>'int', '&dstOffset'=>'int'], +'intltz_get_raw_offset' => ['int', 'object'=>'IntlTimeZone'], +'intltz_get_tz_data_version' => ['string', 'object'=>'IntlTimeZone'], +'intltz_getGMT' => ['IntlTimeZone'], +'intltz_has_same_rules' => ['bool', 'object'=>'IntlTimeZone', 'otherTimeZone'=>'IntlTimeZone'], +'intltz_to_date_time_zone' => ['DateTimeZone', 'object'=>'IntlTimeZone'], +'intltz_use_daylight_time' => ['bool', 'object'=>'IntlTimeZone'], +'intlz_create_default' => ['IntlTimeZone'], +'intval' => ['int', 'var'=>'mixed', 'base='=>'int'], +'InvalidArgumentException::__clone' => ['void'], +'InvalidArgumentException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?InvalidArgumentException'], +'InvalidArgumentException::__toString' => ['string'], +'InvalidArgumentException::getCode' => ['int'], +'InvalidArgumentException::getFile' => ['string'], +'InvalidArgumentException::getLine' => ['int'], +'InvalidArgumentException::getMessage' => ['string'], +'InvalidArgumentException::getPrevious' => ['Throwable|InvalidArgumentException|null'], +'InvalidArgumentException::getTrace' => ['list>'], +'InvalidArgumentException::getTraceAsString' => ['string'], +'ip2long' => ['int|false', 'ip_address'=>'string'], +'iptcembed' => ['string|bool', 'iptcdata'=>'string', 'jpeg_file_name'=>'string', 'spool='=>'int'], +'iptcparse' => ['array|false', 'iptcdata'=>'string'], +'is_a' => ['bool', 'object_or_string'=>'object|string', 'class_name'=>'string', 'allow_string='=>'bool'], +'is_array' => ['bool', 'value'=>'mixed'], +'is_bool' => ['bool', 'value'=>'mixed'], +'is_callable' => ['bool', 'value'=>'callable|mixed', 'syntax_only='=>'bool', '&w_callable_name='=>'string'], +'is_countable' => ['bool', 'value'=>'mixed'], +'is_dir' => ['bool', 'filename'=>'string'], +'is_double' => ['bool', 'value'=>'mixed'], +'is_executable' => ['bool', 'filename'=>'string'], +'is_file' => ['bool', 'filename'=>'string'], +'is_finite' => ['bool', 'number'=>'float'], +'is_float' => ['bool', 'value'=>'mixed'], +'is_infinite' => ['bool', 'number'=>'float'], +'is_int' => ['bool', 'value'=>'mixed'], +'is_integer' => ['bool', 'value'=>'mixed'], +'is_iterable' => ['bool', 'value'=>'mixed'], +'is_link' => ['bool', 'filename'=>'string'], +'is_long' => ['bool', 'value'=>'mixed'], +'is_nan' => ['bool', 'number'=>'float'], +'is_null' => ['bool', 'value'=>'mixed'], +'is_numeric' => ['bool', 'value'=>'mixed'], +'is_object' => ['bool', 'value'=>'mixed'], +'is_readable' => ['bool', 'filename'=>'string'], +'is_real' => ['bool', 'value'=>'mixed'], +'is_resource' => ['bool', 'value'=>'mixed'], +'is_scalar' => ['bool', 'value'=>'mixed'], +'is_soap_fault' => ['bool', 'object'=>'mixed'], +'is_string' => ['bool', 'value'=>'mixed'], +'is_subclass_of' => ['bool', 'object_or_string'=>'object|string', 'class_name'=>'string', 'allow_string='=>'bool'], +'is_tainted' => ['bool', 'string'=>'string'], +'is_uploaded_file' => ['bool', 'path'=>'string'], +'is_writable' => ['bool', 'filename'=>'string'], +'is_writeable' => ['bool', 'filename'=>'string'], +'isset' => ['bool', 'value'=>'mixed', '...rest='=>'mixed'], +'Iterator::current' => ['mixed'], +'Iterator::key' => ['mixed'], +'Iterator::next' => ['void'], +'Iterator::rewind' => ['void'], +'Iterator::valid' => ['bool'], +'iterator_apply' => ['int', 'it'=>'Traversable', 'function'=>'callable(mixed):bool', 'params='=>'array'], +'iterator_count' => ['int', 'it'=>'Traversable'], +'iterator_to_array' => ['array', 'it'=>'Traversable', 'use_keys='=>'bool'], +'IteratorAggregate::getIterator' => ['Traversable'], +'IteratorIterator::__construct' => ['void', 'it'=>'Traversable'], +'IteratorIterator::current' => ['mixed'], +'IteratorIterator::getInnerIterator' => ['Traversable'], +'IteratorIterator::key' => ['mixed'], +'IteratorIterator::next' => ['void'], +'IteratorIterator::rewind' => ['void'], +'IteratorIterator::valid' => ['bool'], +'java_last_exception_clear' => ['void'], +'java_last_exception_get' => ['object'], +'java_reload' => ['array', 'new_jarpath'=>'string'], +'java_require' => ['array', 'new_classpath'=>'string'], +'java_set_encoding' => ['array', 'encoding'=>'string'], +'java_set_ignore_case' => ['void', 'ignore'=>'bool'], +'java_throw_exceptions' => ['void', 'throw'=>'bool'], +'JavaException::getCause' => ['object'], +'jddayofweek' => ['mixed', 'juliandaycount'=>'int', 'mode='=>'int'], +'jdmonthname' => ['string', 'juliandaycount'=>'int', 'mode'=>'int'], +'jdtofrench' => ['string', 'juliandaycount'=>'int'], +'jdtogregorian' => ['string', 'juliandaycount'=>'int'], +'jdtojewish' => ['string', 'juliandaycount'=>'int', 'hebrew='=>'bool', 'fl='=>'int'], +'jdtojulian' => ['string', 'juliandaycount'=>'int'], +'jdtounix' => ['int|false', 'jday'=>'int'], +'jewishtojd' => ['int', 'month'=>'int', 'day'=>'int', 'year'=>'int'], +'jobqueue_license_info' => ['array'], +'join' => ['string', 'glue'=>'string', 'pieces'=>'array'], +'join\'1' => ['string', 'pieces'=>'array'], +'jpeg2wbmp' => ['bool', 'jpegname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'], +'json_decode' => ['mixed', 'json'=>'string', 'assoc='=>'bool', 'depth='=>'int', 'options='=>'int'], +'json_encode' => ['string|false', 'data'=>'mixed', 'options='=>'int', 'depth='=>'int'], +'json_last_error' => ['int'], +'json_last_error_msg' => ['string'], +'JsonException::__clone' => ['void'], +'JsonException::__construct' => ['void'], +'JsonException::__toString' => ['string'], +'JsonException::__wakeup' => ['void'], +'JsonException::getCode' => ['int'], +'JsonException::getFile' => ['string'], +'JsonException::getLine' => ['int'], +'JsonException::getMessage' => ['string'], +'JsonException::getPrevious' => ['?Throwable'], +'JsonException::getTrace' => ['list>'], +'JsonException::getTraceAsString' => ['string'], +'JsonIncrementalParser::__construct' => ['void', 'depth'=>'', 'options'=>''], +'JsonIncrementalParser::get' => ['', 'options'=>''], +'JsonIncrementalParser::getError' => [''], +'JsonIncrementalParser::parse' => ['', 'json'=>''], +'JsonIncrementalParser::parseFile' => ['', 'filename'=>''], +'JsonIncrementalParser::reset' => [''], +'JsonSerializable::jsonSerialize' => ['mixed'], +'Judy::__construct' => ['void', 'judy_type'=>'int'], +'Judy::__destruct' => ['void'], +'Judy::byCount' => ['int', 'nth_index'=>'int'], +'Judy::count' => ['int', 'index_start='=>'int', 'index_end='=>'int'], +'Judy::first' => ['mixed', 'index='=>'mixed'], +'Judy::firstEmpty' => ['mixed', 'index='=>'mixed'], +'Judy::free' => ['int'], +'Judy::getType' => ['int'], +'Judy::last' => ['mixed', 'index='=>'string'], +'Judy::lastEmpty' => ['mixed', 'index='=>'int'], +'Judy::memoryUsage' => ['int'], +'Judy::next' => ['mixed', 'index'=>'mixed'], +'Judy::nextEmpty' => ['mixed', 'index'=>'mixed'], +'Judy::offsetExists' => ['bool', 'offset'=>'mixed'], +'Judy::offsetGet' => ['mixed', 'offset'=>'mixed'], +'Judy::offsetSet' => ['bool', 'offset'=>'mixed', 'value'=>'mixed'], +'Judy::offsetUnset' => ['bool', 'offset'=>'mixed'], +'Judy::prev' => ['mixed', 'index'=>'mixed'], +'Judy::prevEmpty' => ['mixed', 'index'=>'mixed'], +'Judy::size' => ['int'], +'judy_type' => ['int', 'array'=>'judy'], +'judy_version' => ['string'], +'juliantojd' => ['int', 'month'=>'int', 'day'=>'int', 'year'=>'int'], +'kadm5_chpass_principal' => ['bool', 'handle'=>'resource', 'principal'=>'string', 'password'=>'string'], +'kadm5_create_principal' => ['bool', 'handle'=>'resource', 'principal'=>'string', 'password='=>'string', 'options='=>'array'], +'kadm5_delete_principal' => ['bool', 'handle'=>'resource', 'principal'=>'string'], +'kadm5_destroy' => ['bool', 'handle'=>'resource'], +'kadm5_flush' => ['bool', 'handle'=>'resource'], +'kadm5_get_policies' => ['array', 'handle'=>'resource'], +'kadm5_get_principal' => ['array', 'handle'=>'resource', 'principal'=>'string'], +'kadm5_get_principals' => ['array', 'handle'=>'resource'], +'kadm5_init_with_password' => ['resource', 'admin_server'=>'string', 'realm'=>'string', 'principal'=>'string', 'password'=>'string'], +'kadm5_modify_principal' => ['bool', 'handle'=>'resource', 'principal'=>'string', 'options'=>'array'], +'key' => ['int|string|null', 'array'=>'array|object'], +'key_exists' => ['bool', 'key'=>'string|int', 'search'=>'array'], +'krsort' => ['bool', '&rw_array'=>'array', 'sort_flags='=>'int'], +'ksort' => ['bool', '&rw_array'=>'array', 'sort_flags='=>'int'], +'KTaglib_ID3v2_AttachedPictureFrame::getDescription' => ['string'], +'KTaglib_ID3v2_AttachedPictureFrame::getMimeType' => ['string'], +'KTaglib_ID3v2_AttachedPictureFrame::getType' => ['int'], +'KTaglib_ID3v2_AttachedPictureFrame::savePicture' => ['bool', 'filename'=>'string'], +'KTaglib_ID3v2_AttachedPictureFrame::setMimeType' => ['string', 'type'=>'string'], +'KTaglib_ID3v2_AttachedPictureFrame::setPicture' => ['', 'filename'=>'string'], +'KTaglib_ID3v2_AttachedPictureFrame::setType' => ['', 'type'=>'int'], +'KTaglib_ID3v2_Frame::__toString' => ['string'], +'KTaglib_ID3v2_Frame::getDescription' => ['string'], +'KTaglib_ID3v2_Frame::getMimeType' => ['string'], +'KTaglib_ID3v2_Frame::getSize' => ['int'], +'KTaglib_ID3v2_Frame::getType' => ['int'], +'KTaglib_ID3v2_Frame::savePicture' => ['bool', 'filename'=>'string'], +'KTaglib_ID3v2_Frame::setMimeType' => ['string', 'type'=>'string'], +'KTaglib_ID3v2_Frame::setPicture' => ['void', 'filename'=>'string'], +'KTaglib_ID3v2_Frame::setType' => ['void', 'type'=>'int'], +'KTaglib_ID3v2_Tag::addFrame' => ['bool', 'frame'=>'KTaglib_ID3v2_Frame'], +'KTaglib_ID3v2_Tag::getFrameList' => ['array'], +'KTaglib_MPEG_AudioProperties::getBitrate' => ['int'], +'KTaglib_MPEG_AudioProperties::getChannels' => ['int'], +'KTaglib_MPEG_AudioProperties::getLayer' => ['int'], +'KTaglib_MPEG_AudioProperties::getLength' => ['int'], +'KTaglib_MPEG_AudioProperties::getSampleBitrate' => ['int'], +'KTaglib_MPEG_AudioProperties::getVersion' => ['int'], +'KTaglib_MPEG_AudioProperties::isCopyrighted' => ['bool'], +'KTaglib_MPEG_AudioProperties::isOriginal' => ['bool'], +'KTaglib_MPEG_AudioProperties::isProtectionEnabled' => ['bool'], +'KTaglib_MPEG_File::getAudioProperties' => ['KTaglib_MPEG_File'], +'KTaglib_MPEG_File::getID3v1Tag' => ['KTaglib_ID3v1_Tag', 'create='=>'bool'], +'KTaglib_MPEG_File::getID3v2Tag' => ['KTaglib_ID3v2_Tag', 'create='=>'bool'], +'KTaglib_Tag::getAlbum' => ['string'], +'KTaglib_Tag::getArtist' => ['string'], +'KTaglib_Tag::getComment' => ['string'], +'KTaglib_Tag::getGenre' => ['string'], +'KTaglib_Tag::getTitle' => ['string'], +'KTaglib_Tag::getTrack' => ['int'], +'KTaglib_Tag::getYear' => ['int'], +'KTaglib_Tag::isEmpty' => ['bool'], +'labelcacheObj::freeCache' => ['bool'], +'labelObj::__construct' => ['void'], +'labelObj::convertToString' => ['string'], +'labelObj::deleteStyle' => ['int', 'index'=>'int'], +'labelObj::free' => ['void'], +'labelObj::getBinding' => ['string', 'labelbinding'=>'mixed'], +'labelObj::getExpressionString' => ['string'], +'labelObj::getStyle' => ['styleObj', 'index'=>'int'], +'labelObj::getTextString' => ['string'], +'labelObj::moveStyleDown' => ['int', 'index'=>'int'], +'labelObj::moveStyleUp' => ['int', 'index'=>'int'], +'labelObj::removeBinding' => ['int', 'labelbinding'=>'mixed'], +'labelObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'labelObj::setBinding' => ['int', 'labelbinding'=>'mixed', 'value'=>'string'], +'labelObj::setExpression' => ['int', 'expression'=>'string'], +'labelObj::setText' => ['int', 'text'=>'string'], +'labelObj::updateFromString' => ['int', 'snippet'=>'string'], +'Lapack::eigenValues' => ['array', 'a'=>'array', 'left='=>'array', 'right='=>'array'], +'Lapack::identity' => ['array', 'n'=>'int'], +'Lapack::leastSquaresByFactorisation' => ['array', 'a'=>'array', 'b'=>'array'], +'Lapack::leastSquaresBySVD' => ['array', 'a'=>'array', 'b'=>'array'], +'Lapack::pseudoInverse' => ['array', 'a'=>'array'], +'Lapack::singularValues' => ['array', 'a'=>'array'], +'Lapack::solveLinearEquation' => ['array', 'a'=>'array', 'b'=>'array'], +'layerObj::addFeature' => ['int', 'shape'=>'shapeObj'], +'layerObj::applySLD' => ['int', 'sldxml'=>'string', 'namedlayer'=>'string'], +'layerObj::applySLDURL' => ['int', 'sldurl'=>'string', 'namedlayer'=>'string'], +'layerObj::clearProcessing' => ['void'], +'layerObj::close' => ['void'], +'layerObj::convertToString' => ['string'], +'layerObj::draw' => ['int', 'image'=>'imageObj'], +'layerObj::drawQuery' => ['int', 'image'=>'imageObj'], +'layerObj::free' => ['void'], +'layerObj::generateSLD' => ['string'], +'layerObj::getClass' => ['classObj', 'classIndex'=>'int'], +'layerObj::getClassIndex' => ['int', 'shape'=>'', 'classgroup'=>'', 'numclasses'=>''], +'layerObj::getExtent' => ['rectObj'], +'layerObj::getFilterString' => ['?string'], +'layerObj::getGridIntersectionCoordinates' => ['array'], +'layerObj::getItems' => ['array'], +'layerObj::getMetaData' => ['int', 'name'=>'string'], +'layerObj::getNumResults' => ['int'], +'layerObj::getProcessing' => ['array'], +'layerObj::getProjection' => ['string'], +'layerObj::getResult' => ['resultObj', 'index'=>'int'], +'layerObj::getResultsBounds' => ['rectObj'], +'layerObj::getShape' => ['shapeObj', 'result'=>'resultObj'], +'layerObj::getWMSFeatureInfoURL' => ['string', 'clickX'=>'int', 'clickY'=>'int', 'featureCount'=>'int', 'infoFormat'=>'string'], +'layerObj::isVisible' => ['bool'], +'layerObj::moveclassdown' => ['int', 'index'=>'int'], +'layerObj::moveclassup' => ['int', 'index'=>'int'], +'layerObj::ms_newLayerObj' => ['layerObj', 'map'=>'mapObj', 'layer'=>'layerObj'], +'layerObj::nextShape' => ['shapeObj'], +'layerObj::open' => ['int'], +'layerObj::queryByAttributes' => ['int', 'qitem'=>'string', 'qstring'=>'string', 'mode'=>'int'], +'layerObj::queryByFeatures' => ['int', 'slayer'=>'int'], +'layerObj::queryByPoint' => ['int', 'point'=>'pointObj', 'mode'=>'int', 'buffer'=>'float'], +'layerObj::queryByRect' => ['int', 'rect'=>'rectObj'], +'layerObj::queryByShape' => ['int', 'shape'=>'shapeObj'], +'layerObj::removeClass' => ['?classObj', 'index'=>'int'], +'layerObj::removeMetaData' => ['int', 'name'=>'string'], +'layerObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'layerObj::setConnectionType' => ['int', 'connectiontype'=>'int', 'plugin_library'=>'string'], +'layerObj::setFilter' => ['int', 'expression'=>'string'], +'layerObj::setMetaData' => ['int', 'name'=>'string', 'value'=>'string'], +'layerObj::setProjection' => ['int', 'proj_params'=>'string'], +'layerObj::setWKTProjection' => ['int', 'proj_params'=>'string'], +'layerObj::updateFromString' => ['int', 'snippet'=>'string'], +'lcfirst' => ['string', 'string'=>'string'], +'lcg_value' => ['float'], +'lchgrp' => ['bool', 'filename'=>'string', 'group'=>'string|int'], +'lchown' => ['bool', 'filename'=>'string', 'user'=>'string|int'], +'ldap_8859_to_t61' => ['string', 'value'=>'string'], +'ldap_add' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array', 'serverctrls='=>'array'], +'ldap_add_ext' => ['resource|false', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array', 'serverctrls='=>'array'], +'ldap_bind' => ['bool', 'link_identifier'=>'resource', 'bind_rdn='=>'string|null', 'bind_password='=>'string|null'], +'ldap_bind_ext' => ['resource|false', 'link_identifier'=>'resource', 'bind_rdn='=>'string|null', 'bind_password='=>'string|null', 'serverctrls='=>'array'], +'ldap_close' => ['bool', 'link_identifier'=>'resource'], +'ldap_compare' => ['bool|int', 'link_identifier'=>'resource', 'dn'=>'string', 'attr'=>'string', 'value'=>'string'], +'ldap_connect' => ['resource|false', 'host='=>'string', 'port='=>'int', 'wallet='=>'string', 'wallet_passwd='=>'string', 'authmode='=>'int'], +'ldap_control_paged_result' => ['bool', 'link_identifier'=>'resource', 'pagesize'=>'int', 'iscritical='=>'bool', 'cookie='=>'string'], +'ldap_control_paged_result_response' => ['bool', 'link_identifier'=>'resource', 'result_identifier'=>'resource', '&w_cookie'=>'string', '&w_estimated'=>'int'], +'ldap_count_entries' => ['int|false', 'link_identifier'=>'resource', 'result'=>'resource'], +'ldap_delete' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string'], +'ldap_delete_ext' => ['resource|false', 'link_identifier'=>'resource', 'dn'=>'string', 'serverctrls='=>'array'], +'ldap_dn2ufn' => ['string', 'dn'=>'string'], +'ldap_err2str' => ['string', 'errno'=>'int'], +'ldap_errno' => ['int', 'link_identifier'=>'resource'], +'ldap_error' => ['string', 'link_identifier'=>'resource'], +'ldap_escape' => ['string', 'value'=>'string', 'ignore='=>'string', 'flags='=>'int'], +'ldap_exop' => ['mixed', 'link'=>'resource', 'reqoid'=>'string', 'reqdata='=>'string', 'serverctrls='=>'array|null', '&w_retdata='=>'string', '&w_retoid='=>'string'], +'ldap_exop_passwd' => ['mixed', 'link'=>'resource', 'user='=>'string', 'oldpw='=>'string', 'newpw='=>'string', 'serverctrls='=>'array'], +'ldap_exop_refresh' => ['int|false', 'link'=>'resource', 'dn'=>'string', 'ttl'=>'int'], +'ldap_exop_whoami' => ['string|false', 'link'=>'resource'], +'ldap_explode_dn' => ['array|false', 'dn'=>'string', 'with_attrib'=>'int'], +'ldap_first_attribute' => ['string|false', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource'], +'ldap_first_entry' => ['resource|false', 'link_identifier'=>'resource', 'result_identifier'=>'resource'], +'ldap_first_reference' => ['resource|false', 'link_identifier'=>'resource', 'result_identifier'=>'resource'], +'ldap_free_result' => ['bool', 'result_identifier'=>'resource'], +'ldap_get_attributes' => ['array|false', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource'], +'ldap_get_dn' => ['string|false', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource'], +'ldap_get_entries' => ['array|false', 'link_identifier'=>'resource', 'result_identifier'=>'resource'], +'ldap_get_option' => ['bool', 'link_identifier'=>'resource', 'option'=>'int', '&w_retval'=>'mixed'], +'ldap_get_values' => ['array|false', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource', 'attribute'=>'string'], +'ldap_get_values_len' => ['array|false', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource', 'attribute'=>'string'], +'ldap_list' => ['resource|false', 'link'=>'resource|array', 'base_dn'=>'string', 'filter'=>'string', 'attrs='=>'array', 'attrsonly='=>'int', 'sizelimit='=>'int', 'timelimit='=>'int', 'deref='=>'int'], +'ldap_mod_add' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array'], +'ldap_mod_add_ext' => ['resource|false', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array', 'serverctrls='=>'array'], +'ldap_mod_del' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array'], +'ldap_mod_del_ext' => ['resource|false', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array', 'serverctrls='=>'array'], +'ldap_mod_replace' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array'], +'ldap_mod_replace_ext' => ['resource|false', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array', 'serverctrls='=>'array'], +'ldap_modify' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array'], +'ldap_modify_batch' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'modifications'=>'array'], +'ldap_next_attribute' => ['string|false', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource'], +'ldap_next_entry' => ['resource|false', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource'], +'ldap_next_reference' => ['resource|false', 'link_identifier'=>'resource', 'reference_entry_identifier'=>'resource'], +'ldap_parse_exop' => ['bool', 'link'=>'resource', 'result'=>'resource', '&w_retdata='=>'string', '&w_retoid='=>'string'], +'ldap_parse_reference' => ['bool', 'link_identifier'=>'resource', 'reference_entry_identifier'=>'resource', 'referrals'=>'array'], +'ldap_parse_result' => ['bool', 'link_identifier'=>'resource', 'result'=>'resource', '&w_errcode'=>'int', '&w_matcheddn='=>'string', '&w_errmsg='=>'string', '&w_referrals='=>'array', '&w_serverctrls='=>'array'], +'ldap_read' => ['resource|false', 'link'=>'resource|array', 'base_dn'=>'string', 'filter'=>'string', 'attrs='=>'array', 'attrsonly='=>'int', 'sizelimit='=>'int', 'timelimit='=>'int', 'deref='=>'int'], +'ldap_rename' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'newrdn'=>'string', 'newparent'=>'string', 'deleteoldrdn'=>'bool'], +'ldap_rename_ext' => ['resource|false', 'link_identifier'=>'resource', 'dn'=>'string', 'newrdn'=>'string', 'newparent'=>'string', 'deleteoldrdn'=>'bool', 'serverctrls='=>'array'], +'ldap_sasl_bind' => ['bool', 'link_identifier'=>'resource', 'binddn='=>'string', 'password='=>'string', 'sasl_mech='=>'string', 'sasl_realm='=>'string', 'sasl_authc_id='=>'string', 'sasl_authz_id='=>'string', 'props='=>'string'], +'ldap_search' => ['resource|false', 'link_identifier'=>'resource|resource[]', 'base_dn'=>'string', 'filter'=>'string', 'attrs='=>'array', 'attrsonly='=>'int', 'sizelimit='=>'int', 'timelimit='=>'int', 'deref='=>'int'], +'ldap_set_option' => ['bool', 'link_identifier'=>'resource|null', 'option'=>'int', 'newval'=>'mixed'], +'ldap_set_rebind_proc' => ['bool', 'link_identifier'=>'resource', 'callback'=>'string'], +'ldap_sort' => ['bool', 'link_identifier'=>'resource', 'result_identifier'=>'resource', 'sortfilter'=>'string'], +'ldap_start_tls' => ['bool', 'link_identifier'=>'resource'], +'ldap_t61_to_8859' => ['string', 'value'=>'string'], +'ldap_unbind' => ['bool', 'link_identifier'=>'resource'], +'leak' => ['', 'num_bytes'=>'int'], +'leak_variable' => ['', 'variable'=>'', 'leak_data'=>'bool'], +'legendObj::convertToString' => ['string'], +'legendObj::free' => ['void'], +'legendObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'legendObj::updateFromString' => ['int', 'snippet'=>'string'], +'LengthException::__clone' => ['void'], +'LengthException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?LengthException'], +'LengthException::__toString' => ['string'], +'LengthException::getCode' => ['int'], +'LengthException::getFile' => ['string'], +'LengthException::getLine' => ['int'], +'LengthException::getMessage' => ['string'], +'LengthException::getPrevious' => ['Throwable|LengthException|null'], +'LengthException::getTrace' => ['list>'], +'LengthException::getTraceAsString' => ['string'], +'LevelDB::__construct' => ['void', 'name'=>'string', 'options='=>'array', 'read_options='=>'array', 'write_options='=>'array'], +'LevelDB::close' => [''], +'LevelDB::compactRange' => ['', 'start'=>'', 'limit'=>''], +'LevelDB::delete' => ['bool', 'key'=>'string', 'write_options='=>'array'], +'LevelDB::destroy' => ['', 'name'=>'', 'options='=>'array'], +'LevelDB::get' => ['bool|string', 'key'=>'string', 'read_options='=>'array'], +'LevelDB::getApproximateSizes' => ['', 'start'=>'', 'limit'=>''], +'LevelDB::getIterator' => ['LevelDBIterator', 'options='=>'array'], +'LevelDB::getProperty' => ['mixed', 'name'=>'string'], +'LevelDB::getSnapshot' => ['LevelDBSnapshot'], +'LevelDB::put' => ['', 'key'=>'string', 'value'=>'string', 'write_options='=>'array'], +'LevelDB::repair' => ['', 'name'=>'', 'options='=>'array'], +'LevelDB::set' => ['', 'key'=>'string', 'value'=>'string', 'write_options='=>'array'], +'LevelDB::write' => ['', 'batch'=>'LevelDBWriteBatch', 'write_options='=>'array'], +'LevelDBIterator::__construct' => ['void', 'db'=>'LevelDB', 'read_options='=>'array'], +'LevelDBIterator::current' => ['mixed'], +'LevelDBIterator::destroy' => [''], +'LevelDBIterator::getError' => [''], +'LevelDBIterator::key' => ['int|string'], +'LevelDBIterator::last' => [''], +'LevelDBIterator::next' => ['void'], +'LevelDBIterator::prev' => [''], +'LevelDBIterator::rewind' => ['void'], +'LevelDBIterator::seek' => ['', 'key'=>''], +'LevelDBIterator::valid' => ['bool'], +'LevelDBSnapshot::__construct' => ['void', 'db'=>'LevelDB'], +'LevelDBSnapshot::release' => [''], +'LevelDBWriteBatch::__construct' => ['void', 'name'=>'', 'options='=>'array', 'read_options='=>'array', 'write_options='=>'array'], +'LevelDBWriteBatch::clear' => [''], +'LevelDBWriteBatch::delete' => ['', 'key'=>'', 'write_options='=>'array'], +'LevelDBWriteBatch::put' => ['', 'key'=>'', 'value'=>'', 'write_options='=>'array'], +'LevelDBWriteBatch::set' => ['', 'key'=>'', 'value'=>'', 'write_options='=>'array'], +'levenshtein' => ['int', 'string1'=>'string', 'string2'=>'string'], +'levenshtein\'1' => ['int', 'string1'=>'string', 'string2'=>'string', 'cost_ins'=>'int', 'cost_rep'=>'int', 'cost_del'=>'int'], +'libxml_clear_errors' => ['void'], +'libxml_disable_entity_loader' => ['bool', 'disable='=>'bool'], +'libxml_get_errors' => ['array'], +'libxml_get_last_error' => ['LibXMLError|false'], +'libxml_set_external_entity_loader' => ['bool', 'resolver_function'=>'callable'], +'libxml_set_streams_context' => ['void', 'streams_context'=>'resource'], +'libxml_use_internal_errors' => ['bool', 'use_errors='=>'bool'], +'LimitIterator::__construct' => ['void', 'iterator'=>'Iterator', 'offset='=>'int', 'count='=>'int'], +'LimitIterator::current' => ['mixed'], +'LimitIterator::getInnerIterator' => ['Iterator'], +'LimitIterator::getPosition' => ['int'], +'LimitIterator::key' => ['mixed'], +'LimitIterator::next' => ['void'], +'LimitIterator::rewind' => ['void'], +'LimitIterator::seek' => ['int', 'position'=>'int'], +'LimitIterator::valid' => ['bool'], +'lineObj::__construct' => ['void'], +'lineObj::add' => ['int', 'point'=>'pointObj'], +'lineObj::addXY' => ['int', 'x'=>'float', 'y'=>'float', 'm'=>'float'], +'lineObj::addXYZ' => ['int', 'x'=>'float', 'y'=>'float', 'z'=>'float', 'm'=>'float'], +'lineObj::ms_newLineObj' => ['lineObj'], +'lineObj::point' => ['pointObj', 'i'=>'int'], +'lineObj::project' => ['int', 'in'=>'projectionObj', 'out'=>'projectionObj'], +'link' => ['bool', 'target'=>'string', 'link'=>'string'], +'linkinfo' => ['int|false', 'filename'=>'string'], +'litespeed_request_headers' => ['array'], +'litespeed_response_headers' => ['array'], +'Locale::acceptFromHttp' => ['string|false', 'header'=>'string'], +'Locale::canonicalize' => ['string', 'locale'=>'string'], +'Locale::composeLocale' => ['string', 'subtags'=>'array'], +'Locale::filterMatches' => ['bool', 'langtag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'], +'Locale::getAllVariants' => ['array', 'locale'=>'string'], +'Locale::getDefault' => ['string'], +'Locale::getDisplayLanguage' => ['string', 'locale'=>'string', 'in_locale='=>'string'], +'Locale::getDisplayName' => ['string', 'locale'=>'string', 'in_locale='=>'string'], +'Locale::getDisplayRegion' => ['string', 'locale'=>'string', 'in_locale='=>'string'], +'Locale::getDisplayScript' => ['string', 'locale'=>'string', 'in_locale='=>'string'], +'Locale::getDisplayVariant' => ['string', 'locale'=>'string', 'in_locale='=>'string'], +'Locale::getKeywords' => ['array|false', 'locale'=>'string'], +'Locale::getPrimaryLanguage' => ['string', 'locale'=>'string'], +'Locale::getRegion' => ['string', 'locale'=>'string'], +'Locale::getScript' => ['string', 'locale'=>'string'], +'Locale::lookup' => ['string', 'langtag'=>'array', 'locale'=>'string', 'canonicalize='=>'bool', 'default='=>'string'], +'Locale::parseLocale' => ['array', 'locale'=>'string'], +'Locale::setDefault' => ['bool', 'locale'=>'string'], +'locale_accept_from_http' => ['string|false', 'header'=>'string'], +'locale_canonicalize' => ['string', 'arg1'=>'string'], +'locale_compose' => ['string|false', 'subtags'=>'array'], +'locale_filter_matches' => ['bool', 'langtag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'], +'locale_get_all_variants' => ['array', 'locale'=>'string'], +'locale_get_default' => ['string'], +'locale_get_display_language' => ['string', 'locale'=>'string', 'in_locale='=>'string'], +'locale_get_display_name' => ['string', 'locale'=>'string', 'in_locale='=>'string'], +'locale_get_display_region' => ['string', 'locale'=>'string', 'in_locale='=>'string'], +'locale_get_display_script' => ['string', 'locale'=>'string', 'in_locale='=>'string'], +'locale_get_display_variant' => ['string', 'locale'=>'string', 'in_locale='=>'string'], +'locale_get_keywords' => ['array|false', 'locale'=>'string'], +'locale_get_primary_language' => ['string', 'locale'=>'string'], +'locale_get_region' => ['string', 'locale'=>'string'], +'locale_get_script' => ['string', 'locale'=>'string'], +'locale_lookup' => ['string', 'langtag'=>'array', 'locale'=>'string', 'canonicalize='=>'bool', 'default='=>'string'], +'locale_parse' => ['array', 'locale'=>'string'], +'locale_set_default' => ['bool', 'locale'=>'string'], +'localeconv' => ['array'], +'localtime' => ['array', 'timestamp='=>'int', 'associative_array='=>'bool'], +'log' => ['float', 'number'=>'float', 'base='=>'float'], +'log10' => ['float', 'number'=>'float'], +'log1p' => ['float', 'number'=>'float'], +'LogicException::__clone' => ['void'], +'LogicException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?LogicException'], +'LogicException::__toString' => ['string'], +'LogicException::getCode' => ['int'], +'LogicException::getFile' => ['string'], +'LogicException::getLine' => ['int'], +'LogicException::getMessage' => ['string'], +'LogicException::getPrevious' => ['Throwable|LogicException|null'], +'LogicException::getTrace' => ['list>'], +'LogicException::getTraceAsString' => ['string'], +'long2ip' => ['string', 'proper_address'=>'string|int'], +'lstat' => ['array|false', 'filename'=>'string'], +'ltrim' => ['string', 'string'=>'string', 'character_mask='=>'string'], +'Lua::__call' => ['mixed', 'lua_func'=>'callable', 'args='=>'array', 'use_self='=>'int'], +'Lua::__construct' => ['void', 'lua_script_file'=>'string'], +'Lua::assign' => ['?Lua', 'name'=>'string', 'value'=>'mixed'], +'Lua::call' => ['mixed', 'lua_func'=>'callable', 'args='=>'array', 'use_self='=>'int'], +'Lua::eval' => ['mixed', 'statements'=>'string'], +'Lua::getVersion' => ['string'], +'Lua::include' => ['mixed', 'file'=>'string'], +'Lua::registerCallback' => ['Lua|null|false', 'name'=>'string', 'function'=>'callable'], +'LuaClosure::__invoke' => ['void', 'arg'=>'mixed', '...args='=>'mixed'], +'lzf_compress' => ['string', 'data'=>'string'], +'lzf_decompress' => ['string', 'data'=>'string'], +'lzf_optimized_for' => ['int'], +'m_checkstatus' => ['int', 'conn'=>'resource', 'identifier'=>'int'], +'m_completeauthorizations' => ['int', 'conn'=>'resource', 'array'=>'int'], +'m_connect' => ['int', 'conn'=>'resource'], +'m_connectionerror' => ['string', 'conn'=>'resource'], +'m_deletetrans' => ['bool', 'conn'=>'resource', 'identifier'=>'int'], +'m_destroyconn' => ['bool', 'conn'=>'resource'], +'m_destroyengine' => ['void'], +'m_getcell' => ['string', 'conn'=>'resource', 'identifier'=>'int', 'column'=>'string', 'row'=>'int'], +'m_getcellbynum' => ['string', 'conn'=>'resource', 'identifier'=>'int', 'column'=>'int', 'row'=>'int'], +'m_getcommadelimited' => ['string', 'conn'=>'resource', 'identifier'=>'int'], +'m_getheader' => ['string', 'conn'=>'resource', 'identifier'=>'int', 'column_num'=>'int'], +'m_initconn' => ['resource'], +'m_initengine' => ['int', 'location'=>'string'], +'m_iscommadelimited' => ['int', 'conn'=>'resource', 'identifier'=>'int'], +'m_maxconntimeout' => ['bool', 'conn'=>'resource', 'secs'=>'int'], +'m_monitor' => ['int', 'conn'=>'resource'], +'m_numcolumns' => ['int', 'conn'=>'resource', 'identifier'=>'int'], +'m_numrows' => ['int', 'conn'=>'resource', 'identifier'=>'int'], +'m_parsecommadelimited' => ['int', 'conn'=>'resource', 'identifier'=>'int'], +'m_responsekeys' => ['array', 'conn'=>'resource', 'identifier'=>'int'], +'m_responseparam' => ['string', 'conn'=>'resource', 'identifier'=>'int', 'key'=>'string'], +'m_returnstatus' => ['int', 'conn'=>'resource', 'identifier'=>'int'], +'m_setblocking' => ['int', 'conn'=>'resource', 'tf'=>'int'], +'m_setdropfile' => ['int', 'conn'=>'resource', 'directory'=>'string'], +'m_setip' => ['int', 'conn'=>'resource', 'host'=>'string', 'port'=>'int'], +'m_setssl' => ['int', 'conn'=>'resource', 'host'=>'string', 'port'=>'int'], +'m_setssl_cafile' => ['int', 'conn'=>'resource', 'cafile'=>'string'], +'m_setssl_files' => ['int', 'conn'=>'resource', 'sslkeyfile'=>'string', 'sslcertfile'=>'string'], +'m_settimeout' => ['int', 'conn'=>'resource', 'seconds'=>'int'], +'m_sslcert_gen_hash' => ['string', 'filename'=>'string'], +'m_transactionssent' => ['int', 'conn'=>'resource'], +'m_transinqueue' => ['int', 'conn'=>'resource'], +'m_transkeyval' => ['int', 'conn'=>'resource', 'identifier'=>'int', 'key'=>'string', 'value'=>'string'], +'m_transnew' => ['int', 'conn'=>'resource'], +'m_transsend' => ['int', 'conn'=>'resource', 'identifier'=>'int'], +'m_uwait' => ['int', 'microsecs'=>'int'], +'m_validateidentifier' => ['int', 'conn'=>'resource', 'tf'=>'int'], +'m_verifyconnection' => ['bool', 'conn'=>'resource', 'tf'=>'int'], +'m_verifysslcert' => ['bool', 'conn'=>'resource', 'tf'=>'int'], +'magic_quotes_runtime' => ['bool', 'new_setting'=>'bool'], +'mail' => ['bool', 'to'=>'string', 'subject'=>'string', 'message'=>'string', 'additional_headers='=>'string|array|null', 'additional_parameters='=>'string'], +'mailparse_determine_best_xfer_encoding' => ['string', 'fp'=>'resource'], +'mailparse_msg_create' => ['resource'], +'mailparse_msg_extract_part' => ['void', 'mimemail'=>'resource', 'msgbody'=>'string', 'callbackfunc='=>'callable'], +'mailparse_msg_extract_part_file' => ['string', 'mimemail'=>'resource', 'filename'=>'mixed', 'callbackfunc='=>'callable'], +'mailparse_msg_extract_whole_part_file' => ['string', 'mimemail'=>'resource', 'filename'=>'string', 'callbackfunc='=>'callable'], +'mailparse_msg_free' => ['bool', 'mimemail'=>'resource'], +'mailparse_msg_get_part' => ['resource', 'mimemail'=>'resource', 'mimesection'=>'string'], +'mailparse_msg_get_part_data' => ['array', 'mimemail'=>'resource'], +'mailparse_msg_get_structure' => ['array', 'mimemail'=>'resource'], +'mailparse_msg_parse' => ['bool', 'mimemail'=>'resource', 'data'=>'string'], +'mailparse_msg_parse_file' => ['resource|false', 'filename'=>'string'], +'mailparse_rfc822_parse_addresses' => ['array', 'addresses'=>'string'], +'mailparse_stream_encode' => ['bool', 'sourcefp'=>'resource', 'destfp'=>'resource', 'encoding'=>'string'], +'mailparse_uudecode_all' => ['array', 'fp'=>'resource'], +'mapObj::__construct' => ['void', 'map_file_name'=>'string', 'new_map_path'=>'string'], +'mapObj::appendOutputFormat' => ['int', 'outputFormat'=>'outputformatObj'], +'mapObj::applyconfigoptions' => ['int'], +'mapObj::applySLD' => ['int', 'sldxml'=>'string'], +'mapObj::applySLDURL' => ['int', 'sldurl'=>'string'], +'mapObj::convertToString' => ['string'], +'mapObj::draw' => ['?imageObj'], +'mapObj::drawLabelCache' => ['int', 'image'=>'imageObj'], +'mapObj::drawLegend' => ['imageObj'], +'mapObj::drawQuery' => ['?imageObj'], +'mapObj::drawReferenceMap' => ['imageObj'], +'mapObj::drawScaleBar' => ['imageObj'], +'mapObj::embedLegend' => ['int', 'image'=>'imageObj'], +'mapObj::embedScalebar' => ['int', 'image'=>'imageObj'], +'mapObj::free' => ['void'], +'mapObj::generateSLD' => ['string'], +'mapObj::getAllGroupNames' => ['array'], +'mapObj::getAllLayerNames' => ['array'], +'mapObj::getColorbyIndex' => ['colorObj', 'iCloIndex'=>'int'], +'mapObj::getConfigOption' => ['string', 'key'=>'string'], +'mapObj::getLabel' => ['labelcacheMemberObj', 'index'=>'int'], +'mapObj::getLayer' => ['layerObj', 'index'=>'int'], +'mapObj::getLayerByName' => ['layerObj', 'layer_name'=>'string'], +'mapObj::getLayersDrawingOrder' => ['array'], +'mapObj::getLayersIndexByGroup' => ['array', 'groupname'=>'string'], +'mapObj::getMetaData' => ['int', 'name'=>'string'], +'mapObj::getNumSymbols' => ['int'], +'mapObj::getOutputFormat' => ['?outputformatObj', 'index'=>'int'], +'mapObj::getProjection' => ['string'], +'mapObj::getSymbolByName' => ['int', 'symbol_name'=>'string'], +'mapObj::getSymbolObjectById' => ['symbolObj', 'symbolid'=>'int'], +'mapObj::loadMapContext' => ['int', 'filename'=>'string', 'unique_layer_name'=>'bool'], +'mapObj::loadOWSParameters' => ['int', 'request'=>'OwsrequestObj', 'version'=>'string'], +'mapObj::moveLayerDown' => ['int', 'layerindex'=>'int'], +'mapObj::moveLayerUp' => ['int', 'layerindex'=>'int'], +'mapObj::ms_newMapObjFromString' => ['mapObj', 'map_file_string'=>'string', 'new_map_path'=>'string'], +'mapObj::offsetExtent' => ['int', 'x'=>'float', 'y'=>'float'], +'mapObj::owsDispatch' => ['int', 'request'=>'OwsrequestObj'], +'mapObj::prepareImage' => ['imageObj'], +'mapObj::prepareQuery' => ['void'], +'mapObj::processLegendTemplate' => ['string', 'params'=>'array'], +'mapObj::processQueryTemplate' => ['string', 'params'=>'array', 'generateimages'=>'bool'], +'mapObj::processTemplate' => ['string', 'params'=>'array', 'generateimages'=>'bool'], +'mapObj::queryByFeatures' => ['int', 'slayer'=>'int'], +'mapObj::queryByIndex' => ['int', 'layerindex'=>'', 'tileindex'=>'', 'shapeindex'=>'', 'addtoquery'=>''], +'mapObj::queryByPoint' => ['int', 'point'=>'pointObj', 'mode'=>'int', 'buffer'=>'float'], +'mapObj::queryByRect' => ['int', 'rect'=>'rectObj'], +'mapObj::queryByShape' => ['int', 'shape'=>'shapeObj'], +'mapObj::removeLayer' => ['layerObj', 'nIndex'=>'int'], +'mapObj::removeMetaData' => ['int', 'name'=>'string'], +'mapObj::removeOutputFormat' => ['int', 'name'=>'string'], +'mapObj::save' => ['int', 'filename'=>'string'], +'mapObj::saveMapContext' => ['int', 'filename'=>'string'], +'mapObj::saveQuery' => ['int', 'filename'=>'string', 'results'=>'int'], +'mapObj::scaleExtent' => ['int', 'zoomfactor'=>'float', 'minscaledenom'=>'float', 'maxscaledenom'=>'float'], +'mapObj::selectOutputFormat' => ['int', 'type'=>'string'], +'mapObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'mapObj::setCenter' => ['int', 'center'=>'pointObj'], +'mapObj::setConfigOption' => ['int', 'key'=>'string', 'value'=>'string'], +'mapObj::setExtent' => ['void', 'minx'=>'float', 'miny'=>'float', 'maxx'=>'float', 'maxy'=>'float'], +'mapObj::setFontSet' => ['int', 'fileName'=>'string'], +'mapObj::setMetaData' => ['int', 'name'=>'string', 'value'=>'string'], +'mapObj::setProjection' => ['int', 'proj_params'=>'string', 'bSetUnitsAndExtents'=>'bool'], +'mapObj::setRotation' => ['int', 'rotation_angle'=>'float'], +'mapObj::setSize' => ['int', 'width'=>'int', 'height'=>'int'], +'mapObj::setSymbolSet' => ['int', 'fileName'=>'string'], +'mapObj::setWKTProjection' => ['int', 'proj_params'=>'string', 'bSetUnitsAndExtents'=>'bool'], +'mapObj::zoomPoint' => ['int', 'nZoomFactor'=>'int', 'oPixelPos'=>'pointObj', 'nImageWidth'=>'int', 'nImageHeight'=>'int', 'oGeorefExt'=>'rectObj'], +'mapObj::zoomRectangle' => ['int', 'oPixelExt'=>'rectObj', 'nImageWidth'=>'int', 'nImageHeight'=>'int', 'oGeorefExt'=>'rectObj'], +'mapObj::zoomScale' => ['int', 'nScaleDenom'=>'float', 'oPixelPos'=>'pointObj', 'nImageWidth'=>'int', 'nImageHeight'=>'int', 'oGeorefExt'=>'rectObj', 'oMaxGeorefExt'=>'rectObj'], +'max' => ['mixed', 'arg1'=>'non-empty-array'], +'max\'1' => ['mixed', 'arg1'=>'', 'arg2'=>'', '...args='=>''], +'maxdb::__construct' => ['void', 'host='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'], +'maxdb::affected_rows' => ['int', 'link'=>''], +'maxdb::auto_commit' => ['bool', 'link'=>'', 'mode'=>'bool'], +'maxdb::change_user' => ['bool', 'link'=>'', 'user'=>'string', 'password'=>'string', 'database'=>'string'], +'maxdb::character_set_name' => ['string', 'link'=>''], +'maxdb::close' => ['bool', 'link'=>''], +'maxdb::commit' => ['bool', 'link'=>''], +'maxdb::disable_reads_from_master' => ['', 'link'=>''], +'maxdb::errno' => ['int', 'link'=>''], +'maxdb::error' => ['string', 'link'=>''], +'maxdb::field_count' => ['int', 'link'=>''], +'maxdb::get_host_info' => ['string', 'link'=>''], +'maxdb::info' => ['string', 'link'=>''], +'maxdb::insert_id' => ['', 'link'=>''], +'maxdb::kill' => ['bool', 'link'=>'', 'processid'=>'int'], +'maxdb::more_results' => ['bool', 'link'=>''], +'maxdb::multi_query' => ['bool', 'link'=>'', 'query'=>'string'], +'maxdb::next_result' => ['bool', 'link'=>''], +'maxdb::num_rows' => ['int', 'result'=>''], +'maxdb::options' => ['bool', 'link'=>'', 'option'=>'int', 'value'=>''], +'maxdb::ping' => ['bool', 'link'=>''], +'maxdb::prepare' => ['maxdb_stmt', 'link'=>'', 'query'=>'string'], +'maxdb::protocol_version' => ['string', 'link'=>''], +'maxdb::query' => ['', 'link'=>'', 'query'=>'string', 'resultmode='=>'int'], +'maxdb::real_connect' => ['bool', 'link'=>'', 'hostname='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'], +'maxdb::real_escape_string' => ['string', 'link'=>'', 'escapestr'=>'string'], +'maxdb::real_query' => ['bool', 'link'=>'', 'query'=>'string'], +'maxdb::rollback' => ['bool', 'link'=>''], +'maxdb::rpl_query_type' => ['int', 'link'=>''], +'maxdb::select_db' => ['bool', 'link'=>'', 'dbname'=>'string'], +'maxdb::send_query' => ['bool', 'link'=>'', 'query'=>'string'], +'maxdb::server_info' => ['string', 'link'=>''], +'maxdb::server_version' => ['int', 'link'=>''], +'maxdb::sqlstate' => ['string', 'link'=>''], +'maxdb::ssl_set' => ['bool', 'link'=>'', 'key'=>'string', 'cert'=>'string', 'ca'=>'string', 'capath'=>'string', 'cipher'=>'string'], +'maxdb::stat' => ['string', 'link'=>''], +'maxdb::stmt_init' => ['object', 'link'=>''], +'maxdb::store_result' => ['bool', 'link'=>''], +'maxdb::thread_id' => ['int', 'link'=>''], +'maxdb::use_result' => ['resource', 'link'=>''], +'maxdb::warning_count' => ['int', 'link'=>''], +'maxdb_affected_rows' => ['int', 'link'=>'resource'], +'maxdb_autocommit' => ['bool', 'link'=>'', 'mode'=>'bool'], +'maxdb_change_user' => ['bool', 'link'=>'', 'user'=>'string', 'password'=>'string', 'database'=>'string'], +'maxdb_character_set_name' => ['string', 'link'=>''], +'maxdb_close' => ['bool', 'link'=>''], +'maxdb_commit' => ['bool', 'link'=>''], +'maxdb_connect' => ['resource', 'host='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'], +'maxdb_connect_errno' => ['int'], +'maxdb_connect_error' => ['string'], +'maxdb_data_seek' => ['bool', 'result'=>'', 'offset'=>'int'], +'maxdb_debug' => ['void', 'debug'=>'string'], +'maxdb_disable_reads_from_master' => ['', 'link'=>''], +'maxdb_disable_rpl_parse' => ['bool', 'link'=>'resource'], +'maxdb_dump_debug_info' => ['bool', 'link'=>'resource'], +'maxdb_embedded_connect' => ['resource', 'dbname='=>'string'], +'maxdb_enable_reads_from_master' => ['bool', 'link'=>'resource'], +'maxdb_enable_rpl_parse' => ['bool', 'link'=>'resource'], +'maxdb_errno' => ['int', 'link'=>'resource'], +'maxdb_error' => ['string', 'link'=>'resource'], +'maxdb_fetch_array' => ['', 'result'=>'', 'resulttype='=>'int'], +'maxdb_fetch_assoc' => ['array', 'result'=>''], +'maxdb_fetch_field' => ['', 'result'=>''], +'maxdb_fetch_field_direct' => ['', 'result'=>'', 'fieldnr'=>'int'], +'maxdb_fetch_fields' => ['', 'result'=>''], +'maxdb_fetch_lengths' => ['array', 'result'=>'resource'], +'maxdb_fetch_object' => ['object', 'result'=>'object'], +'maxdb_fetch_row' => ['', 'result'=>''], +'maxdb_field_count' => ['int', 'link'=>''], +'maxdb_field_seek' => ['bool', 'result'=>'', 'fieldnr'=>'int'], +'maxdb_field_tell' => ['int', 'result'=>'resource'], +'maxdb_free_result' => ['', 'result'=>''], +'maxdb_get_client_info' => ['string'], +'maxdb_get_client_version' => ['int'], +'maxdb_get_host_info' => ['string', 'link'=>'resource'], +'maxdb_get_proto_info' => ['string', 'link'=>'resource'], +'maxdb_get_server_info' => ['string', 'link'=>'resource'], +'maxdb_get_server_version' => ['int', 'link'=>'resource'], +'maxdb_info' => ['string', 'link'=>'resource'], +'maxdb_init' => ['resource'], +'maxdb_insert_id' => ['mixed', 'link'=>'resource'], +'maxdb_kill' => ['bool', 'link'=>'', 'processid'=>'int'], +'maxdb_master_query' => ['bool', 'link'=>'resource', 'query'=>'string'], +'maxdb_more_results' => ['bool', 'link'=>'resource'], +'maxdb_multi_query' => ['bool', 'link'=>'', 'query'=>'string'], +'maxdb_next_result' => ['bool', 'link'=>'resource'], +'maxdb_num_fields' => ['int', 'result'=>'resource'], +'maxdb_num_rows' => ['int', 'result'=>'resource'], +'maxdb_options' => ['bool', 'link'=>'', 'option'=>'int', 'value'=>''], +'maxdb_ping' => ['bool', 'link'=>''], +'maxdb_prepare' => ['maxdb_stmt', 'link'=>'', 'query'=>'string'], +'maxdb_query' => ['', 'link'=>'', 'query'=>'string', 'resultmode='=>'int'], +'maxdb_real_connect' => ['bool', 'link'=>'', 'hostname='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'], +'maxdb_real_escape_string' => ['string', 'link'=>'', 'escapestr'=>'string'], +'maxdb_real_query' => ['bool', 'link'=>'', 'query'=>'string'], +'maxdb_report' => ['bool', 'flags'=>'int'], +'maxdb_result::current_field' => ['int', 'result'=>''], +'maxdb_result::data_seek' => ['bool', 'result'=>'', 'offset'=>'int'], +'maxdb_result::fetch_array' => ['', 'result'=>'', 'resulttype='=>'int'], +'maxdb_result::fetch_assoc' => ['array', 'result'=>''], +'maxdb_result::fetch_field' => ['', 'result'=>''], +'maxdb_result::fetch_field_direct' => ['', 'result'=>'', 'fieldnr'=>'int'], +'maxdb_result::fetch_fields' => ['', 'result'=>''], +'maxdb_result::fetch_object' => ['object', 'result'=>'object'], +'maxdb_result::fetch_row' => ['', 'result'=>''], +'maxdb_result::field_count' => ['int', 'result'=>''], +'maxdb_result::field_seek' => ['bool', 'result'=>'', 'fieldnr'=>'int'], +'maxdb_result::free' => ['', 'result'=>''], +'maxdb_result::lengths' => ['array', 'result'=>''], +'maxdb_rollback' => ['bool', 'link'=>''], +'maxdb_rpl_parse_enabled' => ['int', 'link'=>'resource'], +'maxdb_rpl_probe' => ['bool', 'link'=>'resource'], +'maxdb_rpl_query_type' => ['int', 'link'=>''], +'maxdb_select_db' => ['bool', 'link'=>'resource', 'dbname'=>'string'], +'maxdb_send_query' => ['bool', 'link'=>'', 'query'=>'string'], +'maxdb_server_end' => ['void'], +'maxdb_server_init' => ['bool', 'server='=>'array', 'groups='=>'array'], +'maxdb_sqlstate' => ['string', 'link'=>'resource'], +'maxdb_ssl_set' => ['bool', 'link'=>'', 'key'=>'string', 'cert'=>'string', 'ca'=>'string', 'capath'=>'string', 'cipher'=>'string'], +'maxdb_stat' => ['string', 'link'=>''], +'maxdb_stmt::affected_rows' => ['int', 'stmt'=>''], +'maxdb_stmt::bind_param' => ['bool', 'stmt'=>'', 'types'=>'string', '&...rw_var'=>''], +'maxdb_stmt::bind_param\'1' => ['bool', 'stmt'=>'', 'types'=>'string', '&rw_var'=>'array'], +'maxdb_stmt::bind_result' => ['bool', 'stmt'=>'', '&w_var1'=>'', '&...w_vars='=>''], +'maxdb_stmt::close' => ['bool', 'stmt'=>''], +'maxdb_stmt::close_long_data' => ['bool', 'stmt'=>'', 'param_nr'=>'int'], +'maxdb_stmt::data_seek' => ['bool', 'statement'=>'', 'offset'=>'int'], +'maxdb_stmt::errno' => ['int', 'stmt'=>''], +'maxdb_stmt::error' => ['string', 'stmt'=>''], +'maxdb_stmt::execute' => ['bool', 'stmt'=>''], +'maxdb_stmt::fetch' => ['bool', 'stmt'=>''], +'maxdb_stmt::free_result' => ['', 'stmt'=>''], +'maxdb_stmt::num_rows' => ['int', 'stmt'=>''], +'maxdb_stmt::param_count' => ['int', 'stmt'=>''], +'maxdb_stmt::prepare' => ['', 'stmt'=>'', 'query'=>'string'], +'maxdb_stmt::reset' => ['bool', 'stmt'=>''], +'maxdb_stmt::result_metadata' => ['resource', 'stmt'=>''], +'maxdb_stmt::send_long_data' => ['bool', 'stmt'=>'', 'param_nr'=>'int', 'data'=>'string'], +'maxdb_stmt::stmt_send_long_data' => ['bool', 'param_nr'=>'int', 'data'=>'string'], +'maxdb_stmt::store_result' => ['bool'], +'maxdb_stmt_affected_rows' => ['int', 'stmt'=>'resource'], +'maxdb_stmt_bind_param' => ['bool', 'stmt'=>'', 'types'=>'string', 'var1'=>'', '...args='=>'', 'var='=>'array'], +'maxdb_stmt_bind_result' => ['bool', 'stmt'=>'', '&w_var1'=>'', '&...w_vars='=>''], +'maxdb_stmt_close' => ['bool', 'stmt'=>''], +'maxdb_stmt_close_long_data' => ['bool', 'stmt'=>'', 'param_nr'=>'int'], +'maxdb_stmt_data_seek' => ['bool', 'statement'=>'', 'offset'=>'int'], +'maxdb_stmt_errno' => ['int', 'stmt'=>'resource'], +'maxdb_stmt_error' => ['string', 'stmt'=>'resource'], +'maxdb_stmt_execute' => ['bool', 'stmt'=>''], +'maxdb_stmt_fetch' => ['bool', 'stmt'=>''], +'maxdb_stmt_free_result' => ['', 'stmt'=>''], +'maxdb_stmt_init' => ['object', 'link'=>''], +'maxdb_stmt_num_rows' => ['int', 'stmt'=>'resource'], +'maxdb_stmt_param_count' => ['int', 'stmt'=>'resource'], +'maxdb_stmt_prepare' => ['', 'stmt'=>'', 'query'=>'string'], +'maxdb_stmt_reset' => ['bool', 'stmt'=>''], +'maxdb_stmt_result_metadata' => ['resource', 'stmt'=>''], +'maxdb_stmt_send_long_data' => ['bool', 'stmt'=>'', 'param_nr'=>'int', 'data'=>'string'], +'maxdb_stmt_sqlstate' => ['string', 'stmt'=>'resource'], +'maxdb_stmt_store_result' => ['bool', 'stmt'=>''], +'maxdb_store_result' => ['bool', 'link'=>''], +'maxdb_thread_id' => ['int', 'link'=>'resource'], +'maxdb_thread_safe' => ['bool'], +'maxdb_use_result' => ['resource', 'link'=>''], +'maxdb_warning_count' => ['int', 'link'=>'resource'], +'mb_check_encoding' => ['bool', 'var='=>'string', 'encoding='=>'string'], +'mb_chr' => ['string|false', 'cp'=>'int', 'encoding='=>'string'], +'mb_convert_case' => ['string|false', 'sourcestring'=>'string', 'mode'=>'int', 'encoding='=>'string'], +'mb_convert_encoding' => ['string', 'val'=>'string', 'to_encoding'=>'string', 'from_encoding='=>'mixed'], +'mb_convert_encoding\'1' => ['array', 'val'=>'array', 'to_encoding'=>'string', 'from_encoding='=>'mixed'], +'mb_convert_kana' => ['string|false', 'string'=>'string', 'option='=>'string', 'encoding='=>'string'], +'mb_convert_variables' => ['string|false', 'to_encoding'=>'string', 'from_encoding'=>'array|string', '&rw_vars'=>'string|array|object', '&...rw_vars='=>'string|array|object'], +'mb_decode_mimeheader' => ['string', 'string'=>'string'], +'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string', 'is_hex='=>'bool'], +'mb_detect_encoding' => ['string|false', 'string'=>'string', 'encoding_list='=>'mixed', 'strict='=>'bool'], +'mb_detect_order' => ['bool|list', 'encoding_list='=>'mixed'], +'mb_encode_mimeheader' => ['string|false', 'string'=>'string', 'charset='=>'string', 'transfer_encoding='=>'string', 'linefeed='=>'string', 'indent='=>'int'], +'mb_encode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string', 'is_hex='=>'bool'], +'mb_encoding_aliases' => ['list|false', 'encoding'=>'string'], +'mb_ereg' => ['int|false', 'pattern'=>'string', 'string'=>'string', '&w_registers='=>'array'], +'mb_ereg_match' => ['bool', 'pattern'=>'string', 'string'=>'string', 'option='=>'string'], +'mb_ereg_replace' => ['string|false', 'pattern'=>'string', 'replacement'=>'string', 'string'=>'string', 'option='=>'string'], +'mb_ereg_replace_callback' => ['string|false', 'pattern'=>'string', 'callback'=>'callable', 'string'=>'string', 'option='=>'string'], +'mb_ereg_search' => ['bool', 'pattern='=>'string', 'option='=>'string'], +'mb_ereg_search_getpos' => ['int'], +'mb_ereg_search_getregs' => ['string[]|false'], +'mb_ereg_search_init' => ['bool', 'string'=>'string', 'pattern='=>'string', 'option='=>'string'], +'mb_ereg_search_pos' => ['int[]|false', 'pattern='=>'string', 'option='=>'string'], +'mb_ereg_search_regs' => ['string[]|false', 'pattern='=>'string', 'option='=>'string'], +'mb_ereg_search_setpos' => ['bool', 'position'=>'int'], +'mb_eregi' => ['int|false', 'pattern'=>'string', 'string'=>'string', '&w_registers='=>'array'], +'mb_eregi_replace' => ['string|false', 'pattern'=>'string', 'replacement'=>'string', 'string'=>'string', 'option='=>'string'], +'mb_get_info' => ['array|mixed', 'type='=>'string'], +'mb_http_input' => ['string|false', 'type='=>'string'], +'mb_http_output' => ['string|bool', 'encoding='=>'string'], +'mb_internal_encoding' => ['string|bool', 'encoding='=>'string'], +'mb_language' => ['string|bool', 'language='=>'string'], +'mb_list_encodings' => ['list'], +'mb_ord' => ['int|false', 'string'=>'string', 'enc='=>'string'], +'mb_output_handler' => ['string', 'contents'=>'string', 'status'=>'int'], +'mb_parse_str' => ['bool', 'encoded_string'=>'string', '&w_result='=>'array'], +'mb_preferred_mime_name' => ['string|false', 'encoding'=>'string'], +'mb_regex_encoding' => ['string|bool', 'encoding='=>'string'], +'mb_regex_set_options' => ['string', 'options='=>'string'], +'mb_scrub' => ['string|false', 'string'=>'string', 'enc='=>'string'], +'mb_send_mail' => ['bool', 'to'=>'string', 'subject'=>'string', 'message'=>'string', 'additional_headers='=>'string|array', 'additional_parameter='=>'string'], +'mb_split' => ['list', 'pattern'=>'string', 'string'=>'string', 'limit='=>'int'], +'mb_strcut' => ['string', 'str'=>'string', 'start'=>'int', 'length='=>'?int', 'encoding='=>'string'], +'mb_strimwidth' => ['string|false', 'string'=>'string', 'start'=>'int', 'width'=>'int', 'trimmarker='=>'string', 'encoding='=>'string'], +'mb_stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'], +'mb_stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'], +'mb_strlen' => ['int|false', 'string'=>'string', 'encoding='=>'string'], +'mb_strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'], +'mb_strrchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'], +'mb_strrichr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'], +'mb_strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'], +'mb_strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'], +'mb_strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'], +'mb_strtolower' => ['string|false', 'string'=>'string', 'encoding='=>'string'], +'mb_strtoupper' => ['string|false', 'string'=>'string', 'encoding='=>'string'], +'mb_strwidth' => ['int|false', 'string'=>'string', 'encoding='=>'string'], +'mb_substitute_character' => ['bool|int|string', 'substchar='=>'mixed'], +'mb_substr' => ['string|false', 'string'=>'string', 'start'=>'int', 'length='=>'?int', 'encoding='=>'string'], +'mb_substr_count' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'encoding='=>'string'], +'mcrypt_cbc' => ['string', 'cipher'=>'string|int', 'key'=>'string', 'data'=>'string', 'mode'=>'int', 'iv='=>'string'], +'mcrypt_cfb' => ['string', 'cipher'=>'string|int', 'key'=>'string', 'data'=>'string', 'mode'=>'int', 'iv='=>'string'], +'mcrypt_create_iv' => ['string|false', 'size'=>'int', 'source='=>'int'], +'mcrypt_decrypt' => ['string', 'cipher'=>'string', 'key'=>'string', 'data'=>'string', 'mode'=>'string', 'iv='=>'string'], +'mcrypt_ecb' => ['string', 'cipher'=>'string|int', 'key'=>'string', 'data'=>'string', 'mode'=>'int', 'iv='=>'string'], +'mcrypt_enc_get_algorithms_name' => ['string', 'td'=>'resource'], +'mcrypt_enc_get_block_size' => ['int', 'td'=>'resource'], +'mcrypt_enc_get_iv_size' => ['int', 'td'=>'resource'], +'mcrypt_enc_get_key_size' => ['int', 'td'=>'resource'], +'mcrypt_enc_get_modes_name' => ['string', 'td'=>'resource'], +'mcrypt_enc_get_supported_key_sizes' => ['array', 'td'=>'resource'], +'mcrypt_enc_is_block_algorithm' => ['bool', 'td'=>'resource'], +'mcrypt_enc_is_block_algorithm_mode' => ['bool', 'td'=>'resource'], +'mcrypt_enc_is_block_mode' => ['bool', 'td'=>'resource'], +'mcrypt_enc_self_test' => ['int|false', 'td'=>'resource'], +'mcrypt_encrypt' => ['string', 'cipher'=>'string', 'key'=>'string', 'data'=>'string', 'mode'=>'string', 'iv='=>'string'], +'mcrypt_generic' => ['string', 'td'=>'resource', 'data'=>'string'], +'mcrypt_generic_deinit' => ['bool', 'td'=>'resource'], +'mcrypt_generic_end' => ['bool', 'td'=>'resource'], +'mcrypt_generic_init' => ['int|false', 'td'=>'resource', 'key'=>'string', 'iv'=>'string'], +'mcrypt_get_block_size' => ['int', 'cipher'=>'int|string', 'module'=>'string'], +'mcrypt_get_cipher_name' => ['string|false', 'cipher'=>'int|string'], +'mcrypt_get_iv_size' => ['int|false', 'cipher'=>'int|string', 'module'=>'string'], +'mcrypt_get_key_size' => ['int', 'cipher'=>'int|string', 'module'=>'string'], +'mcrypt_list_algorithms' => ['array', 'lib_dir='=>'string'], +'mcrypt_list_modes' => ['array', 'lib_dir='=>'string'], +'mcrypt_module_close' => ['bool', 'td'=>'resource'], +'mcrypt_module_get_algo_block_size' => ['int', 'algorithm'=>'string', 'lib_dir='=>'string'], +'mcrypt_module_get_algo_key_size' => ['int', 'algorithm'=>'string', 'lib_dir='=>'string'], +'mcrypt_module_get_supported_key_sizes' => ['array', 'algorithm'=>'string', 'lib_dir='=>'string'], +'mcrypt_module_is_block_algorithm' => ['bool', 'algorithm'=>'string', 'lib_dir='=>'string'], +'mcrypt_module_is_block_algorithm_mode' => ['bool', 'mode'=>'string', 'lib_dir='=>'string'], +'mcrypt_module_is_block_mode' => ['bool', 'mode'=>'string', 'lib_dir='=>'string'], +'mcrypt_module_open' => ['resource|false', 'cipher'=>'string', 'cipher_directory'=>'string', 'mode'=>'string', 'mode_directory'=>'string'], +'mcrypt_module_self_test' => ['bool', 'algorithm'=>'string', 'lib_dir='=>'string'], +'mcrypt_ofb' => ['string', 'cipher'=>'int|string', 'key'=>'string', 'data'=>'string', 'mode'=>'int', 'iv='=>'string'], +'md5' => ['string', 'string'=>'string', 'raw_output='=>'bool'], +'md5_file' => ['string|false', 'filename'=>'string', 'raw_output='=>'bool'], +'mdecrypt_generic' => ['string', 'td'=>'resource', 'data'=>'string'], +'Memcache::add' => ['bool', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'], +'Memcache::addServer' => ['bool', 'host'=>'string', 'port='=>'int', 'persistent='=>'bool', 'weight='=>'int', 'timeout='=>'int', 'retry_interval='=>'int', 'status='=>'bool', 'failure_callback='=>'callable', 'timeoutms='=>'int'], +'Memcache::append' => [''], +'Memcache::cas' => [''], +'Memcache::close' => ['bool'], +'Memcache::connect' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'], +'Memcache::decrement' => ['int', 'key'=>'string', 'value='=>'int'], +'Memcache::delete' => ['bool', 'key'=>'string', 'timeout='=>'int'], +'Memcache::findServer' => [''], +'Memcache::flush' => ['bool'], +'Memcache::get' => ['string|array|false', 'key'=>'string', 'flags='=>'array', 'keys='=>'array'], +'Memcache::get\'1' => ['array', 'key'=>'string[]', 'flags='=>'int[]'], +'Memcache::getExtendedStats' => ['array', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'], +'Memcache::getServerStatus' => ['int', 'host'=>'string', 'port='=>'int'], +'Memcache::getStats' => ['array', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'], +'Memcache::getVersion' => ['string'], +'Memcache::increment' => ['int', 'key'=>'string', 'value='=>'int'], +'Memcache::pconnect' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'], +'Memcache::prepend' => ['string'], +'Memcache::replace' => ['bool', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'], +'Memcache::set' => ['bool', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'], +'Memcache::setCompressThreshold' => ['bool', 'threshold'=>'int', 'min_savings='=>'float'], +'Memcache::setFailureCallback' => [''], +'Memcache::setServerParams' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'int', 'retry_interval='=>'int', 'status='=>'bool', 'failure_callback='=>'callable'], +'memcache_add' => ['bool', 'memcache_obj'=>'Memcache', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'], +'memcache_add_server' => ['bool', 'memcache_obj'=>'Memcache', 'host'=>'string', 'port='=>'int', 'persistent='=>'bool', 'weight='=>'int', 'timeout='=>'int', 'retry_interval='=>'int', 'status='=>'bool', 'failure_callback='=>'callable', 'timeoutms='=>'int'], +'memcache_append' => ['', 'memcache_obj'=>'Memcache'], +'memcache_cas' => ['', 'memcache_obj'=>'Memcache'], +'memcache_close' => ['bool', 'memcache_obj'=>'Memcache'], +'memcache_connect' => ['Memcache|false', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'], +'memcache_debug' => ['bool', 'on_off'=>'bool'], +'memcache_decrement' => ['int', 'memcache_obj'=>'Memcache', 'key'=>'string', 'value='=>'int'], +'memcache_delete' => ['bool', 'memcache_obj'=>'Memcache', 'key'=>'string', 'timeout='=>'int'], +'memcache_flush' => ['bool', 'memcache_obj'=>'Memcache'], +'memcache_get' => ['string', 'memcache_obj'=>'Memcache', 'key'=>'string', 'flags='=>'int'], +'memcache_get\'1' => ['array', 'memcache_obj'=>'Memcache', 'key'=>'string[]', 'flags='=>'int[]'], +'memcache_get_extended_stats' => ['array', 'memcache_obj'=>'Memcache', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'], +'memcache_get_server_status' => ['int', 'memcache_obj'=>'Memcache', 'host'=>'string', 'port='=>'int'], +'memcache_get_stats' => ['array', 'memcache_obj'=>'Memcache', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'], +'memcache_get_version' => ['string', 'memcache_obj'=>'Memcache'], +'memcache_increment' => ['int', 'memcache_obj'=>'Memcache', 'key'=>'string', 'value='=>'int'], +'memcache_pconnect' => ['Memcache|false', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'], +'memcache_prepend' => ['string', 'memcache_obj'=>'Memcache'], +'memcache_replace' => ['bool', 'memcache_obj'=>'Memcache', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'], +'memcache_set' => ['bool', 'memcache_obj'=>'Memcache', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'], +'memcache_set_compress_threshold' => ['bool', 'memcache_obj'=>'Memcache', 'threshold'=>'int', 'min_savings='=>'float'], +'memcache_set_failure_callback' => ['', 'memcache_obj'=>'Memcache'], +'memcache_set_server_params' => ['bool', 'memcache_obj'=>'Memcache', 'host'=>'string', 'port='=>'int', 'timeout='=>'int', 'retry_interval='=>'int', 'status='=>'bool', 'failure_callback='=>'callable'], +'Memcached::__construct' => ['void', 'persistent_id='=>'mixed|string', 'on_new_object_cb='=>'mixed'], +'Memcached::add' => ['bool', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'], +'Memcached::addByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'], +'Memcached::addServer' => ['bool', 'host'=>'string', 'port'=>'int', 'weight='=>'int'], +'Memcached::addServers' => ['bool', 'servers'=>'array'], +'Memcached::append' => ['bool', 'key'=>'string', 'value'=>'string'], +'Memcached::appendByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'value'=>'string'], +'Memcached::cas' => ['bool', 'cas_token'=>'float', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'], +'Memcached::casByKey' => ['bool', 'cas_token'=>'float', 'server_key'=>'string', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'], +'Memcached::decrement' => ['int|false', 'key'=>'string', 'offset='=>'int', 'initial_value='=>'int', 'expiry='=>'int'], +'Memcached::decrementByKey' => ['int|false', 'server_key'=>'string', 'key'=>'string', 'offset='=>'int', 'initial_value='=>'int', 'expiry='=>'int'], +'Memcached::delete' => ['bool', 'key'=>'string', 'time='=>'int'], +'Memcached::deleteByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'time='=>'int'], +'Memcached::deleteMulti' => ['array', 'keys'=>'array', 'time='=>'int'], +'Memcached::deleteMultiByKey' => ['bool', 'server_key'=>'string', 'keys'=>'array', 'time='=>'int'], +'Memcached::fetch' => ['array|false'], +'Memcached::fetchAll' => ['array|false'], +'Memcached::flush' => ['bool', 'delay='=>'int'], +'Memcached::flushBuffers' => [''], +'Memcached::get' => ['mixed|false', 'key'=>'string', 'cache_cb='=>'?callable', 'flags='=>'int'], +'Memcached::getAllKeys' => ['array|false'], +'Memcached::getByKey' => ['mixed|false', 'server_key'=>'string', 'key'=>'string', 'value_cb='=>'?callable', 'flags='=>'int'], +'Memcached::getDelayed' => ['bool', 'keys'=>'array', 'with_cas='=>'bool', 'value_cb='=>'callable'], +'Memcached::getDelayedByKey' => ['bool', 'server_key'=>'string', 'keys'=>'array', 'with_cas='=>'bool', 'value_cb='=>'?callable'], +'Memcached::getLastDisconnectedServer' => [''], +'Memcached::getLastErrorCode' => [''], +'Memcached::getLastErrorErrno' => [''], +'Memcached::getLastErrorMessage' => [''], +'Memcached::getMulti' => ['array|false', 'keys'=>'array', 'flags='=>'int'], +'Memcached::getMultiByKey' => ['array|false', 'server_key'=>'string', 'keys'=>'array', 'flags='=>'int'], +'Memcached::getOption' => ['mixed|false', 'option'=>'int'], +'Memcached::getResultCode' => ['int'], +'Memcached::getResultMessage' => ['string'], +'Memcached::getServerByKey' => ['array', 'server_key'=>'string'], +'Memcached::getServerList' => ['array'], +'Memcached::getStats' => ['array', 'type='=>'?string'], +'Memcached::getVersion' => ['array'], +'Memcached::increment' => ['int|false', 'key'=>'string', 'offset='=>'int', 'initial_value='=>'int', 'expiry='=>'int'], +'Memcached::incrementByKey' => ['int|false', 'server_key'=>'string', 'key'=>'string', 'offset='=>'int', 'initial_value='=>'int', 'expiry='=>'int'], +'Memcached::isPersistent' => ['bool'], +'Memcached::isPristine' => ['bool'], +'Memcached::prepend' => ['bool', 'key'=>'string', 'value'=>'string'], +'Memcached::prependByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'value'=>'string'], +'Memcached::quit' => ['bool'], +'Memcached::replace' => ['bool', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'], +'Memcached::replaceByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'], +'Memcached::resetServerList' => ['bool'], +'Memcached::set' => ['bool', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'], +'Memcached::setBucket' => ['', 'host_map'=>'array', 'forward_map'=>'array', 'replicas'=>''], +'Memcached::setByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'], +'Memcached::setEncodingKey' => ['', 'key'=>''], +'Memcached::setMulti' => ['bool', 'items'=>'array', 'expiration='=>'int'], +'Memcached::setMultiByKey' => ['bool', 'server_key'=>'string', 'items'=>'array', 'expiration='=>'int'], +'Memcached::setOption' => ['bool', 'option'=>'int', 'value'=>'mixed'], +'Memcached::setOptions' => ['bool', 'options'=>'array'], +'Memcached::setSaslAuthData' => ['void', 'username'=>'string', 'password'=>'string'], +'Memcached::touch' => ['bool', 'key'=>'string', 'expiration'=>'int'], +'Memcached::touchByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'expiration'=>'int'], +'MemcachePool::add' => ['bool', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'], +'MemcachePool::addServer' => ['bool', 'host'=>'string', 'port='=>'int', 'persistent='=>'bool', 'weight='=>'int', 'timeout='=>'int', 'retry_interval='=>'int', 'status='=>'bool', 'failure_callback='=>'?callable', 'timeoutms='=>'int'], +'MemcachePool::append' => [''], +'MemcachePool::cas' => [''], +'MemcachePool::close' => ['bool'], +'MemcachePool::connect' => ['bool', 'host'=>'string', 'port'=>'int', 'timeout='=>'int'], +'MemcachePool::decrement' => ['int|false', 'key'=>'', 'value='=>'int|mixed'], +'MemcachePool::delete' => ['bool', 'key'=>'', 'timeout='=>'int|mixed'], +'MemcachePool::findServer' => [''], +'MemcachePool::flush' => ['bool'], +'MemcachePool::get' => ['array|string|false', 'key'=>'array|string', '&flags='=>'array|int'], +'MemcachePool::getExtendedStats' => ['array|false', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'], +'MemcachePool::getServerStatus' => ['int', 'host'=>'string', 'port='=>'int'], +'MemcachePool::getStats' => ['array|false', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'], +'MemcachePool::getVersion' => ['string|false'], +'MemcachePool::increment' => ['int|false', 'key'=>'', 'value='=>'int|mixed'], +'MemcachePool::prepend' => ['string'], +'MemcachePool::replace' => ['bool', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'], +'MemcachePool::set' => ['bool', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'], +'MemcachePool::setCompressThreshold' => ['bool', 'thresold'=>'int', 'min_saving='=>'float'], +'MemcachePool::setFailureCallback' => [''], +'MemcachePool::setServerParams' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'int', 'retry_interval='=>'int', 'status='=>'bool', 'failure_callback='=>'?callable'], +'memory_get_peak_usage' => ['int', 'real_usage='=>'bool'], +'memory_get_usage' => ['int', 'real_usage='=>'bool'], +'MessageFormatter::__construct' => ['void', 'locale'=>'string', 'pattern'=>'string'], +'MessageFormatter::create' => ['MessageFormatter', 'locale'=>'string', 'pattern'=>'string'], +'MessageFormatter::format' => ['false|string', 'args'=>'array'], +'MessageFormatter::formatMessage' => ['false|string', 'locale'=>'string', 'pattern'=>'string', 'args'=>'array'], +'MessageFormatter::getErrorCode' => ['int'], +'MessageFormatter::getErrorMessage' => ['string'], +'MessageFormatter::getLocale' => ['string'], +'MessageFormatter::getPattern' => ['string'], +'MessageFormatter::parse' => ['array|false', 'value'=>'string'], +'MessageFormatter::parseMessage' => ['array|false', 'locale'=>'string', 'pattern'=>'string', 'source'=>'string'], +'MessageFormatter::setPattern' => ['bool', 'pattern'=>'string'], +'metaphone' => ['string|false', 'text'=>'string', 'phones='=>'int'], +'method_exists' => ['bool', 'object'=>'object|class-string', 'method'=>'string'], +'mhash' => ['string', 'hash'=>'int', 'data'=>'string', 'key='=>'string'], +'mhash_count' => ['int'], +'mhash_get_block_size' => ['int|false', 'hash'=>'int'], +'mhash_get_hash_name' => ['string|false', 'hash'=>'int'], +'mhash_keygen_s2k' => ['string|false', 'hash'=>'int', 'input_password'=>'string', 'salt'=>'string', 'bytes'=>'int'], +'microtime' => ['string', 'get_as_float='=>'false'], +'microtime\'1' => ['float', 'get_as_float='=>'true'], +'mime_content_type' => ['string|false', 'filename_or_stream'=>'string|resource'], +'min' => ['mixed', 'arg1'=>'non-empty-array'], +'min\'1' => ['mixed', 'arg1'=>'', 'arg2'=>'', '...args='=>''], +'ming_keypress' => ['int', 'char'=>'string'], +'ming_setcubicthreshold' => ['void', 'threshold'=>'int'], +'ming_setscale' => ['void', 'scale'=>'float'], +'ming_setswfcompression' => ['void', 'level'=>'int'], +'ming_useconstants' => ['void', 'use'=>'int'], +'ming_useswfversion' => ['void', 'version'=>'int'], +'mkdir' => ['bool', 'pathname'=>'string', 'mode='=>'int', 'recursive='=>'bool', 'context='=>'resource'], +'mktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], +'money_format' => ['string', 'format'=>'string', 'value'=>'float'], +'Mongo::__construct' => ['void', 'server='=>'string', 'options='=>'array', 'driver_options='=>'array'], +'Mongo::__get' => ['MongoDB', 'dbname'=>'string'], +'Mongo::__toString' => ['string'], +'Mongo::close' => ['bool'], +'Mongo::connect' => ['bool'], +'Mongo::connectUtil' => ['bool'], +'Mongo::dropDB' => ['array', 'db'=>'mixed'], +'Mongo::forceError' => ['bool'], +'Mongo::getConnections' => ['array'], +'Mongo::getHosts' => ['array'], +'Mongo::getPoolSize' => ['int'], +'Mongo::getReadPreference' => ['array'], +'Mongo::getSlave' => ['?string'], +'Mongo::getSlaveOkay' => ['bool'], +'Mongo::getWriteConcern' => ['array'], +'Mongo::killCursor' => ['', 'server_hash'=>'string', 'id'=>'MongoInt64|int'], +'Mongo::lastError' => ['?array'], +'Mongo::listDBs' => ['array'], +'Mongo::pairConnect' => ['bool'], +'Mongo::pairPersistConnect' => ['bool', 'username='=>'string', 'password='=>'string'], +'Mongo::persistConnect' => ['bool', 'username='=>'string', 'password='=>'string'], +'Mongo::poolDebug' => ['array'], +'Mongo::prevError' => ['array'], +'Mongo::resetError' => ['array'], +'Mongo::selectCollection' => ['MongoCollection', 'db'=>'string', 'collection'=>'string'], +'Mongo::selectDB' => ['MongoDB', 'name'=>'string'], +'Mongo::setPoolSize' => ['bool', 'size'=>'int'], +'Mongo::setReadPreference' => ['bool', 'readPreference'=>'string', 'tags='=>'array'], +'Mongo::setSlaveOkay' => ['bool', 'ok='=>'bool'], +'Mongo::switchSlave' => ['string'], +'MongoBinData::__construct' => ['void', 'data'=>'string', 'type='=>'int'], +'MongoBinData::__toString' => ['string'], +'MongoClient::__construct' => ['void', 'server='=>'string', 'options='=>'array', 'driver_options='=>'array'], +'MongoClient::__get' => ['MongoDB', 'dbname'=>'string'], +'MongoClient::__toString' => ['string'], +'MongoClient::close' => ['bool', 'connection='=>'bool|string'], +'MongoClient::connect' => ['bool'], +'MongoClient::dropDB' => ['array', 'db'=>'mixed'], +'MongoClient::getConnections' => ['array'], +'MongoClient::getHosts' => ['array'], +'MongoClient::getReadPreference' => ['array'], +'MongoClient::getWriteConcern' => ['array'], +'MongoClient::killCursor' => ['bool', 'server_hash'=>'string', 'id'=>'int|MongoInt64'], +'MongoClient::listDBs' => ['array'], +'MongoClient::selectCollection' => ['MongoCollection', 'db'=>'string', 'collection'=>'string'], +'MongoClient::selectDB' => ['MongoDB', 'name'=>'string'], +'MongoClient::setReadPreference' => ['bool', 'read_preference'=>'string', 'tags='=>'array'], +'MongoClient::setWriteConcern' => ['bool', 'w'=>'mixed', 'wtimeout='=>'int'], +'MongoClient::switchSlave' => ['string'], +'MongoCode::__construct' => ['void', 'code'=>'string', 'scope='=>'array'], +'MongoCode::__toString' => ['string'], +'MongoCollection::__construct' => ['void', 'db'=>'MongoDB', 'name'=>'string'], +'MongoCollection::__get' => ['MongoCollection', 'name'=>'string'], +'MongoCollection::__toString' => ['string'], +'MongoCollection::aggregate' => ['array', 'op'=>'array', 'op='=>'array', '...args='=>'array'], +'MongoCollection::aggregate\'1' => ['array', 'pipeline'=>'array', 'options='=>'array'], +'MongoCollection::aggregateCursor' => ['MongoCommandCursor', 'command'=>'array', 'options='=>'array'], +'MongoCollection::batchInsert' => ['array|bool', 'a'=>'array', 'options='=>'array'], +'MongoCollection::count' => ['int', 'query='=>'array', 'limit='=>'int', 'skip='=>'int'], +'MongoCollection::createDBRef' => ['array', 'a'=>'array'], +'MongoCollection::createIndex' => ['array', 'keys'=>'array', 'options='=>'array'], +'MongoCollection::deleteIndex' => ['array', 'keys'=>'string|array'], +'MongoCollection::deleteIndexes' => ['array'], +'MongoCollection::distinct' => ['array|false', 'key'=>'string', 'query='=>'array'], +'MongoCollection::drop' => ['array'], +'MongoCollection::ensureIndex' => ['bool', 'keys'=>'array', 'options='=>'array'], +'MongoCollection::find' => ['MongoCursor', 'query='=>'array', 'fields='=>'array'], +'MongoCollection::findAndModify' => ['array', 'query'=>'array', 'update='=>'array', 'fields='=>'array', 'options='=>'array'], +'MongoCollection::findOne' => ['?array', 'query='=>'array', 'fields='=>'array'], +'MongoCollection::getDBRef' => ['array', 'ref'=>'array'], +'MongoCollection::getIndexInfo' => ['array'], +'MongoCollection::getName' => ['string'], +'MongoCollection::getReadPreference' => ['array'], +'MongoCollection::getSlaveOkay' => ['bool'], +'MongoCollection::getWriteConcern' => ['array'], +'MongoCollection::group' => ['array', 'keys'=>'mixed', 'initial'=>'array', 'reduce'=>'MongoCode', 'options='=>'array'], +'MongoCollection::insert' => ['bool|array', 'a'=>'array|object', 'options='=>'array'], +'MongoCollection::parallelCollectionScan' => ['MongoCommandCursor[]', 'num_cursors'=>'int'], +'MongoCollection::remove' => ['bool|array', 'criteria='=>'array', 'options='=>'array'], +'MongoCollection::save' => ['bool|array', 'a'=>'array|object', 'options='=>'array'], +'MongoCollection::setReadPreference' => ['bool', 'read_preference'=>'string', 'tags='=>'array'], +'MongoCollection::setSlaveOkay' => ['bool', 'ok='=>'bool'], +'MongoCollection::setWriteConcern' => ['bool', 'w'=>'mixed', 'wtimeout='=>'int'], +'MongoCollection::toIndexString' => ['string', 'keys'=>'mixed'], +'MongoCollection::update' => ['bool', 'criteria'=>'array', 'newobj'=>'array', 'options='=>'array'], +'MongoCollection::validate' => ['array', 'scan_data='=>'bool'], +'MongoCommandCursor::__construct' => ['void', 'connection'=>'MongoClient', 'ns'=>'string', 'command'=>'array'], +'MongoCommandCursor::batchSize' => ['MongoCommandCursor', 'batchSize'=>'int'], +'MongoCommandCursor::createFromDocument' => ['MongoCommandCursor', 'connection'=>'MongoClient', 'hash'=>'string', 'document'=>'array'], +'MongoCommandCursor::current' => ['array'], +'MongoCommandCursor::dead' => ['bool'], +'MongoCommandCursor::getReadPreference' => ['array'], +'MongoCommandCursor::info' => ['array'], +'MongoCommandCursor::key' => ['int'], +'MongoCommandCursor::next' => ['void'], +'MongoCommandCursor::rewind' => ['array'], +'MongoCommandCursor::setReadPreference' => ['MongoCommandCursor', 'read_preference'=>'string', 'tags='=>'array'], +'MongoCommandCursor::timeout' => ['MongoCommandCursor', 'ms'=>'int'], +'MongoCommandCursor::valid' => ['bool'], +'MongoCursor::__construct' => ['void', 'connection'=>'MongoClient', 'ns'=>'string', 'query='=>'array', 'fields='=>'array'], +'MongoCursor::addOption' => ['MongoCursor', 'key'=>'string', 'value'=>'mixed'], +'MongoCursor::awaitData' => ['MongoCursor', 'wait='=>'bool'], +'MongoCursor::batchSize' => ['MongoCursor', 'num'=>'int'], +'MongoCursor::count' => ['int', 'foundonly='=>'bool'], +'MongoCursor::current' => ['array'], +'MongoCursor::dead' => ['bool'], +'MongoCursor::doQuery' => ['void'], +'MongoCursor::explain' => ['array'], +'MongoCursor::fields' => ['MongoCursor', 'f'=>'array'], +'MongoCursor::getNext' => ['array'], +'MongoCursor::getReadPreference' => ['array'], +'MongoCursor::hasNext' => ['bool'], +'MongoCursor::hint' => ['MongoCursor', 'key_pattern'=>'string|array|object'], +'MongoCursor::immortal' => ['MongoCursor', 'liveforever='=>'bool'], +'MongoCursor::info' => ['array'], +'MongoCursor::key' => ['string'], +'MongoCursor::limit' => ['MongoCursor', 'num'=>'int'], +'MongoCursor::maxTimeMS' => ['MongoCursor', 'ms'=>'int'], +'MongoCursor::next' => ['array'], +'MongoCursor::partial' => ['MongoCursor', 'okay='=>'bool'], +'MongoCursor::reset' => ['void'], +'MongoCursor::rewind' => ['void'], +'MongoCursor::setFlag' => ['MongoCursor', 'flag'=>'int', 'set='=>'bool'], +'MongoCursor::setReadPreference' => ['MongoCursor', 'read_preference'=>'string', 'tags='=>'array'], +'MongoCursor::skip' => ['MongoCursor', 'num'=>'int'], +'MongoCursor::slaveOkay' => ['MongoCursor', 'okay='=>'bool'], +'MongoCursor::snapshot' => ['MongoCursor'], +'MongoCursor::sort' => ['MongoCursor', 'fields'=>'array'], +'MongoCursor::tailable' => ['MongoCursor', 'tail='=>'bool'], +'MongoCursor::timeout' => ['MongoCursor', 'ms'=>'int'], +'MongoCursor::valid' => ['bool'], +'MongoCursorException::__clone' => ['void'], +'MongoCursorException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'MongoCursorException::__toString' => ['string'], +'MongoCursorException::__wakeup' => ['void'], +'MongoCursorException::getCode' => ['int'], +'MongoCursorException::getFile' => ['string'], +'MongoCursorException::getHost' => ['string'], +'MongoCursorException::getLine' => ['int'], +'MongoCursorException::getMessage' => ['string'], +'MongoCursorException::getPrevious' => ['Exception|Throwable'], +'MongoCursorException::getTrace' => ['list>'], +'MongoCursorException::getTraceAsString' => ['string'], +'MongoCursorInterface::__construct' => ['void'], +'MongoCursorInterface::batchSize' => ['MongoCursorInterface', 'batchSize'=>'int'], +'MongoCursorInterface::current' => ['mixed'], +'MongoCursorInterface::dead' => ['bool'], +'MongoCursorInterface::getReadPreference' => ['array'], +'MongoCursorInterface::info' => ['array'], +'MongoCursorInterface::key' => ['int|string'], +'MongoCursorInterface::next' => ['void'], +'MongoCursorInterface::rewind' => ['void'], +'MongoCursorInterface::setReadPreference' => ['MongoCursorInterface', 'read_preference'=>'string', 'tags='=>'array'], +'MongoCursorInterface::timeout' => ['MongoCursorInterface', 'ms'=>'int'], +'MongoCursorInterface::valid' => ['bool'], +'MongoDate::__construct' => ['void', 'second='=>'int', 'usecond='=>'int'], +'MongoDate::__toString' => ['string'], +'MongoDate::toDateTime' => ['DateTime'], +'MongoDB::__construct' => ['void', 'conn'=>'MongoClient', 'name'=>'string'], +'MongoDB::__get' => ['MongoCollection', 'name'=>'string'], +'MongoDB::__toString' => ['string'], +'MongoDB::authenticate' => ['array', 'username'=>'string', 'password'=>'string'], +'MongoDB::command' => ['array', 'command'=>'array'], +'MongoDB::createCollection' => ['MongoCollection', 'name'=>'string', 'capped='=>'bool', 'size='=>'int', 'max='=>'int'], +'MongoDB::createDBRef' => ['array', 'collection'=>'string', 'a'=>'mixed'], +'MongoDB::drop' => ['array'], +'MongoDB::dropCollection' => ['array', 'coll'=>'MongoCollection|string'], +'MongoDB::execute' => ['array', 'code'=>'MongoCode|string', 'args='=>'array'], +'MongoDB::forceError' => ['bool'], +'MongoDB::getCollectionInfo' => ['array', 'options='=>'array'], +'MongoDB::getCollectionNames' => ['array', 'options='=>'array'], +'MongoDB::getDBRef' => ['array', 'ref'=>'array'], +'MongoDB::getGridFS' => ['MongoGridFS', 'prefix='=>'string'], +'MongoDB::getProfilingLevel' => ['int'], +'MongoDB::getReadPreference' => ['array'], +'MongoDB::getSlaveOkay' => ['bool'], +'MongoDB::getWriteConcern' => ['array'], +'MongoDB::lastError' => ['array'], +'MongoDB::listCollections' => ['array'], +'MongoDB::prevError' => ['array'], +'MongoDB::repair' => ['array', 'preserve_cloned_files='=>'bool', 'backup_original_files='=>'bool'], +'MongoDB::resetError' => ['array'], +'MongoDB::selectCollection' => ['MongoCollection', 'name'=>'string'], +'MongoDB::setProfilingLevel' => ['int', 'level'=>'int'], +'MongoDB::setReadPreference' => ['bool', 'read_preference'=>'string', 'tags='=>'array'], +'MongoDB::setSlaveOkay' => ['bool', 'ok='=>'bool'], +'MongoDB::setWriteConcern' => ['bool', 'w'=>'mixed', 'wtimeout='=>'int'], +'MongoDB\BSON\Binary::__construct' => ['void', 'data'=>'string', 'type'=>'int'], +'MongoDB\BSON\Binary::__toString' => ['string'], +'MongoDB\BSON\Binary::getData' => ['string'], +'MongoDB\BSON\Binary::getType' => ['int'], +'MongoDB\BSON\binary::jsonSerialize' => ['mixed'], +'MongoDB\BSON\binary::serialize' => ['string'], +'MongoDB\BSON\binary::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\binaryinterface::__toString' => ['string'], +'MongoDB\BSON\binaryinterface::getData' => ['string'], +'MongoDB\BSON\binaryinterface::getType' => ['int'], +'MongoDB\BSON\dbpointer::__construct' => ['void'], +'MongoDB\BSON\dbpointer::__toString' => ['string'], +'MongoDB\BSON\dbpointer::jsonSerialize' => ['mixed'], +'MongoDB\BSON\dbpointer::serialize' => ['string'], +'MongoDB\BSON\dbpointer::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\Decimal128::__construct' => ['void', 'value='=>'string'], +'MongoDB\BSON\Decimal128::__toString' => ['string'], +'MongoDB\BSON\decimal128::jsonSerialize' => ['mixed'], +'MongoDB\BSON\decimal128::serialize' => ['string'], +'MongoDB\BSON\decimal128::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\decimal128interface::__toString' => ['string'], +'MongoDB\BSON\fromJSON' => ['string', 'json'=>'string'], +'MongoDB\BSON\fromPHP' => ['string', 'value'=>'array|object'], +'MongoDB\BSON\int64::__construct' => ['void'], +'MongoDB\BSON\int64::__toString' => ['string'], +'MongoDB\BSON\int64::jsonSerialize' => ['mixed'], +'MongoDB\BSON\int64::serialize' => ['string'], +'MongoDB\BSON\int64::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\Javascript::__construct' => ['void', 'code'=>'string', 'scope='=>'array|object'], +'MongoDB\BSON\javascript::__toString' => ['string'], +'MongoDB\BSON\javascript::getCode' => ['string'], +'MongoDB\BSON\javascript::getScope' => ['?object'], +'MongoDB\BSON\javascript::jsonSerialize' => ['mixed'], +'MongoDB\BSON\javascript::serialize' => ['string'], +'MongoDB\BSON\javascript::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\javascriptinterface::__toString' => ['string'], +'MongoDB\BSON\javascriptinterface::getCode' => ['string'], +'MongoDB\BSON\javascriptinterface::getScope' => ['?object'], +'MongoDB\BSON\maxkey::__construct' => ['void'], +'MongoDB\BSON\maxkey::jsonSerialize' => ['mixed'], +'MongoDB\BSON\maxkey::serialize' => ['string'], +'MongoDB\BSON\maxkey::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\minkey::__construct' => ['void'], +'MongoDB\BSON\minkey::jsonSerialize' => ['mixed'], +'MongoDB\BSON\minkey::serialize' => ['string'], +'MongoDB\BSON\minkey::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\ObjectId::__construct' => ['void', 'id='=>'string'], +'MongoDB\BSON\ObjectId::__toString' => ['string'], +'MongoDB\BSON\objectid::getTimestamp' => ['int'], +'MongoDB\BSON\objectid::jsonSerialize' => ['mixed'], +'MongoDB\BSON\objectid::serialize' => ['string'], +'MongoDB\BSON\objectid::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\objectidinterface::__toString' => ['string'], +'MongoDB\BSON\objectidinterface::getTimestamp' => ['int'], +'MongoDB\BSON\Regex::__construct' => ['void', 'pattern'=>'string', 'flags='=>'string'], +'MongoDB\BSON\Regex::__toString' => ['string'], +'MongoDB\BSON\Regex::getFlags' => ['string'], +'MongoDB\BSON\Regex::getPattern' => ['string'], +'MongoDB\BSON\regex::jsonSerialize' => ['mixed'], +'MongoDB\BSON\regex::serialize' => ['string'], +'MongoDB\BSON\regex::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\regexinterface::__toString' => ['string'], +'MongoDB\BSON\regexinterface::getFlags' => ['string'], +'MongoDB\BSON\regexinterface::getPattern' => ['string'], +'MongoDB\BSON\Serializable::bsonSerialize' => ['array|object'], +'MongoDB\BSON\symbol::__construct' => ['void'], +'MongoDB\BSON\symbol::__toString' => ['string'], +'MongoDB\BSON\symbol::jsonSerialize' => ['mixed'], +'MongoDB\BSON\symbol::serialize' => ['string'], +'MongoDB\BSON\symbol::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\Timestamp::__construct' => ['void', 'increment'=>'int', 'timestamp'=>'int'], +'MongoDB\BSON\Timestamp::__toString' => ['string'], +'MongoDB\BSON\timestamp::getIncrement' => ['int'], +'MongoDB\BSON\timestamp::getTimestamp' => ['int'], +'MongoDB\BSON\timestamp::jsonSerialize' => ['mixed'], +'MongoDB\BSON\timestamp::serialize' => ['string'], +'MongoDB\BSON\timestamp::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\timestampinterface::__toString' => ['string'], +'MongoDB\BSON\timestampinterface::getIncrement' => ['int'], +'MongoDB\BSON\timestampinterface::getTimestamp' => ['int'], +'MongoDB\BSON\toJSON' => ['string', 'bson'=>'string'], +'MongoDB\BSON\toPHP' => ['object', 'bson'=>'string', 'typeMap'=>'array'], +'MongoDB\BSON\undefined::__construct' => ['void'], +'MongoDB\BSON\undefined::__toString' => ['string'], +'MongoDB\BSON\undefined::jsonSerialize' => ['mixed'], +'MongoDB\BSON\undefined::serialize' => ['string'], +'MongoDB\BSON\undefined::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\Unserializable::bsonUnserialize' => ['void', 'data'=>'array'], +'MongoDB\BSON\UTCDateTime::__construct' => ['void', 'milliseconds='=>'int|DateTimeInterface'], +'MongoDB\BSON\UTCDateTime::__toString' => ['string'], +'MongoDB\BSON\utcdatetime::jsonSerialize' => ['mixed'], +'MongoDB\BSON\utcdatetime::serialize' => ['string'], +'MongoDB\BSON\UTCDateTime::toDateTime' => ['DateTime'], +'MongoDB\BSON\utcdatetime::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\BSON\utcdatetimeinterface::__toString' => ['string'], +'MongoDB\BSON\utcdatetimeinterface::toDateTime' => ['DateTime'], +'MongoDB\Driver\BulkWrite::__construct' => ['void', 'ordered='=>'bool'], +'MongoDB\Driver\BulkWrite::count' => ['int'], +'MongoDB\Driver\BulkWrite::delete' => ['void', 'filter'=>'array|object', 'deleteOptions='=>'array'], +'MongoDB\Driver\BulkWrite::insert' => ['void|MongoDB\BSON\ObjectId', 'document'=>'array|object'], +'MongoDB\Driver\BulkWrite::update' => ['void', 'filter'=>'array|object', 'newObj'=>'array|object', 'updateOptions='=>'array'], +'MongoDB\Driver\Command::__construct' => ['void', 'document'=>'array|object'], +'MongoDB\Driver\Cursor::__construct' => ['void', 'server'=>'Server', 'responseDocument'=>'string'], +'MongoDB\Driver\Cursor::getId' => ['MongoDB\Driver\CursorId'], +'MongoDB\Driver\Cursor::getServer' => ['MongoDB\Driver\Server'], +'MongoDB\Driver\Cursor::isDead' => ['bool'], +'MongoDB\Driver\Cursor::setTypeMap' => ['void', 'typemap'=>'array'], +'MongoDB\Driver\Cursor::toArray' => ['array'], +'MongoDB\Driver\CursorId::__construct' => ['void', 'id'=>'string'], +'MongoDB\Driver\CursorId::__toString' => ['string'], +'MongoDB\Driver\CursorId::serialize' => ['string'], +'MongoDB\Driver\CursorId::unserialize' => ['void', 'serialized'=>'string'], +'mongodb\driver\exception\commandexception::getResultDocument' => ['object'], +'MongoDB\Driver\Exception\RuntimeException::__clone' => ['void'], +'MongoDB\Driver\Exception\RuntimeException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?RuntimeException|?Throwable'], +'MongoDB\Driver\Exception\RuntimeException::__toString' => ['string'], +'MongoDB\Driver\Exception\RuntimeException::__wakeup' => ['void'], +'MongoDB\Driver\Exception\RuntimeException::getCode' => ['int'], +'MongoDB\Driver\Exception\RuntimeException::getFile' => ['string'], +'MongoDB\Driver\Exception\RuntimeException::getLine' => ['int'], +'MongoDB\Driver\Exception\RuntimeException::getMessage' => ['string'], +'MongoDB\Driver\Exception\RuntimeException::getPrevious' => ['RuntimeException|Throwable'], +'MongoDB\Driver\Exception\RuntimeException::getTrace' => ['list>'], +'MongoDB\Driver\Exception\RuntimeException::getTraceAsString' => ['string'], +'mongodb\driver\exception\runtimeexception::hasErrorLabel' => ['bool', 'errorLabel'=>'string'], +'MongoDB\Driver\Exception\WriteException::__clone' => ['void'], +'MongoDB\Driver\Exception\WriteException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?RuntimeException|?Throwable'], +'MongoDB\Driver\Exception\WriteException::__toString' => ['string'], +'MongoDB\Driver\Exception\WriteException::__wakeup' => ['void'], +'MongoDB\Driver\Exception\WriteException::getCode' => ['int'], +'MongoDB\Driver\Exception\WriteException::getFile' => ['string'], +'MongoDB\Driver\Exception\WriteException::getLine' => ['int'], +'MongoDB\Driver\Exception\WriteException::getMessage' => ['string'], +'MongoDB\Driver\Exception\WriteException::getPrevious' => ['RuntimeException|Throwable'], +'MongoDB\Driver\Exception\WriteException::getTrace' => ['list>'], +'MongoDB\Driver\Exception\WriteException::getTraceAsString' => ['string'], +'MongoDB\Driver\Exception\WriteException::getWriteResult' => ['MongoDB\Driver\WriteResult'], +'MongoDB\Driver\Manager::__construct' => ['void', 'uri'=>'string', 'options='=>'array', 'driverOptions='=>'array'], +'MongoDB\Driver\Manager::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'bulk'=>'MongoDB\Driver\BulkWrite', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], +'MongoDB\Driver\Manager::executeCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'readPreference='=>'MongoDB\Driver\ReadPreference'], +'MongoDB\Driver\Manager::executeDelete' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'filter'=>'array|object', 'deleteOptions='=>'array', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], +'MongoDB\Driver\Manager::executeInsert' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'document'=>'array|object', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], +'MongoDB\Driver\Manager::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace'=>'string', 'query'=>'MongoDB\Driver\Query', 'readPreference='=>'MongoDB\Driver\ReadPreference'], +'mongodb\driver\manager::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], +'mongodb\driver\manager::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], +'MongoDB\Driver\Manager::executeUpdate' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'filter'=>'array|object', 'newObj'=>'array|object', 'updateOptions='=>'array', 'writeConcern='=>'MongoDB\Driver\WriteConcern'], +'mongodb\driver\manager::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], +'MongoDB\Driver\Manager::getReadConcern' => ['MongoDB\Driver\ReadConcern'], +'MongoDB\Driver\Manager::getReadPreference' => ['MongoDB\Driver\ReadPreference'], +'MongoDB\Driver\Manager::getServers' => ['MongoDB\Driver\Server[]'], +'MongoDB\Driver\Manager::getWriteConcern' => ['MongoDB\Driver\WriteConcern'], +'MongoDB\Driver\Manager::selectServer' => ['MongoDB\Driver\Server', 'readPreference'=>'MongoDB\Driver\ReadPreference'], +'mongodb\driver\manager::startSession' => ['MongoDB\Driver\Session', 'options='=>'array'], +'mongodb\driver\monitoring\commandfailedevent::getCommandName' => ['string'], +'mongodb\driver\monitoring\commandfailedevent::getDurationMicros' => ['int'], +'mongodb\driver\monitoring\commandfailedevent::getError' => ['Exception'], +'mongodb\driver\monitoring\commandfailedevent::getOperationId' => ['string'], +'mongodb\driver\monitoring\commandfailedevent::getReply' => ['object'], +'mongodb\driver\monitoring\commandfailedevent::getRequestId' => ['string'], +'mongodb\driver\monitoring\commandfailedevent::getServer' => ['MongoDB\Driver\Server'], +'mongodb\driver\monitoring\commandstartedevent::getCommand' => ['object'], +'mongodb\driver\monitoring\commandstartedevent::getCommandName' => ['string'], +'mongodb\driver\monitoring\commandstartedevent::getDatabaseName' => ['string'], +'mongodb\driver\monitoring\commandstartedevent::getOperationId' => ['string'], +'mongodb\driver\monitoring\commandstartedevent::getRequestId' => ['string'], +'mongodb\driver\monitoring\commandstartedevent::getServer' => ['MongoDB\Driver\Server'], +'mongodb\driver\monitoring\commandsubscriber::commandFailed' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandFailedEvent'], +'mongodb\driver\monitoring\commandsubscriber::commandStarted' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandStartedEvent'], +'mongodb\driver\monitoring\commandsubscriber::commandSucceeded' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandSucceededEvent'], +'mongodb\driver\monitoring\commandsucceededevent::getCommandName' => ['string'], +'mongodb\driver\monitoring\commandsucceededevent::getDurationMicros' => ['int'], +'mongodb\driver\monitoring\commandsucceededevent::getOperationId' => ['string'], +'mongodb\driver\monitoring\commandsucceededevent::getReply' => ['object'], +'mongodb\driver\monitoring\commandsucceededevent::getRequestId' => ['string'], +'mongodb\driver\monitoring\commandsucceededevent::getServer' => ['MongoDB\Driver\Server'], +'MongoDB\Driver\Query::__construct' => ['void', 'filter'=>'array|object', 'queryOptions='=>'array'], +'MongoDB\Driver\ReadConcern::__construct' => ['void', 'level='=>'string'], +'MongoDB\Driver\ReadConcern::bsonSerialize' => ['object'], +'MongoDB\Driver\ReadConcern::getLevel' => ['?string'], +'MongoDB\Driver\ReadConcern::isDefault' => ['bool'], +'MongoDB\Driver\ReadConcern::serialize' => ['string'], +'MongoDB\Driver\ReadConcern::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\Driver\ReadPreference::__construct' => ['void', 'mode'=>'string|int', 'tagSets='=>'array', 'options='=>'array'], +'MongoDB\Driver\ReadPreference::bsonSerialize' => ['object'], +'MongoDB\Driver\ReadPreference::getHedge' => ['object|null'], +'MongoDB\Driver\ReadPreference::getMaxStalenessSeconds' => ['int'], +'MongoDB\Driver\ReadPreference::getMode' => ['int'], +'MongoDB\Driver\ReadPreference::getModeString' => ['string'], +'MongoDB\Driver\ReadPreference::getTagSets' => ['array'], +'MongoDB\Driver\ReadPreference::serialize' => ['string'], +'MongoDB\Driver\ReadPreference::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\Driver\Server::__construct' => ['void', 'host'=>'string', 'port'=>'string', 'options='=>'array', 'driverOptions='=>'array'], +'MongoDB\Driver\Server::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'zwrite'=>'BulkWrite'], +'MongoDB\Driver\Server::executeCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command'], +'MongoDB\Driver\Server::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace'=>'string', 'zquery'=>'Query'], +'mongodb\driver\server::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], +'mongodb\driver\server::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], +'mongodb\driver\server::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'array'], +'MongoDB\Driver\Server::getHost' => ['string'], +'MongoDB\Driver\Server::getInfo' => ['array'], +'MongoDB\Driver\Server::getLatency' => ['int'], +'MongoDB\Driver\Server::getPort' => ['int'], +'MongoDB\Driver\Server::getState' => [''], +'MongoDB\Driver\Server::getTags' => ['array'], +'MongoDB\Driver\Server::getType' => ['int'], +'MongoDB\Driver\Server::isArbiter' => ['bool'], +'MongoDB\Driver\Server::isDelayed' => [''], +'MongoDB\Driver\Server::isHidden' => ['bool'], +'MongoDB\Driver\Server::isPassive' => ['bool'], +'MongoDB\Driver\Server::isPrimary' => ['bool'], +'MongoDB\Driver\Server::isSecondary' => ['bool'], +'mongodb\driver\session::__construct' => ['void'], +'mongodb\driver\session::abortTransaction' => ['void'], +'mongodb\driver\session::advanceClusterTime' => ['void', 'clusterTime'=>'array|object'], +'mongodb\driver\session::advanceOperationTime' => ['void', 'operationTime'=>'MongoDB\BSON\TimestampInterface'], +'mongodb\driver\session::commitTransaction' => ['void'], +'mongodb\driver\session::endSession' => ['void'], +'mongodb\driver\session::getClusterTime' => ['?object'], +'mongodb\driver\session::getLogicalSessionId' => ['object'], +'mongodb\driver\session::getOperationTime' => ['MongoDB\BSON\Timestamp|null'], +'mongodb\driver\session::getTransactionOptions' => ['array|null'], +'mongodb\driver\session::getTransactionState' => ['string'], +'mongodb\driver\session::startTransaction' => ['void', 'options'=>'array|object'], +'MongoDB\Driver\WriteConcern::__construct' => ['void', 'wstring'=>'string', 'wtimeout='=>'int', 'journal='=>'bool', 'fsync='=>'bool'], +'MongoDB\Driver\WriteConcern::bsonSerialize' => ['object'], +'MongoDB\Driver\WriteConcern::getJournal' => ['?bool'], +'MongoDB\Driver\WriteConcern::getJurnal' => ['?bool'], +'MongoDB\Driver\WriteConcern::getW' => ['int|null|string'], +'MongoDB\Driver\WriteConcern::getWtimeout' => ['int'], +'MongoDB\Driver\WriteConcern::isDefault' => ['bool'], +'MongoDB\Driver\WriteConcern::serialize' => ['string'], +'MongoDB\Driver\WriteConcern::unserialize' => ['void', 'serialized'=>'string'], +'MongoDB\Driver\WriteConcernError::getCode' => ['int'], +'MongoDB\Driver\WriteConcernError::getInfo' => ['mixed'], +'MongoDB\Driver\WriteConcernError::getMessage' => ['string'], +'MongoDB\Driver\WriteError::getCode' => ['int'], +'MongoDB\Driver\WriteError::getIndex' => ['int'], +'MongoDB\Driver\WriteError::getInfo' => ['mixed'], +'MongoDB\Driver\WriteError::getMessage' => ['string'], +'MongoDB\Driver\WriteException::getWriteResult' => [''], +'MongoDB\Driver\WriteResult::getDeletedCount' => ['?int'], +'MongoDB\Driver\WriteResult::getInfo' => [''], +'MongoDB\Driver\WriteResult::getInsertedCount' => ['?int'], +'MongoDB\Driver\WriteResult::getMatchedCount' => ['?int'], +'MongoDB\Driver\WriteResult::getModifiedCount' => ['?int'], +'MongoDB\Driver\WriteResult::getServer' => ['MongoDB\Driver\Server'], +'MongoDB\Driver\WriteResult::getUpsertedCount' => ['?int'], +'MongoDB\Driver\WriteResult::getUpsertedIds' => ['array'], +'MongoDB\Driver\WriteResult::getWriteConcernError' => ['MongoDB\Driver\WriteConcernError|null'], +'MongoDB\Driver\WriteResult::getWriteErrors' => ['MongoDB\Driver\WriteError[]'], +'MongoDB\Driver\WriteResult::isAcknowledged' => ['bool'], +'MongoDBRef::create' => ['array', 'collection'=>'string', 'id'=>'mixed', 'database='=>'string'], +'MongoDBRef::get' => ['?array', 'db'=>'MongoDB', 'ref'=>'array'], +'MongoDBRef::isRef' => ['bool', 'ref'=>'mixed'], +'MongoDeleteBatch::__construct' => ['void', 'collection'=>'MongoCollection', 'write_options='=>'array'], +'MongoException::__clone' => ['void'], +'MongoException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'MongoException::__toString' => ['string'], +'MongoException::__wakeup' => ['void'], +'MongoException::getCode' => ['int'], +'MongoException::getFile' => ['string'], +'MongoException::getLine' => ['int'], +'MongoException::getMessage' => ['string'], +'MongoException::getPrevious' => ['Exception|Throwable'], +'MongoException::getTrace' => ['list>'], +'MongoException::getTraceAsString' => ['string'], +'MongoGridFS::__construct' => ['void', 'db'=>'MongoDB', 'prefix='=>'string', 'chunks='=>'mixed'], +'MongoGridFS::__get' => ['MongoCollection', 'name'=>'string'], +'MongoGridFS::__toString' => ['string'], +'MongoGridFS::aggregate' => ['array', 'pipeline'=>'array', 'op'=>'array', 'pipelineOperators'=>'array'], +'MongoGridFS::aggregateCursor' => ['MongoCommandCursor', 'pipeline'=>'array', 'options'=>'array'], +'MongoGridFS::batchInsert' => ['mixed', 'a'=>'array', 'options='=>'array'], +'MongoGridFS::count' => ['int', 'query='=>'stdClass|array'], +'MongoGridFS::createDBRef' => ['array', 'a'=>'array'], +'MongoGridFS::createIndex' => ['array', 'keys'=>'array', 'options='=>'array'], +'MongoGridFS::delete' => ['bool', 'id'=>'mixed'], +'MongoGridFS::deleteIndex' => ['array', 'keys'=>'array|string'], +'MongoGridFS::deleteIndexes' => ['array'], +'MongoGridFS::distinct' => ['array|bool', 'key'=>'string', 'query='=>'?array'], +'MongoGridFS::drop' => ['array'], +'MongoGridFS::ensureIndex' => ['bool', 'keys'=>'array', 'options='=>'array'], +'MongoGridFS::find' => ['MongoGridFSCursor', 'query='=>'array', 'fields='=>'array'], +'MongoGridFS::findAndModify' => ['array', 'query'=>'array', 'update='=>'?array', 'fields='=>'?array', 'options='=>'?array'], +'MongoGridFS::findOne' => ['?MongoGridFSFile', 'query='=>'mixed', 'fields='=>'mixed'], +'MongoGridFS::get' => ['?MongoGridFSFile', 'id'=>'mixed'], +'MongoGridFS::getDBRef' => ['array', 'ref'=>'array'], +'MongoGridFS::getIndexInfo' => ['array'], +'MongoGridFS::getName' => ['string'], +'MongoGridFS::getReadPreference' => ['array'], +'MongoGridFS::getSlaveOkay' => ['bool'], +'MongoGridFS::group' => ['array', 'keys'=>'mixed', 'initial'=>'array', 'reduce'=>'MongoCode', 'condition='=>'array'], +'MongoGridFS::insert' => ['array|bool', 'a'=>'array|object', 'options='=>'array'], +'MongoGridFS::put' => ['mixed', 'filename'=>'string', 'extra='=>'array'], +'MongoGridFS::remove' => ['bool', 'criteria='=>'array', 'options='=>'array'], +'MongoGridFS::save' => ['array|bool', 'a'=>'array|object', 'options='=>'array'], +'MongoGridFS::setReadPreference' => ['bool', 'read_preference'=>'string', 'tags'=>'array'], +'MongoGridFS::setSlaveOkay' => ['bool', 'ok='=>'bool'], +'MongoGridFS::storeBytes' => ['mixed', 'bytes'=>'string', 'extra='=>'array', 'options='=>'array'], +'MongoGridFS::storeFile' => ['mixed', 'filename'=>'string', 'extra='=>'array', 'options='=>'array'], +'MongoGridFS::storeUpload' => ['mixed', 'name'=>'string', 'filename='=>'string'], +'MongoGridFS::toIndexString' => ['string', 'keys'=>'mixed'], +'MongoGridFS::update' => ['bool', 'criteria'=>'array', 'newobj'=>'array', 'options='=>'array'], +'MongoGridFS::validate' => ['array', 'scan_data='=>'bool'], +'MongoGridFSCursor::__construct' => ['void', 'gridfs'=>'MongoGridFS', 'connection'=>'resource', 'ns'=>'string', 'query'=>'array', 'fields'=>'array'], +'MongoGridFSCursor::addOption' => ['MongoCursor', 'key'=>'string', 'value'=>'mixed'], +'MongoGridFSCursor::awaitData' => ['MongoCursor', 'wait='=>'bool'], +'MongoGridFSCursor::batchSize' => ['MongoCursor', 'batchSize'=>'int'], +'MongoGridFSCursor::count' => ['int', 'all='=>'bool'], +'MongoGridFSCursor::current' => ['MongoGridFSFile'], +'MongoGridFSCursor::dead' => ['bool'], +'MongoGridFSCursor::doQuery' => ['void'], +'MongoGridFSCursor::explain' => ['array'], +'MongoGridFSCursor::fields' => ['MongoCursor', 'f'=>'array'], +'MongoGridFSCursor::getNext' => ['MongoGridFSFile'], +'MongoGridFSCursor::getReadPreference' => ['array'], +'MongoGridFSCursor::hasNext' => ['bool'], +'MongoGridFSCursor::hint' => ['MongoCursor', 'key_pattern'=>'mixed'], +'MongoGridFSCursor::immortal' => ['MongoCursor', 'liveForever='=>'bool'], +'MongoGridFSCursor::info' => ['array'], +'MongoGridFSCursor::key' => ['string'], +'MongoGridFSCursor::limit' => ['MongoCursor', 'num'=>'int'], +'MongoGridFSCursor::maxTimeMS' => ['MongoCursor', 'ms'=>'int'], +'MongoGridFSCursor::next' => ['void'], +'MongoGridFSCursor::partial' => ['MongoCursor', 'okay='=>'bool'], +'MongoGridFSCursor::reset' => ['void'], +'MongoGridFSCursor::rewind' => ['void'], +'MongoGridFSCursor::setFlag' => ['MongoCursor', 'flag'=>'int', 'set='=>'bool'], +'MongoGridFSCursor::setReadPreference' => ['MongoCursor', 'read_preference'=>'string', 'tags'=>'array'], +'MongoGridFSCursor::skip' => ['MongoCursor', 'num'=>'int'], +'MongoGridFSCursor::slaveOkay' => ['MongoCursor', 'okay='=>'bool'], +'MongoGridFSCursor::snapshot' => ['MongoCursor'], +'MongoGridFSCursor::sort' => ['MongoCursor', 'fields'=>'array'], +'MongoGridFSCursor::tailable' => ['MongoCursor', 'tail='=>'bool'], +'MongoGridFSCursor::timeout' => ['MongoCursor', 'ms'=>'int'], +'MongoGridFSCursor::valid' => ['bool'], +'MongoGridfsFile::__construct' => ['void', 'gridfs'=>'MongoGridFS', 'file'=>'array'], +'MongoGridFSFile::getBytes' => ['string'], +'MongoGridFSFile::getFilename' => ['string'], +'MongoGridFSFile::getResource' => ['resource'], +'MongoGridFSFile::getSize' => ['int'], +'MongoGridFSFile::write' => ['int', 'filename='=>'string'], +'MongoId::__construct' => ['void', 'id='=>'string|MongoId'], +'MongoId::__set_state' => ['MongoId', 'props'=>'array'], +'MongoId::__toString' => ['string'], +'MongoId::getHostname' => ['string'], +'MongoId::getInc' => ['int'], +'MongoId::getPID' => ['int'], +'MongoId::getTimestamp' => ['int'], +'MongoId::isValid' => ['bool', 'value'=>'mixed'], +'MongoInsertBatch::__construct' => ['void', 'collection'=>'MongoCollection', 'write_options='=>'array'], +'MongoInt32::__construct' => ['void', 'value'=>'string'], +'MongoInt32::__toString' => ['string'], +'MongoInt64::__construct' => ['void', 'value'=>'string'], +'MongoInt64::__toString' => ['string'], +'MongoLog::getCallback' => ['callable'], +'MongoLog::getLevel' => ['int'], +'MongoLog::getModule' => ['int'], +'MongoLog::setCallback' => ['void', 'log_function'=>'callable'], +'MongoLog::setLevel' => ['void', 'level'=>'int'], +'MongoLog::setModule' => ['void', 'module'=>'int'], +'MongoPool::getSize' => ['int'], +'MongoPool::info' => ['array'], +'MongoPool::setSize' => ['bool', 'size'=>'int'], +'MongoRegex::__construct' => ['void', 'regex'=>'string'], +'MongoRegex::__toString' => ['string'], +'MongoResultException::__clone' => ['void'], +'MongoResultException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'MongoResultException::__toString' => ['string'], +'MongoResultException::__wakeup' => ['void'], +'MongoResultException::getCode' => ['int'], +'MongoResultException::getDocument' => ['array'], +'MongoResultException::getFile' => ['string'], +'MongoResultException::getLine' => ['int'], +'MongoResultException::getMessage' => ['string'], +'MongoResultException::getPrevious' => ['Exception|Throwable'], +'MongoResultException::getTrace' => ['list>'], +'MongoResultException::getTraceAsString' => ['string'], +'MongoTimestamp::__construct' => ['void', 'second='=>'int', 'inc='=>'int'], +'MongoTimestamp::__toString' => ['string'], +'MongoUpdateBatch::__construct' => ['void', 'collection'=>'MongoCollection', 'write_options='=>'array'], +'MongoUpdateBatch::add' => ['bool', 'item'=>'array'], +'MongoUpdateBatch::execute' => ['array', 'write_options'=>'array'], +'MongoWriteBatch::__construct' => ['void', 'collection'=>'MongoCollection', 'batch_type'=>'string', 'write_options'=>'array'], +'MongoWriteBatch::add' => ['bool', 'item'=>'array'], +'MongoWriteBatch::execute' => ['array', 'write_options'=>'array'], +'MongoWriteConcernException::__clone' => ['void'], +'MongoWriteConcernException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'MongoWriteConcernException::__toString' => ['string'], +'MongoWriteConcernException::__wakeup' => ['void'], +'MongoWriteConcernException::getCode' => ['int'], +'MongoWriteConcernException::getDocument' => ['array'], +'MongoWriteConcernException::getFile' => ['string'], +'MongoWriteConcernException::getLine' => ['int'], +'MongoWriteConcernException::getMessage' => ['string'], +'MongoWriteConcernException::getPrevious' => ['Exception|Throwable'], +'MongoWriteConcernException::getTrace' => ['list>'], +'MongoWriteConcernException::getTraceAsString' => ['string'], +'monitor_custom_event' => ['void', 'class'=>'string', 'text'=>'string', 'severe='=>'int', 'user_data='=>'mixed'], +'monitor_httperror_event' => ['void', 'error_code'=>'int', 'url'=>'string', 'severe='=>'int'], +'monitor_license_info' => ['array'], +'monitor_pass_error' => ['void', 'errno'=>'int', 'errstr'=>'string', 'errfile'=>'string', 'errline'=>'int'], +'monitor_set_aggregation_hint' => ['void', 'hint'=>'string'], +'move_uploaded_file' => ['bool', 'path'=>'string', 'new_path'=>'string'], +'mqseries_back' => ['void', 'hconn'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_begin' => ['void', 'hconn'=>'resource', 'beginoptions'=>'array', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_close' => ['void', 'hconn'=>'resource', 'hobj'=>'resource', 'options'=>'int', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_cmit' => ['void', 'hconn'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_conn' => ['void', 'qmanagername'=>'string', 'hconn'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_connx' => ['void', 'qmanagername'=>'string', 'connoptions'=>'array', 'hconn'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_disc' => ['void', 'hconn'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_get' => ['void', 'hconn'=>'resource', 'hobj'=>'resource', 'md'=>'array', 'gmo'=>'array', 'bufferlength'=>'int', 'msg'=>'string', 'data_length'=>'int', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_inq' => ['void', 'hconn'=>'resource', 'hobj'=>'resource', 'selectorcount'=>'int', 'selectors'=>'array', 'intattrcount'=>'int', 'intattr'=>'resource', 'charattrlength'=>'int', 'charattr'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_open' => ['void', 'hconn'=>'resource', 'objdesc'=>'array', 'option'=>'int', 'hobj'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_put' => ['void', 'hconn'=>'resource', 'hobj'=>'resource', 'md'=>'array', 'pmo'=>'array', 'message'=>'string', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_put1' => ['void', 'hconn'=>'resource', 'objdesc'=>'resource', 'msgdesc'=>'resource', 'pmo'=>'resource', 'buffer'=>'string', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_set' => ['void', 'hconn'=>'resource', 'hobj'=>'resource', 'selectorcount'=>'int', 'selectors'=>'array', 'intattrcount'=>'int', 'intattrs'=>'array', 'charattrlength'=>'int', 'charattrs'=>'array', 'compcode'=>'resource', 'reason'=>'resource'], +'mqseries_strerror' => ['string', 'reason'=>'int'], +'ms_GetErrorObj' => ['errorObj'], +'ms_GetVersion' => ['string'], +'ms_GetVersionInt' => ['int'], +'ms_iogetStdoutBufferBytes' => ['int'], +'ms_iogetstdoutbufferstring' => ['void'], +'ms_ioinstallstdinfrombuffer' => ['void'], +'ms_ioinstallstdouttobuffer' => ['void'], +'ms_ioresethandlers' => ['void'], +'ms_iostripstdoutbuffercontentheaders' => ['void'], +'ms_iostripstdoutbuffercontenttype' => ['string'], +'ms_ResetErrorList' => ['void'], +'ms_TokenizeMap' => ['array', 'map_file_name'=>'string'], +'msession_connect' => ['bool', 'host'=>'string', 'port'=>'string'], +'msession_count' => ['int'], +'msession_create' => ['bool', 'session'=>'string', 'classname='=>'string', 'data='=>'string'], +'msession_destroy' => ['bool', 'name'=>'string'], +'msession_disconnect' => ['void'], +'msession_find' => ['array', 'name'=>'string', 'value'=>'string'], +'msession_get' => ['string', 'session'=>'string', 'name'=>'string', 'value'=>'string'], +'msession_get_array' => ['array', 'session'=>'string'], +'msession_get_data' => ['string', 'session'=>'string'], +'msession_inc' => ['string', 'session'=>'string', 'name'=>'string'], +'msession_list' => ['array'], +'msession_listvar' => ['array', 'name'=>'string'], +'msession_lock' => ['int', 'name'=>'string'], +'msession_plugin' => ['string', 'session'=>'string', 'value'=>'string', 'param='=>'string'], +'msession_randstr' => ['string', 'param'=>'int'], +'msession_set' => ['bool', 'session'=>'string', 'name'=>'string', 'value'=>'string'], +'msession_set_array' => ['void', 'session'=>'string', 'tuples'=>'array'], +'msession_set_data' => ['bool', 'session'=>'string', 'value'=>'string'], +'msession_timeout' => ['int', 'session'=>'string', 'param='=>'int'], +'msession_uniq' => ['string', 'param'=>'int', 'classname='=>'string', 'data='=>'string'], +'msession_unlock' => ['int', 'session'=>'string', 'key'=>'int'], +'msg_get_queue' => ['resource', 'key'=>'int', 'perms='=>'int'], +'msg_queue_exists' => ['bool', 'key'=>'int'], +'msg_receive' => ['bool', 'queue'=>'resource', 'desiredmsgtype'=>'int', '&w_msgtype'=>'int', 'maxsize'=>'int', '&w_message'=>'mixed', 'unserialize='=>'bool', 'flags='=>'int', '&w_errorcode='=>'int'], +'msg_remove_queue' => ['bool', 'queue'=>'resource'], +'msg_send' => ['bool', 'queue'=>'resource', 'msgtype'=>'int', 'message'=>'mixed', 'serialize='=>'bool', 'blocking='=>'bool', '&w_errorcode='=>'int'], +'msg_set_queue' => ['bool', 'queue'=>'resource', 'data'=>'array'], +'msg_stat_queue' => ['array', 'queue'=>'resource'], +'msgfmt_create' => ['MessageFormatter', 'locale'=>'string', 'pattern'=>'string'], +'msgfmt_format' => ['string|false', 'fmt'=>'MessageFormatter', 'args'=>'array'], +'msgfmt_format_message' => ['string|false', 'locale'=>'string', 'pattern'=>'string', 'args'=>'array'], +'msgfmt_get_error_code' => ['int', 'fmt'=>'MessageFormatter'], +'msgfmt_get_error_message' => ['string', 'fmt'=>'MessageFormatter'], +'msgfmt_get_locale' => ['string', 'formatter'=>'MessageFormatter'], +'msgfmt_get_pattern' => ['string', 'fmt'=>'MessageFormatter'], +'msgfmt_parse' => ['array|false', 'fmt'=>'MessageFormatter', 'value'=>'string'], +'msgfmt_parse_message' => ['array|false', 'locale'=>'string', 'pattern'=>'string', 'source'=>'string'], +'msgfmt_set_pattern' => ['bool', 'fmt'=>'MessageFormatter', 'pattern'=>'string'], +'msql_affected_rows' => ['int', 'result'=>'resource'], +'msql_close' => ['bool', 'link_identifier='=>'?resource'], +'msql_connect' => ['resource', 'hostname='=>'string'], +'msql_create_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource'], +'msql_data_seek' => ['bool', 'result'=>'resource', 'row_number'=>'int'], +'msql_db_query' => ['resource', 'database'=>'string', 'query'=>'string', 'link_identifier='=>'?resource'], +'msql_drop_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource'], +'msql_error' => ['string'], +'msql_fetch_array' => ['array', 'result'=>'resource', 'result_type='=>'int'], +'msql_fetch_field' => ['object', 'result'=>'resource', 'field_offset='=>'int'], +'msql_fetch_object' => ['object', 'result'=>'resource'], +'msql_fetch_row' => ['array', 'result'=>'resource'], +'msql_field_flags' => ['string', 'result'=>'resource', 'field_offset'=>'int'], +'msql_field_len' => ['int', 'result'=>'resource', 'field_offset'=>'int'], +'msql_field_name' => ['string', 'result'=>'resource', 'field_offset'=>'int'], +'msql_field_seek' => ['bool', 'result'=>'resource', 'field_offset'=>'int'], +'msql_field_table' => ['int', 'result'=>'resource', 'field_offset'=>'int'], +'msql_field_type' => ['string', 'result'=>'resource', 'field_offset'=>'int'], +'msql_free_result' => ['bool', 'result'=>'resource'], +'msql_list_dbs' => ['resource', 'link_identifier='=>'?resource'], +'msql_list_fields' => ['resource', 'database'=>'string', 'tablename'=>'string', 'link_identifier='=>'?resource'], +'msql_list_tables' => ['resource', 'database'=>'string', 'link_identifier='=>'?resource'], +'msql_num_fields' => ['int', 'result'=>'resource'], +'msql_num_rows' => ['int', 'query_identifier'=>'resource'], +'msql_pconnect' => ['resource', 'hostname='=>'string'], +'msql_query' => ['resource', 'query'=>'string', 'link_identifier='=>'?resource'], +'msql_result' => ['string', 'result'=>'resource', 'row'=>'int', 'field='=>'mixed'], +'msql_select_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource'], +'mt_getrandmax' => ['int'], +'mt_rand' => ['int', 'min'=>'int', 'max'=>'int'], +'mt_rand\'1' => ['int'], +'mt_srand' => ['void', 'seed='=>'int', 'mode='=>'int'], +'MultipleIterator::__construct' => ['void', 'flags='=>'int'], +'MultipleIterator::attachIterator' => ['void', 'iterator'=>'Iterator', 'infos='=>'string'], +'MultipleIterator::containsIterator' => ['bool', 'iterator'=>'Iterator'], +'MultipleIterator::countIterators' => ['int'], +'MultipleIterator::current' => ['array|false'], +'MultipleIterator::detachIterator' => ['void', 'iterator'=>'Iterator'], +'MultipleIterator::getFlags' => ['int'], +'MultipleIterator::key' => ['array'], +'MultipleIterator::next' => ['void'], +'MultipleIterator::rewind' => ['void'], +'MultipleIterator::setFlags' => ['int', 'flags'=>'int'], +'MultipleIterator::valid' => ['bool'], +'Mutex::create' => ['long', 'lock='=>'bool'], +'Mutex::destroy' => ['bool', 'mutex'=>'long'], +'Mutex::lock' => ['bool', 'mutex'=>'long'], +'Mutex::trylock' => ['bool', 'mutex'=>'long'], +'Mutex::unlock' => ['bool', 'mutex'=>'long', 'destroy='=>'bool'], +'mysql_xdevapi\baseresult::getWarnings' => ['array'], +'mysql_xdevapi\baseresult::getWarningsCount' => ['integer'], +'mysql_xdevapi\collection::add' => ['mysql_xdevapi\CollectionAdd', 'document'=>'mixed'], +'mysql_xdevapi\collection::addOrReplaceOne' => ['mysql_xdevapi\Result', 'id'=>'string', 'doc'=>'string'], +'mysql_xdevapi\collection::count' => ['integer'], +'mysql_xdevapi\collection::createIndex' => ['void', 'index_name'=>'string', 'index_desc_json'=>'string'], +'mysql_xdevapi\collection::dropIndex' => ['bool', 'index_name'=>'string'], +'mysql_xdevapi\collection::existsInDatabase' => ['bool'], +'mysql_xdevapi\collection::find' => ['mysql_xdevapi\CollectionFind', 'search_condition='=>'string'], +'mysql_xdevapi\collection::getName' => ['string'], +'mysql_xdevapi\collection::getOne' => ['Document', 'id'=>'string'], +'mysql_xdevapi\collection::getSchema' => ['mysql_xdevapi\schema'], +'mysql_xdevapi\collection::getSession' => ['Session'], +'mysql_xdevapi\collection::modify' => ['mysql_xdevapi\CollectionModify', 'search_condition'=>'string'], +'mysql_xdevapi\collection::remove' => ['mysql_xdevapi\CollectionRemove', 'search_condition'=>'string'], +'mysql_xdevapi\collection::removeOne' => ['mysql_xdevapi\Result', 'id'=>'string'], +'mysql_xdevapi\collection::replaceOne' => ['mysql_xdevapi\Result', 'id'=>'string', 'doc'=>'string'], +'mysql_xdevapi\collectionadd::execute' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\collectionfind::bind' => ['mysql_xdevapi\CollectionFind', 'placeholder_values'=>'array'], +'mysql_xdevapi\collectionfind::execute' => ['mysql_xdevapi\DocResult'], +'mysql_xdevapi\collectionfind::fields' => ['mysql_xdevapi\CollectionFind', 'projection'=>'string'], +'mysql_xdevapi\collectionfind::groupBy' => ['mysql_xdevapi\CollectionFind', 'sort_expr'=>'string'], +'mysql_xdevapi\collectionfind::having' => ['mysql_xdevapi\CollectionFind', 'sort_expr'=>'string'], +'mysql_xdevapi\collectionfind::limit' => ['mysql_xdevapi\CollectionFind', 'rows'=>'integer'], +'mysql_xdevapi\collectionfind::lockExclusive' => ['mysql_xdevapi\CollectionFind', 'lock_waiting_option='=>'integer'], +'mysql_xdevapi\collectionfind::lockShared' => ['mysql_xdevapi\CollectionFind', 'lock_waiting_option='=>'integer'], +'mysql_xdevapi\collectionfind::offset' => ['mysql_xdevapi\CollectionFind', 'position'=>'integer'], +'mysql_xdevapi\collectionfind::sort' => ['mysql_xdevapi\CollectionFind', 'sort_expr'=>'string'], +'mysql_xdevapi\collectionmodify::arrayAppend' => ['mysql_xdevapi\CollectionModify', 'collection_field'=>'string', 'expression_or_literal'=>'string'], +'mysql_xdevapi\collectionmodify::arrayInsert' => ['mysql_xdevapi\CollectionModify', 'collection_field'=>'string', 'expression_or_literal'=>'string'], +'mysql_xdevapi\collectionmodify::bind' => ['mysql_xdevapi\CollectionModify', 'placeholder_values'=>'array'], +'mysql_xdevapi\collectionmodify::execute' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\collectionmodify::limit' => ['mysql_xdevapi\CollectionModify', 'rows'=>'integer'], +'mysql_xdevapi\collectionmodify::patch' => ['mysql_xdevapi\CollectionModify', 'document'=>'string'], +'mysql_xdevapi\collectionmodify::replace' => ['mysql_xdevapi\CollectionModify', 'collection_field'=>'string', 'expression_or_literal'=>'string'], +'mysql_xdevapi\collectionmodify::set' => ['mysql_xdevapi\CollectionModify', 'collection_field'=>'string', 'expression_or_literal'=>'string'], +'mysql_xdevapi\collectionmodify::skip' => ['mysql_xdevapi\CollectionModify', 'position'=>'integer'], +'mysql_xdevapi\collectionmodify::sort' => ['mysql_xdevapi\CollectionModify', 'sort_expr'=>'string'], +'mysql_xdevapi\collectionmodify::unset' => ['mysql_xdevapi\CollectionModify', 'fields'=>'array'], +'mysql_xdevapi\collectionremove::bind' => ['mysql_xdevapi\CollectionRemove', 'placeholder_values'=>'array'], +'mysql_xdevapi\collectionremove::execute' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\collectionremove::limit' => ['mysql_xdevapi\CollectionRemove', 'rows'=>'integer'], +'mysql_xdevapi\collectionremove::sort' => ['mysql_xdevapi\CollectionRemove', 'sort_expr'=>'string'], +'mysql_xdevapi\columnresult::getCharacterSetName' => ['string'], +'mysql_xdevapi\columnresult::getCollationName' => ['string'], +'mysql_xdevapi\columnresult::getColumnLabel' => ['string'], +'mysql_xdevapi\columnresult::getColumnName' => ['string'], +'mysql_xdevapi\columnresult::getFractionalDigits' => ['integer'], +'mysql_xdevapi\columnresult::getLength' => ['integer'], +'mysql_xdevapi\columnresult::getSchemaName' => ['string'], +'mysql_xdevapi\columnresult::getTableLabel' => ['string'], +'mysql_xdevapi\columnresult::getTableName' => ['string'], +'mysql_xdevapi\columnresult::getType' => ['integer'], +'mysql_xdevapi\columnresult::isNumberSigned' => ['integer'], +'mysql_xdevapi\columnresult::isPadded' => ['integer'], +'mysql_xdevapi\crudoperationbindable::bind' => ['mysql_xdevapi\CrudOperationBindable', 'placeholder_values'=>'array'], +'mysql_xdevapi\crudoperationlimitable::limit' => ['mysql_xdevapi\CrudOperationLimitable', 'rows'=>'integer'], +'mysql_xdevapi\crudoperationskippable::skip' => ['mysql_xdevapi\CrudOperationSkippable', 'skip'=>'integer'], +'mysql_xdevapi\crudoperationsortable::sort' => ['mysql_xdevapi\CrudOperationSortable', 'sort_expr'=>'string'], +'mysql_xdevapi\databaseobject::existsInDatabase' => ['bool'], +'mysql_xdevapi\databaseobject::getName' => ['string'], +'mysql_xdevapi\databaseobject::getSession' => ['mysql_xdevapi\Session'], +'mysql_xdevapi\docresult::fetchAll' => ['Array'], +'mysql_xdevapi\docresult::fetchOne' => ['Object'], +'mysql_xdevapi\docresult::getWarnings' => ['Array'], +'mysql_xdevapi\docresult::getWarningsCount' => ['integer'], +'mysql_xdevapi\executable::execute' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\getsession' => ['mysql_xdevapi\Session', 'uri'=>'string'], +'mysql_xdevapi\result::getAutoIncrementValue' => ['int'], +'mysql_xdevapi\result::getGeneratedIds' => ['ArrayOfInt'], +'mysql_xdevapi\result::getWarnings' => ['array'], +'mysql_xdevapi\result::getWarningsCount' => ['integer'], +'mysql_xdevapi\rowresult::fetchAll' => ['array'], +'mysql_xdevapi\rowresult::fetchOne' => ['object'], +'mysql_xdevapi\rowresult::getColumnCount' => ['integer'], +'mysql_xdevapi\rowresult::getColumnNames' => ['array'], +'mysql_xdevapi\rowresult::getColumns' => ['array'], +'mysql_xdevapi\rowresult::getWarnings' => ['array'], +'mysql_xdevapi\rowresult::getWarningsCount' => ['integer'], +'mysql_xdevapi\schema::createCollection' => ['mysql_xdevapi\Collection', 'name'=>'string'], +'mysql_xdevapi\schema::dropCollection' => ['bool', 'collection_name'=>'string'], +'mysql_xdevapi\schema::existsInDatabase' => ['bool'], +'mysql_xdevapi\schema::getCollection' => ['mysql_xdevapi\Collection', 'name'=>'string'], +'mysql_xdevapi\schema::getCollectionAsTable' => ['mysql_xdevapi\Table', 'name'=>'string'], +'mysql_xdevapi\schema::getCollections' => ['array'], +'mysql_xdevapi\schema::getName' => ['string'], +'mysql_xdevapi\schema::getSession' => ['mysql_xdevapi\Session'], +'mysql_xdevapi\schema::getTable' => ['mysql_xdevapi\Table', 'name'=>'string'], +'mysql_xdevapi\schema::getTables' => ['array'], +'mysql_xdevapi\schemaobject::getSchema' => ['mysql_xdevapi\Schema'], +'mysql_xdevapi\session::close' => ['bool'], +'mysql_xdevapi\session::commit' => ['Object'], +'mysql_xdevapi\session::createSchema' => ['mysql_xdevapi\Schema', 'schema_name'=>'string'], +'mysql_xdevapi\session::dropSchema' => ['bool', 'schema_name'=>'string'], +'mysql_xdevapi\session::executeSql' => ['Object', 'statement'=>'string'], +'mysql_xdevapi\session::generateUUID' => ['string'], +'mysql_xdevapi\session::getClientId' => ['integer'], +'mysql_xdevapi\session::getSchema' => ['mysql_xdevapi\Schema', 'schema_name'=>'string'], +'mysql_xdevapi\session::getSchemas' => ['array'], +'mysql_xdevapi\session::getServerVersion' => ['integer'], +'mysql_xdevapi\session::killClient' => ['object', 'client_id'=>'integer'], +'mysql_xdevapi\session::listClients' => ['array'], +'mysql_xdevapi\session::quoteName' => ['string', 'name'=>'string'], +'mysql_xdevapi\session::releaseSavepoint' => ['void', 'name'=>'string'], +'mysql_xdevapi\session::rollback' => ['void'], +'mysql_xdevapi\session::rollbackTo' => ['void', 'name'=>'string'], +'mysql_xdevapi\session::setSavepoint' => ['string', 'name='=>'string'], +'mysql_xdevapi\session::sql' => ['mysql_xdevapi\SqlStatement', 'query'=>'string'], +'mysql_xdevapi\session::startTransaction' => ['void'], +'mysql_xdevapi\sqlstatement::bind' => ['mysql_xdevapi\SqlStatement', 'param'=>'string'], +'mysql_xdevapi\sqlstatement::execute' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\sqlstatement::getNextResult' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\sqlstatement::getResult' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\sqlstatement::hasMoreResults' => ['bool'], +'mysql_xdevapi\sqlstatementresult::fetchAll' => ['array'], +'mysql_xdevapi\sqlstatementresult::fetchOne' => ['object'], +'mysql_xdevapi\sqlstatementresult::getAffectedItemsCount' => ['integer'], +'mysql_xdevapi\sqlstatementresult::getColumnCount' => ['integer'], +'mysql_xdevapi\sqlstatementresult::getColumnNames' => ['array'], +'mysql_xdevapi\sqlstatementresult::getColumns' => ['Array'], +'mysql_xdevapi\sqlstatementresult::getGeneratedIds' => ['array'], +'mysql_xdevapi\sqlstatementresult::getLastInsertId' => ['String'], +'mysql_xdevapi\sqlstatementresult::getWarnings' => ['array'], +'mysql_xdevapi\sqlstatementresult::getWarningsCount' => ['integer'], +'mysql_xdevapi\sqlstatementresult::hasData' => ['bool'], +'mysql_xdevapi\sqlstatementresult::nextResult' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\statement::getNextResult' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\statement::getResult' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\statement::hasMoreResults' => ['bool'], +'mysql_xdevapi\table::count' => ['integer'], +'mysql_xdevapi\table::delete' => ['mysql_xdevapi\TableDelete'], +'mysql_xdevapi\table::existsInDatabase' => ['bool'], +'mysql_xdevapi\table::getName' => ['string'], +'mysql_xdevapi\table::getSchema' => ['mysql_xdevapi\Schema'], +'mysql_xdevapi\table::getSession' => ['mysql_xdevapi\Session'], +'mysql_xdevapi\table::insert' => ['mysql_xdevapi\TableInsert', 'columns'=>'mixed', '...args='=>'mixed'], +'mysql_xdevapi\table::isView' => ['bool'], +'mysql_xdevapi\table::select' => ['mysql_xdevapi\TableSelect', 'columns'=>'mixed', '...args='=>'mixed'], +'mysql_xdevapi\table::update' => ['mysql_xdevapi\TableUpdate'], +'mysql_xdevapi\tabledelete::bind' => ['mysql_xdevapi\TableDelete', 'placeholder_values'=>'array'], +'mysql_xdevapi\tabledelete::execute' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\tabledelete::limit' => ['mysql_xdevapi\TableDelete', 'rows'=>'integer'], +'mysql_xdevapi\tabledelete::offset' => ['mysql_xdevapi\TableDelete', 'position'=>'integer'], +'mysql_xdevapi\tabledelete::orderby' => ['mysql_xdevapi\TableDelete', 'orderby_expr'=>'string'], +'mysql_xdevapi\tabledelete::where' => ['mysql_xdevapi\TableDelete', 'where_expr'=>'string'], +'mysql_xdevapi\tableinsert::execute' => ['mysql_xdevapi\Result'], +'mysql_xdevapi\tableinsert::values' => ['mysql_xdevapi\TableInsert', 'row_values'=>'array'], +'mysql_xdevapi\tableselect::bind' => ['mysql_xdevapi\TableSelect', 'placeholder_values'=>'array'], +'mysql_xdevapi\tableselect::execute' => ['mysql_xdevapi\RowResult'], +'mysql_xdevapi\tableselect::groupBy' => ['mysql_xdevapi\TableSelect', 'sort_expr'=>'mixed'], +'mysql_xdevapi\tableselect::having' => ['mysql_xdevapi\TableSelect', 'sort_expr'=>'string'], +'mysql_xdevapi\tableselect::limit' => ['mysql_xdevapi\TableSelect', 'rows'=>'integer'], +'mysql_xdevapi\tableselect::lockExclusive' => ['mysql_xdevapi\TableSelect', 'lock_waiting_option='=>'integer'], +'mysql_xdevapi\tableselect::lockShared' => ['mysql_xdevapi\TableSelect', 'lock_waiting_option='=>'integer'], +'mysql_xdevapi\tableselect::offset' => ['mysql_xdevapi\TableSelect', 'position'=>'integer'], +'mysql_xdevapi\tableselect::orderby' => ['mysql_xdevapi\TableSelect', 'sort_expr'=>'mixed', '...args='=>'mixed'], +'mysql_xdevapi\tableselect::where' => ['mysql_xdevapi\TableSelect', 'where_expr'=>'string'], +'mysql_xdevapi\tableupdate::bind' => ['mysql_xdevapi\TableUpdate', 'placeholder_values'=>'array'], +'mysql_xdevapi\tableupdate::execute' => ['mysql_xdevapi\TableUpdate'], +'mysql_xdevapi\tableupdate::limit' => ['mysql_xdevapi\TableUpdate', 'rows'=>'integer'], +'mysql_xdevapi\tableupdate::orderby' => ['mysql_xdevapi\TableUpdate', 'orderby_expr'=>'mixed', '...args='=>'mixed'], +'mysql_xdevapi\tableupdate::set' => ['mysql_xdevapi\TableUpdate', 'table_field'=>'string', 'expression_or_literal'=>'string'], +'mysql_xdevapi\tableupdate::where' => ['mysql_xdevapi\TableUpdate', 'where_expr'=>'string'], +'mysqli::__construct' => ['void', 'host='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'], +'mysqli::autocommit' => ['bool', 'mode'=>'bool'], +'mysqli::begin_transaction' => ['bool', 'flags='=>'int', 'name='=>'string'], +'mysqli::change_user' => ['bool', 'user'=>'string', 'password'=>'string', 'database'=>'string'], +'mysqli::character_set_name' => ['string'], +'mysqli::close' => ['bool'], +'mysqli::commit' => ['bool', 'flags='=>'int', 'name='=>'string'], +'mysqli::debug' => ['bool', 'message'=>'string'], +'mysqli::disable_reads_from_master' => ['bool'], +'mysqli::dump_debug_info' => ['bool'], +'mysqli::escape_string' => ['string', 'escapestr'=>'string'], +'mysqli::get_charset' => ['object'], +'mysqli::get_client_info' => ['string'], +'mysqli::get_connection_stats' => ['array|false'], +'mysqli::get_warnings' => ['mysqli_warning'], +'mysqli::init' => ['mysqli'], +'mysqli::kill' => ['bool', 'processid'=>'int'], +'mysqli::more_results' => ['bool'], +'mysqli::multi_query' => ['bool', 'query'=>'string'], +'mysqli::next_result' => ['bool'], +'mysqli::options' => ['bool', 'option'=>'int', 'value'=>'mixed'], +'mysqli::ping' => ['bool'], +'mysqli::poll' => ['int|false', '&w_read'=>'array', '&w_error'=>'array', '&w_reject'=>'array', 'sec'=>'int', 'usecond='=>'int'], +'mysqli::prepare' => ['mysqli_stmt|false', 'query'=>'string'], +'mysqli::query' => ['bool|mysqli_result', 'query'=>'string', 'resultmode='=>'int'], +'mysqli::real_connect' => ['bool', 'host='=>'string|null', 'username='=>'string', 'passwd='=>'string|null', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string', 'flags='=>'int'], +'mysqli::real_escape_string' => ['string', 'escapestr'=>'string'], +'mysqli::real_query' => ['bool', 'query'=>'string'], +'mysqli::reap_async_query' => ['mysqli_result|false'], +'mysqli::refresh' => ['bool', 'options'=>'int'], +'mysqli::release_savepoint' => ['bool', 'name'=>'string'], +'mysqli::rollback' => ['bool', 'flags='=>'int', 'name='=>'string'], +'mysqli::rpl_query_type' => ['int', 'query'=>'string'], +'mysqli::savepoint' => ['bool', 'name'=>'string'], +'mysqli::select_db' => ['bool', 'dbname'=>'string'], +'mysqli::send_query' => ['bool', 'query'=>'string'], +'mysqli::set_charset' => ['bool', 'charset'=>'string'], +'mysqli::set_local_infile_default' => ['void'], +'mysqli::set_local_infile_handler' => ['bool', 'read_func='=>'callable'], +'mysqli::ssl_set' => ['bool', 'key'=>'string', 'cert'=>'string', 'ca'=>'string', 'capath'=>'string', 'cipher'=>'string'], +'mysqli::stat' => ['string|false'], +'mysqli::stmt_init' => ['mysqli_stmt'], +'mysqli::store_result' => ['mysqli_result|false', 'option='=>'int'], +'mysqli::thread_safe' => ['bool'], +'mysqli::use_result' => ['mysqli_result|false'], +'mysqli_affected_rows' => ['int', 'link'=>'mysqli'], +'mysqli_autocommit' => ['bool', 'link'=>'mysqli', 'mode'=>'bool'], +'mysqli_begin_transaction' => ['bool', 'link'=>'mysqli', 'flags='=>'int', 'name='=>'string'], +'mysqli_change_user' => ['bool', 'link'=>'mysqli', 'user'=>'string', 'password'=>'string', 'database'=>'?string'], +'mysqli_character_set_name' => ['string', 'link'=>'mysqli'], +'mysqli_close' => ['bool', 'link'=>'mysqli'], +'mysqli_commit' => ['bool', 'link'=>'mysqli', 'flags='=>'int', 'name='=>'string'], +'mysqli_connect' => ['mysqli|false', 'host='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'], +'mysqli_connect_errno' => ['int'], +'mysqli_connect_error' => ['string'], +'mysqli_data_seek' => ['bool', 'result'=>'mysqli_result', 'offset'=>'int'], +'mysqli_debug' => ['bool', 'message'=>'string'], +'mysqli_disable_reads_from_master' => ['bool', 'link'=>'mysqli'], +'mysqli_disable_rpl_parse' => ['bool', 'link'=>'mysqli'], +'mysqli_driver::embedded_server_end' => ['void'], +'mysqli_driver::embedded_server_start' => ['bool', 'start'=>'int', 'arguments'=>'array', 'groups'=>'array'], +'mysqli_dump_debug_info' => ['bool', 'link'=>'mysqli'], +'mysqli_embedded_server_end' => ['void'], +'mysqli_embedded_server_start' => ['bool', 'start'=>'int', 'arguments'=>'array', 'groups'=>'array'], +'mysqli_enable_reads_from_master' => ['bool', 'link'=>'mysqli'], +'mysqli_enable_rpl_parse' => ['bool', 'link'=>'mysqli'], +'mysqli_errno' => ['int', 'link'=>'mysqli'], +'mysqli_error' => ['string', 'link'=>'mysqli'], +'mysqli_error_list' => ['array', 'connection'=>'mysqli'], +'mysqli_escape_string' => ['string', 'link'=>'mysqli', 'escapestr'=>'string'], +'mysqli_execute' => ['bool', 'stmt'=>'mysqli_stmt'], +'mysqli_fetch_all' => ['array', 'result'=>'mysqli_result', 'resulttype='=>'int'], +'mysqli_fetch_array' => ['?array', 'result'=>'mysqli_result', 'resulttype='=>'int'], +'mysqli_fetch_assoc' => ['array|null', 'result'=>'mysqli_result'], +'mysqli_fetch_field' => ['object|false', 'result'=>'mysqli_result'], +'mysqli_fetch_field_direct' => ['object|false', 'result'=>'mysqli_result', 'fieldnr'=>'int'], +'mysqli_fetch_fields' => ['array|false', 'result'=>'mysqli_result'], +'mysqli_fetch_lengths' => ['array|false', 'result'=>'mysqli_result'], +'mysqli_fetch_object' => ['?object', 'result'=>'mysqli_result', 'class_name='=>'string', 'params='=>'?array'], +'mysqli_fetch_row' => ['?array', 'result'=>'mysqli_result'], +'mysqli_field_count' => ['int', 'link'=>'mysqli'], +'mysqli_field_seek' => ['bool', 'result'=>'mysqli_result', 'fieldnr'=>'int'], +'mysqli_field_tell' => ['int', 'result'=>'mysqli_result'], +'mysqli_free_result' => ['void', 'link'=>'mysqli_result'], +'mysqli_get_cache_stats' => ['array|false'], +'mysqli_get_charset' => ['object', 'link'=>'mysqli'], +'mysqli_get_client_info' => ['string', 'link'=>'mysqli'], +'mysqli_get_client_stats' => ['array|false'], +'mysqli_get_client_version' => ['int', 'link'=>'mysqli'], +'mysqli_get_connection_stats' => ['array|false', 'link'=>'mysqli'], +'mysqli_get_host_info' => ['string', 'link'=>'mysqli'], +'mysqli_get_links_stats' => ['array'], +'mysqli_get_proto_info' => ['int', 'link'=>'mysqli'], +'mysqli_get_server_info' => ['string', 'link'=>'mysqli'], +'mysqli_get_server_version' => ['int', 'link'=>'mysqli'], +'mysqli_get_warnings' => ['mysqli_warning', 'link'=>'mysqli'], +'mysqli_info' => ['?string', 'link'=>'mysqli'], +'mysqli_init' => ['mysqli'], +'mysqli_insert_id' => ['int|string', 'link'=>'mysqli'], +'mysqli_kill' => ['bool', 'link'=>'mysqli', 'processid'=>'int'], +'mysqli_link_construct' => ['object'], +'mysqli_master_query' => ['bool', 'link'=>'mysqli', 'query'=>'string'], +'mysqli_more_results' => ['bool', 'link'=>'mysqli'], +'mysqli_multi_query' => ['bool', 'link'=>'mysqli', 'query'=>'string'], +'mysqli_next_result' => ['bool', 'link'=>'mysqli'], +'mysqli_num_fields' => ['int', 'link'=>'mysqli_result'], +'mysqli_num_rows' => ['int', 'link'=>'mysqli_result'], +'mysqli_options' => ['bool', 'link'=>'mysqli', 'option'=>'int', 'value'=>'mixed'], +'mysqli_ping' => ['bool', 'link'=>'mysqli'], +'mysqli_poll' => ['int|false', 'read'=>'array', 'error'=>'array', 'reject'=>'array', 'second'=>'int', 'usecond='=>'int'], +'mysqli_prepare' => ['mysqli_stmt|false', 'link'=>'mysqli', 'query'=>'string'], +'mysqli_query' => ['mysqli_result|bool', 'link'=>'mysqli', 'query'=>'string', 'resultmode='=>'int'], +'mysqli_real_connect' => ['bool', 'link='=>'mysqli', 'host='=>'string|null', 'username='=>'string', 'passwd='=>'string|null', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string', 'flags='=>'int'], +'mysqli_real_escape_string' => ['string', 'link'=>'mysqli', 'escapestr'=>'string'], +'mysqli_real_query' => ['bool', 'link'=>'mysqli', 'query'=>'string'], +'mysqli_reap_async_query' => ['mysqli_result|false', 'link'=>'mysqli'], +'mysqli_refresh' => ['bool', 'link'=>'mysqli', 'options'=>'int'], +'mysqli_release_savepoint' => ['bool', 'link'=>'mysqli', 'name'=>'string'], +'mysqli_report' => ['bool', 'flags'=>'int'], +'mysqli_result::__construct' => ['void', 'link'=>'mysqli', 'resultmode='=>'int'], +'mysqli_result::close' => ['void'], +'mysqli_result::data_seek' => ['bool', 'offset'=>'int'], +'mysqli_result::fetch_all' => ['array', 'resulttype='=>'int'], +'mysqli_result::fetch_array' => ['?array', 'resulttype='=>'int'], +'mysqli_result::fetch_assoc' => ['array|null'], +'mysqli_result::fetch_field' => ['object|false'], +'mysqli_result::fetch_field_direct' => ['object|false', 'fieldnr'=>'int'], +'mysqli_result::fetch_fields' => ['array|false'], +'mysqli_result::fetch_object' => ['object|stdClass|null', 'class_name='=>'string', 'params='=>'array'], +'mysqli_result::fetch_row' => ['?array'], +'mysqli_result::field_seek' => ['bool', 'fieldnr'=>'int'], +'mysqli_result::free' => ['void'], +'mysqli_result::free_result' => ['void'], +'mysqli_rollback' => ['bool', 'link'=>'mysqli', 'flags='=>'int', 'name='=>'string'], +'mysqli_rpl_parse_enabled' => ['int', 'link'=>'mysqli'], +'mysqli_rpl_probe' => ['bool', 'link'=>'mysqli'], +'mysqli_rpl_query_type' => ['int', 'link'=>'mysqli', 'query'=>'string'], +'mysqli_savepoint' => ['bool', 'link'=>'mysqli', 'name'=>'string'], +'mysqli_savepoint_libmysql' => ['bool'], +'mysqli_select_db' => ['bool', 'link'=>'mysqli', 'dbname'=>'string'], +'mysqli_send_query' => ['bool', 'link'=>'mysqli', 'query'=>'string'], +'mysqli_set_charset' => ['bool', 'link'=>'mysqli', 'charset'=>'string'], +'mysqli_set_local_infile_default' => ['void', 'link'=>'mysqli'], +'mysqli_set_local_infile_handler' => ['bool', 'link'=>'mysqli', 'read_func'=>'callable'], +'mysqli_set_opt' => ['bool', 'link'=>'mysqli', 'option'=>'int', 'value'=>'mixed'], +'mysqli_slave_query' => ['bool', 'link'=>'mysqli', 'query'=>'string'], +'mysqli_sqlstate' => ['string', 'link'=>'mysqli'], +'mysqli_ssl_set' => ['bool', 'link'=>'mysqli', 'key'=>'string', 'cert'=>'string', 'ca'=>'string', 'capath'=>'string', 'cipher'=>'string'], +'mysqli_stat' => ['string|false', 'link'=>'mysqli'], +'mysqli_stmt::__construct' => ['void', 'link'=>'mysqli', 'query'=>'string'], +'mysqli_stmt::attr_get' => ['false|int', 'attr'=>'int'], +'mysqli_stmt::attr_set' => ['bool', 'attr'=>'int', 'mode'=>'int'], +'mysqli_stmt::bind_param' => ['bool', 'types'=>'string', '&var1'=>'mixed', '&...args='=>'mixed'], +'mysqli_stmt::bind_result' => ['bool', '&w_var1'=>'', '&...w_vars='=>''], +'mysqli_stmt::close' => ['bool'], +'mysqli_stmt::data_seek' => ['void', 'offset'=>'int'], +'mysqli_stmt::execute' => ['bool'], +'mysqli_stmt::fetch' => ['bool|null'], +'mysqli_stmt::free_result' => ['void'], +'mysqli_stmt::get_result' => ['mysqli_result|false'], +'mysqli_stmt::get_warnings' => ['object'], +'mysqli_stmt::more_results' => ['bool'], +'mysqli_stmt::next_result' => ['bool'], +'mysqli_stmt::num_rows' => ['int'], +'mysqli_stmt::prepare' => ['bool', 'query'=>'string'], +'mysqli_stmt::reset' => ['bool'], +'mysqli_stmt::result_metadata' => ['mysqli_result|false'], +'mysqli_stmt::send_long_data' => ['bool', 'param_nr'=>'int', 'data'=>'string'], +'mysqli_stmt::store_result' => ['bool'], +'mysqli_stmt_affected_rows' => ['int|string', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_attr_get' => ['int|false', 'stmt'=>'mysqli_stmt', 'attr'=>'int'], +'mysqli_stmt_attr_set' => ['bool', 'stmt'=>'mysqli_stmt', 'attr'=>'int', 'mode'=>'int'], +'mysqli_stmt_bind_param' => ['bool', 'stmt'=>'mysqli_stmt', 'types'=>'string', '&var1'=>'mixed', '&...args='=>'mixed'], +'mysqli_stmt_bind_result' => ['bool', 'stmt'=>'mysqli_stmt', '&w_var1'=>'', '&...w_vars='=>''], +'mysqli_stmt_close' => ['bool', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_data_seek' => ['void', 'stmt'=>'mysqli_stmt', 'offset'=>'int'], +'mysqli_stmt_errno' => ['int', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_error' => ['string', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_error_list' => ['array', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_execute' => ['bool', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_fetch' => ['bool|null', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_field_count' => ['int', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_free_result' => ['void', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_get_result' => ['mysqli_result|false', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_get_warnings' => ['object', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_init' => ['mysqli_stmt', 'link'=>'mysqli'], +'mysqli_stmt_insert_id' => ['mixed', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_more_results' => ['bool', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_next_result' => ['bool', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_num_rows' => ['int', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_param_count' => ['int', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_prepare' => ['bool', 'stmt'=>'mysqli_stmt', 'query'=>'string'], +'mysqli_stmt_reset' => ['bool', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_result_metadata' => ['mysqli_result|false', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_send_long_data' => ['bool', 'stmt'=>'mysqli_stmt', 'param_nr'=>'int', 'data'=>'string'], +'mysqli_stmt_sqlstate' => ['string', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_store_result' => ['bool', 'stmt'=>'mysqli_stmt'], +'mysqli_store_result' => ['mysqli_result|false', 'link'=>'mysqli', 'option='=>'int'], +'mysqli_thread_id' => ['int', 'link'=>'mysqli'], +'mysqli_thread_safe' => ['bool'], +'mysqli_use_result' => ['mysqli_result|false', 'link'=>'mysqli'], +'mysqli_warning::__construct' => ['void'], +'mysqli_warning::next' => ['bool'], +'mysqli_warning_count' => ['int', 'link'=>'mysqli'], +'mysqlnd_memcache_get_config' => ['array', 'connection'=>'mixed'], +'mysqlnd_memcache_set' => ['bool', 'mysql_connection'=>'mixed', 'memcache_connection='=>'Memcached', 'pattern='=>'string', 'callback='=>'callable'], +'mysqlnd_ms_dump_servers' => ['array', 'connection'=>'mixed'], +'mysqlnd_ms_fabric_select_global' => ['array', 'connection'=>'mixed', 'table_name'=>'mixed'], +'mysqlnd_ms_fabric_select_shard' => ['array', 'connection'=>'mixed', 'table_name'=>'mixed', 'shard_key'=>'mixed'], +'mysqlnd_ms_get_last_gtid' => ['string', 'connection'=>'mixed'], +'mysqlnd_ms_get_last_used_connection' => ['array', 'connection'=>'mixed'], +'mysqlnd_ms_get_stats' => ['array'], +'mysqlnd_ms_match_wild' => ['bool', 'table_name'=>'string', 'wildcard'=>'string'], +'mysqlnd_ms_query_is_select' => ['int', 'query'=>'string'], +'mysqlnd_ms_set_qos' => ['bool', 'connection'=>'mixed', 'service_level'=>'int', 'service_level_option='=>'int', 'option_value='=>'mixed'], +'mysqlnd_ms_set_user_pick_server' => ['bool', 'function'=>'string'], +'mysqlnd_ms_xa_begin' => ['int', 'connection'=>'mixed', 'gtrid'=>'string', 'timeout='=>'int'], +'mysqlnd_ms_xa_commit' => ['int', 'connection'=>'mixed', 'gtrid'=>'string'], +'mysqlnd_ms_xa_gc' => ['int', 'connection'=>'mixed', 'gtrid='=>'string', 'ignore_max_retries='=>'bool'], +'mysqlnd_ms_xa_rollback' => ['int', 'connection'=>'mixed', 'gtrid'=>'string'], +'mysqlnd_qc_change_handler' => ['bool', 'handler'=>''], +'mysqlnd_qc_clear_cache' => ['bool'], +'mysqlnd_qc_get_available_handlers' => ['array'], +'mysqlnd_qc_get_cache_info' => ['array'], +'mysqlnd_qc_get_core_stats' => ['array'], +'mysqlnd_qc_get_handler' => ['array'], +'mysqlnd_qc_get_normalized_query_trace_log' => ['array'], +'mysqlnd_qc_get_query_trace_log' => ['array'], +'mysqlnd_qc_set_cache_condition' => ['bool', 'condition_type'=>'int', 'condition'=>'mixed', 'condition_option'=>'mixed'], +'mysqlnd_qc_set_is_select' => ['mixed', 'callback'=>'string'], +'mysqlnd_qc_set_storage_handler' => ['bool', 'handler'=>'string'], +'mysqlnd_qc_set_user_handlers' => ['bool', 'get_hash'=>'string', 'find_query_in_cache'=>'string', 'return_to_cache'=>'string', 'add_query_to_cache_if_not_exists'=>'string', 'query_is_select'=>'string', 'update_query_run_time_stats'=>'string', 'get_stats'=>'string', 'clear_cache'=>'string'], +'mysqlnd_uh_convert_to_mysqlnd' => ['resource', '&rw_mysql_connection'=>'mysqli'], +'mysqlnd_uh_set_connection_proxy' => ['bool', '&rw_connection_proxy'=>'MysqlndUhConnection', '&rw_mysqli_connection='=>'mysqli'], +'mysqlnd_uh_set_statement_proxy' => ['bool', '&rw_statement_proxy'=>'MysqlndUhStatement'], +'MysqlndUhConnection::__construct' => ['void'], +'MysqlndUhConnection::changeUser' => ['bool', 'connection'=>'mysqlnd_connection', 'user'=>'string', 'password'=>'string', 'database'=>'string', 'silent'=>'bool', 'passwd_len'=>'int'], +'MysqlndUhConnection::charsetName' => ['string', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::close' => ['bool', 'connection'=>'mysqlnd_connection', 'close_type'=>'int'], +'MysqlndUhConnection::connect' => ['bool', 'connection'=>'mysqlnd_connection', 'host'=>'string', 'use'=>'string', 'password'=>'string', 'database'=>'string', 'port'=>'int', 'socket'=>'string', 'mysql_flags'=>'int'], +'MysqlndUhConnection::endPSession' => ['bool', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::escapeString' => ['string', 'connection'=>'mysqlnd_connection', 'escape_string'=>'string'], +'MysqlndUhConnection::getAffectedRows' => ['int', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getErrorNumber' => ['int', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getErrorString' => ['string', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getFieldCount' => ['int', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getHostInformation' => ['string', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getLastInsertId' => ['int', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getLastMessage' => ['void', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getProtocolInformation' => ['string', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getServerInformation' => ['string', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getServerStatistics' => ['string', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getServerVersion' => ['int', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getSqlstate' => ['string', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getStatistics' => ['array', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getThreadId' => ['int', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::getWarningCount' => ['int', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::init' => ['bool', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::killConnection' => ['bool', 'connection'=>'mysqlnd_connection', 'pid'=>'int'], +'MysqlndUhConnection::listFields' => ['array', 'connection'=>'mysqlnd_connection', 'table'=>'string', 'achtung_wild'=>'string'], +'MysqlndUhConnection::listMethod' => ['void', 'connection'=>'mysqlnd_connection', 'query'=>'string', 'achtung_wild'=>'string', 'par1'=>'string'], +'MysqlndUhConnection::moreResults' => ['bool', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::nextResult' => ['bool', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::ping' => ['bool', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::query' => ['bool', 'connection'=>'mysqlnd_connection', 'query'=>'string'], +'MysqlndUhConnection::queryReadResultsetHeader' => ['bool', 'connection'=>'mysqlnd_connection', 'mysqlnd_stmt'=>'mysqlnd_statement'], +'MysqlndUhConnection::reapQuery' => ['bool', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::refreshServer' => ['bool', 'connection'=>'mysqlnd_connection', 'options'=>'int'], +'MysqlndUhConnection::restartPSession' => ['bool', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::selectDb' => ['bool', 'connection'=>'mysqlnd_connection', 'database'=>'string'], +'MysqlndUhConnection::sendClose' => ['bool', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::sendQuery' => ['bool', 'connection'=>'mysqlnd_connection', 'query'=>'string'], +'MysqlndUhConnection::serverDumpDebugInformation' => ['bool', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::setAutocommit' => ['bool', 'connection'=>'mysqlnd_connection', 'mode'=>'int'], +'MysqlndUhConnection::setCharset' => ['bool', 'connection'=>'mysqlnd_connection', 'charset'=>'string'], +'MysqlndUhConnection::setClientOption' => ['bool', 'connection'=>'mysqlnd_connection', 'option'=>'int', 'value'=>'int'], +'MysqlndUhConnection::setServerOption' => ['void', 'connection'=>'mysqlnd_connection', 'option'=>'int'], +'MysqlndUhConnection::shutdownServer' => ['void', 'MYSQLND_UH_RES_MYSQLND_NAME'=>'string', 'level'=>'string'], +'MysqlndUhConnection::simpleCommand' => ['bool', 'connection'=>'mysqlnd_connection', 'command'=>'int', 'arg'=>'string', 'ok_packet'=>'int', 'silent'=>'bool', 'ignore_upsert_status'=>'bool'], +'MysqlndUhConnection::simpleCommandHandleResponse' => ['bool', 'connection'=>'mysqlnd_connection', 'ok_packet'=>'int', 'silent'=>'bool', 'command'=>'int', 'ignore_upsert_status'=>'bool'], +'MysqlndUhConnection::sslSet' => ['bool', 'connection'=>'mysqlnd_connection', 'key'=>'string', 'cert'=>'string', 'ca'=>'string', 'capath'=>'string', 'cipher'=>'string'], +'MysqlndUhConnection::stmtInit' => ['resource', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::storeResult' => ['resource', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::txCommit' => ['bool', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::txRollback' => ['bool', 'connection'=>'mysqlnd_connection'], +'MysqlndUhConnection::useResult' => ['resource', 'connection'=>'mysqlnd_connection'], +'MysqlndUhPreparedStatement::__construct' => ['void'], +'MysqlndUhPreparedStatement::execute' => ['bool', 'statement'=>'mysqlnd_prepared_statement'], +'MysqlndUhPreparedStatement::prepare' => ['bool', 'statement'=>'mysqlnd_prepared_statement', 'query'=>'string'], +'natcasesort' => ['bool', '&rw_array'=>'array'], +'natsort' => ['bool', '&rw_array'=>'array'], +'ncurses_addch' => ['int', 'ch'=>'int'], +'ncurses_addchnstr' => ['int', 's'=>'string', 'n'=>'int'], +'ncurses_addchstr' => ['int', 's'=>'string'], +'ncurses_addnstr' => ['int', 's'=>'string', 'n'=>'int'], +'ncurses_addstr' => ['int', 'text'=>'string'], +'ncurses_assume_default_colors' => ['int', 'fg'=>'int', 'bg'=>'int'], +'ncurses_attroff' => ['int', 'attributes'=>'int'], +'ncurses_attron' => ['int', 'attributes'=>'int'], +'ncurses_attrset' => ['int', 'attributes'=>'int'], +'ncurses_baudrate' => ['int'], +'ncurses_beep' => ['int'], +'ncurses_bkgd' => ['int', 'attrchar'=>'int'], +'ncurses_bkgdset' => ['void', 'attrchar'=>'int'], +'ncurses_border' => ['int', 'left'=>'int', 'right'=>'int', 'top'=>'int', 'bottom'=>'int', 'tl_corner'=>'int', 'tr_corner'=>'int', 'bl_corner'=>'int', 'br_corner'=>'int'], +'ncurses_bottom_panel' => ['int', 'panel'=>'resource'], +'ncurses_can_change_color' => ['bool'], +'ncurses_cbreak' => ['bool'], +'ncurses_clear' => ['bool'], +'ncurses_clrtobot' => ['bool'], +'ncurses_clrtoeol' => ['bool'], +'ncurses_color_content' => ['int', 'color'=>'int', 'r'=>'int', 'g'=>'int', 'b'=>'int'], +'ncurses_color_set' => ['int', 'pair'=>'int'], +'ncurses_curs_set' => ['int', 'visibility'=>'int'], +'ncurses_def_prog_mode' => ['bool'], +'ncurses_def_shell_mode' => ['bool'], +'ncurses_define_key' => ['int', 'definition'=>'string', 'keycode'=>'int'], +'ncurses_del_panel' => ['bool', 'panel'=>'resource'], +'ncurses_delay_output' => ['int', 'milliseconds'=>'int'], +'ncurses_delch' => ['bool'], +'ncurses_deleteln' => ['bool'], +'ncurses_delwin' => ['bool', 'window'=>'resource'], +'ncurses_doupdate' => ['bool'], +'ncurses_echo' => ['bool'], +'ncurses_echochar' => ['int', 'character'=>'int'], +'ncurses_end' => ['int'], +'ncurses_erase' => ['bool'], +'ncurses_erasechar' => ['string'], +'ncurses_filter' => ['void'], +'ncurses_flash' => ['bool'], +'ncurses_flushinp' => ['bool'], +'ncurses_getch' => ['int'], +'ncurses_getmaxyx' => ['void', 'window'=>'resource', 'y'=>'int', 'x'=>'int'], +'ncurses_getmouse' => ['bool', 'mevent'=>'array'], +'ncurses_getyx' => ['void', 'window'=>'resource', 'y'=>'int', 'x'=>'int'], +'ncurses_halfdelay' => ['int', 'tenth'=>'int'], +'ncurses_has_colors' => ['bool'], +'ncurses_has_ic' => ['bool'], +'ncurses_has_il' => ['bool'], +'ncurses_has_key' => ['int', 'keycode'=>'int'], +'ncurses_hide_panel' => ['int', 'panel'=>'resource'], +'ncurses_hline' => ['int', 'charattr'=>'int', 'n'=>'int'], +'ncurses_inch' => ['string'], +'ncurses_init' => ['void'], +'ncurses_init_color' => ['int', 'color'=>'int', 'r'=>'int', 'g'=>'int', 'b'=>'int'], +'ncurses_init_pair' => ['int', 'pair'=>'int', 'fg'=>'int', 'bg'=>'int'], +'ncurses_insch' => ['int', 'character'=>'int'], +'ncurses_insdelln' => ['int', 'count'=>'int'], +'ncurses_insertln' => ['int'], +'ncurses_insstr' => ['int', 'text'=>'string'], +'ncurses_instr' => ['int', 'buffer'=>'string'], +'ncurses_isendwin' => ['bool'], +'ncurses_keyok' => ['int', 'keycode'=>'int', 'enable'=>'bool'], +'ncurses_keypad' => ['int', 'window'=>'resource', 'bf'=>'bool'], +'ncurses_killchar' => ['string'], +'ncurses_longname' => ['string'], +'ncurses_meta' => ['int', 'window'=>'resource', '_8bit'=>'bool'], +'ncurses_mouse_trafo' => ['bool', 'y'=>'int', 'x'=>'int', 'toscreen'=>'bool'], +'ncurses_mouseinterval' => ['int', 'milliseconds'=>'int'], +'ncurses_mousemask' => ['int', 'newmask'=>'int', 'oldmask'=>'int'], +'ncurses_move' => ['int', 'y'=>'int', 'x'=>'int'], +'ncurses_move_panel' => ['int', 'panel'=>'resource', 'startx'=>'int', 'starty'=>'int'], +'ncurses_mvaddch' => ['int', 'y'=>'int', 'x'=>'int', 'c'=>'int'], +'ncurses_mvaddchnstr' => ['int', 'y'=>'int', 'x'=>'int', 's'=>'string', 'n'=>'int'], +'ncurses_mvaddchstr' => ['int', 'y'=>'int', 'x'=>'int', 's'=>'string'], +'ncurses_mvaddnstr' => ['int', 'y'=>'int', 'x'=>'int', 's'=>'string', 'n'=>'int'], +'ncurses_mvaddstr' => ['int', 'y'=>'int', 'x'=>'int', 's'=>'string'], +'ncurses_mvcur' => ['int', 'old_y'=>'int', 'old_x'=>'int', 'new_y'=>'int', 'new_x'=>'int'], +'ncurses_mvdelch' => ['int', 'y'=>'int', 'x'=>'int'], +'ncurses_mvgetch' => ['int', 'y'=>'int', 'x'=>'int'], +'ncurses_mvhline' => ['int', 'y'=>'int', 'x'=>'int', 'attrchar'=>'int', 'n'=>'int'], +'ncurses_mvinch' => ['int', 'y'=>'int', 'x'=>'int'], +'ncurses_mvvline' => ['int', 'y'=>'int', 'x'=>'int', 'attrchar'=>'int', 'n'=>'int'], +'ncurses_mvwaddstr' => ['int', 'window'=>'resource', 'y'=>'int', 'x'=>'int', 'text'=>'string'], +'ncurses_napms' => ['int', 'milliseconds'=>'int'], +'ncurses_new_panel' => ['resource', 'window'=>'resource'], +'ncurses_newpad' => ['resource', 'rows'=>'int', 'cols'=>'int'], +'ncurses_newwin' => ['resource', 'rows'=>'int', 'cols'=>'int', 'y'=>'int', 'x'=>'int'], +'ncurses_nl' => ['bool'], +'ncurses_nocbreak' => ['bool'], +'ncurses_noecho' => ['bool'], +'ncurses_nonl' => ['bool'], +'ncurses_noqiflush' => ['void'], +'ncurses_noraw' => ['bool'], +'ncurses_pair_content' => ['int', 'pair'=>'int', 'f'=>'int', 'b'=>'int'], +'ncurses_panel_above' => ['resource', 'panel'=>'resource'], +'ncurses_panel_below' => ['resource', 'panel'=>'resource'], +'ncurses_panel_window' => ['resource', 'panel'=>'resource'], +'ncurses_pnoutrefresh' => ['int', 'pad'=>'resource', 'pminrow'=>'int', 'pmincol'=>'int', 'sminrow'=>'int', 'smincol'=>'int', 'smaxrow'=>'int', 'smaxcol'=>'int'], +'ncurses_prefresh' => ['int', 'pad'=>'resource', 'pminrow'=>'int', 'pmincol'=>'int', 'sminrow'=>'int', 'smincol'=>'int', 'smaxrow'=>'int', 'smaxcol'=>'int'], +'ncurses_putp' => ['int', 'text'=>'string'], +'ncurses_qiflush' => ['void'], +'ncurses_raw' => ['bool'], +'ncurses_refresh' => ['int', 'ch'=>'int'], +'ncurses_replace_panel' => ['int', 'panel'=>'resource', 'window'=>'resource'], +'ncurses_reset_prog_mode' => ['int'], +'ncurses_reset_shell_mode' => ['int'], +'ncurses_resetty' => ['bool'], +'ncurses_savetty' => ['bool'], +'ncurses_scr_dump' => ['int', 'filename'=>'string'], +'ncurses_scr_init' => ['int', 'filename'=>'string'], +'ncurses_scr_restore' => ['int', 'filename'=>'string'], +'ncurses_scr_set' => ['int', 'filename'=>'string'], +'ncurses_scrl' => ['int', 'count'=>'int'], +'ncurses_show_panel' => ['int', 'panel'=>'resource'], +'ncurses_slk_attr' => ['int'], +'ncurses_slk_attroff' => ['int', 'intarg'=>'int'], +'ncurses_slk_attron' => ['int', 'intarg'=>'int'], +'ncurses_slk_attrset' => ['int', 'intarg'=>'int'], +'ncurses_slk_clear' => ['bool'], +'ncurses_slk_color' => ['int', 'intarg'=>'int'], +'ncurses_slk_init' => ['bool', 'format'=>'int'], +'ncurses_slk_noutrefresh' => ['bool'], +'ncurses_slk_refresh' => ['int'], +'ncurses_slk_restore' => ['int'], +'ncurses_slk_set' => ['bool', 'labelnr'=>'int', 'label'=>'string', 'format'=>'int'], +'ncurses_slk_touch' => ['int'], +'ncurses_standend' => ['int'], +'ncurses_standout' => ['int'], +'ncurses_start_color' => ['int'], +'ncurses_termattrs' => ['bool'], +'ncurses_termname' => ['string'], +'ncurses_timeout' => ['void', 'millisec'=>'int'], +'ncurses_top_panel' => ['int', 'panel'=>'resource'], +'ncurses_typeahead' => ['int', 'fd'=>'int'], +'ncurses_ungetch' => ['int', 'keycode'=>'int'], +'ncurses_ungetmouse' => ['bool', 'mevent'=>'array'], +'ncurses_update_panels' => ['void'], +'ncurses_use_default_colors' => ['bool'], +'ncurses_use_env' => ['void', 'flag'=>'bool'], +'ncurses_use_extended_names' => ['int', 'flag'=>'bool'], +'ncurses_vidattr' => ['int', 'intarg'=>'int'], +'ncurses_vline' => ['int', 'charattr'=>'int', 'n'=>'int'], +'ncurses_waddch' => ['int', 'window'=>'resource', 'ch'=>'int'], +'ncurses_waddstr' => ['int', 'window'=>'resource', 'string'=>'string', 'n='=>'int'], +'ncurses_wattroff' => ['int', 'window'=>'resource', 'attrs'=>'int'], +'ncurses_wattron' => ['int', 'window'=>'resource', 'attrs'=>'int'], +'ncurses_wattrset' => ['int', 'window'=>'resource', 'attrs'=>'int'], +'ncurses_wborder' => ['int', 'window'=>'resource', 'left'=>'int', 'right'=>'int', 'top'=>'int', 'bottom'=>'int', 'tl_corner'=>'int', 'tr_corner'=>'int', 'bl_corner'=>'int', 'br_corner'=>'int'], +'ncurses_wclear' => ['int', 'window'=>'resource'], +'ncurses_wcolor_set' => ['int', 'window'=>'resource', 'color_pair'=>'int'], +'ncurses_werase' => ['int', 'window'=>'resource'], +'ncurses_wgetch' => ['int', 'window'=>'resource'], +'ncurses_whline' => ['int', 'window'=>'resource', 'charattr'=>'int', 'n'=>'int'], +'ncurses_wmouse_trafo' => ['bool', 'window'=>'resource', 'y'=>'int', 'x'=>'int', 'toscreen'=>'bool'], +'ncurses_wmove' => ['int', 'window'=>'resource', 'y'=>'int', 'x'=>'int'], +'ncurses_wnoutrefresh' => ['int', 'window'=>'resource'], +'ncurses_wrefresh' => ['int', 'window'=>'resource'], +'ncurses_wstandend' => ['int', 'window'=>'resource'], +'ncurses_wstandout' => ['int', 'window'=>'resource'], +'ncurses_wvline' => ['int', 'window'=>'resource', 'charattr'=>'int', 'n'=>'int'], +'net_get_interfaces' => ['array>|false'], +'newrelic_add_custom_parameter' => ['bool', 'key'=>'string', 'value'=>'bool|float|int|string'], +'newrelic_add_custom_tracer' => ['bool', 'function_name'=>'string'], +'newrelic_background_job' => ['void', 'flag='=>'bool'], +'newrelic_capture_params' => ['void', 'enable='=>'bool'], +'newrelic_custom_metric' => ['bool', 'metric_name'=>'string', 'value'=>'float'], +'newrelic_disable_autorum' => ['true'], +'newrelic_end_of_transaction' => ['void'], +'newrelic_end_transaction' => ['bool', 'ignore='=>'bool'], +'newrelic_get_browser_timing_footer' => ['string', 'include_tags='=>'bool'], +'newrelic_get_browser_timing_header' => ['string', 'include_tags='=>'bool'], +'newrelic_ignore_apdex' => ['void'], +'newrelic_ignore_transaction' => ['void'], +'newrelic_name_transaction' => ['bool', 'name'=>'string'], +'newrelic_notice_error' => ['void', 'message'=>'string', 'exception='=>'Exception|Throwable'], +'newrelic_notice_error\'1' => ['void', 'unused_1'=>'string', 'message'=>'string', 'unused_2'=>'string', 'unused_3'=>'int', 'unused_4='=>''], +'newrelic_record_custom_event' => ['void', 'name'=>'string', 'attributes'=>'array'], +'newrelic_record_datastore_segment' => ['mixed', 'func'=>'callable', 'parameters'=>'array'], +'newrelic_set_appname' => ['bool', 'name'=>'string', 'license='=>'string', 'xmit='=>'bool'], +'newrelic_set_user_attributes' => ['bool', 'user'=>'string', 'account'=>'string', 'product'=>'string'], +'newrelic_start_transaction' => ['bool', 'appname'=>'string', 'license='=>'string'], +'newt_bell' => ['void'], +'newt_button' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string'], +'newt_button_bar' => ['resource', 'buttons'=>'array'], +'newt_centered_window' => ['int', 'width'=>'int', 'height'=>'int', 'title='=>'string'], +'newt_checkbox' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string', 'def_value'=>'string', 'seq='=>'string'], +'newt_checkbox_get_value' => ['string', 'checkbox'=>'resource'], +'newt_checkbox_set_flags' => ['void', 'checkbox'=>'resource', 'flags'=>'int', 'sense'=>'int'], +'newt_checkbox_set_value' => ['void', 'checkbox'=>'resource', 'value'=>'string'], +'newt_checkbox_tree' => ['resource', 'left'=>'int', 'top'=>'int', 'height'=>'int', 'flags='=>'int'], +'newt_checkbox_tree_add_item' => ['void', 'checkboxtree'=>'resource', 'text'=>'string', 'data'=>'mixed', 'flags'=>'int', 'index'=>'int', '...args='=>'int'], +'newt_checkbox_tree_find_item' => ['array', 'checkboxtree'=>'resource', 'data'=>'mixed'], +'newt_checkbox_tree_get_current' => ['mixed', 'checkboxtree'=>'resource'], +'newt_checkbox_tree_get_entry_value' => ['string', 'checkboxtree'=>'resource', 'data'=>'mixed'], +'newt_checkbox_tree_get_multi_selection' => ['array', 'checkboxtree'=>'resource', 'seqnum'=>'string'], +'newt_checkbox_tree_get_selection' => ['array', 'checkboxtree'=>'resource'], +'newt_checkbox_tree_multi' => ['resource', 'left'=>'int', 'top'=>'int', 'height'=>'int', 'seq'=>'string', 'flags='=>'int'], +'newt_checkbox_tree_set_current' => ['void', 'checkboxtree'=>'resource', 'data'=>'mixed'], +'newt_checkbox_tree_set_entry' => ['void', 'checkboxtree'=>'resource', 'data'=>'mixed', 'text'=>'string'], +'newt_checkbox_tree_set_entry_value' => ['void', 'checkboxtree'=>'resource', 'data'=>'mixed', 'value'=>'string'], +'newt_checkbox_tree_set_width' => ['void', 'checkbox_tree'=>'resource', 'width'=>'int'], +'newt_clear_key_buffer' => ['void'], +'newt_cls' => ['void'], +'newt_compact_button' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string'], +'newt_component_add_callback' => ['void', 'component'=>'resource', 'func_name'=>'mixed', 'data'=>'mixed'], +'newt_component_takes_focus' => ['void', 'component'=>'resource', 'takes_focus'=>'bool'], +'newt_create_grid' => ['resource', 'cols'=>'int', 'rows'=>'int'], +'newt_cursor_off' => ['void'], +'newt_cursor_on' => ['void'], +'newt_delay' => ['void', 'microseconds'=>'int'], +'newt_draw_form' => ['void', 'form'=>'resource'], +'newt_draw_root_text' => ['void', 'left'=>'int', 'top'=>'int', 'text'=>'string'], +'newt_entry' => ['resource', 'left'=>'int', 'top'=>'int', 'width'=>'int', 'init_value='=>'string', 'flags='=>'int'], +'newt_entry_get_value' => ['string', 'entry'=>'resource'], +'newt_entry_set' => ['void', 'entry'=>'resource', 'value'=>'string', 'cursor_at_end='=>'bool'], +'newt_entry_set_filter' => ['void', 'entry'=>'resource', 'filter'=>'callable', 'data'=>'mixed'], +'newt_entry_set_flags' => ['void', 'entry'=>'resource', 'flags'=>'int', 'sense'=>'int'], +'newt_finished' => ['int'], +'newt_form' => ['resource', 'vert_bar='=>'resource', 'help='=>'string', 'flags='=>'int'], +'newt_form_add_component' => ['void', 'form'=>'resource', 'component'=>'resource'], +'newt_form_add_components' => ['void', 'form'=>'resource', 'components'=>'array'], +'newt_form_add_hot_key' => ['void', 'form'=>'resource', 'key'=>'int'], +'newt_form_destroy' => ['void', 'form'=>'resource'], +'newt_form_get_current' => ['resource', 'form'=>'resource'], +'newt_form_run' => ['void', 'form'=>'resource', 'exit_struct'=>'array'], +'newt_form_set_background' => ['void', 'from'=>'resource', 'background'=>'int'], +'newt_form_set_height' => ['void', 'form'=>'resource', 'height'=>'int'], +'newt_form_set_size' => ['void', 'form'=>'resource'], +'newt_form_set_timer' => ['void', 'form'=>'resource', 'milliseconds'=>'int'], +'newt_form_set_width' => ['void', 'form'=>'resource', 'width'=>'int'], +'newt_form_watch_fd' => ['void', 'form'=>'resource', 'stream'=>'resource', 'flags='=>'int'], +'newt_get_screen_size' => ['void', 'cols'=>'int', 'rows'=>'int'], +'newt_grid_add_components_to_form' => ['void', 'grid'=>'resource', 'form'=>'resource', 'recurse'=>'bool'], +'newt_grid_basic_window' => ['resource', 'text'=>'resource', 'middle'=>'resource', 'buttons'=>'resource'], +'newt_grid_free' => ['void', 'grid'=>'resource', 'recurse'=>'bool'], +'newt_grid_get_size' => ['void', 'grid'=>'resource', 'width'=>'int', 'height'=>'int'], +'newt_grid_h_close_stacked' => ['resource', 'element1_type'=>'int', 'element1'=>'resource', '...args='=>'resource'], +'newt_grid_h_stacked' => ['resource', 'element1_type'=>'int', 'element1'=>'resource', '...args='=>'resource'], +'newt_grid_place' => ['void', 'grid'=>'resource', 'left'=>'int', 'top'=>'int'], +'newt_grid_set_field' => ['void', 'grid'=>'resource', 'col'=>'int', 'row'=>'int', 'type'=>'int', 'value'=>'resource', 'pad_left'=>'int', 'pad_top'=>'int', 'pad_right'=>'int', 'pad_bottom'=>'int', 'anchor'=>'int', 'flags='=>'int'], +'newt_grid_simple_window' => ['resource', 'text'=>'resource', 'middle'=>'resource', 'buttons'=>'resource'], +'newt_grid_v_close_stacked' => ['resource', 'element1_type'=>'int', 'element1'=>'resource', '...args='=>'resource'], +'newt_grid_v_stacked' => ['resource', 'element1_type'=>'int', 'element1'=>'resource', '...args='=>'resource'], +'newt_grid_wrapped_window' => ['void', 'grid'=>'resource', 'title'=>'string'], +'newt_grid_wrapped_window_at' => ['void', 'grid'=>'resource', 'title'=>'string', 'left'=>'int', 'top'=>'int'], +'newt_init' => ['int'], +'newt_label' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string'], +'newt_label_set_text' => ['void', 'label'=>'resource', 'text'=>'string'], +'newt_listbox' => ['resource', 'left'=>'int', 'top'=>'int', 'height'=>'int', 'flags='=>'int'], +'newt_listbox_append_entry' => ['void', 'listbox'=>'resource', 'text'=>'string', 'data'=>'mixed'], +'newt_listbox_clear' => ['void', 'listobx'=>'resource'], +'newt_listbox_clear_selection' => ['void', 'listbox'=>'resource'], +'newt_listbox_delete_entry' => ['void', 'listbox'=>'resource', 'key'=>'mixed'], +'newt_listbox_get_current' => ['string', 'listbox'=>'resource'], +'newt_listbox_get_selection' => ['array', 'listbox'=>'resource'], +'newt_listbox_insert_entry' => ['void', 'listbox'=>'resource', 'text'=>'string', 'data'=>'mixed', 'key'=>'mixed'], +'newt_listbox_item_count' => ['int', 'listbox'=>'resource'], +'newt_listbox_select_item' => ['void', 'listbox'=>'resource', 'key'=>'mixed', 'sense'=>'int'], +'newt_listbox_set_current' => ['void', 'listbox'=>'resource', 'num'=>'int'], +'newt_listbox_set_current_by_key' => ['void', 'listbox'=>'resource', 'key'=>'mixed'], +'newt_listbox_set_data' => ['void', 'listbox'=>'resource', 'num'=>'int', 'data'=>'mixed'], +'newt_listbox_set_entry' => ['void', 'listbox'=>'resource', 'num'=>'int', 'text'=>'string'], +'newt_listbox_set_width' => ['void', 'listbox'=>'resource', 'width'=>'int'], +'newt_listitem' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string', 'is_default'=>'bool', 'prev_item'=>'resource', 'data'=>'mixed', 'flags='=>'int'], +'newt_listitem_get_data' => ['mixed', 'item'=>'resource'], +'newt_listitem_set' => ['void', 'item'=>'resource', 'text'=>'string'], +'newt_open_window' => ['int', 'left'=>'int', 'top'=>'int', 'width'=>'int', 'height'=>'int', 'title='=>'string'], +'newt_pop_help_line' => ['void'], +'newt_pop_window' => ['void'], +'newt_push_help_line' => ['void', 'text='=>'string'], +'newt_radio_get_current' => ['resource', 'set_member'=>'resource'], +'newt_radiobutton' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string', 'is_default'=>'bool', 'prev_button='=>'resource'], +'newt_redraw_help_line' => ['void'], +'newt_reflow_text' => ['string', 'text'=>'string', 'width'=>'int', 'flex_down'=>'int', 'flex_up'=>'int', 'actual_width'=>'int', 'actual_height'=>'int'], +'newt_refresh' => ['void'], +'newt_resize_screen' => ['void', 'redraw='=>'bool'], +'newt_resume' => ['void'], +'newt_run_form' => ['resource', 'form'=>'resource'], +'newt_scale' => ['resource', 'left'=>'int', 'top'=>'int', 'width'=>'int', 'full_value'=>'int'], +'newt_scale_set' => ['void', 'scale'=>'resource', 'amount'=>'int'], +'newt_scrollbar_set' => ['void', 'scrollbar'=>'resource', 'where'=>'int', 'total'=>'int'], +'newt_set_help_callback' => ['void', 'function'=>'mixed'], +'newt_set_suspend_callback' => ['void', 'function'=>'callable', 'data'=>'mixed'], +'newt_suspend' => ['void'], +'newt_textbox' => ['resource', 'left'=>'int', 'top'=>'int', 'width'=>'int', 'height'=>'int', 'flags='=>'int'], +'newt_textbox_get_num_lines' => ['int', 'textbox'=>'resource'], +'newt_textbox_reflowed' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'char', 'width'=>'int', 'flex_down'=>'int', 'flex_up'=>'int', 'flags='=>'int'], +'newt_textbox_set_height' => ['void', 'textbox'=>'resource', 'height'=>'int'], +'newt_textbox_set_text' => ['void', 'textbox'=>'resource', 'text'=>'string'], +'newt_vertical_scrollbar' => ['resource', 'left'=>'int', 'top'=>'int', 'height'=>'int', 'normal_colorset='=>'int', 'thumb_colorset='=>'int'], +'newt_wait_for_key' => ['void'], +'newt_win_choice' => ['int', 'title'=>'string', 'button1_text'=>'string', 'button2_text'=>'string', 'format'=>'string', 'args='=>'mixed', '...args='=>'mixed'], +'newt_win_entries' => ['int', 'title'=>'string', 'text'=>'string', 'suggested_width'=>'int', 'flex_down'=>'int', 'flex_up'=>'int', 'data_width'=>'int', 'items'=>'array', 'button1'=>'string', '...args='=>'string'], +'newt_win_menu' => ['int', 'title'=>'string', 'text'=>'string', 'suggestedwidth'=>'int', 'flexdown'=>'int', 'flexup'=>'int', 'maxlistheight'=>'int', 'items'=>'array', 'listitem'=>'int', 'button1='=>'string', '...args='=>'string'], +'newt_win_message' => ['void', 'title'=>'string', 'button_text'=>'string', 'format'=>'string', 'args='=>'mixed', '...args='=>'mixed'], +'newt_win_messagev' => ['void', 'title'=>'string', 'button_text'=>'string', 'format'=>'string', 'args'=>'array'], +'newt_win_ternary' => ['int', 'title'=>'string', 'button1_text'=>'string', 'button2_text'=>'string', 'button3_text'=>'string', 'format'=>'string', 'args='=>'mixed', '...args='=>'mixed'], +'next' => ['mixed', '&r_array'=>'array|object'], +'ngettext' => ['string', 'msgid1'=>'string', 'msgid2'=>'string', 'n'=>'int'], +'nl2br' => ['string', 'string'=>'string', 'is_xhtml='=>'bool'], +'nl_langinfo' => ['string|false', 'item'=>'int'], +'NoRewindIterator::__construct' => ['void', 'iterator'=>'Iterator'], +'NoRewindIterator::current' => ['mixed'], +'NoRewindIterator::getInnerIterator' => ['Iterator'], +'NoRewindIterator::key' => ['mixed'], +'NoRewindIterator::next' => ['void'], +'NoRewindIterator::rewind' => ['void'], +'NoRewindIterator::valid' => ['bool'], +'Normalizer::getRawDecomposition' => ['string|null', 'input'=>'string'], +'Normalizer::isNormalized' => ['bool', 'input'=>'string', 'form='=>'int'], +'Normalizer::normalize' => ['string', 'input'=>'string', 'form='=>'int'], +'normalizer_get_raw_decomposition' => ['string|null', 'input'=>'string'], +'normalizer_is_normalized' => ['bool', 'input'=>'string', 'form='=>'int'], +'normalizer_normalize' => ['string', 'input'=>'string', 'form='=>'int'], +'notes_body' => ['array', 'server'=>'string', 'mailbox'=>'string', 'msg_number'=>'int'], +'notes_copy_db' => ['bool', 'from_database_name'=>'string', 'to_database_name'=>'string'], +'notes_create_db' => ['bool', 'database_name'=>'string'], +'notes_create_note' => ['bool', 'database_name'=>'string', 'form_name'=>'string'], +'notes_drop_db' => ['bool', 'database_name'=>'string'], +'notes_find_note' => ['int', 'database_name'=>'string', 'name'=>'string', 'type='=>'string'], +'notes_header_info' => ['object', 'server'=>'string', 'mailbox'=>'string', 'msg_number'=>'int'], +'notes_list_msgs' => ['bool', 'db'=>'string'], +'notes_mark_read' => ['bool', 'database_name'=>'string', 'user_name'=>'string', 'note_id'=>'string'], +'notes_mark_unread' => ['bool', 'database_name'=>'string', 'user_name'=>'string', 'note_id'=>'string'], +'notes_nav_create' => ['bool', 'database_name'=>'string', 'name'=>'string'], +'notes_search' => ['array', 'database_name'=>'string', 'keywords'=>'string'], +'notes_unread' => ['array', 'database_name'=>'string', 'user_name'=>'string'], +'notes_version' => ['float', 'database_name'=>'string'], +'nsapi_request_headers' => ['array'], +'nsapi_response_headers' => ['array'], +'nsapi_virtual' => ['bool', 'uri'=>'string'], +'nthmac' => ['string', 'clent'=>'string', 'data'=>'string'], +'number_format' => ['string', 'number'=>'float|int', 'num_decimal_places='=>'int'], +'number_format\'1' => ['string', 'number'=>'float|int', 'num_decimal_places'=>'int', 'dec_separator'=>'string', 'thousands_separator'=>'string'], +'NumberFormatter::__construct' => ['void', 'locale'=>'string', 'style'=>'int', 'pattern='=>'string'], +'NumberFormatter::create' => ['NumberFormatter|false', 'locale'=>'string', 'style'=>'int', 'pattern='=>'string'], +'NumberFormatter::format' => ['string|false', 'num'=>'', 'type='=>'int'], +'NumberFormatter::formatCurrency' => ['string', 'num'=>'float', 'currency'=>'string'], +'NumberFormatter::getAttribute' => ['int|false', 'attr'=>'int'], +'NumberFormatter::getErrorCode' => ['int'], +'NumberFormatter::getErrorMessage' => ['string'], +'NumberFormatter::getLocale' => ['string', 'type='=>'int'], +'NumberFormatter::getPattern' => ['string|false'], +'NumberFormatter::getSymbol' => ['string|false', 'attr'=>'int'], +'NumberFormatter::getTextAttribute' => ['string|false', 'attr'=>'int'], +'NumberFormatter::parse' => ['float|false', 'string'=>'string', 'type='=>'int', '&rw_position='=>'int'], +'NumberFormatter::parseCurrency' => ['float|false', 'string'=>'string', '&w_currency'=>'string', '&rw_position='=>'int'], +'NumberFormatter::setAttribute' => ['bool', 'attr'=>'int', 'value'=>''], +'NumberFormatter::setPattern' => ['bool', 'pattern'=>'string'], +'NumberFormatter::setSymbol' => ['bool', 'attr'=>'int', 'symbol'=>'string'], +'NumberFormatter::setTextAttribute' => ['bool', 'attr'=>'int', 'value'=>'string'], +'numfmt_create' => ['NumberFormatter|false', 'locale'=>'string', 'style'=>'int', 'pattern='=>'string'], +'numfmt_format' => ['string|false', 'fmt'=>'NumberFormatter', 'value'=>'int|float', 'type='=>'int'], +'numfmt_format_currency' => ['string|false', 'fmt'=>'NumberFormatter', 'value'=>'float', 'currency'=>'string'], +'numfmt_get_attribute' => ['int|false', 'fmt'=>'NumberFormatter', 'attr'=>'int'], +'numfmt_get_error_code' => ['int', 'fmt'=>'NumberFormatter'], +'numfmt_get_error_message' => ['string', 'fmt'=>'NumberFormatter'], +'numfmt_get_locale' => ['string', 'fmt'=>'NumberFormatter', 'type='=>'int'], +'numfmt_get_pattern' => ['string|false', 'fmt'=>'NumberFormatter'], +'numfmt_get_symbol' => ['string|false', 'fmt'=>'NumberFormatter', 'attr'=>'int'], +'numfmt_get_text_attribute' => ['string|false', 'fmt'=>'NumberFormatter', 'attr'=>'int'], +'numfmt_parse' => ['float|int|false', 'fmt'=>'NumberFormatter', 'value'=>'string', 'type='=>'int', '&rw_position='=>'int'], +'numfmt_parse_currency' => ['float|false', 'fmt'=>'NumberFormatter', 'value'=>'string', '&w_currency'=>'string', '&rw_position='=>'int'], +'numfmt_set_attribute' => ['bool', 'fmt'=>'NumberFormatter', 'attr'=>'int', 'value'=>'int'], +'numfmt_set_pattern' => ['bool', 'fmt'=>'NumberFormatter', 'pattern'=>'string'], +'numfmt_set_symbol' => ['bool', 'fmt'=>'NumberFormatter', 'attr'=>'int', 'value'=>'string'], +'numfmt_set_text_attribute' => ['bool', 'fmt'=>'NumberFormatter', 'attr'=>'int', 'value'=>'string'], +'OAuth::__construct' => ['void', 'consumer_key'=>'string', 'consumer_secret'=>'string', 'signature_method='=>'string', 'auth_type='=>'int'], +'OAuth::__destruct' => ['void'], +'OAuth::disableDebug' => ['bool'], +'OAuth::disableRedirects' => ['bool'], +'OAuth::disableSSLChecks' => ['bool'], +'OAuth::enableDebug' => ['bool'], +'OAuth::enableRedirects' => ['bool'], +'OAuth::enableSSLChecks' => ['bool'], +'OAuth::fetch' => ['mixed', 'protected_resource_url'=>'string', 'extra_parameters='=>'array', 'http_method='=>'string', 'http_headers='=>'array'], +'OAuth::generateSignature' => ['string', 'http_method'=>'string', 'url'=>'string', 'extra_parameters='=>'mixed'], +'OAuth::getAccessToken' => ['array|false', 'access_token_url'=>'string', 'auth_session_handle='=>'string', 'verifier_token='=>'string', 'http_method='=>'string'], +'OAuth::getCAPath' => ['array'], +'OAuth::getLastResponse' => ['string'], +'OAuth::getLastResponseHeaders' => ['string|false'], +'OAuth::getLastResponseInfo' => ['array'], +'OAuth::getRequestHeader' => ['string|false', 'http_method'=>'string', 'url'=>'string', 'extra_parameters='=>'mixed'], +'OAuth::getRequestToken' => ['array|false', 'request_token_url'=>'string', 'callback_url='=>'string', 'http_method='=>'string'], +'OAuth::setAuthType' => ['bool', 'auth_type'=>'int'], +'OAuth::setCAPath' => ['mixed', 'ca_path='=>'string', 'ca_info='=>'string'], +'OAuth::setNonce' => ['mixed', 'nonce'=>'string'], +'OAuth::setRequestEngine' => ['void', 'reqengine'=>'int'], +'OAuth::setRSACertificate' => ['mixed', 'cert'=>'string'], +'OAuth::setSSLChecks' => ['bool', 'sslcheck'=>'int'], +'OAuth::setTimeout' => ['void', 'timeout'=>'int'], +'OAuth::setTimestamp' => ['mixed', 'timestamp'=>'string'], +'OAuth::setToken' => ['bool', 'token'=>'string', 'token_secret'=>'string'], +'OAuth::setVersion' => ['bool', 'version'=>'string'], +'oauth_get_sbs' => ['string', 'http_method'=>'string', 'uri'=>'string', 'request_parameters='=>'array'], +'oauth_urlencode' => ['string', 'uri'=>'string'], +'OAuthProvider::__construct' => ['void', 'params_array='=>'array'], +'OAuthProvider::addRequiredParameter' => ['bool', 'req_params'=>'string'], +'OAuthProvider::callconsumerHandler' => ['void'], +'OAuthProvider::callTimestampNonceHandler' => ['void'], +'OAuthProvider::calltokenHandler' => ['void'], +'OAuthProvider::checkOAuthRequest' => ['void', 'uri='=>'string', 'method='=>'string'], +'OAuthProvider::consumerHandler' => ['void', 'callback_function'=>'callable'], +'OAuthProvider::generateToken' => ['string', 'size'=>'int', 'strong='=>'bool'], +'OAuthProvider::is2LeggedEndpoint' => ['void', 'params_array'=>'mixed'], +'OAuthProvider::isRequestTokenEndpoint' => ['void', 'will_issue_request_token'=>'bool'], +'OAuthProvider::removeRequiredParameter' => ['bool', 'req_params'=>'string'], +'OAuthProvider::reportProblem' => ['string', 'oauthexception'=>'string', 'send_headers='=>'bool'], +'OAuthProvider::setParam' => ['bool', 'param_key'=>'string', 'param_val='=>'mixed'], +'OAuthProvider::setRequestTokenPath' => ['bool', 'path'=>'string'], +'OAuthProvider::timestampNonceHandler' => ['void', 'callback_function'=>'callable'], +'OAuthProvider::tokenHandler' => ['void', 'callback_function'=>'callable'], +'ob_clean' => ['bool'], +'ob_deflatehandler' => ['string', 'data'=>'string', 'mode'=>'int'], +'ob_end_clean' => ['bool'], +'ob_end_flush' => ['bool'], +'ob_etaghandler' => ['string', 'data'=>'string', 'mode'=>'int'], +'ob_flush' => ['bool'], +'ob_get_clean' => ['string|false'], +'ob_get_contents' => ['string|false'], +'ob_get_flush' => ['string|false'], +'ob_get_length' => ['int|false'], +'ob_get_level' => ['int'], +'ob_get_status' => ['array', 'full_status='=>'bool'], +'ob_gzhandler' => ['string|false', 'data'=>'string', 'flags'=>'int'], +'ob_iconv_handler' => ['string', 'contents'=>'string', 'status'=>'int'], +'ob_implicit_flush' => ['void', 'flag='=>'int'], +'ob_inflatehandler' => ['string', 'data'=>'string', 'mode'=>'int'], +'ob_list_handlers' => ['false|list'], +'ob_start' => ['bool', 'user_function='=>'string|array|?callable', 'chunk_size='=>'int', 'flags='=>'int'], +'ob_tidyhandler' => ['string', 'input'=>'string', 'mode='=>'int'], +'oci_bind_array_by_name' => ['bool', 'stmt'=>'resource', 'name'=>'string', '&rw_var'=>'array', 'max_table_length'=>'int', 'max_item_length='=>'int', 'type='=>'int'], +'oci_bind_by_name' => ['bool', 'stmt'=>'resource', 'name'=>'string', '&rw_var'=>'mixed', 'maxlength='=>'int', 'type='=>'int'], +'oci_cancel' => ['bool', 'stmt'=>'resource'], +'oci_client_version' => ['string'], +'oci_close' => ['bool', 'connection'=>'resource'], +'OCI_Collection::append' => ['bool', 'value'=>'mixed'], +'OCI_Collection::assign' => ['bool', 'from'=>'OCI_Collection'], +'OCI_Collection::assignElem' => ['bool', 'index'=>'int', 'value'=>'mixed'], +'OCI_Collection::free' => ['bool'], +'OCI_Collection::getElem' => ['mixed', 'index'=>'int'], +'OCI_Collection::max' => ['int|false'], +'OCI_Collection::size' => ['int|false'], +'OCI_Collection::trim' => ['bool', 'num'=>'int'], +'oci_collection_append' => ['bool', 'value'=>'string'], +'oci_collection_assign' => ['bool', 'from'=>'object'], +'oci_collection_element_assign' => ['bool', 'index'=>'int', 'value'=>'string'], +'oci_collection_element_get' => ['string', 'ndx'=>'int'], +'oci_collection_max' => ['int'], +'oci_collection_size' => ['int'], +'oci_collection_trim' => ['bool', 'num'=>'int'], +'oci_commit' => ['bool', 'connection'=>'resource'], +'oci_connect' => ['resource|false', 'user'=>'string', 'pass'=>'string', 'db='=>'string', 'charset='=>'string', 'session_mode='=>'int'], +'oci_define_by_name' => ['bool', 'stmt'=>'resource', 'name'=>'string', '&w_var'=>'mixed', 'type='=>'int'], +'oci_error' => ['array|false', 'resource='=>'resource'], +'oci_execute' => ['bool', 'stmt'=>'resource', 'mode='=>'int'], +'oci_fetch' => ['bool', 'stmt'=>'resource'], +'oci_fetch_all' => ['int|false', 'stmt'=>'resource', '&w_output'=>'array', 'skip='=>'int', 'maxrows='=>'int', 'flags='=>'int'], +'oci_fetch_array' => ['array|false', 'stmt'=>'resource', 'mode='=>'int'], +'oci_fetch_assoc' => ['array|false', 'stmt'=>'resource'], +'oci_fetch_object' => ['object|false', 'stmt'=>'resource'], +'oci_fetch_row' => ['array|false', 'stmt'=>'resource'], +'oci_field_is_null' => ['bool', 'stmt'=>'resource', 'col'=>'mixed'], +'oci_field_name' => ['string|false', 'stmt'=>'resource', 'col'=>'mixed'], +'oci_field_precision' => ['int|false', 'stmt'=>'resource', 'col'=>'mixed'], +'oci_field_scale' => ['int|false', 'stmt'=>'resource', 'col'=>'mixed'], +'oci_field_size' => ['int|false', 'stmt'=>'resource', 'col'=>'mixed'], +'oci_field_type' => ['mixed|false', 'stmt'=>'resource', 'col'=>'mixed'], +'oci_field_type_raw' => ['int|false', 'stmt'=>'resource', 'col'=>'mixed'], +'oci_free_collection' => ['bool'], +'oci_free_cursor' => ['bool', 'stmt'=>'resource'], +'oci_free_descriptor' => ['bool'], +'oci_free_statement' => ['bool', 'stmt'=>'resource'], +'oci_get_implicit' => ['bool', 'stmt'=>''], +'oci_get_implicit_resultset' => ['resource|false', 'statement'=>'resource'], +'oci_internal_debug' => ['void', 'onoff'=>'bool'], +'OCI_Lob::append' => ['bool', 'lob_from'=>'OCI_Lob'], +'OCI_Lob::close' => ['bool'], +'OCI_Lob::eof' => ['bool'], +'OCI_Lob::erase' => ['int|false', 'offset='=>'int', 'length='=>'int'], +'OCI_Lob::export' => ['bool', 'filename'=>'string', 'start='=>'int', 'length='=>'int'], +'OCI_Lob::flush' => ['bool', 'flag='=>'int'], +'OCI_Lob::free' => ['bool'], +'OCI_Lob::getbuffering' => ['bool'], +'OCI_Lob::import' => ['bool', 'filename'=>'string'], +'OCI_Lob::load' => ['string|false'], +'OCI_Lob::read' => ['string|false', 'length'=>'int'], +'OCI_Lob::rewind' => ['bool'], +'OCI_Lob::save' => ['bool', 'data'=>'string', 'offset='=>'int'], +'OCI_Lob::savefile' => ['bool', 'filename'=>''], +'OCI_Lob::seek' => ['bool', 'offset'=>'int', 'whence='=>'int'], +'OCI_Lob::setbuffering' => ['bool', 'on_off'=>'bool'], +'OCI_Lob::size' => ['int|false'], +'OCI_Lob::tell' => ['int|false'], +'OCI_Lob::truncate' => ['bool', 'length='=>'int'], +'OCI_Lob::write' => ['int|false', 'data'=>'string', 'length='=>'int'], +'OCI_Lob::writeTemporary' => ['bool', 'data'=>'string', 'lob_type='=>'int'], +'OCI_Lob::writetofile' => ['bool', 'filename'=>'', 'start'=>'', 'length'=>''], +'oci_lob_append' => ['bool', 'lob'=>'object'], +'oci_lob_close' => ['bool'], +'oci_lob_copy' => ['bool', 'lob_to'=>'OCI_Lob', 'lob_from'=>'OCI_Lob', 'length='=>'int'], +'oci_lob_eof' => ['bool'], +'oci_lob_erase' => ['int', 'offset'=>'int', 'length'=>'int'], +'oci_lob_export' => ['bool', 'filename'=>'string', 'start'=>'int', 'length'=>'int'], +'oci_lob_flush' => ['bool', 'flag'=>'int'], +'oci_lob_import' => ['bool', 'filename'=>'string'], +'oci_lob_is_equal' => ['bool', 'lob1'=>'OCI_Lob', 'lob2'=>'OCI_Lob'], +'oci_lob_load' => ['string'], +'oci_lob_read' => ['string', 'length'=>'int'], +'oci_lob_rewind' => ['bool'], +'oci_lob_save' => ['bool', 'data'=>'string', 'offset'=>'int'], +'oci_lob_seek' => ['bool', 'offset'=>'int', 'whence'=>'int'], +'oci_lob_size' => ['int'], +'oci_lob_tell' => ['int'], +'oci_lob_truncate' => ['bool', 'length'=>'int'], +'oci_lob_write' => ['int', 'string'=>'string', 'length'=>'int'], +'oci_lob_write_temporary' => ['bool', 'value'=>'string', 'lob_type'=>'int'], +'oci_new_collection' => ['OCI_Collection|false', 'connection'=>'resource', 'tdo'=>'string', 'schema='=>'string'], +'oci_new_connect' => ['resource|false', 'user'=>'string', 'pass'=>'string', 'db='=>'string', 'charset='=>'string', 'session_mode='=>'int'], +'oci_new_cursor' => ['resource|false', 'connection'=>'resource'], +'oci_new_descriptor' => ['OCI_Lob|false', 'connection'=>'resource', 'type='=>'int'], +'oci_num_fields' => ['int|false', 'stmt'=>'resource'], +'oci_num_rows' => ['int|false', 'stmt'=>'resource'], +'oci_parse' => ['resource|false', 'connection'=>'resource', 'statement'=>'string'], +'oci_password_change' => ['bool', 'connection'=>'resource', 'username'=>'string', 'old_password'=>'string', 'new_password'=>'string'], +'oci_pconnect' => ['resource|false', 'user'=>'string', 'pass'=>'string', 'db='=>'string', 'charset='=>'string', 'session_mode='=>'int'], +'oci_register_taf_callback' => ['bool', 'connection'=>'resource', 'callback='=>'callable'], +'oci_result' => ['mixed|false', 'stmt'=>'resource', 'column'=>'mixed'], +'oci_rollback' => ['bool', 'connection'=>'resource'], +'oci_server_version' => ['string|false', 'connection'=>'resource'], +'oci_set_action' => ['bool', 'connection'=>'resource', 'value'=>'string'], +'oci_set_call_timeout' => ['bool', 'connection'=>'resource', 'time_out'=>'int'], +'oci_set_client_identifier' => ['bool', 'connection'=>'resource', 'value'=>'string'], +'oci_set_client_info' => ['bool', 'connection'=>'resource', 'value'=>'string'], +'oci_set_db_operation' => ['bool', 'connection'=>'resource', 'value'=>'string'], +'oci_set_edition' => ['bool', 'value'=>'string'], +'oci_set_module_name' => ['bool', 'connection'=>'resource', 'value'=>'string'], +'oci_set_prefetch' => ['bool', 'stmt'=>'resource', 'prefetch_rows'=>'int'], +'oci_statement_type' => ['string|false', 'stmt'=>'resource'], +'oci_unregister_taf_callback' => ['bool', 'connection'=>'resource'], +'ocifetchinto' => ['int|bool', 'stmt'=>'resource', '&w_output'=>'array', 'mode='=>'int'], +'ocigetbufferinglob' => ['bool'], +'ocisetbufferinglob' => ['bool', 'flag'=>'bool'], +'octdec' => ['int|float', 'octal_number'=>'string'], +'odbc_autocommit' => ['mixed', 'connection_id'=>'resource', 'onoff='=>'bool'], +'odbc_binmode' => ['bool', 'result_id'=>'resource', 'mode'=>'int'], +'odbc_close' => ['void', 'connection_id'=>'resource'], +'odbc_close_all' => ['void'], +'odbc_columnprivileges' => ['resource|false', 'connection_id'=>'resource', 'catalog'=>'string', 'schema'=>'string', 'table'=>'string', 'column'=>'string'], +'odbc_columns' => ['resource|false', 'connection_id'=>'resource', 'qualifier='=>'string', 'owner='=>'string', 'table_name='=>'string', 'column_name='=>'string'], +'odbc_commit' => ['bool', 'connection_id'=>'resource'], +'odbc_connect' => ['resource|false', 'dsn'=>'string', 'user'=>'string', 'password'=>'string', 'cursor_option='=>'int'], +'odbc_cursor' => ['string', 'result_id'=>'resource'], +'odbc_data_source' => ['array|false', 'connection_id'=>'resource', 'fetch_type'=>'int'], +'odbc_do' => ['resource', 'connection_id'=>'resource', 'query'=>'string', 'flags='=>'int'], +'odbc_error' => ['string', 'connection_id='=>'resource'], +'odbc_errormsg' => ['string', 'connection_id='=>'resource'], +'odbc_exec' => ['resource', 'connection_id'=>'resource', 'query'=>'string', 'flags='=>'int'], +'odbc_execute' => ['bool', 'result_id'=>'resource', 'parameters_array='=>'array'], +'odbc_fetch_array' => ['array|false', 'result'=>'resource', 'rownumber='=>'int'], +'odbc_fetch_into' => ['int', 'result_id'=>'resource', '&w_result_array'=>'array', 'rownumber='=>'int'], +'odbc_fetch_object' => ['object|false', 'result'=>'resource', 'rownumber='=>'int'], +'odbc_fetch_row' => ['bool', 'result_id'=>'resource', 'row_number='=>'int'], +'odbc_field_len' => ['int|false', 'result_id'=>'resource', 'field_number'=>'int'], +'odbc_field_name' => ['string|false', 'result_id'=>'resource', 'field_number'=>'int'], +'odbc_field_num' => ['int|false', 'result_id'=>'resource', 'field_name'=>'string'], +'odbc_field_precision' => ['int', 'result_id'=>'resource', 'field_number'=>'int'], +'odbc_field_scale' => ['int|false', 'result_id'=>'resource', 'field_number'=>'int'], +'odbc_field_type' => ['string|false', 'result_id'=>'resource', 'field_number'=>'int'], +'odbc_foreignkeys' => ['resource|false', 'connection_id'=>'resource', 'pk_qualifier'=>'string', 'pk_owner'=>'string', 'pk_table'=>'string', 'fk_qualifier'=>'string', 'fk_owner'=>'string', 'fk_table'=>'string'], +'odbc_free_result' => ['bool', 'result_id'=>'resource'], +'odbc_gettypeinfo' => ['resource', 'connection_id'=>'resource', 'data_type='=>'int'], +'odbc_longreadlen' => ['bool', 'result_id'=>'resource', 'length'=>'int'], +'odbc_next_result' => ['bool', 'result_id'=>'resource'], +'odbc_num_fields' => ['int', 'result_id'=>'resource'], +'odbc_num_rows' => ['int', 'result_id'=>'resource'], +'odbc_pconnect' => ['resource|false', 'dsn'=>'string', 'user'=>'string', 'password'=>'string', 'cursor_option='=>'int'], +'odbc_prepare' => ['resource|false', 'connection_id'=>'resource', 'query'=>'string'], +'odbc_primarykeys' => ['resource|false', 'connection_id'=>'resource', 'qualifier'=>'string', 'owner'=>'string', 'table'=>'string'], +'odbc_procedurecolumns' => ['resource|false', 'connection_id'=>'resource', 'qualifier'=>'string', 'owner'=>'string', 'proc'=>'string', 'column'=>'string'], +'odbc_procedures' => ['resource|false', 'connection_id'=>'resource', 'qualifier'=>'string', 'owner'=>'string', 'name'=>'string'], +'odbc_result' => ['mixed|false', 'result_id'=>'resource', 'field'=>'mixed'], +'odbc_result_all' => ['int|false', 'result_id'=>'resource', 'format='=>'string'], +'odbc_rollback' => ['bool', 'connection_id'=>'resource'], +'odbc_setoption' => ['bool', 'result_id'=>'resource', 'which'=>'int', 'option'=>'int', 'value'=>'int'], +'odbc_specialcolumns' => ['resource|false', 'connection_id'=>'resource', 'type'=>'int', 'qualifier'=>'string', 'owner'=>'string', 'table'=>'string', 'scope'=>'int', 'nullable'=>'int'], +'odbc_statistics' => ['resource|false', 'connection_id'=>'resource', 'qualifier'=>'string', 'owner'=>'string', 'name'=>'string', 'unique'=>'int', 'accuracy'=>'int'], +'odbc_tableprivileges' => ['resource|false', 'connection_id'=>'resource', 'qualifier'=>'string', 'owner'=>'string', 'name'=>'string'], +'odbc_tables' => ['resource|false', 'connection_id'=>'resource', 'qualifier='=>'string', 'owner='=>'string', 'name='=>'string', 'table_types='=>'string'], +'opcache_compile_file' => ['bool', 'file'=>'string'], +'opcache_get_configuration' => ['array'], +'opcache_get_status' => ['array|false', 'get_scripts='=>'bool'], +'opcache_invalidate' => ['bool', 'script'=>'string', 'force='=>'bool'], +'opcache_is_script_cached' => ['bool', 'script'=>'string'], +'opcache_reset' => ['bool'], +'openal_buffer_create' => ['resource'], +'openal_buffer_data' => ['bool', 'buffer'=>'resource', 'format'=>'int', 'data'=>'string', 'freq'=>'int'], +'openal_buffer_destroy' => ['bool', 'buffer'=>'resource'], +'openal_buffer_get' => ['int', 'buffer'=>'resource', 'property'=>'int'], +'openal_buffer_loadwav' => ['bool', 'buffer'=>'resource', 'wavfile'=>'string'], +'openal_context_create' => ['resource', 'device'=>'resource'], +'openal_context_current' => ['bool', 'context'=>'resource'], +'openal_context_destroy' => ['bool', 'context'=>'resource'], +'openal_context_process' => ['bool', 'context'=>'resource'], +'openal_context_suspend' => ['bool', 'context'=>'resource'], +'openal_device_close' => ['bool', 'device'=>'resource'], +'openal_device_open' => ['resource|false', 'device_desc='=>'string'], +'openal_listener_get' => ['mixed', 'property'=>'int'], +'openal_listener_set' => ['bool', 'property'=>'int', 'setting'=>'mixed'], +'openal_source_create' => ['resource'], +'openal_source_destroy' => ['bool', 'source'=>'resource'], +'openal_source_get' => ['mixed', 'source'=>'resource', 'property'=>'int'], +'openal_source_pause' => ['bool', 'source'=>'resource'], +'openal_source_play' => ['bool', 'source'=>'resource'], +'openal_source_rewind' => ['bool', 'source'=>'resource'], +'openal_source_set' => ['bool', 'source'=>'resource', 'property'=>'int', 'setting'=>'mixed'], +'openal_source_stop' => ['bool', 'source'=>'resource'], +'openal_stream' => ['resource', 'source'=>'resource', 'format'=>'int', 'rate'=>'int'], +'opendir' => ['resource|false', 'path'=>'string', 'context='=>'resource'], +'openlog' => ['bool', 'ident'=>'string', 'option'=>'int', 'facility'=>'int'], +'openssl_cipher_iv_length' => ['int|false', 'method'=>'string'], +'openssl_csr_export' => ['bool', 'csr'=>'string|resource', '&w_out'=>'string', 'notext='=>'bool'], +'openssl_csr_export_to_file' => ['bool', 'csr'=>'string|resource', 'outfilename'=>'string', 'notext='=>'bool'], +'openssl_csr_get_public_key' => ['resource|false', 'csr'=>'string|resource', 'use_shortnames='=>'bool'], +'openssl_csr_get_subject' => ['array|false', 'csr'=>'string|resource', 'use_shortnames='=>'bool'], +'openssl_csr_new' => ['resource|false', 'dn'=>'array', '&w_privkey'=>'resource', 'configargs='=>'array', 'extraattribs='=>'array'], +'openssl_csr_sign' => ['resource|false', 'csr'=>'string|resource', 'x509'=>'string|resource|null', 'priv_key'=>'string|resource|array', 'days'=>'int', 'config_args='=>'array', 'serial='=>'int'], +'openssl_decrypt' => ['string|false', 'data'=>'string', 'method'=>'string', 'key'=>'string', 'options='=>'int', 'iv='=>'string', 'tag='=>'string', 'aad='=>'string'], +'openssl_dh_compute_key' => ['string|false', 'pub_key'=>'string', 'dh_key'=>'resource'], +'openssl_digest' => ['string|false', 'data'=>'string', 'method'=>'string', 'raw_output='=>'bool'], +'openssl_encrypt' => ['string|false', 'data'=>'string', 'method'=>'string', 'key'=>'string', 'options='=>'int', 'iv='=>'string', '&w_tag='=>'string', 'aad='=>'string', 'tag_length='=>'int'], +'openssl_error_string' => ['string|false'], +'openssl_free_key' => ['void', 'key_identifier'=>'resource'], +'openssl_get_cert_locations' => ['array'], +'openssl_get_cipher_methods' => ['array', 'aliases='=>'bool'], +'openssl_get_curve_names' => ['list'], +'openssl_get_md_methods' => ['array', 'aliases='=>'bool'], +'openssl_get_privatekey' => ['resource|false', 'key'=>'string', 'passphrase='=>'string'], +'openssl_get_publickey' => ['resource|false', 'cert'=>'resource|string'], +'openssl_open' => ['bool', 'sealed_data'=>'string', '&w_open_data'=>'string', 'env_key'=>'string', 'priv_key_id'=>'string|array|resource', 'method='=>'string', 'iv='=>'string'], +'openssl_pbkdf2' => ['string|false', 'password'=>'string', 'salt'=>'string', 'key_length'=>'int', 'iterations'=>'int', 'digest_algorithm='=>'string'], +'openssl_pkcs12_export' => ['bool', 'x509'=>'string|resource', '&w_out'=>'string', 'priv_key'=>'string|array|resource', 'pass'=>'string', 'args='=>'array'], +'openssl_pkcs12_export_to_file' => ['bool', 'x509'=>'string|resource', 'filename'=>'string', 'priv_key'=>'string|array|resource', 'pass'=>'string', 'args='=>'array'], +'openssl_pkcs12_read' => ['bool', 'pkcs12'=>'string', '&w_certs'=>'array', 'pass'=>'string'], +'openssl_pkcs7_decrypt' => ['bool', 'infilename'=>'string', 'outfilename'=>'string', 'recipcert'=>'string|resource', 'recipkey='=>'string|resource|array'], +'openssl_pkcs7_encrypt' => ['bool', 'infile'=>'string', 'outfile'=>'string', 'recipcerts'=>'string|resource|array', 'headers'=>'array', 'flags='=>'int', 'cipherid='=>'int'], +'openssl_pkcs7_read' => ['bool', 'infilename'=>'string', '&w_certs'=>'array'], +'openssl_pkcs7_sign' => ['bool', 'infile'=>'string', 'outfile'=>'string', 'signcert'=>'string|resource', 'privkey'=>'string|resource|array', 'headers'=>'array', 'flags='=>'int', 'extracerts='=>'string'], +'openssl_pkcs7_verify' => ['bool|int', 'filename'=>'string', 'flags'=>'int', 'outfilename='=>'string', 'cainfo='=>'array', 'extracerts='=>'string', 'content='=>'string', 'p7bfilename='=>'string'], +'openssl_pkey_derive' => ['string|false', 'peer_pub_key'=>'mixed', 'priv_key'=>'mixed', 'keylen='=>'?int'], +'openssl_pkey_export' => ['bool', 'key'=>'resource', '&w_out'=>'string', 'passphrase='=>'string|null', 'configargs='=>'array'], +'openssl_pkey_export_to_file' => ['bool', 'key'=>'resource|string|array', 'outfilename'=>'string', 'passphrase='=>'string|null', 'configargs='=>'array'], +'openssl_pkey_free' => ['void', 'key'=>'resource'], +'openssl_pkey_get_details' => ['array|false', 'key'=>'resource'], +'openssl_pkey_get_private' => ['resource|false', 'key'=>'string', 'passphrase='=>'string'], +'openssl_pkey_get_public' => ['resource|false', 'certificate'=>'resource|string'], +'openssl_pkey_new' => ['resource|false', 'configargs='=>'array'], +'openssl_private_decrypt' => ['bool', 'data'=>'string', '&w_decrypted'=>'string', 'key'=>'string|resource|array', 'padding='=>'int'], +'openssl_private_encrypt' => ['bool', 'data'=>'string', '&w_crypted'=>'string', 'key'=>'string|resource|array', 'padding='=>'int'], +'openssl_public_decrypt' => ['bool', 'data'=>'string', '&w_decrypted'=>'string', 'key'=>'string|resource', 'padding='=>'int'], +'openssl_public_encrypt' => ['bool', 'data'=>'string', '&w_crypted'=>'string', 'key'=>'string|resource', 'padding='=>'int'], +'openssl_random_pseudo_bytes' => ['string|false', 'length'=>'int', '&w_crypto_strong='=>'bool'], +'openssl_seal' => ['int|false', 'data'=>'string', '&w_sealed_data'=>'string', '&w_env_keys'=>'array', 'pub_key_ids'=>'array', 'method='=>'string', '&rw_iv='=>'string'], +'openssl_sign' => ['bool', 'data'=>'string', '&w_signature'=>'string', 'priv_key_id'=>'resource|string', 'signature_alg='=>'int|string'], +'openssl_spki_export' => ['?string', 'spkac'=>'string'], +'openssl_spki_export_challenge' => ['?string', 'spkac'=>'string'], +'openssl_spki_new' => ['?string', 'privkey'=>'resource', 'challenge'=>'string', 'algorithm='=>'int'], +'openssl_spki_verify' => ['bool', 'spkac'=>'string'], +'openssl_verify' => ['-1|0|1', 'data'=>'string', 'signature'=>'string', 'pub_key_id'=>'resource|string', 'signature_alg='=>'int|string'], +'openssl_x509_check_private_key' => ['bool', 'cert'=>'string|resource', 'key'=>'string|resource|array'], +'openssl_x509_checkpurpose' => ['bool|int', 'x509cert'=>'string|resource', 'purpose'=>'int', 'cainfo='=>'array', 'untrustedfile='=>'string'], +'openssl_x509_export' => ['bool', 'x509'=>'string|resource', '&w_output'=>'string', 'notext='=>'bool'], +'openssl_x509_export_to_file' => ['bool', 'x509'=>'string|resource', 'outfilename'=>'string', 'notext='=>'bool'], +'openssl_x509_fingerprint' => ['string|false', 'x509'=>'string|resource', 'hash_algorithm='=>'string', 'raw_output='=>'bool'], +'openssl_x509_free' => ['void', 'x509'=>'resource'], +'openssl_x509_parse' => ['array|false', 'x509cert'=>'string|resource', 'shortnames='=>'bool'], +'openssl_x509_read' => ['resource|false', 'x509certdata'=>'string|resource'], +'ord' => ['int', 'character'=>'string'], +'OuterIterator::current' => ['mixed'], +'OuterIterator::getInnerIterator' => ['Iterator'], +'OuterIterator::key' => ['int|string'], +'OuterIterator::next' => ['void'], +'OuterIterator::rewind' => ['void'], +'OuterIterator::valid' => ['bool'], +'OutOfBoundsException::__clone' => ['void'], +'OutOfBoundsException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?OutOfBoundsException'], +'OutOfBoundsException::__toString' => ['string'], +'OutOfBoundsException::getCode' => ['int'], +'OutOfBoundsException::getFile' => ['string'], +'OutOfBoundsException::getLine' => ['int'], +'OutOfBoundsException::getMessage' => ['string'], +'OutOfBoundsException::getPrevious' => ['Throwable|OutOfBoundsException|null'], +'OutOfBoundsException::getTrace' => ['list>'], +'OutOfBoundsException::getTraceAsString' => ['string'], +'OutOfRangeException::__clone' => ['void'], +'OutOfRangeException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?OutOfRangeException'], +'OutOfRangeException::__toString' => ['string'], +'OutOfRangeException::getCode' => ['int'], +'OutOfRangeException::getFile' => ['string'], +'OutOfRangeException::getLine' => ['int'], +'OutOfRangeException::getMessage' => ['string'], +'OutOfRangeException::getPrevious' => ['Throwable|OutOfRangeException|null'], +'OutOfRangeException::getTrace' => ['list>'], +'OutOfRangeException::getTraceAsString' => ['string'], +'output_add_rewrite_var' => ['bool', 'name'=>'string', 'value'=>'string'], +'output_cache_disable' => ['void'], +'output_cache_disable_compression' => ['void'], +'output_cache_exists' => ['bool', 'key'=>'string', 'lifetime'=>'int'], +'output_cache_fetch' => ['string', 'key'=>'string', 'function'=>'', 'lifetime'=>'int'], +'output_cache_get' => ['mixed|false', 'key'=>'string', 'lifetime'=>'int'], +'output_cache_output' => ['string', 'key'=>'string', 'function'=>'', 'lifetime'=>'int'], +'output_cache_put' => ['bool', 'key'=>'string', 'data'=>'mixed'], +'output_cache_remove' => ['bool', 'filename'=>''], +'output_cache_remove_key' => ['bool', 'key'=>'string'], +'output_cache_remove_url' => ['bool', 'url'=>'string'], +'output_cache_stop' => ['void'], +'output_reset_rewrite_vars' => ['bool'], +'outputformatObj::getOption' => ['string', 'property_name'=>'string'], +'outputformatObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'outputformatObj::setOption' => ['void', 'property_name'=>'string', 'new_value'=>'string'], +'outputformatObj::validate' => ['int'], +'OverflowException::__clone' => ['void'], +'OverflowException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?OverflowException'], +'OverflowException::__toString' => ['string'], +'OverflowException::getCode' => ['int'], +'OverflowException::getFile' => ['string'], +'OverflowException::getLine' => ['int'], +'OverflowException::getMessage' => ['string'], +'OverflowException::getPrevious' => ['Throwable|OverflowException|null'], +'OverflowException::getTrace' => ['list>'], +'OverflowException::getTraceAsString' => ['string'], +'overload' => ['', 'class_name'=>'string'], +'override_function' => ['bool', 'function_name'=>'string', 'function_args'=>'string', 'function_code'=>'string'], +'OwsrequestObj::__construct' => ['void'], +'OwsrequestObj::addParameter' => ['int', 'name'=>'string', 'value'=>'string'], +'OwsrequestObj::getName' => ['string', 'index'=>'int'], +'OwsrequestObj::getValue' => ['string', 'index'=>'int'], +'OwsrequestObj::getValueByName' => ['string', 'name'=>'string'], +'OwsrequestObj::loadParams' => ['int'], +'OwsrequestObj::setParameter' => ['int', 'name'=>'string', 'value'=>'string'], +'pack' => ['string|false', 'format'=>'string', '...args='=>'mixed'], +'parallel\Future::done' => ['bool'], +'parallel\Future::select' => ['mixed', '&resolving'=>'parallel\Future[]', '&w_resolved'=>'parallel\Future[]', '&w_errored'=>'parallel\Future[]', '&w_timedout='=>'parallel\Future[]', 'timeout='=>'int'], +'parallel\Future::value' => ['mixed', 'timeout='=>'int'], +'parallel\Runtime::__construct' => ['void', 'arg'=>'string|array'], +'parallel\Runtime::__construct\'1' => ['void', 'bootstrap'=>'string', 'configuration'=>'array'], +'parallel\Runtime::close' => ['void'], +'parallel\Runtime::kill' => ['void'], +'parallel\Runtime::run' => ['?parallel\Future', 'closure'=>'Closure', 'args='=>'array'], +'ParentIterator::__construct' => ['void', 'iterator'=>'RecursiveIterator'], +'ParentIterator::accept' => ['bool'], +'ParentIterator::getChildren' => ['ParentIterator'], +'ParentIterator::hasChildren' => ['bool'], +'ParentIterator::next' => ['void'], +'ParentIterator::rewind' => ['void'], +'ParentIterator::valid' => [''], +'Parle\Lexer::advance' => ['void'], +'Parle\Lexer::build' => ['void'], +'Parle\Lexer::callout' => ['void', 'id'=>'int', 'callback'=>'callable'], +'Parle\Lexer::consume' => ['void', 'data'=>'string'], +'Parle\Lexer::dump' => ['void'], +'Parle\Lexer::getToken' => ['Parle\Token'], +'Parle\Lexer::insertMacro' => ['void', 'name'=>'string', 'regex'=>'string'], +'Parle\Lexer::push' => ['void', 'regex'=>'string', 'id'=>'int'], +'Parle\Lexer::reset' => ['void', 'pos'=>'int'], +'Parle\Parser::advance' => ['void'], +'Parle\Parser::build' => ['void'], +'Parle\Parser::consume' => ['void', 'data'=>'string', 'lexer'=>'Parle\Lexer'], +'Parle\Parser::dump' => ['void'], +'Parle\Parser::errorInfo' => ['Parle\ErrorInfo'], +'Parle\Parser::left' => ['void', 'token'=>'string'], +'Parle\Parser::nonassoc' => ['void', 'token'=>'string'], +'Parle\Parser::precedence' => ['void', 'token'=>'string'], +'Parle\Parser::push' => ['int', 'name'=>'string', 'rule'=>'string'], +'Parle\Parser::reset' => ['void', 'tokenId'=>'int'], +'Parle\Parser::right' => ['void', 'token'=>'string'], +'Parle\Parser::sigil' => ['string', 'idx'=>'array'], +'Parle\Parser::token' => ['void', 'token'=>'string'], +'Parle\Parser::tokenId' => ['int', 'token'=>'string'], +'Parle\Parser::trace' => ['string'], +'Parle\Parser::validate' => ['bool', 'data'=>'string', 'lexer'=>'Parle\Lexer'], +'Parle\RLexer::advance' => ['void'], +'Parle\RLexer::build' => ['void'], +'Parle\RLexer::callout' => ['void', 'id'=>'int', 'callback'=>'callable'], +'Parle\RLexer::consume' => ['void', 'data'=>'string'], +'Parle\RLexer::dump' => ['void'], +'Parle\RLexer::getToken' => ['Parle\Token'], +'parle\rlexer::insertMacro' => ['void', 'name'=>'string', 'regex'=>'string'], +'Parle\RLexer::push' => ['void', 'state'=>'string', 'regex'=>'string', 'newState'=>'string'], +'Parle\RLexer::pushState' => ['int', 'state'=>'string'], +'Parle\RLexer::reset' => ['void', 'pos'=>'int'], +'Parle\RParser::advance' => ['void'], +'Parle\RParser::build' => ['void'], +'Parle\RParser::consume' => ['void', 'data'=>'string', 'lexer'=>'Parle\Lexer'], +'Parle\RParser::dump' => ['void'], +'Parle\RParser::errorInfo' => ['Parle\ErrorInfo'], +'Parle\RParser::left' => ['void', 'token'=>'string'], +'Parle\RParser::nonassoc' => ['void', 'token'=>'string'], +'Parle\RParser::precedence' => ['void', 'token'=>'string'], +'Parle\RParser::push' => ['int', 'name'=>'string', 'rule'=>'string'], +'Parle\RParser::reset' => ['void', 'tokenId'=>'int'], +'Parle\RParser::right' => ['void', 'token'=>'string'], +'Parle\RParser::sigil' => ['string', 'idx'=>'array'], +'Parle\RParser::token' => ['void', 'token'=>'string'], +'Parle\RParser::tokenId' => ['int', 'token'=>'string'], +'Parle\RParser::trace' => ['string'], +'Parle\RParser::validate' => ['bool', 'data'=>'string', 'lexer'=>'Parle\Lexer'], +'Parle\Stack::pop' => ['void'], +'Parle\Stack::push' => ['void', 'item'=>'mixed'], +'parse_ini_file' => ['array|false', 'filename'=>'string', 'process_sections='=>'bool', 'scanner_mode='=>'int'], +'parse_ini_string' => ['array|false', 'ini_string'=>'string', 'process_sections='=>'bool', 'scanner_mode='=>'int'], +'parse_str' => ['void', 'encoded_string'=>'string', '&w_result='=>'array'], +'parse_url' => ['mixed|false', 'url'=>'string', 'url_component='=>'int'], +'ParseError::__clone' => ['void'], +'ParseError::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?ParseError'], +'ParseError::__toString' => ['string'], +'ParseError::getCode' => ['int'], +'ParseError::getFile' => ['string'], +'ParseError::getLine' => ['int'], +'ParseError::getMessage' => ['string'], +'ParseError::getPrevious' => ['Throwable|ParseError|null'], +'ParseError::getTrace' => ['list>'], +'ParseError::getTraceAsString' => ['string'], +'parsekit_compile_file' => ['array', 'filename'=>'string', 'errors='=>'array', 'options='=>'int'], +'parsekit_compile_string' => ['array', 'phpcode'=>'string', 'errors='=>'array', 'options='=>'int'], +'parsekit_func_arginfo' => ['array', 'function'=>'mixed'], +'passthru' => ['void', 'command'=>'string', '&w_return_value='=>'int'], +'password_get_info' => ['array', 'hash'=>'string'], +'password_hash' => ['string|null', 'password'=>'string', 'algo'=>'int|string|null', 'options='=>'array'], +'password_make_salt' => ['bool', 'password'=>'string', 'hash'=>'string'], +'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'int|string|null', 'options='=>'array'], +'password_verify' => ['bool', 'password'=>'string', 'hash'=>'string'], +'pathinfo' => ['array|string', 'path'=>'string', 'options='=>'int'], +'pclose' => ['int', 'fp'=>'resource'], +'pcnlt_sigwaitinfo' => ['int', 'set'=>'array', '&w_siginfo'=>'array'], +'pcntl_alarm' => ['int', 'seconds'=>'int'], +'pcntl_async_signals' => ['bool', 'on='=>'bool'], +'pcntl_errno' => ['int'], +'pcntl_exec' => ['null|false', 'path'=>'string', 'args='=>'array', 'envs='=>'array'], +'pcntl_fork' => ['int'], +'pcntl_get_last_error' => ['int'], +'pcntl_getpriority' => ['int', 'pid='=>'int', 'process_identifier='=>'int'], +'pcntl_setpriority' => ['bool', 'priority'=>'int', 'pid='=>'int', 'process_identifier='=>'int'], +'pcntl_signal' => ['bool', 'signo'=>'int', 'handle'=>'callable():void|callable(int):void|callable(int,array):void|int', 'restart_syscalls='=>'bool'], +'pcntl_signal_dispatch' => ['bool'], +'pcntl_signal_get_handler' => ['int|string', 'signo'=>'int'], +'pcntl_sigprocmask' => ['bool', 'how'=>'int', 'set'=>'array', '&w_oldset='=>'array'], +'pcntl_sigtimedwait' => ['int', 'set'=>'array', '&w_siginfo='=>'array', 'seconds='=>'int', 'nanoseconds='=>'int'], +'pcntl_sigwaitinfo' => ['int', 'set'=>'array', '&w_siginfo='=>'array'], +'pcntl_strerror' => ['string|false', 'errno'=>'int'], +'pcntl_wait' => ['int', '&w_status'=>'int', 'options='=>'int', '&w_rusage='=>'array'], +'pcntl_waitpid' => ['int', 'pid'=>'int', '&w_status'=>'int', 'options='=>'int', '&w_rusage='=>'array'], +'pcntl_wexitstatus' => ['int', 'status'=>'int'], +'pcntl_wifcontinued' => ['bool', 'status'=>'int'], +'pcntl_wifexited' => ['bool', 'status'=>'int'], +'pcntl_wifsignaled' => ['bool', 'status'=>'int'], +'pcntl_wifstopped' => ['bool', 'status'=>'int'], +'pcntl_wstopsig' => ['int', 'status'=>'int'], +'pcntl_wtermsig' => ['int', 'status'=>'int'], +'PDF_activate_item' => ['bool', 'pdfdoc'=>'resource', 'id'=>'int'], +'PDF_add_launchlink' => ['bool', 'pdfdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'filename'=>'string'], +'PDF_add_locallink' => ['bool', 'pdfdoc'=>'resource', 'lowerleftx'=>'float', 'lowerlefty'=>'float', 'upperrightx'=>'float', 'upperrighty'=>'float', 'page'=>'int', 'dest'=>'string'], +'PDF_add_nameddest' => ['bool', 'pdfdoc'=>'resource', 'name'=>'string', 'optlist'=>'string'], +'PDF_add_note' => ['bool', 'pdfdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'contents'=>'string', 'title'=>'string', 'icon'=>'string', 'open'=>'int'], +'PDF_add_pdflink' => ['bool', 'pdfdoc'=>'resource', 'bottom_left_x'=>'float', 'bottom_left_y'=>'float', 'up_right_x'=>'float', 'up_right_y'=>'float', 'filename'=>'string', 'page'=>'int', 'dest'=>'string'], +'PDF_add_table_cell' => ['int', 'pdfdoc'=>'resource', 'table'=>'int', 'column'=>'int', 'row'=>'int', 'text'=>'string', 'optlist'=>'string'], +'PDF_add_textflow' => ['int', 'pdfdoc'=>'resource', 'textflow'=>'int', 'text'=>'string', 'optlist'=>'string'], +'PDF_add_thumbnail' => ['bool', 'pdfdoc'=>'resource', 'image'=>'int'], +'PDF_add_weblink' => ['bool', 'pdfdoc'=>'resource', 'lowerleftx'=>'float', 'lowerlefty'=>'float', 'upperrightx'=>'float', 'upperrighty'=>'float', 'url'=>'string'], +'PDF_arc' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float', 'r'=>'float', 'alpha'=>'float', 'beta'=>'float'], +'PDF_arcn' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float', 'r'=>'float', 'alpha'=>'float', 'beta'=>'float'], +'PDF_attach_file' => ['bool', 'pdfdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'filename'=>'string', 'description'=>'string', 'author'=>'string', 'mimetype'=>'string', 'icon'=>'string'], +'PDF_begin_document' => ['int', 'pdfdoc'=>'resource', 'filename'=>'string', 'optlist'=>'string'], +'PDF_begin_font' => ['bool', 'pdfdoc'=>'resource', 'filename'=>'string', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'e'=>'float', 'f'=>'float', 'optlist'=>'string'], +'PDF_begin_glyph' => ['bool', 'pdfdoc'=>'resource', 'glyphname'=>'string', 'wx'=>'float', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float'], +'PDF_begin_item' => ['int', 'pdfdoc'=>'resource', 'tag'=>'string', 'optlist'=>'string'], +'PDF_begin_layer' => ['bool', 'pdfdoc'=>'resource', 'layer'=>'int'], +'PDF_begin_page' => ['bool', 'pdfdoc'=>'resource', 'width'=>'float', 'height'=>'float'], +'PDF_begin_page_ext' => ['bool', 'pdfdoc'=>'resource', 'width'=>'float', 'height'=>'float', 'optlist'=>'string'], +'PDF_begin_pattern' => ['int', 'pdfdoc'=>'resource', 'width'=>'float', 'height'=>'float', 'xstep'=>'float', 'ystep'=>'float', 'painttype'=>'int'], +'PDF_begin_template' => ['int', 'pdfdoc'=>'resource', 'width'=>'float', 'height'=>'float'], +'PDF_begin_template_ext' => ['int', 'pdfdoc'=>'resource', 'width'=>'float', 'height'=>'float', 'optlist'=>'string'], +'PDF_circle' => ['bool', 'pdfdoc'=>'resource', 'x'=>'float', 'y'=>'float', 'r'=>'float'], +'PDF_clip' => ['bool', 'p'=>'resource'], +'PDF_close' => ['bool', 'p'=>'resource'], +'PDF_close_image' => ['bool', 'p'=>'resource', 'image'=>'int'], +'PDF_close_pdi' => ['bool', 'p'=>'resource', 'doc'=>'int'], +'PDF_close_pdi_page' => ['bool', 'p'=>'resource', 'page'=>'int'], +'PDF_closepath' => ['bool', 'p'=>'resource'], +'PDF_closepath_fill_stroke' => ['bool', 'p'=>'resource'], +'PDF_closepath_stroke' => ['bool', 'p'=>'resource'], +'PDF_concat' => ['bool', 'p'=>'resource', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'e'=>'float', 'f'=>'float'], +'PDF_continue_text' => ['bool', 'p'=>'resource', 'text'=>'string'], +'PDF_create_3dview' => ['int', 'pdfdoc'=>'resource', 'username'=>'string', 'optlist'=>'string'], +'PDF_create_action' => ['int', 'pdfdoc'=>'resource', 'type'=>'string', 'optlist'=>'string'], +'PDF_create_annotation' => ['bool', 'pdfdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'type'=>'string', 'optlist'=>'string'], +'PDF_create_bookmark' => ['int', 'pdfdoc'=>'resource', 'text'=>'string', 'optlist'=>'string'], +'PDF_create_field' => ['bool', 'pdfdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'name'=>'string', 'type'=>'string', 'optlist'=>'string'], +'PDF_create_fieldgroup' => ['bool', 'pdfdoc'=>'resource', 'name'=>'string', 'optlist'=>'string'], +'PDF_create_gstate' => ['int', 'pdfdoc'=>'resource', 'optlist'=>'string'], +'PDF_create_pvf' => ['bool', 'pdfdoc'=>'resource', 'filename'=>'string', 'data'=>'string', 'optlist'=>'string'], +'PDF_create_textflow' => ['int', 'pdfdoc'=>'resource', 'text'=>'string', 'optlist'=>'string'], +'PDF_curveto' => ['bool', 'p'=>'resource', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float'], +'PDF_define_layer' => ['int', 'pdfdoc'=>'resource', 'name'=>'string', 'optlist'=>'string'], +'PDF_delete' => ['bool', 'pdfdoc'=>'resource'], +'PDF_delete_pvf' => ['int', 'pdfdoc'=>'resource', 'filename'=>'string'], +'PDF_delete_table' => ['bool', 'pdfdoc'=>'resource', 'table'=>'int', 'optlist'=>'string'], +'PDF_delete_textflow' => ['bool', 'pdfdoc'=>'resource', 'textflow'=>'int'], +'PDF_encoding_set_char' => ['bool', 'pdfdoc'=>'resource', 'encoding'=>'string', 'slot'=>'int', 'glyphname'=>'string', 'uv'=>'int'], +'PDF_end_document' => ['bool', 'pdfdoc'=>'resource', 'optlist'=>'string'], +'PDF_end_font' => ['bool', 'pdfdoc'=>'resource'], +'PDF_end_glyph' => ['bool', 'pdfdoc'=>'resource'], +'PDF_end_item' => ['bool', 'pdfdoc'=>'resource', 'id'=>'int'], +'PDF_end_layer' => ['bool', 'pdfdoc'=>'resource'], +'PDF_end_page' => ['bool', 'p'=>'resource'], +'PDF_end_page_ext' => ['bool', 'pdfdoc'=>'resource', 'optlist'=>'string'], +'PDF_end_pattern' => ['bool', 'p'=>'resource'], +'PDF_end_template' => ['bool', 'p'=>'resource'], +'PDF_endpath' => ['bool', 'p'=>'resource'], +'PDF_fill' => ['bool', 'p'=>'resource'], +'PDF_fill_imageblock' => ['int', 'pdfdoc'=>'resource', 'page'=>'int', 'blockname'=>'string', 'image'=>'int', 'optlist'=>'string'], +'PDF_fill_pdfblock' => ['int', 'pdfdoc'=>'resource', 'page'=>'int', 'blockname'=>'string', 'contents'=>'int', 'optlist'=>'string'], +'PDF_fill_stroke' => ['bool', 'p'=>'resource'], +'PDF_fill_textblock' => ['int', 'pdfdoc'=>'resource', 'page'=>'int', 'blockname'=>'string', 'text'=>'string', 'optlist'=>'string'], +'PDF_findfont' => ['int', 'p'=>'resource', 'fontname'=>'string', 'encoding'=>'string', 'embed'=>'int'], +'PDF_fit_image' => ['bool', 'pdfdoc'=>'resource', 'image'=>'int', 'x'=>'float', 'y'=>'float', 'optlist'=>'string'], +'PDF_fit_pdi_page' => ['bool', 'pdfdoc'=>'resource', 'page'=>'int', 'x'=>'float', 'y'=>'float', 'optlist'=>'string'], +'PDF_fit_table' => ['string', 'pdfdoc'=>'resource', 'table'=>'int', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'optlist'=>'string'], +'PDF_fit_textflow' => ['string', 'pdfdoc'=>'resource', 'textflow'=>'int', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'optlist'=>'string'], +'PDF_fit_textline' => ['bool', 'pdfdoc'=>'resource', 'text'=>'string', 'x'=>'float', 'y'=>'float', 'optlist'=>'string'], +'PDF_get_apiname' => ['string', 'pdfdoc'=>'resource'], +'PDF_get_buffer' => ['string', 'p'=>'resource'], +'PDF_get_errmsg' => ['string', 'pdfdoc'=>'resource'], +'PDF_get_errnum' => ['int', 'pdfdoc'=>'resource'], +'PDF_get_majorversion' => ['int'], +'PDF_get_minorversion' => ['int'], +'PDF_get_parameter' => ['string', 'p'=>'resource', 'key'=>'string', 'modifier'=>'float'], +'PDF_get_pdi_parameter' => ['string', 'p'=>'resource', 'key'=>'string', 'doc'=>'int', 'page'=>'int', 'reserved'=>'int'], +'PDF_get_pdi_value' => ['float', 'p'=>'resource', 'key'=>'string', 'doc'=>'int', 'page'=>'int', 'reserved'=>'int'], +'PDF_get_value' => ['float', 'p'=>'resource', 'key'=>'string', 'modifier'=>'float'], +'PDF_info_font' => ['float', 'pdfdoc'=>'resource', 'font'=>'int', 'keyword'=>'string', 'optlist'=>'string'], +'PDF_info_matchbox' => ['float', 'pdfdoc'=>'resource', 'boxname'=>'string', 'num'=>'int', 'keyword'=>'string'], +'PDF_info_table' => ['float', 'pdfdoc'=>'resource', 'table'=>'int', 'keyword'=>'string'], +'PDF_info_textflow' => ['float', 'pdfdoc'=>'resource', 'textflow'=>'int', 'keyword'=>'string'], +'PDF_info_textline' => ['float', 'pdfdoc'=>'resource', 'text'=>'string', 'keyword'=>'string', 'optlist'=>'string'], +'PDF_initgraphics' => ['bool', 'p'=>'resource'], +'PDF_lineto' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float'], +'PDF_load_3ddata' => ['int', 'pdfdoc'=>'resource', 'filename'=>'string', 'optlist'=>'string'], +'PDF_load_font' => ['int', 'pdfdoc'=>'resource', 'fontname'=>'string', 'encoding'=>'string', 'optlist'=>'string'], +'PDF_load_iccprofile' => ['int', 'pdfdoc'=>'resource', 'profilename'=>'string', 'optlist'=>'string'], +'PDF_load_image' => ['int', 'pdfdoc'=>'resource', 'imagetype'=>'string', 'filename'=>'string', 'optlist'=>'string'], +'PDF_makespotcolor' => ['int', 'p'=>'resource', 'spotname'=>'string'], +'PDF_moveto' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float'], +'PDF_new' => ['resource'], +'PDF_open_ccitt' => ['int', 'pdfdoc'=>'resource', 'filename'=>'string', 'width'=>'int', 'height'=>'int', 'bitreverse'=>'int', 'k'=>'int', 'blackls1'=>'int'], +'PDF_open_file' => ['bool', 'p'=>'resource', 'filename'=>'string'], +'PDF_open_image' => ['int', 'p'=>'resource', 'imagetype'=>'string', 'source'=>'string', 'data'=>'string', 'length'=>'int', 'width'=>'int', 'height'=>'int', 'components'=>'int', 'bpc'=>'int', 'params'=>'string'], +'PDF_open_image_file' => ['int', 'p'=>'resource', 'imagetype'=>'string', 'filename'=>'string', 'stringparam'=>'string', 'intparam'=>'int'], +'PDF_open_memory_image' => ['int', 'p'=>'resource', 'image'=>'resource'], +'PDF_open_pdi' => ['int', 'pdfdoc'=>'resource', 'filename'=>'string', 'optlist'=>'string', 'length'=>'int'], +'PDF_open_pdi_document' => ['int', 'p'=>'resource', 'filename'=>'string', 'optlist'=>'string'], +'PDF_open_pdi_page' => ['int', 'p'=>'resource', 'doc'=>'int', 'pagenumber'=>'int', 'optlist'=>'string'], +'PDF_pcos_get_number' => ['float', 'p'=>'resource', 'doc'=>'int', 'path'=>'string'], +'PDF_pcos_get_stream' => ['string', 'p'=>'resource', 'doc'=>'int', 'optlist'=>'string', 'path'=>'string'], +'PDF_pcos_get_string' => ['string', 'p'=>'resource', 'doc'=>'int', 'path'=>'string'], +'PDF_place_image' => ['bool', 'pdfdoc'=>'resource', 'image'=>'int', 'x'=>'float', 'y'=>'float', 'scale'=>'float'], +'PDF_place_pdi_page' => ['bool', 'pdfdoc'=>'resource', 'page'=>'int', 'x'=>'float', 'y'=>'float', 'sx'=>'float', 'sy'=>'float'], +'PDF_process_pdi' => ['int', 'pdfdoc'=>'resource', 'doc'=>'int', 'page'=>'int', 'optlist'=>'string'], +'PDF_rect' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'], +'PDF_restore' => ['bool', 'p'=>'resource'], +'PDF_resume_page' => ['bool', 'pdfdoc'=>'resource', 'optlist'=>'string'], +'PDF_rotate' => ['bool', 'p'=>'resource', 'phi'=>'float'], +'PDF_save' => ['bool', 'p'=>'resource'], +'PDF_scale' => ['bool', 'p'=>'resource', 'sx'=>'float', 'sy'=>'float'], +'PDF_set_border_color' => ['bool', 'p'=>'resource', 'red'=>'float', 'green'=>'float', 'blue'=>'float'], +'PDF_set_border_dash' => ['bool', 'pdfdoc'=>'resource', 'black'=>'float', 'white'=>'float'], +'PDF_set_border_style' => ['bool', 'pdfdoc'=>'resource', 'style'=>'string', 'width'=>'float'], +'PDF_set_gstate' => ['bool', 'pdfdoc'=>'resource', 'gstate'=>'int'], +'PDF_set_info' => ['bool', 'p'=>'resource', 'key'=>'string', 'value'=>'string'], +'PDF_set_layer_dependency' => ['bool', 'pdfdoc'=>'resource', 'type'=>'string', 'optlist'=>'string'], +'PDF_set_parameter' => ['bool', 'p'=>'resource', 'key'=>'string', 'value'=>'string'], +'PDF_set_text_pos' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float'], +'PDF_set_value' => ['bool', 'p'=>'resource', 'key'=>'string', 'value'=>'float'], +'PDF_setcolor' => ['bool', 'p'=>'resource', 'fstype'=>'string', 'colorspace'=>'string', 'c1'=>'float', 'c2'=>'float', 'c3'=>'float', 'c4'=>'float'], +'PDF_setdash' => ['bool', 'pdfdoc'=>'resource', 'b'=>'float', 'w'=>'float'], +'PDF_setdashpattern' => ['bool', 'pdfdoc'=>'resource', 'optlist'=>'string'], +'PDF_setflat' => ['bool', 'pdfdoc'=>'resource', 'flatness'=>'float'], +'PDF_setfont' => ['bool', 'pdfdoc'=>'resource', 'font'=>'int', 'fontsize'=>'float'], +'PDF_setgray' => ['bool', 'p'=>'resource', 'g'=>'float'], +'PDF_setgray_fill' => ['bool', 'p'=>'resource', 'g'=>'float'], +'PDF_setgray_stroke' => ['bool', 'p'=>'resource', 'g'=>'float'], +'PDF_setlinecap' => ['bool', 'p'=>'resource', 'linecap'=>'int'], +'PDF_setlinejoin' => ['bool', 'p'=>'resource', 'value'=>'int'], +'PDF_setlinewidth' => ['bool', 'p'=>'resource', 'width'=>'float'], +'PDF_setmatrix' => ['bool', 'p'=>'resource', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'e'=>'float', 'f'=>'float'], +'PDF_setmiterlimit' => ['bool', 'pdfdoc'=>'resource', 'miter'=>'float'], +'PDF_setrgbcolor' => ['bool', 'p'=>'resource', 'red'=>'float', 'green'=>'float', 'blue'=>'float'], +'PDF_setrgbcolor_fill' => ['bool', 'p'=>'resource', 'red'=>'float', 'green'=>'float', 'blue'=>'float'], +'PDF_setrgbcolor_stroke' => ['bool', 'p'=>'resource', 'red'=>'float', 'green'=>'float', 'blue'=>'float'], +'PDF_shading' => ['int', 'pdfdoc'=>'resource', 'shtype'=>'string', 'x0'=>'float', 'y0'=>'float', 'x1'=>'float', 'y1'=>'float', 'c1'=>'float', 'c2'=>'float', 'c3'=>'float', 'c4'=>'float', 'optlist'=>'string'], +'PDF_shading_pattern' => ['int', 'pdfdoc'=>'resource', 'shading'=>'int', 'optlist'=>'string'], +'PDF_shfill' => ['bool', 'pdfdoc'=>'resource', 'shading'=>'int'], +'PDF_show' => ['bool', 'pdfdoc'=>'resource', 'text'=>'string'], +'PDF_show_boxed' => ['int', 'p'=>'resource', 'text'=>'string', 'left'=>'float', 'top'=>'float', 'width'=>'float', 'height'=>'float', 'mode'=>'string', 'feature'=>'string'], +'PDF_show_xy' => ['bool', 'p'=>'resource', 'text'=>'string', 'x'=>'float', 'y'=>'float'], +'PDF_skew' => ['bool', 'p'=>'resource', 'alpha'=>'float', 'beta'=>'float'], +'PDF_stringwidth' => ['float', 'p'=>'resource', 'text'=>'string', 'font'=>'int', 'fontsize'=>'float'], +'PDF_stroke' => ['bool', 'p'=>'resource'], +'PDF_suspend_page' => ['bool', 'pdfdoc'=>'resource', 'optlist'=>'string'], +'PDF_translate' => ['bool', 'p'=>'resource', 'tx'=>'float', 'ty'=>'float'], +'PDF_utf16_to_utf8' => ['string', 'pdfdoc'=>'resource', 'utf16string'=>'string'], +'PDF_utf32_to_utf16' => ['string', 'pdfdoc'=>'resource', 'utf32string'=>'string', 'ordering'=>'string'], +'PDF_utf8_to_utf16' => ['string', 'pdfdoc'=>'resource', 'utf8string'=>'string', 'ordering'=>'string'], +'PDFlib::activate_item' => ['bool', 'id'=>''], +'PDFlib::add_launchlink' => ['bool', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'filename'=>'string'], +'PDFlib::add_locallink' => ['bool', 'lowerleftx'=>'float', 'lowerlefty'=>'float', 'upperrightx'=>'float', 'upperrighty'=>'float', 'page'=>'int', 'dest'=>'string'], +'PDFlib::add_nameddest' => ['bool', 'name'=>'string', 'optlist'=>'string'], +'PDFlib::add_note' => ['bool', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'contents'=>'string', 'title'=>'string', 'icon'=>'string', 'open'=>'int'], +'PDFlib::add_pdflink' => ['bool', 'bottom_left_x'=>'float', 'bottom_left_y'=>'float', 'up_right_x'=>'float', 'up_right_y'=>'float', 'filename'=>'string', 'page'=>'int', 'dest'=>'string'], +'PDFlib::add_table_cell' => ['int', 'table'=>'int', 'column'=>'int', 'row'=>'int', 'text'=>'string', 'optlist'=>'string'], +'PDFlib::add_textflow' => ['int', 'textflow'=>'int', 'text'=>'string', 'optlist'=>'string'], +'PDFlib::add_thumbnail' => ['bool', 'image'=>'int'], +'PDFlib::add_weblink' => ['bool', 'lowerleftx'=>'float', 'lowerlefty'=>'float', 'upperrightx'=>'float', 'upperrighty'=>'float', 'url'=>'string'], +'PDFlib::arc' => ['bool', 'x'=>'float', 'y'=>'float', 'r'=>'float', 'alpha'=>'float', 'beta'=>'float'], +'PDFlib::arcn' => ['bool', 'x'=>'float', 'y'=>'float', 'r'=>'float', 'alpha'=>'float', 'beta'=>'float'], +'PDFlib::attach_file' => ['bool', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'filename'=>'string', 'description'=>'string', 'author'=>'string', 'mimetype'=>'string', 'icon'=>'string'], +'PDFlib::begin_document' => ['int', 'filename'=>'string', 'optlist'=>'string'], +'PDFlib::begin_font' => ['bool', 'filename'=>'string', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'e'=>'float', 'f'=>'float', 'optlist'=>'string'], +'PDFlib::begin_glyph' => ['bool', 'glyphname'=>'string', 'wx'=>'float', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float'], +'PDFlib::begin_item' => ['int', 'tag'=>'string', 'optlist'=>'string'], +'PDFlib::begin_layer' => ['bool', 'layer'=>'int'], +'PDFlib::begin_page' => ['bool', 'width'=>'float', 'height'=>'float'], +'PDFlib::begin_page_ext' => ['bool', 'width'=>'float', 'height'=>'float', 'optlist'=>'string'], +'PDFlib::begin_pattern' => ['int', 'width'=>'float', 'height'=>'float', 'xstep'=>'float', 'ystep'=>'float', 'painttype'=>'int'], +'PDFlib::begin_template' => ['int', 'width'=>'float', 'height'=>'float'], +'PDFlib::begin_template_ext' => ['int', 'width'=>'float', 'height'=>'float', 'optlist'=>'string'], +'PDFlib::circle' => ['bool', 'x'=>'float', 'y'=>'float', 'r'=>'float'], +'PDFlib::clip' => ['bool'], +'PDFlib::close' => ['bool'], +'PDFlib::close_image' => ['bool', 'image'=>'int'], +'PDFlib::close_pdi' => ['bool', 'doc'=>'int'], +'PDFlib::close_pdi_page' => ['bool', 'page'=>'int'], +'PDFlib::closepath' => ['bool'], +'PDFlib::closepath_fill_stroke' => ['bool'], +'PDFlib::closepath_stroke' => ['bool'], +'PDFlib::concat' => ['bool', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'e'=>'float', 'f'=>'float'], +'PDFlib::continue_text' => ['bool', 'text'=>'string'], +'PDFlib::create_3dview' => ['int', 'username'=>'string', 'optlist'=>'string'], +'PDFlib::create_action' => ['int', 'type'=>'string', 'optlist'=>'string'], +'PDFlib::create_annotation' => ['bool', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'type'=>'string', 'optlist'=>'string'], +'PDFlib::create_bookmark' => ['int', 'text'=>'string', 'optlist'=>'string'], +'PDFlib::create_field' => ['bool', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'name'=>'string', 'type'=>'string', 'optlist'=>'string'], +'PDFlib::create_fieldgroup' => ['bool', 'name'=>'string', 'optlist'=>'string'], +'PDFlib::create_gstate' => ['int', 'optlist'=>'string'], +'PDFlib::create_pvf' => ['bool', 'filename'=>'string', 'data'=>'string', 'optlist'=>'string'], +'PDFlib::create_textflow' => ['int', 'text'=>'string', 'optlist'=>'string'], +'PDFlib::curveto' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float'], +'PDFlib::define_layer' => ['int', 'name'=>'string', 'optlist'=>'string'], +'PDFlib::delete' => ['bool'], +'PDFlib::delete_pvf' => ['int', 'filename'=>'string'], +'PDFlib::delete_table' => ['bool', 'table'=>'int', 'optlist'=>'string'], +'PDFlib::delete_textflow' => ['bool', 'textflow'=>'int'], +'PDFlib::encoding_set_char' => ['bool', 'encoding'=>'string', 'slot'=>'int', 'glyphname'=>'string', 'uv'=>'int'], +'PDFlib::end_document' => ['bool', 'optlist'=>'string'], +'PDFlib::end_font' => ['bool'], +'PDFlib::end_glyph' => ['bool'], +'PDFlib::end_item' => ['bool', 'id'=>'int'], +'PDFlib::end_layer' => ['bool'], +'PDFlib::end_page' => ['bool', 'p'=>''], +'PDFlib::end_page_ext' => ['bool', 'optlist'=>'string'], +'PDFlib::end_pattern' => ['bool', 'p'=>''], +'PDFlib::end_template' => ['bool', 'p'=>''], +'PDFlib::endpath' => ['bool', 'p'=>''], +'PDFlib::fill' => ['bool'], +'PDFlib::fill_imageblock' => ['int', 'page'=>'int', 'blockname'=>'string', 'image'=>'int', 'optlist'=>'string'], +'PDFlib::fill_pdfblock' => ['int', 'page'=>'int', 'blockname'=>'string', 'contents'=>'int', 'optlist'=>'string'], +'PDFlib::fill_stroke' => ['bool'], +'PDFlib::fill_textblock' => ['int', 'page'=>'int', 'blockname'=>'string', 'text'=>'string', 'optlist'=>'string'], +'PDFlib::findfont' => ['int', 'fontname'=>'string', 'encoding'=>'string', 'embed'=>'int'], +'PDFlib::fit_image' => ['bool', 'image'=>'int', 'x'=>'float', 'y'=>'float', 'optlist'=>'string'], +'PDFlib::fit_pdi_page' => ['bool', 'page'=>'int', 'x'=>'float', 'y'=>'float', 'optlist'=>'string'], +'PDFlib::fit_table' => ['string', 'table'=>'int', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'optlist'=>'string'], +'PDFlib::fit_textflow' => ['string', 'textflow'=>'int', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'optlist'=>'string'], +'PDFlib::fit_textline' => ['bool', 'text'=>'string', 'x'=>'float', 'y'=>'float', 'optlist'=>'string'], +'PDFlib::get_apiname' => ['string'], +'PDFlib::get_buffer' => ['string'], +'PDFlib::get_errmsg' => ['string'], +'PDFlib::get_errnum' => ['int'], +'PDFlib::get_majorversion' => ['int'], +'PDFlib::get_minorversion' => ['int'], +'PDFlib::get_parameter' => ['string', 'key'=>'string', 'modifier'=>'float'], +'PDFlib::get_pdi_parameter' => ['string', 'key'=>'string', 'doc'=>'int', 'page'=>'int', 'reserved'=>'int'], +'PDFlib::get_pdi_value' => ['float', 'key'=>'string', 'doc'=>'int', 'page'=>'int', 'reserved'=>'int'], +'PDFlib::get_value' => ['float', 'key'=>'string', 'modifier'=>'float'], +'PDFlib::info_font' => ['float', 'font'=>'int', 'keyword'=>'string', 'optlist'=>'string'], +'PDFlib::info_matchbox' => ['float', 'boxname'=>'string', 'num'=>'int', 'keyword'=>'string'], +'PDFlib::info_table' => ['float', 'table'=>'int', 'keyword'=>'string'], +'PDFlib::info_textflow' => ['float', 'textflow'=>'int', 'keyword'=>'string'], +'PDFlib::info_textline' => ['float', 'text'=>'string', 'keyword'=>'string', 'optlist'=>'string'], +'PDFlib::initgraphics' => ['bool'], +'PDFlib::lineto' => ['bool', 'x'=>'float', 'y'=>'float'], +'PDFlib::load_3ddata' => ['int', 'filename'=>'string', 'optlist'=>'string'], +'PDFlib::load_font' => ['int', 'fontname'=>'string', 'encoding'=>'string', 'optlist'=>'string'], +'PDFlib::load_iccprofile' => ['int', 'profilename'=>'string', 'optlist'=>'string'], +'PDFlib::load_image' => ['int', 'imagetype'=>'string', 'filename'=>'string', 'optlist'=>'string'], +'PDFlib::makespotcolor' => ['int', 'spotname'=>'string'], +'PDFlib::moveto' => ['bool', 'x'=>'float', 'y'=>'float'], +'PDFlib::open_ccitt' => ['int', 'filename'=>'string', 'width'=>'int', 'height'=>'int', 'BitReverse'=>'int', 'k'=>'int', 'Blackls1'=>'int'], +'PDFlib::open_file' => ['bool', 'filename'=>'string'], +'PDFlib::open_image' => ['int', 'imagetype'=>'string', 'source'=>'string', 'data'=>'string', 'length'=>'int', 'width'=>'int', 'height'=>'int', 'components'=>'int', 'bpc'=>'int', 'params'=>'string'], +'PDFlib::open_image_file' => ['int', 'imagetype'=>'string', 'filename'=>'string', 'stringparam'=>'string', 'intparam'=>'int'], +'PDFlib::open_memory_image' => ['int', 'image'=>'resource'], +'PDFlib::open_pdi' => ['int', 'filename'=>'string', 'optlist'=>'string', 'length'=>'int'], +'PDFlib::open_pdi_document' => ['int', 'filename'=>'string', 'optlist'=>'string'], +'PDFlib::open_pdi_page' => ['int', 'doc'=>'int', 'pagenumber'=>'int', 'optlist'=>'string'], +'PDFlib::pcos_get_number' => ['float', 'doc'=>'int', 'path'=>'string'], +'PDFlib::pcos_get_stream' => ['string', 'doc'=>'int', 'optlist'=>'string', 'path'=>'string'], +'PDFlib::pcos_get_string' => ['string', 'doc'=>'int', 'path'=>'string'], +'PDFlib::place_image' => ['bool', 'image'=>'int', 'x'=>'float', 'y'=>'float', 'scale'=>'float'], +'PDFlib::place_pdi_page' => ['bool', 'page'=>'int', 'x'=>'float', 'y'=>'float', 'sx'=>'float', 'sy'=>'float'], +'PDFlib::process_pdi' => ['int', 'doc'=>'int', 'page'=>'int', 'optlist'=>'string'], +'PDFlib::rect' => ['bool', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'], +'PDFlib::restore' => ['bool', 'p'=>''], +'PDFlib::resume_page' => ['bool', 'optlist'=>'string'], +'PDFlib::rotate' => ['bool', 'phi'=>'float'], +'PDFlib::save' => ['bool', 'p'=>''], +'PDFlib::scale' => ['bool', 'sx'=>'float', 'sy'=>'float'], +'PDFlib::set_border_color' => ['bool', 'red'=>'float', 'green'=>'float', 'blue'=>'float'], +'PDFlib::set_border_dash' => ['bool', 'black'=>'float', 'white'=>'float'], +'PDFlib::set_border_style' => ['bool', 'style'=>'string', 'width'=>'float'], +'PDFlib::set_gstate' => ['bool', 'gstate'=>'int'], +'PDFlib::set_info' => ['bool', 'key'=>'string', 'value'=>'string'], +'PDFlib::set_layer_dependency' => ['bool', 'type'=>'string', 'optlist'=>'string'], +'PDFlib::set_parameter' => ['bool', 'key'=>'string', 'value'=>'string'], +'PDFlib::set_text_pos' => ['bool', 'x'=>'float', 'y'=>'float'], +'PDFlib::set_value' => ['bool', 'key'=>'string', 'value'=>'float'], +'PDFlib::setcolor' => ['bool', 'fstype'=>'string', 'colorspace'=>'string', 'c1'=>'float', 'c2'=>'float', 'c3'=>'float', 'c4'=>'float'], +'PDFlib::setdash' => ['bool', 'b'=>'float', 'w'=>'float'], +'PDFlib::setdashpattern' => ['bool', 'optlist'=>'string'], +'PDFlib::setflat' => ['bool', 'flatness'=>'float'], +'PDFlib::setfont' => ['bool', 'font'=>'int', 'fontsize'=>'float'], +'PDFlib::setgray' => ['bool', 'g'=>'float'], +'PDFlib::setgray_fill' => ['bool', 'g'=>'float'], +'PDFlib::setgray_stroke' => ['bool', 'g'=>'float'], +'PDFlib::setlinecap' => ['bool', 'linecap'=>'int'], +'PDFlib::setlinejoin' => ['bool', 'value'=>'int'], +'PDFlib::setlinewidth' => ['bool', 'width'=>'float'], +'PDFlib::setmatrix' => ['bool', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'e'=>'float', 'f'=>'float'], +'PDFlib::setmiterlimit' => ['bool', 'miter'=>'float'], +'PDFlib::setrgbcolor' => ['bool', 'red'=>'float', 'green'=>'float', 'blue'=>'float'], +'PDFlib::setrgbcolor_fill' => ['bool', 'red'=>'float', 'green'=>'float', 'blue'=>'float'], +'PDFlib::setrgbcolor_stroke' => ['bool', 'red'=>'float', 'green'=>'float', 'blue'=>'float'], +'PDFlib::shading' => ['int', 'shtype'=>'string', 'x0'=>'float', 'y0'=>'float', 'x1'=>'float', 'y1'=>'float', 'c1'=>'float', 'c2'=>'float', 'c3'=>'float', 'c4'=>'float', 'optlist'=>'string'], +'PDFlib::shading_pattern' => ['int', 'shading'=>'int', 'optlist'=>'string'], +'PDFlib::shfill' => ['bool', 'shading'=>'int'], +'PDFlib::show' => ['bool', 'text'=>'string'], +'PDFlib::show_boxed' => ['int', 'text'=>'string', 'left'=>'float', 'top'=>'float', 'width'=>'float', 'height'=>'float', 'mode'=>'string', 'feature'=>'string'], +'PDFlib::show_xy' => ['bool', 'text'=>'string', 'x'=>'float', 'y'=>'float'], +'PDFlib::skew' => ['bool', 'alpha'=>'float', 'beta'=>'float'], +'PDFlib::stringwidth' => ['float', 'text'=>'string', 'font'=>'int', 'fontsize'=>'float'], +'PDFlib::stroke' => ['bool', 'p'=>''], +'PDFlib::suspend_page' => ['bool', 'optlist'=>'string'], +'PDFlib::translate' => ['bool', 'tx'=>'float', 'ty'=>'float'], +'PDFlib::utf16_to_utf8' => ['string', 'utf16string'=>'string'], +'PDFlib::utf32_to_utf16' => ['string', 'utf32string'=>'string', 'ordering'=>'string'], +'PDFlib::utf8_to_utf16' => ['string', 'utf8string'=>'string', 'ordering'=>'string'], +'PDO::__construct' => ['void', 'dsn'=>'string', 'username='=>'?string', 'passwd='=>'?string', 'options='=>'?array'], +'PDO::__sleep' => ['list'], +'PDO::__wakeup' => ['void'], +'PDO::beginTransaction' => ['bool'], +'PDO::commit' => ['bool'], +'PDO::cubrid_schema' => ['array', 'schema_type'=>'int', 'table_name='=>'string', 'col_name='=>'string'], +'PDO::errorCode' => ['?string'], +'PDO::errorInfo' => ['array'], +'PDO::exec' => ['int|false', 'query'=>'string'], +'PDO::getAttribute' => ['', 'attribute'=>'int'], +'PDO::getAvailableDrivers' => ['array'], +'PDO::inTransaction' => ['bool'], +'PDO::lastInsertId' => ['string', 'name='=>'string|null'], +'PDO::pgsqlCopyFromArray' => ['bool', 'table_name'=>'string', 'rows'=>'array', 'delimiter'=>'string', 'null_as'=>'string', 'fields'=>'string'], +'PDO::pgsqlCopyFromFile' => ['bool', 'table_name'=>'string', 'filename'=>'string', 'delimiter'=>'string', 'null_as'=>'string', 'fields'=>'string'], +'PDO::pgsqlCopyToArray' => ['array', 'table_name'=>'string', 'delimiter'=>'string', 'null_as'=>'string', 'fields'=>'string'], +'PDO::pgsqlCopyToFile' => ['bool', 'table_name'=>'string', 'filename'=>'string', 'delimiter'=>'string', 'null_as'=>'string', 'fields'=>'string'], +'PDO::pgsqlGetNotify' => ['array', 'result_type'=>'int', 'ms_timeout'=>'int'], +'PDO::pgsqlGetPid' => ['int'], +'PDO::pgsqlLOBCreate' => ['string'], +'PDO::pgsqlLOBOpen' => ['resource', 'oid'=>'string', 'mode='=>'string'], +'PDO::pgsqlLOBUnlink' => ['bool', 'oid'=>'string'], +'PDO::prepare' => ['PDOStatement|false', 'statement'=>'string', 'options='=>'array'], +'PDO::query' => ['PDOStatement|false', 'sql'=>'string'], +'PDO::query\'1' => ['PDOStatement|false', 'sql'=>'string', 'fetch_column'=>'int', 'colno='=>'int'], +'PDO::query\'2' => ['PDOStatement|false', 'sql'=>'string', 'fetch_class'=>'int', 'classname'=>'string', 'ctorargs'=>'array'], +'PDO::query\'3' => ['PDOStatement|false', 'sql'=>'string', 'fetch_into'=>'int', 'object'=>'object'], +'PDO::quote' => ['string|false', 'string'=>'string', 'paramtype='=>'int'], +'PDO::rollBack' => ['bool'], +'PDO::setAttribute' => ['bool', 'attribute'=>'int', 'value'=>''], +'PDO::sqliteCreateAggregate' => ['bool', 'function_name'=>'string', 'step_func'=>'callable', 'finalize_func'=>'callable', 'num_args='=>'int'], +'PDO::sqliteCreateCollation' => ['bool', 'name'=>'string', 'callback'=>'callable'], +'PDO::sqliteCreateFunction' => ['bool', 'function_name'=>'string', 'callback'=>'callable', 'num_args='=>'int'], +'pdo_drivers' => ['array'], +'PDOException::getCode' => ['string'], +'PDOException::getFile' => ['string'], +'PDOException::getLine' => ['int'], +'PDOException::getMessage' => ['string'], +'PDOException::getPrevious' => ['?Throwable'], +'PDOException::getTrace' => ['list>'], +'PDOException::getTraceAsString' => ['string'], +'PDOStatement::__sleep' => ['list'], +'PDOStatement::__wakeup' => ['void'], +'PDOStatement::bindColumn' => ['bool', 'column'=>'mixed', '&rw_param'=>'mixed', 'type='=>'int', 'maxlen='=>'int', 'driverdata='=>'mixed'], +'PDOStatement::bindParam' => ['bool', 'paramno'=>'mixed', '&rw_param'=>'mixed', 'type='=>'int', 'maxlen='=>'int', 'driverdata='=>'mixed'], +'PDOStatement::bindValue' => ['bool', 'paramno'=>'mixed', 'param'=>'mixed', 'type='=>'int'], +'PDOStatement::closeCursor' => ['bool'], +'PDOStatement::columnCount' => ['int'], +'PDOStatement::debugDumpParams' => ['void'], +'PDOStatement::errorCode' => ['string'], +'PDOStatement::errorInfo' => ['array'], +'PDOStatement::execute' => ['bool', 'bound_input_params='=>'?array'], +'PDOStatement::fetch' => ['mixed', 'how='=>'int', 'orientation='=>'int', 'offset='=>'int'], +'PDOStatement::fetchAll' => ['array|false', 'how='=>'int', 'fetch_argument='=>'int|string|callable', 'ctor_args='=>'?array'], +'PDOStatement::fetchColumn' => ['string|int|float|bool|null', 'column_number='=>'int'], +'PDOStatement::fetchObject' => ['mixed|false', 'class_name='=>'string', 'ctor_args='=>'?array'], +'PDOStatement::getAttribute' => ['mixed', 'attribute'=>'int'], +'PDOStatement::getColumnMeta' => ['array|false', 'column'=>'int'], +'PDOStatement::nextRowset' => ['bool'], +'PDOStatement::rowCount' => ['int'], +'PDOStatement::setAttribute' => ['bool', 'attribute'=>'int', 'value'=>'mixed'], +'PDOStatement::setFetchMode' => ['bool', 'mode'=>'int'], +'PDOStatement::setFetchMode\'1' => ['bool', 'fetch_column'=>'int', 'colno'=>'int'], +'PDOStatement::setFetchMode\'2' => ['bool', 'fetch_class'=>'int', 'classname'=>'string', 'ctorargs'=>'array'], +'PDOStatement::setFetchMode\'3' => ['bool', 'fetch_into'=>'int', 'object'=>'object'], +'pfsockopen' => ['resource|false', 'hostname'=>'string', 'port='=>'int', '&w_errno='=>'int', '&w_errstr='=>'string', 'timeout='=>'float'], +'pg_affected_rows' => ['int', 'result'=>'resource'], +'pg_cancel_query' => ['bool', 'connection'=>'resource'], +'pg_client_encoding' => ['string|false', 'connection='=>'resource'], +'pg_close' => ['bool', 'connection='=>'resource'], +'pg_connect' => ['resource|false', 'connection_string'=>'string', 'connect_type='=>'int'], +'pg_connect_poll' => ['int', 'connection'=>'resource'], +'pg_connection_busy' => ['bool', 'connection'=>'resource'], +'pg_connection_reset' => ['bool', 'connection'=>'resource'], +'pg_connection_status' => ['int', 'connection'=>'resource'], +'pg_consume_input' => ['bool', 'connection'=>'resource'], +'pg_convert' => ['array', 'db'=>'resource', 'table'=>'string', 'values'=>'array', 'options='=>'int'], +'pg_copy_from' => ['bool', 'connection'=>'resource', 'table_name'=>'string', 'rows'=>'array', 'delimiter='=>'string', 'null_as='=>'string'], +'pg_copy_to' => ['array|false', 'connection'=>'resource', 'table_name'=>'string', 'delimiter='=>'string', 'null_as='=>'string'], +'pg_dbname' => ['string|false', 'connection='=>'resource'], +'pg_delete' => ['string|bool', 'db'=>'resource', 'table'=>'string', 'ids'=>'array', 'options='=>'int'], +'pg_end_copy' => ['bool', 'connection='=>'resource'], +'pg_escape_bytea' => ['string', 'connection'=>'resource', 'data'=>'string'], +'pg_escape_bytea\'1' => ['string', 'data'=>'string'], +'pg_escape_identifier' => ['string', 'connection'=>'resource', 'data'=>'string'], +'pg_escape_identifier\'1' => ['string', 'data'=>'string'], +'pg_escape_literal' => ['string', 'connection'=>'resource', 'data'=>'string'], +'pg_escape_literal\'1' => ['string', 'data'=>'string'], +'pg_escape_string' => ['string', 'connection'=>'resource', 'data'=>'string'], +'pg_escape_string\'1' => ['string', 'data'=>'string'], +'pg_exec' => ['resource|false', 'connection'=>'resource', 'query'=>'string'], +'pg_execute' => ['resource|false', 'connection'=>'resource', 'stmtname'=>'string', 'params'=>'array'], +'pg_execute\'1' => ['resource|false', 'stmtname'=>'string', 'params'=>'array'], +'pg_fetch_all' => ['array|false', 'result'=>'resource', 'result_type='=>'int'], +'pg_fetch_all_columns' => ['array|false', 'result'=>'resource', 'column_number='=>'int'], +'pg_fetch_array' => ['array|false', 'result'=>'resource', 'row='=>'?int', 'result_type='=>'int'], +'pg_fetch_assoc' => ['array|false', 'result'=>'resource', 'row='=>'?int'], +'pg_fetch_object' => ['object', 'result'=>'resource', 'row='=>'?int', 'result_type='=>'int'], +'pg_fetch_object\'1' => ['object', 'result'=>'resource', 'row='=>'?int', 'class_name='=>'string', 'ctor_params='=>'array'], +'pg_fetch_result' => ['string', 'result'=>'resource', 'field_name'=>'string|int'], +'pg_fetch_result\'1' => ['string', 'result'=>'resource', 'row'=>'?int', 'field_name'=>'string|int'], +'pg_fetch_row' => ['array', 'result'=>'resource', 'row='=>'?int', 'result_type='=>'int'], +'pg_field_is_null' => ['int', 'result'=>'resource', 'field_name_or_number'=>'string|int'], +'pg_field_is_null\'1' => ['int', 'result'=>'resource', 'row'=>'int', 'field_name_or_number'=>'string|int'], +'pg_field_name' => ['string|false', 'result'=>'resource', 'field_number'=>'int'], +'pg_field_num' => ['int', 'result'=>'resource', 'field_name'=>'string'], +'pg_field_prtlen' => ['int|false', 'result'=>'resource', 'field_name_or_number'=>''], +'pg_field_prtlen\'1' => ['int', 'result'=>'resource', 'row'=>'int', 'field_name_or_number'=>'string|int'], +'pg_field_size' => ['int', 'result'=>'resource', 'field_number'=>'int'], +'pg_field_table' => ['mixed|false', 'result'=>'resource', 'field_number'=>'int', 'oid_only='=>'bool'], +'pg_field_type' => ['string|false', 'result'=>'resource', 'field_number'=>'int'], +'pg_field_type_oid' => ['int|false', 'result'=>'resource', 'field_number'=>'int'], +'pg_flush' => ['mixed', 'connection'=>'resource'], +'pg_free_result' => ['bool', 'result'=>'resource'], +'pg_get_notify' => ['array|false', 'connection'=>'resource', 'result_type='=>'int'], +'pg_get_pid' => ['int|false', 'connection'=>'resource'], +'pg_get_result' => ['resource|false', 'connection='=>'resource'], +'pg_host' => ['string|false', 'connection='=>'resource'], +'pg_insert' => ['resource|string|false', 'db'=>'resource', 'table'=>'string', 'values'=>'array', 'options='=>'int'], +'pg_last_error' => ['string', 'connection='=>'resource', 'operation='=>'int'], +'pg_last_notice' => ['string|array|bool', 'connection'=>'resource', 'option='=>'int'], +'pg_last_oid' => ['string', 'result'=>'resource'], +'pg_lo_close' => ['bool', 'large_object'=>'resource'], +'pg_lo_create' => ['int|false', 'connection='=>'resource', 'large_object_oid='=>''], +'pg_lo_export' => ['bool', 'connection'=>'resource', 'oid'=>'int', 'filename'=>'string'], +'pg_lo_export\'1' => ['bool', 'oid'=>'int', 'pathname'=>'string'], +'pg_lo_import' => ['int', 'connection'=>'resource', 'pathname'=>'string', 'oid'=>''], +'pg_lo_import\'1' => ['int', 'pathname'=>'string', 'oid'=>''], +'pg_lo_open' => ['resource|false', 'connection'=>'resource', 'oid'=>'int', 'mode'=>'string'], +'pg_lo_read' => ['string', 'large_object'=>'resource', 'length='=>'int'], +'pg_lo_read_all' => ['int|false', 'large_object'=>'resource'], +'pg_lo_seek' => ['bool', 'large_object'=>'resource', 'offset'=>'int', 'whence='=>'int'], +'pg_lo_tell' => ['int', 'large_object'=>'resource'], +'pg_lo_truncate' => ['bool', 'large_object'=>'resource', 'size'=>'int'], +'pg_lo_unlink' => ['bool', 'connection'=>'resource', 'oid'=>'int'], +'pg_lo_write' => ['int|false', 'large_object'=>'resource', 'data'=>'string', 'length='=>'int'], +'pg_meta_data' => ['array', 'db'=>'resource', 'table'=>'string', 'extended='=>'bool'], +'pg_num_fields' => ['int', 'result'=>'resource'], +'pg_num_rows' => ['int', 'result'=>'resource'], +'pg_options' => ['string', 'connection='=>'resource'], +'pg_parameter_status' => ['string|false', 'connection'=>'resource', 'param_name'=>'string'], +'pg_parameter_status\'1' => ['string|false', 'param_name'=>'string'], +'pg_pconnect' => ['resource|false', 'connection_string'=>'string', 'host='=>'string', 'port='=>'string|int', 'options='=>'string', 'tty='=>'string', 'database='=>'string'], +'pg_ping' => ['bool', 'connection='=>'resource'], +'pg_port' => ['int', 'connection='=>'resource'], +'pg_prepare' => ['resource|false', 'connection'=>'resource', 'stmtname'=>'string', 'query'=>'string'], +'pg_prepare\'1' => ['resource|false', 'stmtname'=>'string', 'query'=>'string'], +'pg_put_line' => ['bool', 'connection'=>'resource', 'data'=>'string'], +'pg_put_line\'1' => ['bool', 'data'=>'string'], +'pg_query' => ['resource|false', 'connection'=>'resource', 'query'=>'string'], +'pg_query\'1' => ['resource|false', 'query'=>'string'], +'pg_query_params' => ['resource|false', 'connection'=>'resource', 'query'=>'string', 'params'=>'array'], +'pg_query_params\'1' => ['resource|false', 'query'=>'string', 'params'=>'array'], +'pg_result_error' => ['string|false', 'result'=>'resource'], +'pg_result_error_field' => ['string|?false', 'result'=>'resource', 'fieldcode'=>'int'], +'pg_result_seek' => ['bool', 'result'=>'resource', 'offset'=>'int'], +'pg_result_status' => ['mixed', 'result'=>'resource', 'result_type='=>'int'], +'pg_select' => ['string|bool', 'db'=>'resource', 'table'=>'string', 'ids'=>'array', 'options='=>'int', 'result_type='=>'int'], +'pg_send_execute' => ['bool', 'connection'=>'resource', 'stmtname'=>'string', 'params'=>'array'], +'pg_send_prepare' => ['bool', 'connection'=>'resource', 'stmtname'=>'string', 'query'=>'string'], +'pg_send_query' => ['bool', 'connection'=>'resource', 'query'=>'string'], +'pg_send_query_params' => ['bool', 'connection'=>'resource', 'query'=>'string', 'params'=>'array'], +'pg_set_client_encoding' => ['int', 'connection'=>'resource', 'encoding'=>'string'], +'pg_set_client_encoding\'1' => ['int', 'encoding'=>'string'], +'pg_set_error_verbosity' => ['int', 'connection'=>'resource', 'verbosity'=>'int'], +'pg_set_error_verbosity\'1' => ['int', 'verbosity'=>'int'], +'pg_socket' => ['resource|false', 'connection'=>'resource'], +'pg_trace' => ['bool', 'filename'=>'string', 'mode='=>'string', 'connection='=>'resource'], +'pg_transaction_status' => ['int', 'connection'=>'resource'], +'pg_tty' => ['string', 'connection='=>'resource'], +'pg_tty\'1' => ['string'], +'pg_unescape_bytea' => ['string', 'data'=>'string'], +'pg_untrace' => ['bool', 'connection='=>'resource'], +'pg_untrace\'1' => ['bool'], +'pg_update' => ['mixed', 'db'=>'resource', 'table'=>'string', 'fields'=>'array', 'ids'=>'array', 'options='=>'int'], +'pg_version' => ['array', 'connection='=>'resource'], +'Phar::__construct' => ['void', 'fname'=>'string', 'flags='=>'int', 'alias='=>'string'], +'Phar::addEmptyDir' => ['void', 'dirname'=>'string'], +'Phar::addFile' => ['void', 'file'=>'string', 'localname='=>'string'], +'Phar::addFromString' => ['void', 'localname'=>'string', 'contents'=>'string'], +'Phar::apiVersion' => ['string'], +'Phar::buildFromDirectory' => ['array', 'base_dir'=>'string', 'regex='=>'string'], +'Phar::buildFromIterator' => ['array', 'iter'=>'Iterator', 'base_directory='=>'string'], +'Phar::canCompress' => ['bool', 'method='=>'int'], +'Phar::canWrite' => ['bool'], +'Phar::compress' => ['Phar', 'compression'=>'int', 'extension='=>'string'], +'Phar::compressAllFilesBZIP2' => ['bool'], +'Phar::compressAllFilesGZ' => ['bool'], +'Phar::compressFiles' => ['void', 'compression'=>'int'], +'Phar::convertToData' => ['PharData', 'format='=>'int', 'compression='=>'int', 'extension='=>'string'], +'Phar::convertToExecutable' => ['Phar', 'format='=>'int', 'compression='=>'int', 'extension='=>'string'], +'Phar::copy' => ['bool', 'oldfile'=>'string', 'newfile'=>'string'], +'Phar::count' => ['int'], +'Phar::createDefaultStub' => ['string', 'indexfile='=>'string', 'webindexfile='=>'string'], +'Phar::decompress' => ['Phar', 'extension='=>'string'], +'Phar::decompressFiles' => ['bool'], +'Phar::delete' => ['bool', 'entry'=>'string'], +'Phar::delMetadata' => ['bool'], +'Phar::extractTo' => ['bool', 'pathto'=>'string', 'files='=>'string|array|null', 'overwrite='=>'bool'], +'Phar::getAlias' => ['string'], +'Phar::getMetadata' => ['mixed'], +'Phar::getModified' => ['bool'], +'Phar::getPath' => ['string'], +'Phar::getSignature' => ['array{hash:string, hash_type:string}'], +'Phar::getStub' => ['string'], +'Phar::getSupportedCompression' => ['array'], +'Phar::getSupportedSignatures' => ['array'], +'Phar::getVersion' => ['string'], +'Phar::hasMetadata' => ['bool'], +'Phar::interceptFileFuncs' => ['void'], +'Phar::isBuffering' => ['bool'], +'Phar::isCompressed' => ['mixed|false'], +'Phar::isFileFormat' => ['bool', 'format'=>'int'], +'Phar::isValidPharFilename' => ['bool', 'filename'=>'string', 'executable='=>'bool'], +'Phar::isWritable' => ['bool'], +'Phar::loadPhar' => ['bool', 'filename'=>'string', 'alias='=>'string'], +'Phar::mapPhar' => ['bool', 'alias='=>'string', 'dataoffset='=>'int'], +'Phar::mount' => ['void', 'pharpath'=>'string', 'externalpath'=>'string'], +'Phar::mungServer' => ['void', 'munglist'=>'array'], +'Phar::offsetExists' => ['bool', 'offset'=>'string'], +'Phar::offsetGet' => ['PharFileInfo', 'offset'=>'string'], +'Phar::offsetSet' => ['void', 'offset'=>'string', 'value'=>'string'], +'Phar::offsetUnset' => ['bool', 'offset'=>'string'], +'Phar::running' => ['string', 'retphar='=>'bool'], +'Phar::setAlias' => ['bool', 'alias'=>'string'], +'Phar::setDefaultStub' => ['bool', 'index='=>'string', 'webindex='=>'string'], +'Phar::setMetadata' => ['void', 'metadata'=>''], +'Phar::setSignatureAlgorithm' => ['void', 'sigtype'=>'int', 'privatekey='=>'string'], +'Phar::setStub' => ['bool', 'stub'=>'string', 'length='=>'int'], +'Phar::startBuffering' => ['void'], +'Phar::stopBuffering' => ['void'], +'Phar::uncompressAllFiles' => ['bool'], +'Phar::unlinkArchive' => ['bool', 'archive'=>'string'], +'Phar::webPhar' => ['', 'alias='=>'string', 'index='=>'string', 'f404='=>'string', 'mimetypes='=>'array', 'rewrites='=>'array'], +'PharData::__construct' => ['void', 'fname'=>'string', 'flags='=>'?int', 'alias='=>'?string', 'format='=>'int'], +'PharData::addEmptyDir' => ['bool', 'dirname'=>'string'], +'PharData::addFile' => ['void', 'file'=>'string', 'localname='=>'string'], +'PharData::addFromString' => ['bool', 'localname'=>'string', 'contents'=>'string'], +'PharData::buildFromDirectory' => ['array', 'base_dir'=>'string', 'regex='=>'string'], +'PharData::buildFromIterator' => ['array', 'iter'=>'Iterator', 'base_directory='=>'string'], +'PharData::compress' => ['PharData', 'compression'=>'int', 'extension='=>'string'], +'PharData::compressFiles' => ['bool', 'compression'=>'int'], +'PharData::convertToData' => ['PharData', 'format='=>'int', 'compression='=>'int', 'extension='=>'string'], +'PharData::convertToExecutable' => ['Phar', 'format='=>'int', 'compression='=>'int', 'extension='=>'string'], +'PharData::copy' => ['bool', 'oldfile'=>'string', 'newfile'=>'string'], +'PharData::decompress' => ['PharData', 'extension='=>'string'], +'PharData::decompressFiles' => ['bool'], +'PharData::delete' => ['bool', 'entry'=>'string'], +'PharData::delMetadata' => ['bool'], +'PharData::extractTo' => ['bool', 'pathto'=>'string', 'files='=>'string|array|null', 'overwrite='=>'bool'], +'PharData::isWritable' => ['bool'], +'PharData::offsetExists' => ['bool', 'offset'=>'string'], +'PharData::offsetGet' => ['PharFileInfo', 'offset'=>'string'], +'PharData::offsetSet' => ['void', 'offset'=>'string', 'value'=>'string'], +'PharData::offsetUnset' => ['bool', 'offset'=>'string'], +'PharData::setAlias' => ['bool', 'alias'=>'string'], +'PharData::setDefaultStub' => ['bool', 'index='=>'string', 'webindex='=>'string'], +'phardata::setMetadata' => ['void', 'metadata'=>'mixed'], +'phardata::setSignatureAlgorithm' => ['void', 'sigtype'=>'int'], +'PharData::setStub' => ['bool', 'stub'=>'string', 'length='=>'int'], +'PharFileInfo::__construct' => ['void', 'entry'=>'string'], +'PharFileInfo::chmod' => ['void', 'permissions'=>'int'], +'PharFileInfo::compress' => ['bool', 'compression'=>'int'], +'PharFileInfo::decompress' => ['bool'], +'PharFileInfo::delMetadata' => ['bool'], +'PharFileInfo::getCompressedSize' => ['int'], +'PharFileInfo::getContent' => ['string'], +'PharFileInfo::getCRC32' => ['int'], +'PharFileInfo::getMetadata' => ['mixed'], +'PharFileInfo::getPharFlags' => ['int'], +'PharFileInfo::hasMetadata' => ['bool'], +'PharFileInfo::isCompressed' => ['bool', 'compression_type='=>'int'], +'PharFileInfo::isCompressedBZIP2' => ['bool'], +'PharFileInfo::isCompressedGZ' => ['bool'], +'PharFileInfo::isCRCChecked' => ['bool'], +'PharFileInfo::setCompressedBZIP2' => ['bool'], +'PharFileInfo::setCompressedGZ' => ['bool'], +'PharFileInfo::setMetadata' => ['void', 'metadata'=>'mixed'], +'PharFileInfo::setUncompressed' => ['bool'], +'phdfs::__construct' => ['void', 'ip'=>'string', 'port'=>'string'], +'phdfs::__destruct' => ['void'], +'phdfs::connect' => ['bool'], +'phdfs::copy' => ['bool', 'source_file'=>'string', 'destination_file'=>'string'], +'phdfs::create_directory' => ['bool', 'path'=>'string'], +'phdfs::delete' => ['bool', 'path'=>'string'], +'phdfs::disconnect' => ['bool'], +'phdfs::exists' => ['bool', 'path'=>'string'], +'phdfs::file_info' => ['array', 'path'=>'string'], +'phdfs::list_directory' => ['array', 'path'=>'string'], +'phdfs::read' => ['string', 'path'=>'string', 'length='=>'string'], +'phdfs::rename' => ['bool', 'old_path'=>'string', 'new_path'=>'string'], +'phdfs::tell' => ['int', 'path'=>'string'], +'phdfs::write' => ['bool', 'path'=>'string', 'buffer'=>'string', 'mode='=>'string'], +'php_check_syntax' => ['bool', 'filename'=>'string', 'error_message='=>'string'], +'php_ini_loaded_file' => ['string|false'], +'php_ini_scanned_files' => ['string|false'], +'php_logo_guid' => ['string'], +'php_sapi_name' => ['string'], +'php_strip_whitespace' => ['string', 'file_name'=>'string'], +'php_uname' => ['string', 'mode='=>'string'], +'php_user_filter::filter' => ['int', 'in'=>'resource', 'out'=>'resource', '&rw_consumed'=>'int', 'closing'=>'bool'], +'php_user_filter::onClose' => ['void'], +'php_user_filter::onCreate' => ['bool'], +'phpcredits' => ['bool', 'flag='=>'int'], +'phpdbg_break_file' => ['void', 'file'=>'string', 'line'=>'int'], +'phpdbg_break_function' => ['void', 'function'=>'string'], +'phpdbg_break_method' => ['void', 'class'=>'string', 'method'=>'string'], +'phpdbg_break_next' => ['void'], +'phpdbg_clear' => ['void'], +'phpdbg_color' => ['void', 'element'=>'int', 'color'=>'string'], +'phpdbg_end_oplog' => ['array', 'options='=>'array'], +'phpdbg_exec' => ['mixed', 'context='=>'string'], +'phpdbg_get_executable' => ['array', 'options='=>'array'], +'phpdbg_prompt' => ['void', 'prompt'=>'string'], +'phpdbg_start_oplog' => ['void'], +'phpinfo' => ['bool', 'what='=>'int'], +'phpversion' => ['string|false', 'extension='=>'string'], +'pht\AtomicInteger::__construct' => ['void', 'value='=>'int'], +'pht\AtomicInteger::dec' => ['void'], +'pht\AtomicInteger::get' => ['int'], +'pht\AtomicInteger::inc' => ['void'], +'pht\AtomicInteger::lock' => ['void'], +'pht\AtomicInteger::set' => ['void', 'value'=>'int'], +'pht\AtomicInteger::unlock' => ['void'], +'pht\HashTable::lock' => ['void'], +'pht\HashTable::size' => ['int'], +'pht\HashTable::unlock' => ['void'], +'pht\Queue::front' => ['mixed'], +'pht\Queue::lock' => ['void'], +'pht\Queue::pop' => ['mixed'], +'pht\Queue::push' => ['void', 'value'=>'mixed'], +'pht\Queue::size' => ['int'], +'pht\Queue::unlock' => ['void'], +'pht\Runnable::run' => ['void'], +'pht\thread::addClassTask' => ['void', 'className'=>'string', '...ctorArgs='=>'mixed'], +'pht\thread::addFileTask' => ['void', 'fileName'=>'string', '...globals='=>'mixed'], +'pht\thread::addFunctionTask' => ['void', 'func'=>'callable', '...funcArgs='=>'mixed'], +'pht\thread::join' => ['void'], +'pht\thread::start' => ['void'], +'pht\thread::taskCount' => ['int'], +'pht\threaded::lock' => ['void'], +'pht\threaded::unlock' => ['void'], +'pht\Vector::__construct' => ['void', 'size='=>'int', 'value='=>'mixed'], +'pht\Vector::deleteAt' => ['void', 'offset'=>'int'], +'pht\Vector::insertAt' => ['void', 'value'=>'mixed', 'offset'=>'int'], +'pht\Vector::lock' => ['void'], +'pht\Vector::pop' => ['mixed'], +'pht\Vector::push' => ['void', 'value'=>'mixed'], +'pht\Vector::resize' => ['void', 'size'=>'int', 'value='=>'mixed'], +'pht\Vector::shift' => ['mixed'], +'pht\Vector::size' => ['int'], +'pht\Vector::unlock' => ['void'], +'pht\Vector::unshift' => ['void', 'value'=>'mixed'], +'pht\Vector::updateAt' => ['void', 'value'=>'mixed', 'offset'=>'int'], +'pi' => ['float'], +'png2wbmp' => ['bool', 'pngname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'], +'pointObj::__construct' => ['void'], +'pointObj::distanceToLine' => ['float', 'p1'=>'pointObj', 'p2'=>'pointObj'], +'pointObj::distanceToPoint' => ['float', 'poPoint'=>'pointObj'], +'pointObj::distanceToShape' => ['float', 'shape'=>'shapeObj'], +'pointObj::draw' => ['int', 'map'=>'mapObj', 'layer'=>'layerObj', 'img'=>'imageObj', 'class_index'=>'int', 'text'=>'string'], +'pointObj::ms_newPointObj' => ['pointObj'], +'pointObj::project' => ['int', 'in'=>'projectionObj', 'out'=>'projectionObj'], +'pointObj::setXY' => ['int', 'x'=>'float', 'y'=>'float', 'm'=>'float'], +'pointObj::setXYZ' => ['int', 'x'=>'float', 'y'=>'float', 'z'=>'float', 'm'=>'float'], +'Pool::__construct' => ['void', 'size'=>'int', 'class'=>'string', 'ctor='=>'array'], +'Pool::collect' => ['int', 'collector='=>'Callable'], +'Pool::resize' => ['void', 'size'=>'int'], +'Pool::shutdown' => ['void'], +'Pool::submit' => ['int', 'task'=>'Threaded'], +'Pool::submitTo' => ['int', 'worker'=>'int', 'task'=>'Threaded'], +'popen' => ['resource|false', 'command'=>'string', 'mode'=>'string'], +'pos' => ['mixed', 'array'=>'array'], +'posix_access' => ['bool', 'file'=>'string', 'mode='=>'int'], +'posix_ctermid' => ['string|false'], +'posix_errno' => ['int'], +'posix_get_last_error' => ['int'], +'posix_getcwd' => ['string'], +'posix_getegid' => ['int'], +'posix_geteuid' => ['int'], +'posix_getgid' => ['int'], +'posix_getgrgid' => ['array', 'gid'=>'int'], +'posix_getgrnam' => ['array|false', 'groupname'=>'string'], +'posix_getgroups' => ['array'], +'posix_getlogin' => ['string'], +'posix_getpgid' => ['int|false', 'pid'=>'int'], +'posix_getpgrp' => ['int'], +'posix_getpid' => ['int'], +'posix_getppid' => ['int'], +'posix_getpwnam' => ['array|false', 'groupname'=>'string'], +'posix_getpwuid' => ['array', 'uid'=>'int'], +'posix_getrlimit' => ['array'], +'posix_getsid' => ['int', 'pid'=>'int'], +'posix_getuid' => ['int'], +'posix_initgroups' => ['bool', 'name'=>'string', 'base_group_id'=>'int'], +'posix_isatty' => ['bool', 'fd'=>'resource|int'], +'posix_kill' => ['bool', 'pid'=>'int', 'sig'=>'int'], +'posix_mkfifo' => ['bool', 'pathname'=>'string', 'mode'=>'int'], +'posix_mknod' => ['bool', 'pathname'=>'string', 'mode'=>'int', 'major='=>'int', 'minor='=>'int'], +'posix_setegid' => ['bool', 'uid'=>'int'], +'posix_seteuid' => ['bool', 'uid'=>'int'], +'posix_setgid' => ['bool', 'uid'=>'int'], +'posix_setpgid' => ['bool', 'pid'=>'int', 'pgid'=>'int'], +'posix_setrlimit' => ['bool', 'resource'=>'int', 'softlimit'=>'int', 'hardlimit'=>'int'], +'posix_setsid' => ['int'], +'posix_setuid' => ['bool', 'uid'=>'int'], +'posix_strerror' => ['string', 'errno'=>'int'], +'posix_times' => ['array'], +'posix_ttyname' => ['string|false', 'fd'=>'resource|int'], +'posix_uname' => ['array'], +'Postal\Expand::expand_address' => ['string[]', 'address'=>'string', 'options='=>'array'], +'Postal\Parser::parse_address' => ['array', 'address'=>'string', 'options='=>'array'], +'pow' => ['float|int', 'base'=>'int|float', 'exponent'=>'int|float'], +'preg_filter' => ['null|string|string[]', 'regex'=>'mixed', 'replace'=>'mixed', 'subject'=>'mixed', 'limit='=>'int', '&w_count='=>'int'], +'preg_grep' => ['array|false', 'regex'=>'string', 'input'=>'array', 'flags='=>'int'], +'preg_last_error' => ['int'], +'preg_match' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'string[]', 'flags='=>'0|', 'offset='=>'int'], +'preg_match\'1' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'array', 'flags='=>'int', 'offset='=>'int'], +'preg_match_all' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'array', 'flags='=>'int', 'offset='=>'int'], +'preg_quote' => ['string', 'string'=>'string', 'delim_char='=>'string'], +'preg_replace' => ['string|string[]|null', 'regex'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], +'preg_replace_callback' => ['string|null', 'regex'=>'string|array', 'callback'=>'callable(array):string', 'subject'=>'string', 'limit='=>'int', '&w_count='=>'int'], +'preg_replace_callback\'1' => ['string[]|null', 'regex'=>'string|array', 'callback'=>'callable(array):string', 'subject'=>'string[]', 'limit='=>'int', '&w_count='=>'int'], +'preg_replace_callback_array' => ['string|string[]|null', 'pattern'=>'array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], +'preg_split' => ['list|false', 'pattern'=>'string', 'subject'=>'string', 'limit'=>'int', 'flags='=>'null'], +'preg_split\'1' => ['list|list>|false', 'pattern'=>'string', 'subject'=>'string', 'limit='=>'int', 'flags='=>'int'], +'prev' => ['mixed', '&r_array'=>'array|object'], +'print' => ['int', 'arg'=>'string'], +'print_r' => ['string', 'value'=>'mixed'], +'print_r\'1' => ['true', 'value'=>'mixed', 'return='=>'bool'], +'printf' => ['int', 'format'=>'string', '...args='=>'string|int|float'], +'proc_close' => ['int', 'process'=>'resource'], +'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}|false', 'process'=>'resource'], +'proc_nice' => ['bool', 'priority'=>'int'], +'proc_open' => ['resource|false', 'command'=>'string|array', 'descriptorspec'=>'array', '&w_pipes'=>'resource[]', 'cwd='=>'?string', 'env='=>'?array', 'other_options='=>'array'], +'proc_terminate' => ['bool', 'process'=>'resource', 'signal='=>'int'], +'projectionObj::__construct' => ['void', 'projectionString'=>'string'], +'projectionObj::getUnits' => ['int'], +'projectionObj::ms_newProjectionObj' => ['projectionObj', 'projectionString'=>'string'], +'property_exists' => ['bool', 'object_or_class'=>'object|string', 'property_name'=>'string'], +'ps_add_bookmark' => ['int', 'psdoc'=>'resource', 'text'=>'string', 'parent='=>'int', 'open='=>'int'], +'ps_add_launchlink' => ['bool', 'psdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'filename'=>'string'], +'ps_add_locallink' => ['bool', 'psdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'page'=>'int', 'dest'=>'string'], +'ps_add_note' => ['bool', 'psdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'contents'=>'string', 'title'=>'string', 'icon'=>'string', 'open'=>'int'], +'ps_add_pdflink' => ['bool', 'psdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'filename'=>'string', 'page'=>'int', 'dest'=>'string'], +'ps_add_weblink' => ['bool', 'psdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'url'=>'string'], +'ps_arc' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'alpha'=>'float', 'beta'=>'float'], +'ps_arcn' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'alpha'=>'float', 'beta'=>'float'], +'ps_begin_page' => ['bool', 'psdoc'=>'resource', 'width'=>'float', 'height'=>'float'], +'ps_begin_pattern' => ['int', 'psdoc'=>'resource', 'width'=>'float', 'height'=>'float', 'xstep'=>'float', 'ystep'=>'float', 'painttype'=>'int'], +'ps_begin_template' => ['int', 'psdoc'=>'resource', 'width'=>'float', 'height'=>'float'], +'ps_circle' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float', 'radius'=>'float'], +'ps_clip' => ['bool', 'psdoc'=>'resource'], +'ps_close' => ['bool', 'psdoc'=>'resource'], +'ps_close_image' => ['void', 'psdoc'=>'resource', 'imageid'=>'int'], +'ps_closepath' => ['bool', 'psdoc'=>'resource'], +'ps_closepath_stroke' => ['bool', 'psdoc'=>'resource'], +'ps_continue_text' => ['bool', 'psdoc'=>'resource', 'text'=>'string'], +'ps_curveto' => ['bool', 'psdoc'=>'resource', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float'], +'ps_delete' => ['bool', 'psdoc'=>'resource'], +'ps_end_page' => ['bool', 'psdoc'=>'resource'], +'ps_end_pattern' => ['bool', 'psdoc'=>'resource'], +'ps_end_template' => ['bool', 'psdoc'=>'resource'], +'ps_fill' => ['bool', 'psdoc'=>'resource'], +'ps_fill_stroke' => ['bool', 'psdoc'=>'resource'], +'ps_findfont' => ['int', 'psdoc'=>'resource', 'fontname'=>'string', 'encoding'=>'string', 'embed='=>'bool'], +'ps_get_buffer' => ['string', 'psdoc'=>'resource'], +'ps_get_parameter' => ['string', 'psdoc'=>'resource', 'name'=>'string', 'modifier='=>'float'], +'ps_get_value' => ['float', 'psdoc'=>'resource', 'name'=>'string', 'modifier='=>'float'], +'ps_hyphenate' => ['array', 'psdoc'=>'resource', 'text'=>'string'], +'ps_include_file' => ['bool', 'psdoc'=>'resource', 'file'=>'string'], +'ps_lineto' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float'], +'ps_makespotcolor' => ['int', 'psdoc'=>'resource', 'name'=>'string', 'reserved='=>'int'], +'ps_moveto' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float'], +'ps_new' => ['resource'], +'ps_open_file' => ['bool', 'psdoc'=>'resource', 'filename='=>'string'], +'ps_open_image' => ['int', 'psdoc'=>'resource', 'type'=>'string', 'source'=>'string', 'data'=>'string', 'length'=>'int', 'width'=>'int', 'height'=>'int', 'components'=>'int', 'bpc'=>'int', 'params'=>'string'], +'ps_open_image_file' => ['int', 'psdoc'=>'resource', 'type'=>'string', 'filename'=>'string', 'stringparam='=>'string', 'intparam='=>'int'], +'ps_open_memory_image' => ['int', 'psdoc'=>'resource', 'gd'=>'int'], +'ps_place_image' => ['bool', 'psdoc'=>'resource', 'imageid'=>'int', 'x'=>'float', 'y'=>'float', 'scale'=>'float'], +'ps_rect' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'], +'ps_restore' => ['bool', 'psdoc'=>'resource'], +'ps_rotate' => ['bool', 'psdoc'=>'resource', 'rot'=>'float'], +'ps_save' => ['bool', 'psdoc'=>'resource'], +'ps_scale' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float'], +'ps_set_border_color' => ['bool', 'psdoc'=>'resource', 'red'=>'float', 'green'=>'float', 'blue'=>'float'], +'ps_set_border_dash' => ['bool', 'psdoc'=>'resource', 'black'=>'float', 'white'=>'float'], +'ps_set_border_style' => ['bool', 'psdoc'=>'resource', 'style'=>'string', 'width'=>'float'], +'ps_set_info' => ['bool', 'p'=>'resource', 'key'=>'string', 'value'=>'string'], +'ps_set_parameter' => ['bool', 'psdoc'=>'resource', 'name'=>'string', 'value'=>'string'], +'ps_set_text_pos' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float'], +'ps_set_value' => ['bool', 'psdoc'=>'resource', 'name'=>'string', 'value'=>'float'], +'ps_setcolor' => ['bool', 'psdoc'=>'resource', 'type'=>'string', 'colorspace'=>'string', 'c1'=>'float', 'c2'=>'float', 'c3'=>'float', 'c4'=>'float'], +'ps_setdash' => ['bool', 'psdoc'=>'resource', 'on'=>'float', 'off'=>'float'], +'ps_setflat' => ['bool', 'psdoc'=>'resource', 'value'=>'float'], +'ps_setfont' => ['bool', 'psdoc'=>'resource', 'fontid'=>'int', 'size'=>'float'], +'ps_setgray' => ['bool', 'psdoc'=>'resource', 'gray'=>'float'], +'ps_setlinecap' => ['bool', 'psdoc'=>'resource', 'type'=>'int'], +'ps_setlinejoin' => ['bool', 'psdoc'=>'resource', 'type'=>'int'], +'ps_setlinewidth' => ['bool', 'psdoc'=>'resource', 'width'=>'float'], +'ps_setmiterlimit' => ['bool', 'psdoc'=>'resource', 'value'=>'float'], +'ps_setoverprintmode' => ['bool', 'psdoc'=>'resource', 'mode'=>'int'], +'ps_setpolydash' => ['bool', 'psdoc'=>'resource', 'arr'=>'float'], +'ps_shading' => ['int', 'psdoc'=>'resource', 'type'=>'string', 'x0'=>'float', 'y0'=>'float', 'x1'=>'float', 'y1'=>'float', 'c1'=>'float', 'c2'=>'float', 'c3'=>'float', 'c4'=>'float', 'optlist'=>'string'], +'ps_shading_pattern' => ['int', 'psdoc'=>'resource', 'shadingid'=>'int', 'optlist'=>'string'], +'ps_shfill' => ['bool', 'psdoc'=>'resource', 'shadingid'=>'int'], +'ps_show' => ['bool', 'psdoc'=>'resource', 'text'=>'string'], +'ps_show2' => ['bool', 'psdoc'=>'resource', 'text'=>'string', 'length'=>'int'], +'ps_show_boxed' => ['int', 'psdoc'=>'resource', 'text'=>'string', 'left'=>'float', 'bottom'=>'float', 'width'=>'float', 'height'=>'float', 'hmode'=>'string', 'feature='=>'string'], +'ps_show_xy' => ['bool', 'psdoc'=>'resource', 'text'=>'string', 'x'=>'float', 'y'=>'float'], +'ps_show_xy2' => ['bool', 'psdoc'=>'resource', 'text'=>'string', 'length'=>'int', 'xcoor'=>'float', 'ycoor'=>'float'], +'ps_string_geometry' => ['array', 'psdoc'=>'resource', 'text'=>'string', 'fontid='=>'int', 'size='=>'float'], +'ps_stringwidth' => ['float', 'psdoc'=>'resource', 'text'=>'string', 'fontid='=>'int', 'size='=>'float'], +'ps_stroke' => ['bool', 'psdoc'=>'resource'], +'ps_symbol' => ['bool', 'psdoc'=>'resource', 'ord'=>'int'], +'ps_symbol_name' => ['string', 'psdoc'=>'resource', 'ord'=>'int', 'fontid='=>'int'], +'ps_symbol_width' => ['float', 'psdoc'=>'resource', 'ord'=>'int', 'fontid='=>'int', 'size='=>'float'], +'ps_translate' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float'], +'pspell_add_to_personal' => ['bool', 'pspell'=>'int', 'word'=>'string'], +'pspell_add_to_session' => ['bool', 'pspell'=>'int', 'word'=>'string'], +'pspell_check' => ['bool', 'pspell'=>'int', 'word'=>'string'], +'pspell_clear_session' => ['bool', 'pspell'=>'int'], +'pspell_config_create' => ['int|false', 'language'=>'string', 'spelling='=>'string', 'jargon='=>'string', 'encoding='=>'string'], +'pspell_config_data_dir' => ['bool', 'conf'=>'int', 'directory'=>'string'], +'pspell_config_dict_dir' => ['bool', 'conf'=>'int', 'directory'=>'string'], +'pspell_config_ignore' => ['bool', 'conf'=>'int', 'ignore'=>'int'], +'pspell_config_mode' => ['bool', 'conf'=>'int', 'mode'=>'int'], +'pspell_config_personal' => ['bool', 'conf'=>'int', 'personal'=>'string'], +'pspell_config_repl' => ['bool', 'conf'=>'int', 'repl'=>'string'], +'pspell_config_runtogether' => ['bool', 'conf'=>'int', 'runtogether'=>'bool'], +'pspell_config_save_repl' => ['bool', 'conf'=>'int', 'save'=>'bool'], +'pspell_new' => ['int|false', 'language'=>'string', 'spelling='=>'string', 'jargon='=>'string', 'encoding='=>'string', 'mode='=>'int'], +'pspell_new_config' => ['int|false', 'config'=>'int'], +'pspell_new_personal' => ['int|false', 'personal'=>'string', 'language'=>'string', 'spelling='=>'string', 'jargon='=>'string', 'encoding='=>'string', 'mode='=>'int'], +'pspell_save_wordlist' => ['bool', 'pspell'=>'int'], +'pspell_store_replacement' => ['bool', 'pspell'=>'int', 'misspell'=>'string', 'correct'=>'string'], +'pspell_suggest' => ['array', 'pspell'=>'int', 'word'=>'string'], +'putenv' => ['bool', 'setting'=>'string'], +'px_close' => ['bool', 'pxdoc'=>'resource'], +'px_create_fp' => ['bool', 'pxdoc'=>'resource', 'file'=>'resource', 'fielddesc'=>'array'], +'px_date2string' => ['string', 'pxdoc'=>'resource', 'value'=>'int', 'format'=>'string'], +'px_delete' => ['bool', 'pxdoc'=>'resource'], +'px_delete_record' => ['bool', 'pxdoc'=>'resource', 'num'=>'int'], +'px_get_field' => ['array', 'pxdoc'=>'resource', 'fieldno'=>'int'], +'px_get_info' => ['array', 'pxdoc'=>'resource'], +'px_get_parameter' => ['string', 'pxdoc'=>'resource', 'name'=>'string'], +'px_get_record' => ['array', 'pxdoc'=>'resource', 'num'=>'int', 'mode='=>'int'], +'px_get_schema' => ['array', 'pxdoc'=>'resource', 'mode='=>'int'], +'px_get_value' => ['float', 'pxdoc'=>'resource', 'name'=>'string'], +'px_insert_record' => ['int', 'pxdoc'=>'resource', 'data'=>'array'], +'px_new' => ['resource'], +'px_numfields' => ['int', 'pxdoc'=>'resource'], +'px_numrecords' => ['int', 'pxdoc'=>'resource'], +'px_open_fp' => ['bool', 'pxdoc'=>'resource', 'file'=>'resource'], +'px_put_record' => ['bool', 'pxdoc'=>'resource', 'record'=>'array', 'recpos='=>'int'], +'px_retrieve_record' => ['array', 'pxdoc'=>'resource', 'num'=>'int', 'mode='=>'int'], +'px_set_blob_file' => ['bool', 'pxdoc'=>'resource', 'filename'=>'string'], +'px_set_parameter' => ['bool', 'pxdoc'=>'resource', 'name'=>'string', 'value'=>'string'], +'px_set_tablename' => ['void', 'pxdoc'=>'resource', 'name'=>'string'], +'px_set_targetencoding' => ['bool', 'pxdoc'=>'resource', 'encoding'=>'string'], +'px_set_value' => ['bool', 'pxdoc'=>'resource', 'name'=>'string', 'value'=>'float'], +'px_timestamp2string' => ['string', 'pxdoc'=>'resource', 'value'=>'float', 'format'=>'string'], +'px_update_record' => ['bool', 'pxdoc'=>'resource', 'data'=>'array', 'num'=>'int'], +'qdom_error' => ['string'], +'qdom_tree' => ['QDomDocument', 'doc'=>'string'], +'querymapObj::convertToString' => ['string'], +'querymapObj::free' => ['void'], +'querymapObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'querymapObj::updateFromString' => ['int', 'snippet'=>'string'], +'QuickHashIntHash::__construct' => ['void', 'size'=>'int', 'options='=>'int'], +'QuickHashIntHash::add' => ['bool', 'key'=>'int', 'value='=>'int'], +'QuickHashIntHash::delete' => ['bool', 'key'=>'int'], +'QuickHashIntHash::exists' => ['bool', 'key'=>'int'], +'QuickHashIntHash::get' => ['int', 'key'=>'int'], +'QuickHashIntHash::getSize' => ['int'], +'QuickHashIntHash::loadFromFile' => ['QuickHashIntHash', 'filename'=>'string', 'options='=>'int'], +'QuickHashIntHash::loadFromString' => ['QuickHashIntHash', 'contents'=>'string', 'options='=>'int'], +'QuickHashIntHash::saveToFile' => ['void', 'filename'=>'string'], +'QuickHashIntHash::saveToString' => ['string'], +'QuickHashIntHash::set' => ['bool', 'key'=>'int', 'value'=>'int'], +'QuickHashIntHash::update' => ['bool', 'key'=>'int', 'value'=>'int'], +'QuickHashIntSet::__construct' => ['void', 'size'=>'int', 'options='=>'int'], +'QuickHashIntSet::add' => ['bool', 'key'=>'int'], +'QuickHashIntSet::delete' => ['bool', 'key'=>'int'], +'QuickHashIntSet::exists' => ['bool', 'key'=>'int'], +'QuickHashIntSet::getSize' => ['int'], +'QuickHashIntSet::loadFromFile' => ['QuickHashIntSet', 'filename'=>'string', 'size='=>'int', 'options='=>'int'], +'QuickHashIntSet::loadFromString' => ['QuickHashIntSet', 'contents'=>'string', 'size='=>'int', 'options='=>'int'], +'QuickHashIntSet::saveToFile' => ['void', 'filename'=>'string'], +'QuickHashIntSet::saveToString' => ['string'], +'QuickHashIntStringHash::__construct' => ['void', 'size'=>'int', 'options='=>'int'], +'QuickHashIntStringHash::add' => ['bool', 'key'=>'int', 'value'=>'string'], +'QuickHashIntStringHash::delete' => ['bool', 'key'=>'int'], +'QuickHashIntStringHash::exists' => ['bool', 'key'=>'int'], +'QuickHashIntStringHash::get' => ['mixed', 'key'=>'int'], +'QuickHashIntStringHash::getSize' => ['int'], +'QuickHashIntStringHash::loadFromFile' => ['QuickHashIntStringHash', 'filename'=>'string', 'size='=>'int', 'options='=>'int'], +'QuickHashIntStringHash::loadFromString' => ['QuickHashIntStringHash', 'contents'=>'string', 'size='=>'int', 'options='=>'int'], +'QuickHashIntStringHash::saveToFile' => ['void', 'filename'=>'string'], +'QuickHashIntStringHash::saveToString' => ['string'], +'QuickHashIntStringHash::set' => ['int', 'key'=>'int', 'value'=>'string'], +'QuickHashIntStringHash::update' => ['bool', 'key'=>'int', 'value'=>'string'], +'QuickHashStringIntHash::__construct' => ['void', 'size'=>'int', 'options='=>'int'], +'QuickHashStringIntHash::add' => ['bool', 'key'=>'string', 'value'=>'int'], +'QuickHashStringIntHash::delete' => ['bool', 'key'=>'string'], +'QuickHashStringIntHash::exists' => ['bool', 'key'=>'string'], +'QuickHashStringIntHash::get' => ['mixed', 'key'=>'string'], +'QuickHashStringIntHash::getSize' => ['int'], +'QuickHashStringIntHash::loadFromFile' => ['QuickHashStringIntHash', 'filename'=>'string', 'size='=>'int', 'options='=>'int'], +'QuickHashStringIntHash::loadFromString' => ['QuickHashStringIntHash', 'contents'=>'string', 'size='=>'int', 'options='=>'int'], +'QuickHashStringIntHash::saveToFile' => ['void', 'filename'=>'string'], +'QuickHashStringIntHash::saveToString' => ['string'], +'QuickHashStringIntHash::set' => ['int', 'key'=>'string', 'value'=>'int'], +'QuickHashStringIntHash::update' => ['bool', 'key'=>'string', 'value'=>'int'], +'quoted_printable_decode' => ['string', 'string'=>'string'], +'quoted_printable_encode' => ['string', 'string'=>'string'], +'quotemeta' => ['string', 'string'=>'string'], +'rad2deg' => ['float', 'number'=>'float'], +'radius_acct_open' => ['resource|false'], +'radius_add_server' => ['bool', 'radius_handle'=>'resource', 'hostname'=>'string', 'port'=>'int', 'secret'=>'string', 'timeout'=>'int', 'max_tries'=>'int'], +'radius_auth_open' => ['resource|false'], +'radius_close' => ['bool', 'radius_handle'=>'resource'], +'radius_config' => ['bool', 'radius_handle'=>'resource', 'file'=>'string'], +'radius_create_request' => ['bool', 'radius_handle'=>'resource', 'type'=>'int'], +'radius_cvt_addr' => ['string', 'data'=>'string'], +'radius_cvt_int' => ['int', 'data'=>'string'], +'radius_cvt_string' => ['string', 'data'=>'string'], +'radius_demangle' => ['string', 'radius_handle'=>'resource', 'mangled'=>'string'], +'radius_demangle_mppe_key' => ['string', 'radius_handle'=>'resource', 'mangled'=>'string'], +'radius_get_attr' => ['mixed', 'radius_handle'=>'resource'], +'radius_get_tagged_attr_data' => ['string', 'data'=>'string'], +'radius_get_tagged_attr_tag' => ['int', 'data'=>'string'], +'radius_get_vendor_attr' => ['array', 'data'=>'string'], +'radius_put_addr' => ['bool', 'radius_handle'=>'resource', 'type'=>'int', 'addr'=>'string'], +'radius_put_attr' => ['bool', 'radius_handle'=>'resource', 'type'=>'int', 'value'=>'string'], +'radius_put_int' => ['bool', 'radius_handle'=>'resource', 'type'=>'int', 'value'=>'int'], +'radius_put_string' => ['bool', 'radius_handle'=>'resource', 'type'=>'int', 'value'=>'string'], +'radius_put_vendor_addr' => ['bool', 'radius_handle'=>'resource', 'vendor'=>'int', 'type'=>'int', 'addr'=>'string'], +'radius_put_vendor_attr' => ['bool', 'radius_handle'=>'resource', 'vendor'=>'int', 'type'=>'int', 'value'=>'string'], +'radius_put_vendor_int' => ['bool', 'radius_handle'=>'resource', 'vendor'=>'int', 'type'=>'int', 'value'=>'int'], +'radius_put_vendor_string' => ['bool', 'radius_handle'=>'resource', 'vendor'=>'int', 'type'=>'int', 'value'=>'string'], +'radius_request_authenticator' => ['string', 'radius_handle'=>'resource'], +'radius_salt_encrypt_attr' => ['string', 'radius_handle'=>'resource', 'data'=>'string'], +'radius_send_request' => ['int', 'radius_handle'=>'resource'], +'radius_server_secret' => ['string', 'radius_handle'=>'resource'], +'radius_strerror' => ['string', 'radius_handle'=>'resource'], +'rand' => ['int', 'min'=>'int', 'max'=>'int'], +'rand\'1' => ['int'], +'random_bytes' => ['string', 'length'=>'int'], +'random_int' => ['int', 'min'=>'int', 'max'=>'int'], +'range' => ['array', 'low'=>'mixed', 'high'=>'mixed', 'step='=>'int|float'], +'RangeException::__clone' => ['void'], +'RangeException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?RangeException'], +'RangeException::__toString' => ['string'], +'RangeException::getCode' => ['int'], +'RangeException::getFile' => ['string'], +'RangeException::getLine' => ['int'], +'RangeException::getMessage' => ['string'], +'RangeException::getPrevious' => ['Throwable|RangeException|null'], +'RangeException::getTrace' => ['list>'], +'RangeException::getTraceAsString' => ['string'], +'rar_allow_broken_set' => ['bool', 'rarfile'=>'RarArchive', 'allow_broken'=>'bool'], +'rar_broken_is' => ['bool', 'rarfile'=>'rararchive'], +'rar_close' => ['bool', 'rarfile'=>'rararchive'], +'rar_comment_get' => ['string', 'rarfile'=>'rararchive'], +'rar_entry_get' => ['RarEntry', 'rarfile'=>'RarArchive', 'entryname'=>'string'], +'rar_list' => ['RarArchive', 'rarfile'=>'rararchive'], +'rar_open' => ['RarArchive', 'filename'=>'string', 'password='=>'string', 'volume_callback='=>'callable'], +'rar_solid_is' => ['bool', 'rarfile'=>'rararchive'], +'rar_wrapper_cache_stats' => ['string'], +'RarArchive::__toString' => ['string'], +'RarArchive::close' => ['bool'], +'RarArchive::getComment' => ['string|null'], +'RarArchive::getEntries' => ['RarEntry[]|false'], +'RarArchive::getEntry' => ['RarEntry|false', 'entryname'=>'string'], +'RarArchive::isBroken' => ['bool'], +'RarArchive::isSolid' => ['bool'], +'RarArchive::open' => ['RarArchive|false', 'filename'=>'string', 'password='=>'string', 'volume_callback='=>'callable'], +'RarArchive::setAllowBroken' => ['bool', 'allow_broken'=>'bool'], +'RarEntry::__toString' => ['string'], +'RarEntry::extract' => ['bool', 'dir'=>'string', 'filepath='=>'string', 'password='=>'string', 'extended_data='=>'bool'], +'RarEntry::getAttr' => ['int|false'], +'RarEntry::getCrc' => ['string|false'], +'RarEntry::getFileTime' => ['string|false'], +'RarEntry::getHostOs' => ['int|false'], +'RarEntry::getMethod' => ['int|false'], +'RarEntry::getName' => ['string|false'], +'RarEntry::getPackedSize' => ['int|false'], +'RarEntry::getStream' => ['resource|false', 'password='=>'string'], +'RarEntry::getUnpackedSize' => ['int|false'], +'RarEntry::getVersion' => ['int|false'], +'RarEntry::isDirectory' => ['bool'], +'RarEntry::isEncrypted' => ['bool'], +'RarException::getCode' => ['int'], +'RarException::getFile' => ['string'], +'RarException::getLine' => ['int'], +'RarException::getMessage' => ['string'], +'RarException::getPrevious' => ['Exception|Throwable'], +'RarException::getTrace' => ['list>'], +'RarException::getTraceAsString' => ['string'], +'RarException::isUsingExceptions' => ['bool'], +'RarException::setUsingExceptions' => ['RarEntry', 'using_exceptions'=>'bool'], +'rawurldecode' => ['string', 'string'=>'string'], +'rawurlencode' => ['string', 'string'=>'string'], +'rd_kafka_err2str' => ['string', 'err'=>'int'], +'rd_kafka_errno' => ['int'], +'rd_kafka_errno2err' => ['int', 'errnox'=>'int'], +'rd_kafka_offset_tail' => ['int', 'cnt'=>'int'], +'RdKafka::addBrokers' => ['int', 'broker_list'=>'string'], +'RdKafka::flush' => ['int', 'timeout_ms'=>'int'], +'RdKafka::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\Topic', 'timeout_ms'=>'int'], +'RdKafka::getOutQLen' => ['int'], +'RdKafka::newQueue' => ['RdKafka\Queue'], +'RdKafka::newTopic' => ['RdKafka\Topic', 'topic_name'=>'string', 'topic_conf='=>'?RdKafka\TopicConf'], +'RdKafka::poll' => ['void', 'timeout_ms'=>'int'], +'RdKafka::setLogLevel' => ['void', 'level'=>'int'], +'RdKafka\Conf::dump' => ['array'], +'RdKafka\Conf::set' => ['void', 'name'=>'string', 'value'=>'string'], +'RdKafka\Conf::setDefaultTopicConf' => ['void', 'topic_conf'=>'RdKafka\TopicConf'], +'RdKafka\Conf::setDrMsgCb' => ['void', 'callback'=>'callable'], +'RdKafka\Conf::setErrorCb' => ['void', 'callback'=>'callable'], +'RdKafka\Conf::setRebalanceCb' => ['void', 'callback'=>'callable'], +'RdKafka\Conf::setStatsCb' => ['void', 'callback'=>'callable'], +'RdKafka\Consumer::__construct' => ['void', 'conf='=>'?RdKafka\Conf'], +'RdKafka\Consumer::addBrokers' => ['int', 'broker_list'=>'string'], +'RdKafka\Consumer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\Topic', 'timeout_ms'=>'int'], +'RdKafka\Consumer::getOutQLen' => ['int'], +'RdKafka\Consumer::newQueue' => ['RdKafka\Queue'], +'RdKafka\Consumer::newTopic' => ['RdKafka\ConsumerTopic', 'topic_name'=>'string', 'topic_conf='=>'?RdKafka\TopicConf'], +'RdKafka\Consumer::poll' => ['void', 'timeout_ms'=>'int'], +'RdKafka\Consumer::setLogLevel' => ['void', 'level'=>'int'], +'RdKafka\ConsumerTopic::__construct' => ['void'], +'RdKafka\ConsumerTopic::consume' => ['RdKafka\Message', 'partition'=>'int', 'timeout_ms'=>'int'], +'RdKafka\ConsumerTopic::consumeQueueStart' => ['void', 'partition'=>'int', 'offset'=>'int', 'queue'=>'RdKafka\Queue'], +'RdKafka\ConsumerTopic::consumeStart' => ['void', 'partition'=>'int', 'offset'=>'int'], +'RdKafka\ConsumerTopic::consumeStop' => ['void', 'partition'=>'int'], +'RdKafka\ConsumerTopic::getName' => ['string'], +'RdKafka\ConsumerTopic::offsetStore' => ['void', 'partition'=>'int', 'offset'=>'int'], +'RdKafka\KafkaConsumer::__construct' => ['void', 'conf'=>'RdKafka\Conf'], +'RdKafka\KafkaConsumer::assign' => ['void', 'topic_partitions='=>'RdKafka\TopicPartition[]|null'], +'RdKafka\KafkaConsumer::commit' => ['void', 'message_or_offsets='=>'RdKafka\Message|RdKafka\TopicPartition[]|null'], +'RdKafka\KafkaConsumer::commitAsync' => ['void', 'message_or_offsets='=>'string'], +'RdKafka\KafkaConsumer::consume' => ['RdKafka\Message', 'timeout_ms'=>'int'], +'RdKafka\KafkaConsumer::getAssignment' => ['RdKafka\TopicPartition[]'], +'RdKafka\KafkaConsumer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\KafkaConsumerTopic', 'timeout_ms'=>'int'], +'RdKafka\KafkaConsumer::getSubscription' => ['array'], +'RdKafka\KafkaConsumer::subscribe' => ['void', 'topics'=>'array'], +'RdKafka\KafkaConsumer::unsubscribe' => ['void'], +'RdKafka\KafkaConsumerTopic::getName' => ['string'], +'RdKafka\KafkaConsumerTopic::offsetStore' => ['void', 'partition'=>'int', 'offset'=>'int'], +'RdKafka\Message::errstr' => ['string'], +'RdKafka\Metadata::getBrokers' => ['RdKafka\Metadata\Collection'], +'RdKafka\Metadata::getOrigBrokerId' => ['int'], +'RdKafka\Metadata::getOrigBrokerName' => ['string'], +'RdKafka\Metadata::getTopics' => ['RdKafka\Metadata\Collection|RdKafka\Metadata\Topic[]'], +'RdKafka\Metadata\Collection::__construct' => ['void'], +'RdKafka\Metadata\Collection::count' => ['int'], +'RdKafka\Metadata\Collection::current' => ['mixed'], +'RdKafka\Metadata\Collection::key' => ['mixed'], +'RdKafka\Metadata\Collection::next' => ['void'], +'RdKafka\Metadata\Collection::rewind' => ['void'], +'RdKafka\Metadata\Collection::valid' => ['bool'], +'RdKafka\Metadata\Partition::getErr' => ['mixed'], +'RdKafka\Metadata\Partition::getId' => ['int'], +'RdKafka\Metadata\Partition::getIsrs' => ['mixed'], +'RdKafka\Metadata\Partition::getLeader' => ['mixed'], +'RdKafka\Metadata\Partition::getReplicas' => ['mixed'], +'RdKafka\Metadata\Topic::getErr' => ['mixed'], +'RdKafka\Metadata\Topic::getPartitions' => ['RdKafka\Metadata\Partition[]'], +'RdKafka\Metadata\Topic::getTopic' => ['string'], +'RdKafka\Producer::__construct' => ['void', 'conf='=>'?RdKafka\Conf'], +'RdKafka\Producer::addBrokers' => ['int', 'broker_list'=>'string'], +'RdKafka\Producer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\Topic', 'timeout_ms'=>'int'], +'RdKafka\Producer::getOutQLen' => ['int'], +'RdKafka\Producer::newQueue' => ['RdKafka\Queue'], +'RdKafka\Producer::newTopic' => ['RdKafka\ProducerTopic', 'topic_name'=>'string', 'topic_conf='=>'?RdKafka\TopicConf'], +'RdKafka\Producer::poll' => ['void', 'timeout_ms'=>'int'], +'RdKafka\Producer::setLogLevel' => ['void', 'level'=>'int'], +'RdKafka\ProducerTopic::__construct' => ['void'], +'RdKafka\ProducerTopic::getName' => ['string'], +'RdKafka\ProducerTopic::produce' => ['void', 'partition'=>'int', 'msgflags'=>'int', 'payload'=>'string', 'key='=>'?string'], +'RdKafka\ProducerTopic::producev' => ['void', 'partition'=>'int', 'msgflags'=>'int', 'payload'=>'string', 'key='=>'?string', 'headers'=>'?array', 'timestamp_ms'=>'?int'], +'RdKafka\Queue::__construct' => ['void'], +'RdKafka\Queue::consume' => ['?RdKafka\Message', 'timeout_ms'=>'string'], +'RdKafka\Topic::getName' => ['string'], +'RdKafka\TopicConf::dump' => ['array'], +'RdKafka\TopicConf::set' => ['void', 'name'=>'string', 'value'=>'string'], +'RdKafka\TopicConf::setPartitioner' => ['void', 'partitioner'=>'int'], +'RdKafka\TopicPartition::__construct' => ['void', 'topic'=>'string', 'partition'=>'int', 'offset='=>'int'], +'RdKafka\TopicPartition::getOffset' => ['int'], +'RdKafka\TopicPartition::getPartition' => ['int'], +'RdKafka\TopicPartition::getTopic' => ['string'], +'RdKafka\TopicPartition::setOffset' => ['void', 'offset'=>'string'], +'RdKafka\TopicPartition::setPartition' => ['void', 'partition'=>'string'], +'RdKafka\TopicPartition::setTopic' => ['void', 'topic_name'=>'string'], +'read_exif_data' => ['array', 'filename'=>'string|resource', 'sections_needed='=>'string', 'sub_arrays='=>'bool', 'read_thumbnail='=>'bool'], +'readdir' => ['string|false', 'dir_handle='=>'resource'], +'readfile' => ['int|false', 'filename'=>'string', 'use_include_path='=>'bool', 'context='=>'resource'], +'readgzfile' => ['int|false', 'filename'=>'string', 'use_include_path='=>'int'], +'readline' => ['string|false', 'prompt='=>'?string'], +'readline_add_history' => ['bool', 'prompt'=>'string'], +'readline_callback_handler_install' => ['bool', 'prompt'=>'string', 'callback'=>'callable'], +'readline_callback_handler_remove' => ['bool'], +'readline_callback_read_char' => ['void'], +'readline_clear_history' => ['bool'], +'readline_completion_function' => ['bool', 'funcname'=>'callable'], +'readline_info' => ['mixed', 'varname='=>'string', 'newvalue='=>'string|int|bool'], +'readline_list_history' => ['array'], +'readline_on_new_line' => ['void'], +'readline_read_history' => ['bool', 'filename='=>'string'], +'readline_redisplay' => ['void'], +'readline_write_history' => ['bool', 'filename='=>'string'], +'readlink' => ['string|false', 'filename'=>'string'], +'realpath' => ['string|false', 'path'=>'string'], +'realpath_cache_get' => ['array'], +'realpath_cache_size' => ['int'], +'recode' => ['string', 'request'=>'string', 'string'=>'string'], +'recode_file' => ['bool', 'request'=>'string', 'input'=>'resource', 'output'=>'resource'], +'recode_string' => ['string|false', 'request'=>'string', 'string'=>'string'], +'rectObj::__construct' => ['void'], +'rectObj::draw' => ['int', 'map'=>'mapObj', 'layer'=>'layerObj', 'img'=>'imageObj', 'class_index'=>'int', 'text'=>'string'], +'rectObj::fit' => ['float', 'width'=>'int', 'height'=>'int'], +'rectObj::ms_newRectObj' => ['rectObj'], +'rectObj::project' => ['int', 'in'=>'projectionObj', 'out'=>'projectionObj'], +'rectObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'rectObj::setextent' => ['void', 'minx'=>'float', 'miny'=>'float', 'maxx'=>'float', 'maxy'=>'float'], +'RecursiveArrayIterator::__construct' => ['void', 'array='=>'array|object', 'flags='=>'int'], +'RecursiveArrayIterator::append' => ['void', 'value'=>'mixed'], +'RecursiveArrayIterator::asort' => ['void'], +'RecursiveArrayIterator::count' => ['int'], +'RecursiveArrayIterator::current' => ['mixed'], +'RecursiveArrayIterator::getArrayCopy' => ['array'], +'RecursiveArrayIterator::getChildren' => ['RecursiveArrayIterator'], +'RecursiveArrayIterator::getFlags' => ['void'], +'RecursiveArrayIterator::hasChildren' => ['bool'], +'RecursiveArrayIterator::key' => ['false|int|string'], +'RecursiveArrayIterator::ksort' => ['void'], +'RecursiveArrayIterator::natcasesort' => ['void'], +'RecursiveArrayIterator::natsort' => ['void'], +'RecursiveArrayIterator::next' => ['void'], +'RecursiveArrayIterator::offsetExists' => ['void', 'index'=>'string'], +'RecursiveArrayIterator::offsetGet' => ['mixed', 'index'=>'string'], +'RecursiveArrayIterator::offsetSet' => ['void', 'index'=>'string', 'newval'=>'string'], +'RecursiveArrayIterator::offsetUnset' => ['void', 'index'=>'string'], +'RecursiveArrayIterator::rewind' => ['void'], +'RecursiveArrayIterator::seek' => ['void', 'position'=>'int'], +'RecursiveArrayIterator::serialize' => ['string'], +'RecursiveArrayIterator::setFlags' => ['void', 'flags'=>'string'], +'RecursiveArrayIterator::uasort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'], +'RecursiveArrayIterator::uksort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'], +'RecursiveArrayIterator::unserialize' => ['string', 'serialized'=>'string'], +'RecursiveArrayIterator::valid' => ['bool'], +'RecursiveCachingIterator::__construct' => ['void', 'it'=>'Iterator', 'flags='=>'int'], +'RecursiveCachingIterator::__toString' => ['string'], +'RecursiveCachingIterator::count' => ['int'], +'RecursiveCachingIterator::current' => ['void'], +'RecursiveCachingIterator::getCache' => ['array'], +'RecursiveCachingIterator::getChildren' => ['RecursiveCachingIterator'], +'RecursiveCachingIterator::getFlags' => ['int'], +'RecursiveCachingIterator::getInnerIterator' => ['Iterator'], +'RecursiveCachingIterator::hasChildren' => ['bool'], +'RecursiveCachingIterator::hasNext' => ['bool'], +'RecursiveCachingIterator::key' => ['bool|float|int|string'], +'RecursiveCachingIterator::next' => ['void'], +'RecursiveCachingIterator::offsetExists' => ['bool', 'index'=>'string'], +'RecursiveCachingIterator::offsetGet' => ['string', 'index'=>'string'], +'RecursiveCachingIterator::offsetSet' => ['void', 'index'=>'string', 'newval'=>'string'], +'RecursiveCachingIterator::offsetUnset' => ['void', 'index'=>'string'], +'RecursiveCachingIterator::rewind' => ['void'], +'RecursiveCachingIterator::setFlags' => ['void', 'flags'=>'int'], +'RecursiveCachingIterator::valid' => ['bool'], +'RecursiveCallbackFilterIterator::__construct' => ['void', 'iterator'=>'RecursiveIterator', 'func'=>'callable'], +'RecursiveCallbackFilterIterator::accept' => ['bool'], +'RecursiveCallbackFilterIterator::current' => ['mixed'], +'RecursiveCallbackFilterIterator::getChildren' => ['RecursiveCallbackFilterIterator'], +'RecursiveCallbackFilterIterator::getInnerIterator' => ['Iterator'], +'RecursiveCallbackFilterIterator::hasChildren' => ['bool'], +'RecursiveCallbackFilterIterator::key' => ['bool|float|int|string'], +'RecursiveCallbackFilterIterator::next' => ['void'], +'RecursiveCallbackFilterIterator::rewind' => ['void'], +'RecursiveCallbackFilterIterator::valid' => ['bool'], +'RecursiveDirectoryIterator::__construct' => ['void', 'path'=>'string', 'flags='=>'int'], +'RecursiveDirectoryIterator::__toString' => ['string'], +'RecursiveDirectoryIterator::_bad_state_ex' => [''], +'RecursiveDirectoryIterator::current' => ['string|SplFileInfo|FilesystemIterator'], +'RecursiveDirectoryIterator::getATime' => ['int'], +'RecursiveDirectoryIterator::getBasename' => ['string', 'suffix='=>'string'], +'RecursiveDirectoryIterator::getChildren' => ['RecursiveDirectoryIterator'], +'RecursiveDirectoryIterator::getCTime' => ['int'], +'RecursiveDirectoryIterator::getExtension' => ['string'], +'RecursiveDirectoryIterator::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], +'RecursiveDirectoryIterator::getFilename' => ['string'], +'RecursiveDirectoryIterator::getFlags' => ['int'], +'RecursiveDirectoryIterator::getGroup' => ['int'], +'RecursiveDirectoryIterator::getInode' => ['int'], +'RecursiveDirectoryIterator::getLinkTarget' => ['string'], +'RecursiveDirectoryIterator::getMTime' => ['int'], +'RecursiveDirectoryIterator::getOwner' => ['int'], +'RecursiveDirectoryIterator::getPath' => ['string'], +'RecursiveDirectoryIterator::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], +'RecursiveDirectoryIterator::getPathname' => ['string'], +'RecursiveDirectoryIterator::getPerms' => ['int'], +'RecursiveDirectoryIterator::getRealPath' => ['string'], +'RecursiveDirectoryIterator::getSize' => ['int'], +'RecursiveDirectoryIterator::getSubPath' => ['string'], +'RecursiveDirectoryIterator::getSubPathname' => ['string'], +'RecursiveDirectoryIterator::getType' => ['string'], +'RecursiveDirectoryIterator::hasChildren' => ['bool', 'allow_links='=>'bool'], +'RecursiveDirectoryIterator::isDir' => ['bool'], +'RecursiveDirectoryIterator::isDot' => ['bool'], +'RecursiveDirectoryIterator::isExecutable' => ['bool'], +'RecursiveDirectoryIterator::isFile' => ['bool'], +'RecursiveDirectoryIterator::isLink' => ['bool'], +'RecursiveDirectoryIterator::isReadable' => ['bool'], +'RecursiveDirectoryIterator::isWritable' => ['bool'], +'RecursiveDirectoryIterator::key' => ['string'], +'RecursiveDirectoryIterator::next' => ['void'], +'RecursiveDirectoryIterator::openFile' => ['SplFileObject', 'mode='=>'string', 'use_include_path='=>'bool', 'context='=>'resource'], +'RecursiveDirectoryIterator::rewind' => ['void'], +'RecursiveDirectoryIterator::seek' => ['void', 'position'=>'int'], +'RecursiveDirectoryIterator::setFileClass' => ['void', 'class_name='=>'string'], +'RecursiveDirectoryIterator::setFlags' => ['void', 'flags='=>'int'], +'RecursiveDirectoryIterator::setInfoClass' => ['void', 'class_name='=>'string'], +'RecursiveDirectoryIterator::valid' => ['bool'], +'RecursiveFilterIterator::__construct' => ['void', 'iterator'=>'RecursiveIterator'], +'RecursiveFilterIterator::accept' => ['bool'], +'RecursiveFilterIterator::current' => ['mixed'], +'RecursiveFilterIterator::getChildren' => ['RecursiveFilterIterator'], +'RecursiveFilterIterator::getInnerIterator' => ['Iterator'], +'RecursiveFilterIterator::hasChildren' => ['bool'], +'RecursiveFilterIterator::key' => ['mixed'], +'RecursiveFilterIterator::next' => ['void'], +'RecursiveFilterIterator::rewind' => ['void'], +'RecursiveFilterIterator::valid' => ['bool'], +'RecursiveIterator::__construct' => ['void'], +'RecursiveIterator::current' => ['mixed'], +'RecursiveIterator::getChildren' => ['RecursiveIterator'], +'RecursiveIterator::hasChildren' => ['bool'], +'RecursiveIterator::key' => ['int|string'], +'RecursiveIterator::next' => ['void'], +'RecursiveIterator::rewind' => ['void'], +'RecursiveIterator::valid' => ['bool'], +'RecursiveIteratorIterator::__construct' => ['void', 'iterator'=>'RecursiveIterator|IteratorAggregate', 'mode='=>'int', 'flags='=>'int'], +'RecursiveIteratorIterator::beginChildren' => ['void'], +'RecursiveIteratorIterator::beginIteration' => ['RecursiveIterator'], +'RecursiveIteratorIterator::callGetChildren' => ['RecursiveIterator'], +'RecursiveIteratorIterator::callHasChildren' => ['bool'], +'RecursiveIteratorIterator::current' => ['mixed'], +'RecursiveIteratorIterator::endChildren' => ['void'], +'RecursiveIteratorIterator::endIteration' => ['RecursiveIterator'], +'RecursiveIteratorIterator::getDepth' => ['int'], +'RecursiveIteratorIterator::getInnerIterator' => ['RecursiveIterator'], +'RecursiveIteratorIterator::getMaxDepth' => ['int|false'], +'RecursiveIteratorIterator::getSubIterator' => ['RecursiveIterator', 'level='=>'int'], +'RecursiveIteratorIterator::key' => ['mixed'], +'RecursiveIteratorIterator::next' => ['void'], +'RecursiveIteratorIterator::nextElement' => ['void'], +'RecursiveIteratorIterator::rewind' => ['void'], +'RecursiveIteratorIterator::setMaxDepth' => ['void', 'max_depth='=>'int'], +'RecursiveIteratorIterator::valid' => ['bool'], +'RecursiveRegexIterator::__construct' => ['void', 'iterator'=>'RecursiveIterator', 'regex'=>'string', 'mode='=>'int', 'flags='=>'int', 'preg_flags='=>'int'], +'RecursiveRegexIterator::accept' => ['bool'], +'RecursiveRegexIterator::current' => [''], +'RecursiveRegexIterator::getChildren' => ['RecursiveRegexIterator'], +'RecursiveRegexIterator::getFlags' => ['int'], +'RecursiveRegexIterator::getInnerIterator' => [''], +'RecursiveRegexIterator::getMode' => ['int'], +'RecursiveRegexIterator::getPregFlags' => ['int'], +'RecursiveRegexIterator::getRegex' => ['string'], +'RecursiveRegexIterator::hasChildren' => ['bool'], +'RecursiveRegexIterator::key' => [''], +'RecursiveRegexIterator::next' => [''], +'RecursiveRegexIterator::rewind' => [''], +'RecursiveRegexIterator::setFlags' => ['void', 'new_flags'=>'int'], +'RecursiveRegexIterator::setMode' => ['void', 'new_mode'=>'int'], +'RecursiveRegexIterator::setPregFlags' => ['void', 'new_flags'=>'int'], +'RecursiveRegexIterator::valid' => [''], +'RecursiveTreeIterator::__construct' => ['void', 'iterator'=>'RecursiveIterator|IteratorAggregate', 'flags='=>'int', 'cit_flags='=>'int', 'mode'=>'int'], +'RecursiveTreeIterator::beginChildren' => ['void'], +'RecursiveTreeIterator::beginIteration' => ['RecursiveIterator'], +'RecursiveTreeIterator::callGetChildren' => ['RecursiveIterator'], +'RecursiveTreeIterator::callHasChildren' => ['bool'], +'RecursiveTreeIterator::current' => ['string'], +'RecursiveTreeIterator::endChildren' => ['void'], +'RecursiveTreeIterator::endIteration' => ['void'], +'RecursiveTreeIterator::getDepth' => ['int'], +'RecursiveTreeIterator::getEntry' => ['string'], +'RecursiveTreeIterator::getInnerIterator' => ['RecursiveIterator'], +'RecursiveTreeIterator::getMaxDepth' => ['false|int'], +'RecursiveTreeIterator::getPostfix' => ['string'], +'RecursiveTreeIterator::getPrefix' => ['string'], +'RecursiveTreeIterator::getSubIterator' => ['RecursiveIterator', 'level='=>'int'], +'RecursiveTreeIterator::key' => ['string'], +'RecursiveTreeIterator::next' => ['void'], +'RecursiveTreeIterator::nextElement' => ['void'], +'RecursiveTreeIterator::rewind' => ['void'], +'RecursiveTreeIterator::setMaxDepth' => ['void', 'max_depth='=>'int'], +'RecursiveTreeIterator::setPostfix' => ['void', 'prefix'=>'string'], +'RecursiveTreeIterator::setPrefixPart' => ['void', 'part'=>'int', 'prefix'=>'string'], +'RecursiveTreeIterator::valid' => ['bool'], +'Redis::__construct' => ['void'], +'Redis::__destruct' => ['void'], +'Redis::_prefix' => ['string', 'value'=>'mixed'], +'Redis::_serialize' => ['mixed', 'value'=>'mixed'], +'Redis::_unserialize' => ['mixed', 'value'=>'string'], +'Redis::append' => ['int', 'key'=>'string', 'value'=>'string'], +'Redis::auth' => ['bool', 'password'=>'string'], +'Redis::bgRewriteAOF' => ['bool'], +'Redis::bgSave' => ['bool'], +'Redis::bitCount' => ['int', 'key'=>'string'], +'Redis::bitOp' => ['int', 'operation'=>'string', 'ret_key'=>'string', 'key'=>'string', '...other_keys='=>'string'], +'Redis::bitpos' => ['int', 'key'=>'string', 'bit'=>'int', 'start='=>'int', 'end='=>'int'], +'Redis::blPop' => ['array', 'keys'=>'string[]', 'timeout'=>'int'], +'Redis::blPop\'1' => ['array', 'key'=>'string', 'timeout_or_key'=>'int|string', '...extra_args'=>'int|string'], +'Redis::brPop' => ['array', 'keys'=>'string[]', 'timeout'=>'int'], +'Redis::brPop\'1' => ['array', 'key'=>'string', 'timeout_or_key'=>'int|string', '...extra_args'=>'int|string'], +'Redis::brpoplpush' => ['string|false', 'srcKey'=>'string', 'dstKey'=>'string', 'timeout'=>'int'], +'Redis::clearLastError' => ['bool'], +'Redis::client' => ['mixed', 'command'=>'string', 'arg='=>'string'], +'Redis::close' => ['bool'], +'Redis::command' => ['', '...args'=>''], +'Redis::config' => ['string', 'operation'=>'string', 'key'=>'string', 'value='=>'string'], +'Redis::connect' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'float', 'reserved='=>'null', 'retry_interval='=>'?int', 'read_timeout='=>'float'], +'Redis::dbSize' => ['int'], +'Redis::debug' => ['', 'key'=>''], +'Redis::decr' => ['int', 'key'=>'string'], +'Redis::decrBy' => ['int', 'key'=>'string', 'value'=>'int'], +'Redis::decrByFloat' => ['float', 'key'=>'string', 'value'=>'float'], +'Redis::del' => ['int', 'key'=>'string', '...args'=>'string'], +'Redis::del\'1' => ['int', 'key'=>'string[]'], +'Redis::delete' => ['int', 'key'=>'string', '...args'=>'string'], +'Redis::delete\'1' => ['int', 'key'=>'string[]'], +'Redis::discard' => [''], +'Redis::dump' => ['string|false', 'key'=>'string'], +'Redis::echo' => ['string', 'message'=>'string'], +'Redis::eval' => ['mixed', 'script'=>'', 'args='=>'', 'numKeys='=>''], +'Redis::evalSha' => ['mixed', 'scriptSha'=>'string', 'args='=>'array', 'numKeys='=>'int'], +'Redis::evaluate' => ['mixed', 'script'=>'string', 'args='=>'array', 'numKeys='=>'int'], +'Redis::evaluateSha' => ['', 'scriptSha'=>'string', 'args='=>'array', 'numKeys='=>'int'], +'Redis::exec' => ['array'], +'Redis::exists' => ['int', 'keys'=>'string|string[]'], +'Redis::exists\'1' => ['int', '...keys'=>'string'], +'Redis::expire' => ['bool', 'key'=>'string', 'ttl'=>'int'], +'Redis::expireAt' => ['bool', 'key'=>'string', 'expiry'=>'int'], +'Redis::flushAll' => ['bool', 'async='=>'bool'], +'Redis::flushDb' => ['bool', 'async='=>'bool'], +'Redis::geoAdd' => ['int', 'key'=>'string', 'longitude'=>'float', 'latitude'=>'float', 'member'=>'string', '...other_triples='=>'string|int|float'], +'Redis::geoDist' => ['float', 'key'=>'string', 'member1'=>'string', 'member2'=>'string', 'unit='=>'string'], +'Redis::geoHash' => ['array', 'key'=>'string', 'member'=>'string', '...other_members='=>'string'], +'Redis::geoPos' => ['array', 'key'=>'string', 'member'=>'string', '...members='=>'string'], +'Redis::geoRadius' => ['array|int', 'key'=>'string', 'longitude'=>'float', 'latitude'=>'float', 'radius'=>'float', 'unit'=>'float', 'options='=>'array'], +'Redis::geoRadiusByMember' => ['array|int', 'key'=>'string', 'member'=>'string', 'radius'=>'float', 'units'=>'string', 'options='=>'array'], +'Redis::get' => ['string|false', 'key'=>'string'], +'Redis::getAuth' => ['string|false|null'], +'Redis::getBit' => ['int', 'key'=>'string', 'offset'=>'int'], +'Redis::getDBNum' => ['int|false'], +'Redis::getHost' => ['string|false'], +'Redis::getKeys' => ['array', 'pattern'=>'string'], +'Redis::getLastError' => ['?string'], +'Redis::getMode' => ['int'], +'Redis::getMultiple' => ['array', 'keys'=>'string[]'], +'Redis::getOption' => ['int', 'name'=>'int'], +'Redis::getPersistentID' => ['string|false|null'], +'Redis::getPort' => ['int|false'], +'Redis::getRange' => ['int', 'key'=>'string', 'start'=>'int', 'end'=>'int'], +'Redis::getReadTimeout' => ['float|false'], +'Redis::getSet' => ['string', 'key'=>'string', 'string'=>'string'], +'Redis::getTimeout' => ['float|false'], +'Redis::hDel' => ['int|false', 'key'=>'string', 'hashKey1'=>'string', '...otherHashKeys='=>'string'], +'Redis::hExists' => ['bool', 'key'=>'string', 'hashKey'=>'string'], +'Redis::hGet' => ['string|false', 'key'=>'string', 'hashKey'=>'string'], +'Redis::hGetAll' => ['array', 'key'=>'string'], +'Redis::hIncrBy' => ['int', 'key'=>'string', 'hashKey'=>'string', 'value'=>'int'], +'Redis::hIncrByFloat' => ['float', 'key'=>'string', 'field'=>'string', 'increment'=>'float'], +'Redis::hKeys' => ['array', 'key'=>'string'], +'Redis::hLen' => ['int|false', 'key'=>'string'], +'Redis::hMGet' => ['array', 'key'=>'string', 'hashKeys'=>'array'], +'Redis::hMSet' => ['bool', 'key'=>'string', 'hashKeys'=>'array'], +'Redis::hScan' => ['array', 'key'=>'string', '&iterator'=>'int', 'pattern='=>'string', 'count='=>'int'], +'Redis::hSet' => ['int|false', 'key'=>'string', 'hashKey'=>'string', 'value'=>'string'], +'Redis::hSetNx' => ['bool', 'key'=>'string', 'hashKey'=>'string', 'value'=>'string'], +'Redis::hStrLen' => ['', 'key'=>'', 'member'=>''], +'Redis::hVals' => ['array', 'key'=>'string'], +'Redis::incr' => ['int', 'key'=>'string'], +'Redis::incrBy' => ['int', 'key'=>'string', 'value'=>'int'], +'Redis::incrByFloat' => ['float', 'key'=>'string', 'value'=>'float'], +'Redis::info' => ['array', 'option='=>'string'], +'Redis::isConnected' => ['bool'], +'Redis::keys' => ['array', 'pattern'=>'string'], +'Redis::lastSave' => ['int'], +'Redis::lGet' => ['string', 'key'=>'string', 'index'=>'int'], +'Redis::lGetRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int'], +'Redis::lIndex' => ['string|false', 'key'=>'string', 'index'=>'int'], +'Redis::lInsert' => ['int', 'key'=>'string', 'position'=>'int', 'pivot'=>'string', 'value'=>'string'], +'Redis::listTrim' => ['', 'key'=>'string', 'start'=>'int', 'stop'=>'int'], +'Redis::lLen' => ['int|false', 'key'=>'string'], +'Redis::lPop' => ['string|false', 'key'=>'string'], +'Redis::lPush' => ['int|false', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'], +'Redis::lPushx' => ['int|false', 'key'=>'string', 'value'=>'string'], +'Redis::lRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int'], +'Redis::lRem' => ['int|false', 'key'=>'string', 'value'=>'string', 'count'=>'int'], +'Redis::lRemove' => ['int', 'key'=>'string', 'value'=>'string', 'count'=>'int'], +'Redis::lSet' => ['bool', 'key'=>'string', 'index'=>'int', 'value'=>'string'], +'Redis::lSize' => ['int', 'key'=>'string'], +'Redis::lTrim' => ['array|false', 'key'=>'string', 'start'=>'int', 'stop'=>'int'], +'Redis::mGet' => ['array', 'keys'=>'string[]'], +'Redis::migrate' => ['bool', 'host'=>'string', 'port'=>'int', 'key'=>'string|string[]', 'db'=>'int', 'timeout'=>'int', 'copy='=>'bool', 'replace='=>'bool'], +'Redis::move' => ['bool', 'key'=>'string', 'dbindex'=>'int'], +'Redis::mSet' => ['bool', 'pairs'=>'array'], +'Redis::mSetNx' => ['bool', 'pairs'=>'array'], +'Redis::multi' => ['Redis', 'mode='=>'int'], +'Redis::object' => ['string|long|false', 'info'=>'string', 'key'=>'string'], +'Redis::open' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'float', 'reserved='=>'null', 'retry_interval='=>'?int', 'read_timeout='=>'float'], +'Redis::pconnect' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'float', 'persistent_id='=>'string', 'retry_interval='=>'?int'], +'Redis::persist' => ['bool', 'key'=>'string'], +'Redis::pExpire' => ['bool', 'key'=>'string', 'ttl'=>'int'], +'Redis::pexpireAt' => ['bool', 'key'=>'string', 'expiry'=>'int'], +'Redis::pfAdd' => ['bool', 'key'=>'string', 'elements'=>'array'], +'Redis::pfCount' => ['int', 'key'=>'array|string'], +'Redis::pfMerge' => ['bool', 'destkey'=>'string', 'sourcekeys'=>'array'], +'Redis::ping' => ['string'], +'Redis::pipeline' => ['Redis'], +'Redis::popen' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'float', 'persistent_id='=>'string', 'retry_interval='=>'?int'], +'Redis::psetex' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'], +'Redis::psubscribe' => ['', 'patterns'=>'array', 'callback'=>'array|string'], +'Redis::pttl' => ['int|false', 'key'=>'string'], +'Redis::publish' => ['int', 'channel'=>'string', 'message'=>'string'], +'Redis::pubsub' => ['array|int', 'keyword'=>'string', 'argument='=>'array|string'], +'Redis::punsubscribe' => ['', 'pattern'=>'string', '...other_patterns='=>'string'], +'Redis::randomKey' => ['string'], +'Redis::rawCommand' => ['mixed', 'command'=>'string', '...arguments='=>'mixed'], +'Redis::rename' => ['bool', 'srckey'=>'string', 'dstkey'=>'string'], +'Redis::renameKey' => ['bool', 'srckey'=>'string', 'dstkey'=>'string'], +'Redis::renameNx' => ['bool', 'srckey'=>'string', 'dstkey'=>'string'], +'Redis::resetStat' => ['bool'], +'Redis::restore' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'], +'Redis::role' => ['array', 'nodeParams'=>'string|array{0:string,1:int}'], +'Redis::rPop' => ['string|false', 'key'=>'string'], +'Redis::rpoplpush' => ['string', 'srcKey'=>'string', 'dstKey'=>'string'], +'Redis::rPush' => ['int|false', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'], +'Redis::rPushx' => ['int|false', 'key'=>'string', 'value'=>'string'], +'Redis::sAdd' => ['int|false', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'], +'Redis::sAddArray' => ['bool', 'key'=>'string', 'values'=>'array'], +'Redis::save' => ['bool'], +'Redis::scan' => ['array|false', '&rw_iterator'=>'?int', 'pattern='=>'?string', 'count='=>'?int'], +'Redis::sCard' => ['int', 'key'=>'string'], +'Redis::sContains' => ['', 'key'=>'string', 'value'=>'string'], +'Redis::script' => ['mixed', 'command'=>'string', '...args='=>'mixed'], +'Redis::sDiff' => ['array', 'key1'=>'string', '...other_keys='=>'string'], +'Redis::sDiffStore' => ['int|false', 'dstKey'=>'string', 'key'=>'string', '...other_keys='=>'string'], +'Redis::select' => ['bool', 'dbindex'=>'int'], +'Redis::sendEcho' => ['string', 'msg'=>'string'], +'Redis::set' => ['bool', 'key'=>'string', 'value'=>'mixed', 'options='=>'array'], +'Redis::set\'1' => ['bool', 'key'=>'string', 'value'=>'mixed', 'timeout='=>'int'], +'Redis::setBit' => ['int', 'key'=>'string', 'offset'=>'int', 'value'=>'int'], +'Redis::setEx' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'], +'Redis::setNx' => ['bool', 'key'=>'string', 'value'=>'string'], +'Redis::setOption' => ['bool', 'name'=>'int', 'value'=>'mixed'], +'Redis::setRange' => ['int', 'key'=>'string', 'offset'=>'int', 'end'=>'int'], +'Redis::setTimeout' => ['', 'key'=>'string', 'ttl'=>'int'], +'Redis::sGetMembers' => ['', 'key'=>'string'], +'Redis::sInter' => ['array|false', 'key'=>'string', '...other_keys='=>'string'], +'Redis::sInterStore' => ['int|false', 'dstKey'=>'string', 'key'=>'string', '...other_keys='=>'string'], +'Redis::sIsMember' => ['bool', 'key'=>'string', 'value'=>'string'], +'Redis::slave' => ['bool', 'host'=>'string', 'port'=>'int'], +'Redis::slave\'1' => ['bool', 'host'=>'string', 'port'=>'int'], +'Redis::slaveof' => ['bool', 'host='=>'string', 'port='=>'int'], +'Redis::slowLog' => ['mixed', 'operation'=>'string', 'length='=>'int'], +'Redis::sMembers' => ['array', 'key'=>'string'], +'Redis::sMove' => ['bool', 'srcKey'=>'string', 'dstKey'=>'string', 'member'=>'string'], +'Redis::sort' => ['array|int', 'key'=>'string', 'options='=>'array'], +'Redis::sortAsc' => ['array', 'key'=>'string', 'pattern='=>'string', 'get='=>'string', 'start='=>'int', 'end='=>'int', 'getList='=>'bool'], +'Redis::sortAscAlpha' => ['array', 'key'=>'string', 'pattern='=>'', 'get='=>'string', 'start='=>'int', 'end='=>'int', 'getList='=>'bool'], +'Redis::sortDesc' => ['array', 'key'=>'string', 'pattern='=>'', 'get='=>'string', 'start='=>'int', 'end='=>'int', 'getList='=>'bool'], +'Redis::sortDescAlpha' => ['array', 'key'=>'string', 'pattern='=>'', 'get='=>'string', 'start='=>'int', 'end='=>'int', 'getList='=>'bool'], +'Redis::sPop' => ['string|false', 'key'=>'string'], +'Redis::sRandMember' => ['array|string|false', 'key'=>'string', 'count='=>'int'], +'Redis::sRem' => ['int', 'key'=>'string', 'member1'=>'string', '...other_members='=>'string'], +'Redis::sRemove' => ['int', 'key'=>'string', 'member1'=>'string', '...other_members='=>'string'], +'Redis::sScan' => ['array|bool', 'key'=>'string', '&iterator'=>'int', 'pattern='=>'string', 'count='=>'int'], +'Redis::sSize' => ['int', 'key'=>'string'], +'Redis::strLen' => ['int', 'key'=>'string'], +'Redis::subscribe' => ['mixed|null', 'channels'=>'array', 'callback'=>'string|array'], +'Redis::substr' => ['', 'key'=>'string', 'start'=>'int', 'end'=>'int'], +'Redis::sUnion' => ['array', 'key'=>'string', '...other_keys='=>'string'], +'Redis::sUnionStore' => ['int', 'dstKey'=>'string', 'key'=>'string', '...other_keys='=>'string'], +'Redis::swapdb' => ['bool', 'srcdb'=>'int', 'dstdb'=>'int'], +'Redis::time' => ['array'], +'Redis::ttl' => ['int|false', 'key'=>'string'], +'Redis::type' => ['int', 'key'=>'string'], +'Redis::unlink' => ['int', 'key'=>'string', '...args'=>'string'], +'Redis::unlink\'1' => ['int', 'key'=>'string[]'], +'Redis::unsubscribe' => ['', 'channel'=>'string', '...other_channels='=>'string'], +'Redis::unwatch' => [''], +'Redis::wait' => ['int', 'numSlaves'=>'int', 'timeout'=>'int'], +'Redis::watch' => ['void', 'key'=>'string', '...other_keys='=>'string'], +'Redis::xack' => ['', 'str_key'=>'string', 'str_group'=>'string', 'arr_ids'=>'array'], +'Redis::xadd' => ['', 'str_key'=>'string', 'str_id'=>'string', 'arr_fields'=>'array', 'i_maxlen='=>'', 'boo_approximate='=>''], +'Redis::xclaim' => ['', 'str_key'=>'string', 'str_group'=>'string', 'str_consumer'=>'string', 'i_min_idle'=>'', 'arr_ids'=>'array', 'arr_opts='=>'array'], +'Redis::xdel' => ['', 'str_key'=>'string', 'arr_ids'=>'array'], +'Redis::xgroup' => ['', 'str_operation'=>'string', 'str_key='=>'string', 'str_arg1='=>'', 'str_arg2='=>'', 'str_arg3='=>''], +'Redis::xinfo' => ['', 'str_cmd'=>'string', 'str_key='=>'string', 'str_group='=>'string'], +'Redis::xlen' => ['', 'key'=>''], +'Redis::xpending' => ['', 'str_key'=>'string', 'str_group'=>'string', 'str_start='=>'', 'str_end='=>'', 'i_count='=>'', 'str_consumer='=>'string'], +'Redis::xrange' => ['', 'str_key'=>'string', 'str_start'=>'', 'str_end'=>'', 'i_count='=>''], +'Redis::xread' => ['', 'arr_streams'=>'array', 'i_count='=>'', 'i_block='=>''], +'Redis::xreadgroup' => ['', 'str_group'=>'string', 'str_consumer'=>'string', 'arr_streams'=>'array', 'i_count='=>'', 'i_block='=>''], +'Redis::xrevrange' => ['', 'str_key'=>'string', 'str_start'=>'', 'str_end'=>'', 'i_count='=>''], +'Redis::xtrim' => ['', 'str_key'=>'string', 'i_maxlen'=>'', 'boo_approximate='=>''], +'Redis::zAdd' => ['int', 'key'=>'string', 'score1'=>'float', 'value1'=>'string', 'score2='=>'float', 'value2='=>'string', 'scoreN='=>'float', 'valueN='=>'string'], +'Redis::zAdd\'1' => ['int', 'options'=>'array', 'key'=>'string', 'score1'=>'float', 'value1'=>'string', 'score2='=>'float', 'value2='=>'string', 'scoreN='=>'float', 'valueN='=>'string'], +'Redis::zCard' => ['int', 'key'=>'string'], +'Redis::zCount' => ['int', 'key'=>'string', 'start'=>'string', 'end'=>'string'], +'Redis::zDelete' => ['int', 'key'=>'string', 'member'=>'string', '...other_members='=>'string'], +'Redis::zDeleteRangeByRank' => ['', 'key'=>'string', 'start'=>'int', 'end'=>'int'], +'Redis::zDeleteRangeByScore' => ['', 'key'=>'string', 'start'=>'float', 'end'=>'float'], +'Redis::zIncrBy' => ['float', 'key'=>'string', 'value'=>'float', 'member'=>'string'], +'Redis::zInter' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'], +'Redis::zInterStore' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'], +'Redis::zLexCount' => ['int', 'key'=>'string', 'min'=>'string', 'max'=>'string'], +'Redis::zRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'withscores='=>'bool'], +'Redis::zRangeByLex' => ['array|false', 'key'=>'string', 'min'=>'int', 'max'=>'int', 'offset='=>'int', 'limit='=>'int'], +'Redis::zRangeByScore' => ['array', 'key'=>'string', 'start'=>'int|string', 'end'=>'int|string', 'options='=>'array'], +'Redis::zRank' => ['int', 'key'=>'string', 'member'=>'string'], +'Redis::zRem' => ['int', 'key'=>'string', 'member'=>'string', '...other_members='=>'string'], +'Redis::zRemove' => ['int', 'key'=>'string', 'member'=>'string', '...other_members='=>'string'], +'Redis::zRemoveRangeByRank' => ['int', 'key'=>'string', 'start'=>'int', 'end'=>'int'], +'Redis::zRemoveRangeByScore' => ['int', 'key'=>'string', 'start'=>'float|string', 'end'=>'float|string'], +'Redis::zRemRangeByLex' => ['int', 'key'=>'string', 'min'=>'string', 'max'=>'string'], +'Redis::zRemRangeByRank' => ['int', 'key'=>'string', 'start'=>'int', 'end'=>'int'], +'Redis::zRemRangeByScore' => ['int', 'key'=>'string', 'start'=>'float|string', 'end'=>'float|string'], +'Redis::zReverseRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'withscore='=>'bool'], +'Redis::zRevRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'withscore='=>'bool'], +'Redis::zRevRangeByLex' => ['array', 'key'=>'string', 'min'=>'string', 'max'=>'string', 'offset='=>'int', 'limit='=>'int'], +'Redis::zRevRangeByScore' => ['array', 'key'=>'string', 'start'=>'string', 'end'=>'string', 'options='=>'array'], +'Redis::zRevRank' => ['int', 'key'=>'string', 'member'=>'string'], +'Redis::zScan' => ['array|bool', 'key'=>'string', '&iterator'=>'int', 'pattern='=>'string', 'count='=>'int'], +'Redis::zScore' => ['float|false', 'key'=>'string', 'member'=>'string'], +'Redis::zSize' => ['', 'key'=>'string'], +'Redis::zUnion' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'], +'Redis::zUnionStore' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'], +'RedisArray::__call' => ['mixed', 'function_name'=>'string', 'arguments'=>'array'], +'RedisArray::__construct' => ['void', 'name='=>'string', 'hosts='=>'?array', 'opts='=>'?array'], +'RedisArray::_continuum' => [''], +'RedisArray::_distributor' => [''], +'RedisArray::_function' => ['string'], +'RedisArray::_hosts' => ['array'], +'RedisArray::_instance' => ['', 'host'=>''], +'RedisArray::_rehash' => ['', 'callable='=>'callable'], +'RedisArray::_target' => ['string', 'key'=>'string'], +'RedisArray::bgsave' => [''], +'RedisArray::del' => ['bool', 'key'=>'string', '...args'=>'string'], +'RedisArray::delete' => ['bool', 'key'=>'string', '...args'=>'string'], +'RedisArray::delete\'1' => ['bool', 'key'=>'string[]'], +'RedisArray::discard' => [''], +'RedisArray::exec' => ['array'], +'RedisArray::flushAll' => ['bool', 'async='=>'bool'], +'RedisArray::flushDb' => ['bool', 'async='=>'bool'], +'RedisArray::getMultiple' => ['', 'keys'=>''], +'RedisArray::getOption' => ['', 'opt'=>''], +'RedisArray::info' => ['array'], +'RedisArray::keys' => ['array', 'pattern'=>''], +'RedisArray::mGet' => ['array', 'keys'=>'string[]'], +'RedisArray::mSet' => ['bool', 'pairs'=>'array'], +'RedisArray::multi' => ['RedisArray', 'host'=>'string', 'mode='=>'int'], +'RedisArray::ping' => ['string'], +'RedisArray::save' => ['bool'], +'RedisArray::select' => ['', 'index'=>''], +'RedisArray::setOption' => ['', 'opt'=>'', 'value'=>''], +'RedisArray::unlink' => ['int', 'key'=>'string', '...other_keys='=>'string'], +'RedisArray::unlink\'1' => ['int', 'key'=>'string[]'], +'RedisArray::unwatch' => [''], +'RedisCluster::__construct' => ['void', 'name'=>'?string', 'seeds='=>'string[]', 'timeout='=>'float', 'readTimeout='=>'float', 'persistent='=>'bool', 'auth='=>'?string'], +'RedisCluster::_masters' => ['array'], +'RedisCluster::_prefix' => ['string', 'value'=>'mixed'], +'RedisCluster::_redir' => [''], +'RedisCluster::_serialize' => ['mixed', 'value'=>'mixed'], +'RedisCluster::_unserialize' => ['mixed', 'value'=>'string'], +'RedisCluster::append' => ['int', 'key'=>'string', 'value'=>'string'], +'RedisCluster::bgrewriteaof' => ['bool', 'nodeParams'=>'string|array{0:string,1:int}'], +'RedisCluster::bgsave' => ['bool', 'nodeParams'=>'string|array{0:string,1:int}'], +'RedisCluster::bitCount' => ['int', 'key'=>'string'], +'RedisCluster::bitOp' => ['int', 'operation'=>'string', 'retKey'=>'string', 'key1'=>'string', '...other_keys='=>'string'], +'RedisCluster::bitpos' => ['int', 'key'=>'string', 'bit'=>'int', 'start='=>'int', 'end='=>'int'], +'RedisCluster::blPop' => ['array', 'keys'=>'array', 'timeout'=>'int'], +'RedisCluster::brPop' => ['array', 'keys'=>'array', 'timeout'=>'int'], +'RedisCluster::brpoplpush' => ['string|false', 'srcKey'=>'string', 'dstKey'=>'string', 'timeout'=>'int'], +'RedisCluster::clearLastError' => ['bool'], +'RedisCluster::client' => ['', 'nodeParams'=>'string|array{0:string,1:int}', 'subCmd='=>'string', '...args='=>''], +'RedisCluster::close' => [''], +'RedisCluster::cluster' => ['mixed', 'nodeParams'=>'string|array{0:string,1:int}', 'command'=>'string', 'arguments='=>'mixed'], +'RedisCluster::command' => ['array|bool'], +'RedisCluster::config' => ['array|bool', 'nodeParams'=>'string|array{0:string,1:int}', 'operation'=>'string', 'key'=>'string', 'value='=>'string'], +'RedisCluster::dbSize' => ['int', 'nodeParams'=>'string|array{0:string,1:int}'], +'RedisCluster::decr' => ['int', 'key'=>'string'], +'RedisCluster::decrBy' => ['int', 'key'=>'string', 'value'=>'int'], +'RedisCluster::del' => ['int', 'key'=>'string', '...other_keys='=>'string'], +'RedisCluster::del\'1' => ['int', 'key'=>'string[]'], +'RedisCluster::discard' => [''], +'RedisCluster::dump' => ['string|false', 'key'=>'string'], +'RedisCluster::echo' => ['string', 'nodeParams'=>'string|array{0:string,1:int}', 'msg'=>'string'], +'RedisCluster::eval' => ['mixed', 'script'=>'', 'args='=>'', 'numKeys='=>''], +'RedisCluster::evalSha' => ['mixed', 'scriptSha'=>'string', 'args='=>'array', 'numKeys='=>'int'], +'RedisCluster::exec' => ['array|void'], +'RedisCluster::exists' => ['bool', 'key'=>'string'], +'RedisCluster::expire' => ['bool', 'key'=>'string', 'ttl'=>'int'], +'RedisCluster::expireAt' => ['bool', 'key'=>'string', 'timestamp'=>'int'], +'RedisCluster::flushAll' => ['bool', 'nodeParams'=>'string|array{0:string,1:int}', 'async='=>'bool'], +'RedisCluster::flushDB' => ['bool', 'nodeParams'=>'string|array{0:string,1:int}', 'async='=>'bool'], +'RedisCluster::geoAdd' => ['int', 'key'=>'string', 'longitude'=>'float', 'latitude'=>'float', 'member'=>'string', '...other_members='=>'float|string'], +'RedisCluster::geoDist' => ['', 'key'=>'string', 'member1'=>'string', 'member2'=>'string', 'unit='=>'string'], +'RedisCluster::geohash' => ['array', 'key'=>'string', 'member'=>'string', '...other_members='=>'string'], +'RedisCluster::geopos' => ['array', 'key'=>'string', 'member'=>'string', '...other_members='=>'string'], +'RedisCluster::geoRadius' => ['', 'key'=>'string', 'longitude'=>'float', 'latitude'=>'float', 'radius'=>'float', 'radiusUnit'=>'string', 'options='=>'array'], +'RedisCluster::geoRadiusByMember' => ['string[]', 'key'=>'string', 'member'=>'string', 'radius'=>'float', 'radiusUnit'=>'string', 'options='=>'array'], +'RedisCluster::get' => ['string|false', 'key'=>'string'], +'RedisCluster::getBit' => ['int', 'key'=>'string', 'offset'=>'int'], +'RedisCluster::getLastError' => ['?string'], +'RedisCluster::getMode' => ['int'], +'RedisCluster::getOption' => ['int', 'name'=>'string'], +'RedisCluster::getRange' => ['string', 'key'=>'string', 'start'=>'int', 'end'=>'int'], +'RedisCluster::getSet' => ['string', 'key'=>'string', 'value'=>'string'], +'RedisCluster::hDel' => ['int|false', 'key'=>'string', 'hashKey'=>'string', '...other_hashKeys='=>'string[]'], +'RedisCluster::hExists' => ['bool', 'key'=>'string', 'hashKey'=>'string'], +'RedisCluster::hGet' => ['string|false', 'key'=>'string', 'hashKey'=>'string'], +'RedisCluster::hGetAll' => ['array', 'key'=>'string'], +'RedisCluster::hIncrBy' => ['int', 'key'=>'string', 'hashKey'=>'string', 'value'=>'int'], +'RedisCluster::hIncrByFloat' => ['float', 'key'=>'string', 'field'=>'string', 'increment'=>'float'], +'RedisCluster::hKeys' => ['array', 'key'=>'string'], +'RedisCluster::hLen' => ['int|false', 'key'=>'string'], +'RedisCluster::hMGet' => ['array', 'key'=>'string', 'hashKeys'=>'array'], +'RedisCluster::hMSet' => ['bool', 'key'=>'string', 'hashKeys'=>'array'], +'RedisCluster::hScan' => ['array', 'key'=>'string', '&iterator'=>'int', 'pattern='=>'string', 'count='=>'int'], +'RedisCluster::hSet' => ['int', 'key'=>'string', 'hashKey'=>'string', 'value'=>'string'], +'RedisCluster::hSetNx' => ['bool', 'key'=>'string', 'hashKey'=>'string', 'value'=>'string'], +'RedisCluster::hStrlen' => ['int', 'key'=>'string', 'member'=>'string'], +'RedisCluster::hVals' => ['array', 'key'=>'string'], +'RedisCluster::incr' => ['int', 'key'=>'string'], +'RedisCluster::incrBy' => ['int', 'key'=>'string', 'value'=>'int'], +'RedisCluster::incrByFloat' => ['float', 'key'=>'string', 'increment'=>'float'], +'RedisCluster::info' => ['array', 'nodeParams'=>'string|array{0:string,1:int}', 'option='=>'string'], +'RedisCluster::keys' => ['array', 'pattern'=>'string'], +'RedisCluster::lastSave' => ['int', 'nodeParams'=>'string|array{0:string,1:int}'], +'RedisCluster::lGet' => ['', 'key'=>'string', 'index'=>'int'], +'RedisCluster::lIndex' => ['string|false', 'key'=>'string', 'index'=>'int'], +'RedisCluster::lInsert' => ['int', 'key'=>'string', 'position'=>'int', 'pivot'=>'string', 'value'=>'string'], +'RedisCluster::lLen' => ['int', 'key'=>'string'], +'RedisCluster::lPop' => ['string|false', 'key'=>'string'], +'RedisCluster::lPush' => ['int|false', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'], +'RedisCluster::lPushx' => ['int|false', 'key'=>'string', 'value'=>'string'], +'RedisCluster::lRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int'], +'RedisCluster::lRem' => ['int|false', 'key'=>'string', 'value'=>'string', 'count'=>'int'], +'RedisCluster::lSet' => ['bool', 'key'=>'string', 'index'=>'int', 'value'=>'string'], +'RedisCluster::lTrim' => ['array|false', 'key'=>'string', 'start'=>'int', 'stop'=>'int'], +'RedisCluster::mget' => ['array', 'array'=>'array'], +'RedisCluster::mset' => ['bool', 'array'=>'array'], +'RedisCluster::msetnx' => ['int', 'array'=>'array'], +'RedisCluster::multi' => ['Redis', 'mode='=>'int'], +'RedisCluster::object' => ['string|int|false', 'string'=>'string', 'key'=>'string'], +'RedisCluster::persist' => ['bool', 'key'=>'string'], +'RedisCluster::pExpire' => ['bool', 'key'=>'string', 'ttl'=>'int'], +'RedisCluster::pExpireAt' => ['bool', 'key'=>'string', 'timestamp'=>'int'], +'RedisCluster::pfAdd' => ['bool', 'key'=>'string', 'elements'=>'array'], +'RedisCluster::pfCount' => ['int', 'key'=>'string'], +'RedisCluster::pfMerge' => ['bool', 'destKey'=>'string', 'sourceKeys'=>'array'], +'RedisCluster::ping' => ['string', 'nodeParams'=>'string|array{0:string,1:int}'], +'RedisCluster::psetex' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'], +'RedisCluster::psubscribe' => ['mixed', 'patterns'=>'array', 'callback'=>'string'], +'RedisCluster::pttl' => ['int', 'key'=>'string'], +'RedisCluster::publish' => ['int', 'channel'=>'string', 'message'=>'string'], +'RedisCluster::pubsub' => ['array', 'nodeParams'=>'string', 'keyword'=>'string', '...argument='=>'string'], +'RedisCluster::punSubscribe' => ['', 'channels'=>'', 'callback'=>''], +'RedisCluster::randomKey' => ['string', 'nodeParams'=>'string|array{0:string,1:int}'], +'RedisCluster::rawCommand' => ['mixed', 'nodeParams'=>'string|array{0:string,1:int}', 'command'=>'string', 'arguments='=>'mixed'], +'RedisCluster::rename' => ['bool', 'srcKey'=>'string', 'dstKey'=>'string'], +'RedisCluster::renameNx' => ['bool', 'srcKey'=>'string', 'dstKey'=>'string'], +'RedisCluster::restore' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'], +'RedisCluster::role' => ['array'], +'RedisCluster::rPop' => ['string|false', 'key'=>'string'], +'RedisCluster::rpoplpush' => ['string|false', 'srcKey'=>'string', 'dstKey'=>'string'], +'RedisCluster::rPush' => ['int|false', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'], +'RedisCluster::rPushx' => ['int|false', 'key'=>'string', 'value'=>'string'], +'RedisCluster::sAdd' => ['int|false', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'], +'RedisCluster::sAddArray' => ['int|false', 'key'=>'string', 'valueArray'=>'array'], +'RedisCluster::save' => ['bool', 'nodeParams'=>'string|array{0:string,1:int}'], +'RedisCluster::scan' => ['array|false', '&iterator'=>'int', 'nodeParams'=>'string|array{0:string,1:int}', 'pattern='=>'string', 'count='=>'int'], +'RedisCluster::sCard' => ['int', 'key'=>'string'], +'RedisCluster::script' => ['string|bool|array', 'nodeParams'=>'string|array{0:string,1:int}', 'command'=>'string', 'script='=>'string', '...other_scripts='=>'string[]'], +'RedisCluster::sDiff' => ['list', 'key1'=>'string', 'key2'=>'string', '...other_keys='=>'string'], +'RedisCluster::sDiffStore' => ['int', 'dstKey'=>'string', 'key1'=>'string', '...other_keys='=>'string'], +'RedisCluster::set' => ['bool', 'key'=>'string', 'value'=>'string', 'timeout='=>'array|int'], +'RedisCluster::setBit' => ['int', 'key'=>'string', 'offset'=>'int', 'value'=>'bool|int'], +'RedisCluster::setex' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'], +'RedisCluster::setnx' => ['bool', 'key'=>'string', 'value'=>'string'], +'RedisCluster::setOption' => ['bool', 'name'=>'string', 'value'=>'string'], +'RedisCluster::setRange' => ['string', 'key'=>'string', 'offset'=>'int', 'value'=>'string'], +'RedisCluster::sInter' => ['list', 'key'=>'string', '...other_keys='=>'string'], +'RedisCluster::sInterStore' => ['int', 'dstKey'=>'string', 'key'=>'string', '...other_keys='=>'string'], +'RedisCluster::sIsMember' => ['bool', 'key'=>'string', 'value'=>'string'], +'RedisCluster::slowLog' => ['array|int|bool', 'nodeParams'=>'string|array{0:string,1:int}', 'command'=>'string', 'length='=>'int'], +'RedisCluster::sMembers' => ['list', 'key'=>'string'], +'RedisCluster::sMove' => ['bool', 'srcKey'=>'string', 'dstKey'=>'string', 'member'=>'string'], +'RedisCluster::sort' => ['array', 'key'=>'string', 'option='=>'array'], +'RedisCluster::sPop' => ['string', 'key'=>'string'], +'RedisCluster::sRandMember' => ['array|string', 'key'=>'string', 'count='=>'int'], +'RedisCluster::sRem' => ['int', 'key'=>'string', 'member1'=>'string', '...other_members='=>'string'], +'RedisCluster::sScan' => ['array|false', 'key'=>'string', '&iterator'=>'int', 'pattern='=>'null', 'count='=>'int'], +'RedisCluster::strlen' => ['int', 'key'=>'string'], +'RedisCluster::subscribe' => ['mixed', 'channels'=>'array', 'callback'=>'string'], +'RedisCluster::sUnion' => ['list', 'key1'=>'string', '...other_keys='=>'string'], +'RedisCluster::sUnion\'1' => ['list', 'keys'=>'string[]'], +'RedisCluster::sUnionStore' => ['int', 'dstKey'=>'string', 'key1'=>'string', '...other_keys='=>'string'], +'RedisCluster::time' => ['array'], +'RedisCluster::ttl' => ['int', 'key'=>'string'], +'RedisCluster::type' => ['int', 'key'=>'string'], +'RedisCluster::unlink' => ['int', 'key'=>'string', '...other_keys='=>'string'], +'RedisCluster::unSubscribe' => ['', 'channels'=>'', '...other_channels='=>''], +'RedisCluster::unwatch' => [''], +'RedisCluster::watch' => ['void', 'key'=>'string', '...other_keys='=>'string'], +'RedisCluster::xack' => ['', 'str_key'=>'string', 'str_group'=>'string', 'arr_ids'=>'array'], +'RedisCluster::xadd' => ['', 'str_key'=>'string', 'str_id'=>'string', 'arr_fields'=>'array', 'i_maxlen='=>'', 'boo_approximate='=>''], +'RedisCluster::xclaim' => ['', 'str_key'=>'string', 'str_group'=>'string', 'str_consumer'=>'string', 'i_min_idle'=>'', 'arr_ids'=>'array', 'arr_opts='=>'array'], +'RedisCluster::xdel' => ['', 'str_key'=>'string', 'arr_ids'=>'array'], +'RedisCluster::xgroup' => ['', 'str_operation'=>'string', 'str_key='=>'string', 'str_arg1='=>'', 'str_arg2='=>'', 'str_arg3='=>''], +'RedisCluster::xinfo' => ['', 'str_cmd'=>'string', 'str_key='=>'string', 'str_group='=>'string'], +'RedisCluster::xlen' => ['', 'key'=>''], +'RedisCluster::xpending' => ['', 'str_key'=>'string', 'str_group'=>'string', 'str_start='=>'', 'str_end='=>'', 'i_count='=>'', 'str_consumer='=>'string'], +'RedisCluster::xrange' => ['', 'str_key'=>'string', 'str_start'=>'', 'str_end'=>'', 'i_count='=>''], +'RedisCluster::xread' => ['', 'arr_streams'=>'array', 'i_count='=>'', 'i_block='=>''], +'RedisCluster::xreadgroup' => ['', 'str_group'=>'string', 'str_consumer'=>'string', 'arr_streams'=>'array', 'i_count='=>'', 'i_block='=>''], +'RedisCluster::xrevrange' => ['', 'str_key'=>'string', 'str_start'=>'', 'str_end'=>'', 'i_count='=>''], +'RedisCluster::xtrim' => ['', 'str_key'=>'string', 'i_maxlen'=>'', 'boo_approximate='=>''], +'RedisCluster::zAdd' => ['int', 'key'=>'string', 'score1'=>'float', 'value1'=>'string', 'score2='=>'float', 'value2='=>'string', 'scoreN='=>'float', 'valueN='=>'string'], +'RedisCluster::zCard' => ['int', 'key'=>'string'], +'RedisCluster::zCount' => ['int', 'key'=>'string', 'start'=>'string', 'end'=>'string'], +'RedisCluster::zIncrBy' => ['float', 'key'=>'string', 'value'=>'float', 'member'=>'string'], +'RedisCluster::zInterStore' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'], +'RedisCluster::zLexCount' => ['int', 'key'=>'string', 'min'=>'int', 'max'=>'int'], +'RedisCluster::zRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'withscores='=>'bool'], +'RedisCluster::zRangeByLex' => ['array', 'key'=>'string', 'min'=>'int', 'max'=>'int', 'offset='=>'int', 'limit='=>'int'], +'RedisCluster::zRangeByScore' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'options='=>'array'], +'RedisCluster::zRank' => ['int', 'key'=>'string', 'member'=>'string'], +'RedisCluster::zRem' => ['int', 'key'=>'string', 'member1'=>'string', '...other_members='=>'string'], +'RedisCluster::zRemRangeByLex' => ['array', 'key'=>'string', 'min'=>'int', 'max'=>'int'], +'RedisCluster::zRemRangeByRank' => ['int', 'key'=>'string', 'start'=>'int', 'end'=>'int'], +'RedisCluster::zRemRangeByScore' => ['int', 'key'=>'string', 'start'=>'float|string', 'end'=>'float|string'], +'RedisCluster::zRevRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'withscore='=>'bool'], +'RedisCluster::zRevRangeByLex' => ['array', 'key'=>'string', 'min'=>'int', 'max'=>'int', 'offset='=>'int', 'limit='=>'int'], +'RedisCluster::zRevRangeByScore' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'options='=>'array'], +'RedisCluster::zRevRank' => ['int', 'key'=>'string', 'member'=>'string'], +'RedisCluster::zScan' => ['array|false', 'key'=>'string', '&iterator'=>'int', 'pattern='=>'string', 'count='=>'int'], +'RedisCluster::zScore' => ['float', 'key'=>'string', 'member'=>'string'], +'RedisCluster::zUnionStore' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'], +'Reflection::export' => ['?string', 'r'=>'reflector', 'return='=>'bool'], +'Reflection::getModifierNames' => ['array', 'modifiers'=>'int'], +'ReflectionClass::__clone' => ['void'], +'ReflectionClass::__construct' => ['void', 'argument'=>'object|class-string'], +'ReflectionClass::__toString' => ['string'], +'ReflectionClass::export' => ['?string', 'argument'=>'string|object', 'return='=>'bool'], +'ReflectionClass::getConstant' => ['mixed', 'name'=>'string'], +'ReflectionClass::getConstants' => ['array'], +'ReflectionClass::getConstructor' => ['?ReflectionMethod'], +'ReflectionClass::getDefaultProperties' => ['array'], +'ReflectionClass::getDocComment' => ['string|false'], +'ReflectionClass::getEndLine' => ['int|false'], +'ReflectionClass::getExtension' => ['?ReflectionExtension'], +'ReflectionClass::getExtensionName' => ['string|false'], +'ReflectionClass::getFileName' => ['string|false'], +'ReflectionClass::getInterfaceNames' => ['list'], +'ReflectionClass::getInterfaces' => ['array'], +'ReflectionClass::getMethod' => ['ReflectionMethod', 'name'=>'string'], +'ReflectionClass::getMethods' => ['list', 'filter='=>'int'], +'ReflectionClass::getModifiers' => ['int'], +'ReflectionClass::getName' => ['class-string'], +'ReflectionClass::getNamespaceName' => ['string'], +'ReflectionClass::getParentClass' => ['ReflectionClass|false'], +'ReflectionClass::getProperties' => ['list', 'filter='=>'int'], +'ReflectionClass::getProperty' => ['ReflectionProperty', 'name'=>'string'], +'ReflectionClass::getReflectionConstant' => ['ReflectionClassConstant|false', 'name'=>'string'], +'ReflectionClass::getReflectionConstants' => ['list'], +'ReflectionClass::getShortName' => ['string'], +'ReflectionClass::getStartLine' => ['int|false'], +'ReflectionClass::getStaticProperties' => ['array'], +'ReflectionClass::getStaticPropertyValue' => ['mixed', 'name'=>'string', 'default='=>'mixed'], +'ReflectionClass::getTraitAliases' => ['array|null'], +'ReflectionClass::getTraitNames' => ['list|null'], +'ReflectionClass::getTraits' => ['array'], +'ReflectionClass::hasConstant' => ['bool', 'name'=>'string'], +'ReflectionClass::hasMethod' => ['bool', 'name'=>'string'], +'ReflectionClass::hasProperty' => ['bool', 'name'=>'string'], +'ReflectionClass::implementsInterface' => ['bool', 'interface_name'=>'class-string|ReflectionClass'], +'ReflectionClass::inNamespace' => ['bool'], +'ReflectionClass::isAbstract' => ['bool'], +'ReflectionClass::isAnonymous' => ['bool'], +'ReflectionClass::isCloneable' => ['bool'], +'ReflectionClass::isFinal' => ['bool'], +'ReflectionClass::isInstance' => ['bool', 'object'=>'object'], +'ReflectionClass::isInstantiable' => ['bool'], +'ReflectionClass::isInterface' => ['bool'], +'ReflectionClass::isInternal' => ['bool'], +'ReflectionClass::isIterable' => ['bool'], +'ReflectionClass::isIterateable' => ['bool'], +'ReflectionClass::isSubclassOf' => ['bool', 'class'=>'class-string|ReflectionClass'], +'ReflectionClass::isTrait' => ['bool'], +'ReflectionClass::isUserDefined' => ['bool'], +'ReflectionClass::newInstance' => ['object', '...args='=>'mixed'], +'ReflectionClass::newInstanceArgs' => ['object', 'args='=>'list'], +'ReflectionClass::newInstanceWithoutConstructor' => ['object'], +'ReflectionClass::setStaticPropertyValue' => ['void', 'name'=>'string', 'value'=>'mixed'], +'ReflectionClassConstant::__construct' => ['void', 'class'=>'mixed', 'name'=>'string'], +'ReflectionClassConstant::__toString' => ['string'], +'ReflectionClassConstant::export' => ['string', 'class'=>'mixed', 'name'=>'string', 'return='=>'bool'], +'ReflectionClassConstant::getDeclaringClass' => ['ReflectionClass'], +'ReflectionClassConstant::getDocComment' => ['string|false'], +'ReflectionClassConstant::getModifiers' => ['int'], +'ReflectionClassConstant::getName' => ['string'], +'ReflectionClassConstant::getValue' => ['scalar|array|null'], +'ReflectionClassConstant::isPrivate' => ['bool'], +'ReflectionClassConstant::isProtected' => ['bool'], +'ReflectionClassConstant::isPublic' => ['bool'], +'ReflectionExtension::__clone' => ['void'], +'ReflectionExtension::__construct' => ['void', 'name'=>'string'], +'ReflectionExtension::__toString' => ['string'], +'ReflectionExtension::export' => ['?string', 'name'=>'string', 'return='=>'bool'], +'ReflectionExtension::getClasses' => ['array'], +'ReflectionExtension::getClassNames' => ['list'], +'ReflectionExtension::getConstants' => ['array'], +'ReflectionExtension::getDependencies' => ['array'], +'ReflectionExtension::getFunctions' => ['array'], +'ReflectionExtension::getINIEntries' => ['array'], +'ReflectionExtension::getName' => ['string'], +'ReflectionExtension::getVersion' => ['string'], +'ReflectionExtension::info' => ['void'], +'ReflectionExtension::isPersistent' => ['bool'], +'ReflectionExtension::isTemporary' => ['bool'], +'ReflectionFunction::__construct' => ['void', 'name'=>'callable-string|Closure'], +'ReflectionFunction::__toString' => ['string'], +'ReflectionFunction::export' => ['?string', 'name'=>'string', 'return='=>'bool'], +'ReflectionFunction::getClosure' => ['?Closure'], +'ReflectionFunction::getClosureScopeClass' => ['ReflectionClass'], +'ReflectionFunction::getClosureThis' => ['bool'], +'ReflectionFunction::getDocComment' => ['string|false'], +'ReflectionFunction::getEndLine' => ['int|false'], +'ReflectionFunction::getExtension' => ['?ReflectionExtension'], +'ReflectionFunction::getExtensionName' => ['string|false'], +'ReflectionFunction::getFileName' => ['string|false'], +'ReflectionFunction::getName' => ['callable-string'], +'ReflectionFunction::getNamespaceName' => ['string'], +'ReflectionFunction::getNumberOfParameters' => ['int'], +'ReflectionFunction::getNumberOfRequiredParameters' => ['int'], +'ReflectionFunction::getParameters' => ['list'], +'ReflectionFunction::getReturnType' => ['?ReflectionType'], +'ReflectionFunction::getShortName' => ['string'], +'ReflectionFunction::getStartLine' => ['int|false'], +'ReflectionFunction::getStaticVariables' => ['array'], +'ReflectionFunction::hasReturnType' => ['bool'], +'ReflectionFunction::inNamespace' => ['bool'], +'ReflectionFunction::invoke' => ['mixed', '...args='=>'mixed'], +'ReflectionFunction::invokeArgs' => ['mixed', 'args'=>'array'], +'ReflectionFunction::isClosure' => ['bool'], +'ReflectionFunction::isDeprecated' => ['bool'], +'ReflectionFunction::isDisabled' => ['bool'], +'ReflectionFunction::isGenerator' => ['bool'], +'ReflectionFunction::isInternal' => ['bool'], +'ReflectionFunction::isUserDefined' => ['bool'], +'ReflectionFunction::isVariadic' => ['bool'], +'ReflectionFunction::returnsReference' => ['bool'], +'ReflectionFunctionAbstract::__clone' => ['void'], +'ReflectionFunctionAbstract::__toString' => ['string'], +'ReflectionFunctionAbstract::export' => ['?string'], +'ReflectionFunctionAbstract::getClosureScopeClass' => ['ReflectionClass|null'], +'ReflectionFunctionAbstract::getClosureThis' => ['object|null'], +'ReflectionFunctionAbstract::getDocComment' => ['string|false'], +'ReflectionFunctionAbstract::getEndLine' => ['int|false'], +'ReflectionFunctionAbstract::getExtension' => ['ReflectionExtension'], +'ReflectionFunctionAbstract::getExtensionName' => ['string'], +'ReflectionFunctionAbstract::getFileName' => ['string|false'], +'ReflectionFunctionAbstract::getName' => ['string'], +'ReflectionFunctionAbstract::getNamespaceName' => ['string'], +'ReflectionFunctionAbstract::getNumberOfParameters' => ['int'], +'ReflectionFunctionAbstract::getNumberOfRequiredParameters' => ['int'], +'ReflectionFunctionAbstract::getParameters' => ['list'], +'ReflectionFunctionAbstract::getReturnType' => ['?ReflectionType'], +'ReflectionFunctionAbstract::getShortName' => ['string'], +'ReflectionFunctionAbstract::getStartLine' => ['int|false'], +'ReflectionFunctionAbstract::getStaticVariables' => ['array'], +'ReflectionFunctionAbstract::hasReturnType' => ['bool'], +'ReflectionFunctionAbstract::inNamespace' => ['bool'], +'ReflectionFunctionAbstract::isClosure' => ['bool'], +'ReflectionFunctionAbstract::isDeprecated' => ['bool'], +'ReflectionFunctionAbstract::isGenerator' => ['bool'], +'ReflectionFunctionAbstract::isInternal' => ['bool'], +'ReflectionFunctionAbstract::isUserDefined' => ['bool'], +'ReflectionFunctionAbstract::isVariadic' => ['bool'], +'ReflectionFunctionAbstract::returnsReference' => ['bool'], +'ReflectionGenerator::__construct' => ['void', 'generator'=>'object'], +'ReflectionGenerator::getExecutingFile' => ['string'], +'ReflectionGenerator::getExecutingGenerator' => ['Generator'], +'ReflectionGenerator::getExecutingLine' => ['int'], +'ReflectionGenerator::getFunction' => ['ReflectionFunctionAbstract'], +'ReflectionGenerator::getThis' => ['?object'], +'ReflectionGenerator::getTrace' => ['array', 'options='=>'int'], +'ReflectionMethod::__construct' => ['void', 'class'=>'class-string|object', 'name'=>'string'], +'ReflectionMethod::__construct\'1' => ['void', 'class_method'=>'string'], +'ReflectionMethod::__toString' => ['string'], +'ReflectionMethod::export' => ['?string', 'class'=>'string', 'name'=>'string', 'return='=>'bool'], +'ReflectionMethod::getClosure' => ['?Closure', 'object='=>'object'], +'ReflectionMethod::getClosureScopeClass' => ['ReflectionClass'], +'ReflectionMethod::getClosureThis' => ['object'], +'ReflectionMethod::getDeclaringClass' => ['ReflectionClass'], +'ReflectionMethod::getDocComment' => ['false|string'], +'ReflectionMethod::getEndLine' => ['false|int'], +'ReflectionMethod::getExtension' => ['ReflectionExtension'], +'ReflectionMethod::getExtensionName' => ['string'], +'ReflectionMethod::getFileName' => ['false|string'], +'ReflectionMethod::getModifiers' => ['int'], +'ReflectionMethod::getName' => ['string'], +'ReflectionMethod::getNamespaceName' => ['string'], +'ReflectionMethod::getNumberOfParameters' => ['int'], +'ReflectionMethod::getNumberOfRequiredParameters' => ['int'], +'ReflectionMethod::getParameters' => ['list<\ReflectionParameter>'], +'ReflectionMethod::getPrototype' => ['ReflectionMethod'], +'ReflectionMethod::getReturnType' => ['?ReflectionType'], +'ReflectionMethod::getShortName' => ['string'], +'ReflectionMethod::getStartLine' => ['false|int'], +'ReflectionMethod::getStaticVariables' => ['array'], +'ReflectionMethod::hasReturnType' => ['bool'], +'ReflectionMethod::inNamespace' => ['bool'], +'ReflectionMethod::invoke' => ['mixed', 'object'=>'?object', '...args='=>'mixed'], +'ReflectionMethod::invokeArgs' => ['mixed', 'object'=>'?object', 'args'=>'array'], +'ReflectionMethod::isAbstract' => ['bool'], +'ReflectionMethod::isClosure' => ['bool'], +'ReflectionMethod::isConstructor' => ['bool'], +'ReflectionMethod::isDeprecated' => ['bool'], +'ReflectionMethod::isDestructor' => ['bool'], +'ReflectionMethod::isFinal' => ['bool'], +'ReflectionMethod::isGenerator' => ['bool'], +'ReflectionMethod::isInternal' => ['bool'], +'ReflectionMethod::isPrivate' => ['bool'], +'ReflectionMethod::isProtected' => ['bool'], +'ReflectionMethod::isPublic' => ['bool'], +'ReflectionMethod::isStatic' => ['bool'], +'ReflectionMethod::isUserDefined' => ['bool'], +'ReflectionMethod::isVariadic' => ['bool'], +'ReflectionMethod::returnsReference' => ['bool'], +'ReflectionMethod::setAccessible' => ['void', 'visible'=>'bool'], +'ReflectionNamedType::__clone' => ['void'], +'ReflectionNamedType::__toString' => ['string'], +'ReflectionNamedType::allowsNull' => [''], +'ReflectionNamedType::getName' => ['string'], +'ReflectionNamedType::isBuiltin' => [''], +'ReflectionObject::__clone' => ['void'], +'ReflectionObject::__construct' => ['void', 'argument'=>'object'], +'ReflectionObject::__toString' => ['string'], +'ReflectionObject::export' => ['?string', 'argument'=>'object', 'return='=>'bool'], +'ReflectionObject::getConstant' => ['mixed', 'name'=>'string'], +'ReflectionObject::getConstants' => ['array'], +'ReflectionObject::getConstructor' => ['?ReflectionMethod'], +'ReflectionObject::getDefaultProperties' => ['array'], +'ReflectionObject::getDocComment' => ['false|string'], +'ReflectionObject::getEndLine' => ['false|int'], +'ReflectionObject::getExtension' => ['?ReflectionExtension'], +'ReflectionObject::getExtensionName' => ['false|string'], +'ReflectionObject::getFileName' => ['false|string'], +'ReflectionObject::getInterfaceNames' => ['class-string[]'], +'ReflectionObject::getInterfaces' => ['array'], +'ReflectionObject::getMethod' => ['ReflectionMethod', 'name'=>'string'], +'ReflectionObject::getMethods' => ['ReflectionMethod[]', 'filter='=>'int'], +'ReflectionObject::getModifiers' => ['int'], +'ReflectionObject::getName' => ['string'], +'ReflectionObject::getNamespaceName' => ['string'], +'ReflectionObject::getParentClass' => ['ReflectionClass|false'], +'ReflectionObject::getProperties' => ['ReflectionProperty[]', 'filter='=>'int'], +'ReflectionObject::getProperty' => ['ReflectionProperty', 'name'=>'string'], +'ReflectionObject::getReflectionConstant' => ['ReflectionClassConstant', 'name'=>'string'], +'ReflectionObject::getReflectionConstants' => ['list<\ReflectionClassConstant>'], +'ReflectionObject::getShortName' => ['string'], +'ReflectionObject::getStartLine' => ['false|int'], +'ReflectionObject::getStaticProperties' => ['ReflectionProperty[]'], +'ReflectionObject::getStaticPropertyValue' => ['mixed', 'name'=>'string', 'default='=>'mixed'], +'ReflectionObject::getTraitAliases' => ['array'], +'ReflectionObject::getTraitNames' => ['list'], +'ReflectionObject::getTraits' => ['array'], +'ReflectionObject::hasConstant' => ['bool', 'name'=>'string'], +'ReflectionObject::hasMethod' => ['bool', 'name'=>'string'], +'ReflectionObject::hasProperty' => ['bool', 'name'=>'string'], +'ReflectionObject::implementsInterface' => ['bool', 'interface_name'=>'ReflectionClass|string'], +'ReflectionObject::inNamespace' => ['bool'], +'ReflectionObject::isAbstract' => ['bool'], +'ReflectionObject::isAnonymous' => ['bool'], +'ReflectionObject::isCloneable' => ['bool'], +'ReflectionObject::isFinal' => ['bool'], +'ReflectionObject::isInstance' => ['bool', 'object'=>'object'], +'ReflectionObject::isInstantiable' => ['bool'], +'ReflectionObject::isInterface' => ['bool'], +'ReflectionObject::isInternal' => ['bool'], +'ReflectionObject::isIterable' => ['bool'], +'ReflectionObject::isIterateable' => ['bool'], +'ReflectionObject::isSubclassOf' => ['bool', 'class'=>'ReflectionClass|string'], +'ReflectionObject::isTrait' => ['bool'], +'ReflectionObject::isUserDefined' => ['bool'], +'ReflectionObject::newInstance' => ['object', 'args='=>'mixed', '...args='=>'array'], +'ReflectionObject::newInstanceArgs' => ['object', 'args='=>'array'], +'ReflectionObject::newInstanceWithoutConstructor' => ['object'], +'ReflectionObject::setStaticPropertyValue' => ['void', 'name'=>'string', 'value'=>'string'], +'ReflectionParameter::__clone' => ['void'], +'ReflectionParameter::__construct' => ['void', 'function'=>'', 'parameter'=>''], +'ReflectionParameter::__toString' => ['string'], +'ReflectionParameter::allowsNull' => ['bool'], +'ReflectionParameter::canBePassedByValue' => ['bool'], +'ReflectionParameter::export' => ['?string', 'function'=>'string', 'parameter'=>'string', 'return='=>'bool'], +'ReflectionParameter::getClass' => ['?ReflectionClass'], +'ReflectionParameter::getDeclaringClass' => ['?ReflectionClass'], +'ReflectionParameter::getDeclaringFunction' => ['ReflectionFunctionAbstract'], +'ReflectionParameter::getDefaultValue' => ['mixed'], +'ReflectionParameter::getDefaultValueConstantName' => ['?string'], +'ReflectionParameter::getName' => ['string'], +'ReflectionParameter::getPosition' => ['int'], +'ReflectionParameter::getType' => ['?ReflectionType'], +'ReflectionParameter::hasType' => ['bool'], +'ReflectionParameter::isArray' => ['bool'], +'ReflectionParameter::isCallable' => ['?bool'], +'ReflectionParameter::isDefaultValueAvailable' => ['bool'], +'ReflectionParameter::isDefaultValueConstant' => ['bool'], +'ReflectionParameter::isOptional' => ['bool'], +'ReflectionParameter::isPassedByReference' => ['bool'], +'ReflectionParameter::isVariadic' => ['bool'], +'ReflectionProperty::__clone' => ['void'], +'ReflectionProperty::__construct' => ['void', 'class'=>'', 'name'=>'string'], +'ReflectionProperty::__toString' => ['string'], +'ReflectionProperty::export' => ['?string', 'class'=>'mixed', 'name'=>'string', 'return='=>'bool'], +'ReflectionProperty::getDeclaringClass' => ['ReflectionClass'], +'ReflectionProperty::getDocComment' => ['string|false'], +'ReflectionProperty::getModifiers' => ['int'], +'ReflectionProperty::getName' => ['string'], +'ReflectionProperty::getType' => ['?ReflectionType'], +'ReflectionProperty::getValue' => ['mixed', 'object='=>'object'], +'ReflectionProperty::isDefault' => ['bool'], +'ReflectionProperty::isPrivate' => ['bool'], +'ReflectionProperty::isProtected' => ['bool'], +'ReflectionProperty::isPublic' => ['bool'], +'ReflectionProperty::isStatic' => ['bool'], +'ReflectionProperty::setAccessible' => ['void', 'visible'=>'bool'], +'ReflectionProperty::setValue' => ['void', 'object'=>'null|object', 'value'=>''], +'ReflectionProperty::setValue\'1' => ['void', 'value'=>''], +'ReflectionType::__clone' => ['void'], +'ReflectionType::__toString' => ['string'], +'ReflectionType::allowsNull' => ['bool'], +'ReflectionType::getName' => ['string'], +'ReflectionType::isBuiltin' => ['bool'], +'ReflectionZendExtension::__clone' => ['void'], +'ReflectionZendExtension::__construct' => ['void', 'name'=>'string'], +'ReflectionZendExtension::__toString' => ['string'], +'ReflectionZendExtension::export' => ['?string', 'name'=>'string', 'return='=>'bool'], +'ReflectionZendExtension::getAuthor' => ['string'], +'ReflectionZendExtension::getCopyright' => ['string'], +'ReflectionZendExtension::getName' => ['string'], +'ReflectionZendExtension::getURL' => ['string'], +'ReflectionZendExtension::getVersion' => ['string'], +'Reflector::__toString' => ['string'], +'Reflector::export' => ['?string'], +'RegexIterator::__construct' => ['void', 'iterator'=>'Iterator', 'regex'=>'string', 'mode='=>'int', 'flags='=>'int', 'preg_flags='=>'int'], +'RegexIterator::accept' => ['bool'], +'RegexIterator::current' => ['mixed'], +'RegexIterator::getFlags' => ['int'], +'RegexIterator::getInnerIterator' => ['Iterator'], +'RegexIterator::getMode' => ['int'], +'RegexIterator::getPregFlags' => ['int'], +'RegexIterator::getRegex' => ['string'], +'RegexIterator::key' => ['mixed'], +'RegexIterator::next' => ['void'], +'RegexIterator::rewind' => ['void'], +'RegexIterator::setFlags' => ['void', 'new_flags'=>'int'], +'RegexIterator::setMode' => ['void', 'new_mode'=>'int'], +'RegexIterator::setPregFlags' => ['void', 'new_flags'=>'int'], +'RegexIterator::valid' => ['bool'], +'register_event_handler' => ['bool', 'event_handler_func'=>'string', 'handler_register_name'=>'string', 'event_type_mask'=>'int'], +'register_shutdown_function' => ['void', 'function'=>'callable', '...parameter='=>'mixed'], +'register_tick_function' => ['bool', 'function'=>'callable():void', '...args='=>'mixed'], +'rename' => ['bool', 'old_name'=>'string', 'new_name'=>'string', 'context='=>'resource'], +'rename_function' => ['bool', 'original_name'=>'string', 'new_name'=>'string'], +'reset' => ['mixed|false', '&r_array'=>'array|object'], +'ResourceBundle::__construct' => ['void', 'locale'=>'string', 'bundlename'=>'string', 'fallback='=>'bool'], +'ResourceBundle::count' => ['int'], +'ResourceBundle::create' => ['?ResourceBundle', 'locale'=>'string', 'bundlename'=>'string', 'fallback='=>'bool'], +'ResourceBundle::get' => ['', 'index'=>'string|int', 'fallback='=>'bool'], +'ResourceBundle::getErrorCode' => ['int'], +'ResourceBundle::getErrorMessage' => ['string'], +'ResourceBundle::getLocales' => ['array', 'bundlename'=>'string'], +'resourcebundle_count' => ['int', 'r'=>'ResourceBundle'], +'resourcebundle_create' => ['?ResourceBundle', 'locale'=>'string', 'bundlename'=>'string', 'fallback='=>'bool'], +'resourcebundle_get' => ['mixed|null', 'r'=>'ResourceBundle', 'index'=>'string|int', 'fallback='=>'bool'], +'resourcebundle_get_error_code' => ['int', 'r'=>'ResourceBundle'], +'resourcebundle_get_error_message' => ['string', 'r'=>'ResourceBundle'], +'resourcebundle_locales' => ['array', 'bundlename'=>'string'], +'restore_error_handler' => ['true'], +'restore_exception_handler' => ['bool'], +'restore_include_path' => ['void'], +'rewind' => ['bool', 'fp'=>'resource'], +'rewinddir' => ['null|false', 'dir_handle='=>'resource'], +'rmdir' => ['bool', 'dirname'=>'string', 'context='=>'resource'], +'round' => ['float', 'number'=>'float', 'precision='=>'int', 'mode='=>'int'], +'rpm_close' => ['bool', 'rpmr'=>'resource'], +'rpm_get_tag' => ['mixed', 'rpmr'=>'resource', 'tagnum'=>'int'], +'rpm_is_valid' => ['bool', 'filename'=>'string'], +'rpm_open' => ['resource|false', 'filename'=>'string'], +'rpm_version' => ['string'], +'rpmaddtag' => ['bool', 'tag'=>'int'], +'rpmdbinfo' => ['array', 'nevr'=>'string', 'full='=>'bool'], +'rpmdbsearch' => ['array', 'pattern'=>'string', 'rpmtag='=>'int', 'rpmmire='=>'int', 'full='=>'bool'], +'rpminfo' => ['array', 'path'=>'string', 'full='=>'bool', 'error='=>'string'], +'rpmvercmp' => ['int', 'evr1'=>'string', 'evr2'=>'string'], +'rrd_create' => ['bool', 'filename'=>'string', 'options'=>'array'], +'rrd_disconnect' => ['void'], +'rrd_error' => ['string'], +'rrd_fetch' => ['array', 'filename'=>'string', 'options'=>'array'], +'rrd_first' => ['int|false', 'file'=>'string', 'raaindex='=>'int'], +'rrd_graph' => ['array|false', 'filename'=>'string', 'options'=>'array'], +'rrd_info' => ['array|false', 'filename'=>'string'], +'rrd_last' => ['int', 'filename'=>'string'], +'rrd_lastupdate' => ['array|false', 'filename'=>'string'], +'rrd_restore' => ['bool', 'xml_file'=>'string', 'rrd_file'=>'string', 'options='=>'array'], +'rrd_tune' => ['bool', 'filename'=>'string', 'options'=>'array'], +'rrd_update' => ['bool', 'filename'=>'string', 'options'=>'array'], +'rrd_version' => ['string'], +'rrd_xport' => ['array|false', 'options'=>'array'], +'rrdc_disconnect' => ['void'], +'RRDCreator::__construct' => ['void', 'path'=>'string', 'starttime='=>'string', 'step='=>'int'], +'RRDCreator::addArchive' => ['void', 'description'=>'string'], +'RRDCreator::addDataSource' => ['void', 'description'=>'string'], +'RRDCreator::save' => ['bool'], +'RRDGraph::__construct' => ['void', 'path'=>'string'], +'RRDGraph::save' => ['array|false'], +'RRDGraph::saveVerbose' => ['array|false'], +'RRDGraph::setOptions' => ['void', 'options'=>'array'], +'RRDUpdater::__construct' => ['void', 'path'=>'string'], +'RRDUpdater::update' => ['bool', 'values'=>'array', 'time='=>'string'], +'rsort' => ['bool', '&rw_array'=>'array', 'sort_flags='=>'int'], +'rtrim' => ['string', 'string'=>'string', 'character_mask='=>'string'], +'runkit7_constant_add' => ['bool', 'constant_name'=>'string', 'value'=>'mixed', 'new_visibility='=>'int'], +'runkit7_constant_redefine' => ['bool', 'constant_name'=>'string', 'value'=>'mixed', 'new_visibility='=>'?int'], +'runkit7_constant_remove' => ['bool', 'constant_name'=>'string'], +'runkit7_function_add' => ['bool', 'function_name'=>'string', 'argument_list_or_closure'=>'Closure|string', 'code_or_doc_comment='=>'?string', 'return_by_reference='=>'?bool', 'doc_comment='=>'?string', 'return_type='=>'?string', 'is_strict='=>'?bool'], +'runkit7_function_copy' => ['bool', 'source_name'=>'string', 'target_name'=>'string'], +'runkit7_function_redefine' => ['bool', 'function_name'=>'string', 'argument_list_or_closure'=>'Closure|string', 'code_or_doc_comment='=>'?string', 'return_by_reference='=>'?bool', 'doc_comment='=>'?string', 'return_type='=>'?string', 'is_strict='=>'?bool'], +'runkit7_function_remove' => ['bool', 'function_name'=>'string'], +'runkit7_function_rename' => ['bool', 'source_name'=>'string', 'target_name'=>'string'], +'runkit7_import' => ['bool', 'filename'=>'string', 'flags='=>'?int'], +'runkit7_method_add' => ['bool', 'class_name'=>'string', 'method_name'=>'string', 'argument_list_or_closure'=>'Closure|string', 'code_or_flags='=>'int|null|string', 'flags_or_doc_comment='=>'int|null|string', 'doc_comment='=>'?string', 'return_type='=>'?string', 'is_strict='=>'?bool'], +'runkit7_method_copy' => ['bool', 'destination_class'=>'string', 'destination_method'=>'string', 'source_class'=>'string', 'source_method='=>'?string'], +'runkit7_method_redefine' => ['bool', 'class_name'=>'string', 'method_name'=>'string', 'argument_list_or_closure'=>'Closure|string', 'code_or_flags='=>'int|null|string', 'flags_or_doc_comment='=>'int|null|string', 'doc_comment='=>'?string', 'return_type='=>'?string', 'is_strict='=>'?bool'], +'runkit7_method_remove' => ['bool', 'class_name'=>'string', 'method_name'=>'string'], +'runkit7_method_rename' => ['bool', 'class_name'=>'string', 'source_method_name'=>'string', 'source_target_name'=>'string'], +'runkit7_superglobals' => ['array'], +'runkit7_zval_inspect' => ['array', 'value'=>'mixed'], +'runkit_class_adopt' => ['bool', 'classname'=>'string', 'parentname'=>'string'], +'runkit_class_emancipate' => ['bool', 'classname'=>'string'], +'runkit_constant_add' => ['bool', 'constname'=>'string', 'value'=>'mixed'], +'runkit_constant_redefine' => ['bool', 'constname'=>'string', 'newvalue'=>'mixed'], +'runkit_constant_remove' => ['bool', 'constname'=>'string'], +'runkit_function_add' => ['bool', 'funcname'=>'string', 'arglist'=>'string', 'code'=>'string', 'doccomment='=>'?string'], +'runkit_function_add\'1' => ['bool', 'funcname'=>'string', 'closure'=>'Closure', 'doccomment='=>'?string'], +'runkit_function_copy' => ['bool', 'funcname'=>'string', 'targetname'=>'string'], +'runkit_function_redefine' => ['bool', 'funcname'=>'string', 'arglist'=>'string', 'code'=>'string', 'doccomment='=>'?string'], +'runkit_function_redefine\'1' => ['bool', 'funcname'=>'string', 'closure'=>'Closure', 'doccomment='=>'?string'], +'runkit_function_remove' => ['bool', 'funcname'=>'string'], +'runkit_function_rename' => ['bool', 'funcname'=>'string', 'newname'=>'string'], +'runkit_import' => ['bool', 'filename'=>'string', 'flags='=>'int'], +'runkit_lint' => ['bool', 'code'=>'string'], +'runkit_lint_file' => ['bool', 'filename'=>'string'], +'runkit_method_add' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'args'=>'string', 'code'=>'string', 'flags='=>'int', 'doccomment='=>'?string'], +'runkit_method_add\'1' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'closure'=>'Closure', 'flags='=>'int', 'doccomment='=>'?string'], +'runkit_method_copy' => ['bool', 'dclass'=>'string', 'dmethod'=>'string', 'sclass'=>'string', 'smethod='=>'string'], +'runkit_method_redefine' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'args'=>'string', 'code'=>'string', 'flags='=>'int', 'doccomment='=>'?string'], +'runkit_method_redefine\'1' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'closure'=>'Closure', 'flags='=>'int', 'doccomment='=>'?string'], +'runkit_method_remove' => ['bool', 'classname'=>'string', 'methodname'=>'string'], +'runkit_method_rename' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'newname'=>'string'], +'runkit_return_value_used' => ['bool'], +'Runkit_Sandbox::__construct' => ['void', 'options='=>'array'], +'runkit_sandbox_output_handler' => ['mixed', 'sandbox'=>'object', 'callback='=>'mixed'], +'Runkit_Sandbox_Parent' => [''], +'Runkit_Sandbox_Parent::__construct' => ['void'], +'runkit_superglobals' => ['array'], +'runkit_zval_inspect' => ['array', 'value'=>'mixed'], +'RuntimeException::__clone' => ['void'], +'RuntimeException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?RuntimeException'], +'RuntimeException::__toString' => ['string'], +'RuntimeException::getCode' => ['int'], +'RuntimeException::getFile' => ['string'], +'RuntimeException::getLine' => ['int'], +'RuntimeException::getMessage' => ['string'], +'RuntimeException::getPrevious' => ['Throwable|RuntimeException|null'], +'RuntimeException::getTrace' => ['list>'], +'RuntimeException::getTraceAsString' => ['string'], +'SAMConnection::commit' => ['bool'], +'SAMConnection::connect' => ['bool', 'protocol'=>'string', 'properties='=>'array'], +'SAMConnection::disconnect' => ['bool'], +'SAMConnection::errno' => ['int'], +'SAMConnection::error' => ['string'], +'SAMConnection::isConnected' => ['bool'], +'SAMConnection::peek' => ['SAMMessage', 'target'=>'string', 'properties='=>'array'], +'SAMConnection::peekAll' => ['array', 'target'=>'string', 'properties='=>'array'], +'SAMConnection::receive' => ['SAMMessage', 'target'=>'string', 'properties='=>'array'], +'SAMConnection::remove' => ['SAMMessage', 'target'=>'string', 'properties='=>'array'], +'SAMConnection::rollback' => ['bool'], +'SAMConnection::send' => ['string', 'target'=>'string', 'msg'=>'sammessage', 'properties='=>'array'], +'SAMConnection::setDebug' => ['', 'switch'=>'bool'], +'SAMConnection::subscribe' => ['string', 'targettopic'=>'string'], +'SAMConnection::unsubscribe' => ['bool', 'subscriptionid'=>'string', 'targettopic='=>'string'], +'SAMMessage::body' => ['string'], +'SAMMessage::header' => ['object'], +'sapi_windows_cp_conv' => ['string', 'in_codepage'=>'int|string', 'out_codepage'=>'int|string', 'subject'=>'string'], +'sapi_windows_cp_get' => ['int'], +'sapi_windows_cp_is_utf8' => ['bool'], +'sapi_windows_cp_set' => ['bool', 'code_page'=>'int'], +'sapi_windows_vt100_support' => ['bool', 'stream'=>'resource', 'enable='=>'bool'], +'Saxon\SaxonProcessor::__construct' => ['void', 'license='=>'bool', 'cwd='=>'string'], +'Saxon\SaxonProcessor::createAtomicValue' => ['Saxon\XdmValue', 'primitive_type_val'=>'bool|float|int|string'], +'Saxon\SaxonProcessor::newSchemaValidator' => ['Saxon\SchemaValidator'], +'Saxon\SaxonProcessor::newXPathProcessor' => ['Saxon\XPathProcessor'], +'Saxon\SaxonProcessor::newXQueryProcessor' => ['Saxon\XQueryProcessor'], +'Saxon\SaxonProcessor::newXsltProcessor' => ['Saxon\XsltProcessor'], +'Saxon\SaxonProcessor::parseXmlFromFile' => ['Saxon\XdmNode', 'fileName'=>'string'], +'Saxon\SaxonProcessor::parseXmlFromString' => ['Saxon\XdmNode', 'value'=>'string'], +'Saxon\SaxonProcessor::registerPHPFunctions' => ['void', 'library'=>'string'], +'Saxon\SaxonProcessor::setConfigurationProperty' => ['void', 'name'=>'string', 'value'=>'string'], +'Saxon\SaxonProcessor::setcwd' => ['void', 'cwd'=>'string'], +'Saxon\SaxonProcessor::setResourceDirectory' => ['void', 'dir'=>'string'], +'Saxon\SaxonProcessor::version' => ['string'], +'Saxon\SchemaValidator::clearParameters' => ['void'], +'Saxon\SchemaValidator::clearProperties' => ['void'], +'Saxon\SchemaValidator::exceptionClear' => ['void'], +'Saxon\SchemaValidator::getErrorCode' => ['string', 'i'=>'int'], +'Saxon\SchemaValidator::getErrorMessage' => ['string', 'i'=>'int'], +'Saxon\SchemaValidator::getExceptionCount' => ['int'], +'Saxon\SchemaValidator::getValidationReport' => ['Saxon\XdmNode'], +'Saxon\SchemaValidator::registerSchemaFromFile' => ['void', 'fileName'=>'string'], +'Saxon\SchemaValidator::registerSchemaFromString' => ['void', 'schemaStr'=>'string'], +'Saxon\SchemaValidator::setOutputFile' => ['void', 'fileName'=>'string'], +'Saxon\SchemaValidator::setParameter' => ['void', 'name'=>'string', 'value'=>'Saxon\XdmValue'], +'Saxon\SchemaValidator::setProperty' => ['void', 'name'=>'string', 'value'=>'string'], +'Saxon\SchemaValidator::setSourceNode' => ['void', 'node'=>'Saxon\XdmNode'], +'Saxon\SchemaValidator::validate' => ['void', 'filename='=>'?string'], +'Saxon\SchemaValidator::validateToNode' => ['Saxon\XdmNode', 'filename='=>'?string'], +'Saxon\XdmAtomicValue::addXdmItem' => ['', 'item'=>'Saxon\XdmItem'], +'Saxon\XdmAtomicValue::getAtomicValue' => ['?Saxon\XdmAtomicValue'], +'Saxon\XdmAtomicValue::getBooleanValue' => ['bool'], +'Saxon\XdmAtomicValue::getDoubleValue' => ['float'], +'Saxon\XdmAtomicValue::getHead' => ['Saxon\XdmItem'], +'Saxon\XdmAtomicValue::getLongValue' => ['int'], +'Saxon\XdmAtomicValue::getNodeValue' => ['?Saxon\XdmNode'], +'Saxon\XdmAtomicValue::getStringValue' => ['string'], +'Saxon\XdmAtomicValue::isAtomic' => ['true'], +'Saxon\XdmAtomicValue::isNode' => ['bool'], +'Saxon\XdmAtomicValue::itemAt' => ['Saxon\XdmItem', 'index'=>'int'], +'Saxon\XdmAtomicValue::size' => ['int'], +'Saxon\XdmItem::addXdmItem' => ['', 'item'=>'Saxon\XdmItem'], +'Saxon\XdmItem::getAtomicValue' => ['?Saxon\XdmAtomicValue'], +'Saxon\XdmItem::getHead' => ['Saxon\XdmItem'], +'Saxon\XdmItem::getNodeValue' => ['?Saxon\XdmNode'], +'Saxon\XdmItem::getStringValue' => ['string'], +'Saxon\XdmItem::isAtomic' => ['bool'], +'Saxon\XdmItem::isNode' => ['bool'], +'Saxon\XdmItem::itemAt' => ['Saxon\XdmItem', 'index'=>'int'], +'Saxon\XdmItem::size' => ['int'], +'Saxon\XdmNode::addXdmItem' => ['', 'item'=>'Saxon\XdmItem'], +'Saxon\XdmNode::getAtomicValue' => ['?Saxon\XdmAtomicValue'], +'Saxon\XdmNode::getAttributeCount' => ['int'], +'Saxon\XdmNode::getAttributeNode' => ['?Saxon\XdmNode', 'index'=>'int'], +'Saxon\XdmNode::getAttributeValue' => ['?string', 'index'=>'int'], +'Saxon\XdmNode::getChildCount' => ['int'], +'Saxon\XdmNode::getChildNode' => ['?Saxon\XdmNode', 'index'=>'int'], +'Saxon\XdmNode::getHead' => ['Saxon\XdmItem'], +'Saxon\XdmNode::getNodeKind' => ['int'], +'Saxon\XdmNode::getNodeName' => ['string'], +'Saxon\XdmNode::getNodeValue' => ['?Saxon\XdmNode'], +'Saxon\XdmNode::getParent' => ['?Saxon\XdmNode'], +'Saxon\XdmNode::getStringValue' => ['string'], +'Saxon\XdmNode::isAtomic' => ['false'], +'Saxon\XdmNode::isNode' => ['bool'], +'Saxon\XdmNode::itemAt' => ['Saxon\XdmItem', 'index'=>'int'], +'Saxon\XdmNode::size' => ['int'], +'Saxon\XdmValue::addXdmItem' => ['', 'item'=>'Saxon\XdmItem'], +'Saxon\XdmValue::getHead' => ['Saxon\XdmItem'], +'Saxon\XdmValue::itemAt' => ['Saxon\XdmItem', 'index'=>'int'], +'Saxon\XdmValue::size' => ['int'], +'Saxon\XPathProcessor::clearParameters' => ['void'], +'Saxon\XPathProcessor::clearProperties' => ['void'], +'Saxon\XPathProcessor::declareNamespace' => ['void', 'prefix'=>'', 'namespace'=>''], +'Saxon\XPathProcessor::effectiveBooleanValue' => ['bool', 'xpathStr'=>'string'], +'Saxon\XPathProcessor::evaluate' => ['Saxon\XdmValue', 'xpathStr'=>'string'], +'Saxon\XPathProcessor::evaluateSingle' => ['Saxon\XdmItem', 'xpathStr'=>'string'], +'Saxon\XPathProcessor::exceptionClear' => ['void'], +'Saxon\XPathProcessor::getErrorCode' => ['string', 'i'=>'int'], +'Saxon\XPathProcessor::getErrorMessage' => ['string', 'i'=>'int'], +'Saxon\XPathProcessor::getExceptionCount' => ['int'], +'Saxon\XPathProcessor::setBaseURI' => ['void', 'uri'=>'string'], +'Saxon\XPathProcessor::setContextFile' => ['void', 'fileName'=>'string'], +'Saxon\XPathProcessor::setContextItem' => ['void', 'item'=>'Saxon\XdmItem'], +'Saxon\XPathProcessor::setParameter' => ['void', 'name'=>'string', 'value'=>'Saxon\XdmValue'], +'Saxon\XPathProcessor::setProperty' => ['void', 'name'=>'string', 'value'=>'string'], +'Saxon\XQueryProcessor::clearParameters' => ['void'], +'Saxon\XQueryProcessor::clearProperties' => ['void'], +'Saxon\XQueryProcessor::declareNamespace' => ['void', 'prefix'=>'string', 'namespace'=>'string'], +'Saxon\XQueryProcessor::exceptionClear' => ['void'], +'Saxon\XQueryProcessor::getErrorCode' => ['string', 'i'=>'int'], +'Saxon\XQueryProcessor::getErrorMessage' => ['string', 'i'=>'int'], +'Saxon\XQueryProcessor::getExceptionCount' => ['int'], +'Saxon\XQueryProcessor::runQueryToFile' => ['void', 'outfilename'=>'string'], +'Saxon\XQueryProcessor::runQueryToString' => ['?string'], +'Saxon\XQueryProcessor::runQueryToValue' => ['?Saxon\XdmValue'], +'Saxon\XQueryProcessor::setContextItem' => ['void', 'object'=>'Saxon\XdmAtomicValue|Saxon\XdmItem|Saxon\XdmNode|Saxon\XdmValue'], +'Saxon\XQueryProcessor::setContextItemFromFile' => ['void', 'fileName'=>'string'], +'Saxon\XQueryProcessor::setParameter' => ['void', 'name'=>'string', 'value'=>'Saxon\XdmValue'], +'Saxon\XQueryProcessor::setProperty' => ['void', 'name'=>'string', 'value'=>'string'], +'Saxon\XQueryProcessor::setQueryBaseURI' => ['void', 'uri'=>'string'], +'Saxon\XQueryProcessor::setQueryContent' => ['void', 'string'=>'string'], +'Saxon\XQueryProcessor::setQueryFile' => ['void', 'filename'=>'string'], +'Saxon\XQueryProcessor::setQueryItem' => ['void', 'item'=>'Saxon\XdmItem'], +'Saxon\XsltProcessor::clearParameters' => ['void'], +'Saxon\XsltProcessor::clearProperties' => ['void'], +'Saxon\XsltProcessor::compileFromFile' => ['void', 'fileName'=>'string'], +'Saxon\XsltProcessor::compileFromString' => ['void', 'string'=>'string'], +'Saxon\XsltProcessor::compileFromValue' => ['void', 'node'=>'Saxon\XdmNode'], +'Saxon\XsltProcessor::exceptionClear' => ['void'], +'Saxon\XsltProcessor::getErrorCode' => ['string', 'i'=>'int'], +'Saxon\XsltProcessor::getErrorMessage' => ['string', 'i'=>'int'], +'Saxon\XsltProcessor::getExceptionCount' => ['int'], +'Saxon\XsltProcessor::setOutputFile' => ['void', 'fileName'=>'string'], +'Saxon\XsltProcessor::setParameter' => ['void', 'name'=>'string', 'value'=>'Saxon\XdmValue'], +'Saxon\XsltProcessor::setProperty' => ['void', 'name'=>'string', 'value'=>'string'], +'Saxon\XsltProcessor::setSourceFromFile' => ['void', 'filename'=>'string'], +'Saxon\XsltProcessor::setSourceFromXdmValue' => ['void', 'value'=>'Saxon\XdmValue'], +'Saxon\XsltProcessor::transformFileToFile' => ['void', 'sourceFileName'=>'string', 'stylesheetFileName'=>'string', 'outputfileName'=>'string'], +'Saxon\XsltProcessor::transformFileToString' => ['?string', 'sourceFileName'=>'string', 'stylesheetFileName'=>'string'], +'Saxon\XsltProcessor::transformFileToValue' => ['Saxon\XdmValue', 'fileName'=>'string'], +'Saxon\XsltProcessor::transformToFile' => ['void'], +'Saxon\XsltProcessor::transformToString' => ['string'], +'Saxon\XsltProcessor::transformToValue' => ['?Saxon\XdmValue'], +'SCA::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'], +'SCA::getService' => ['', 'target'=>'string', 'binding='=>'string', 'config='=>'array'], +'SCA_LocalProxy::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'], +'SCA_SoapProxy::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'], +'scalebarObj::convertToString' => ['string'], +'scalebarObj::free' => ['void'], +'scalebarObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'scalebarObj::setImageColor' => ['int', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], +'scalebarObj::updateFromString' => ['int', 'snippet'=>'string'], +'scandir' => ['list|false', 'dir'=>'string', 'sorting_order='=>'int', 'context='=>'resource'], +'SDO_DAS_ChangeSummary::beginLogging' => [''], +'SDO_DAS_ChangeSummary::endLogging' => [''], +'SDO_DAS_ChangeSummary::getChangedDataObjects' => ['SDO_List'], +'SDO_DAS_ChangeSummary::getChangeType' => ['int', 'dataobject'=>'sdo_dataobject'], +'SDO_DAS_ChangeSummary::getOldContainer' => ['SDO_DataObject', 'data_object'=>'sdo_dataobject'], +'SDO_DAS_ChangeSummary::getOldValues' => ['SDO_List', 'data_object'=>'sdo_dataobject'], +'SDO_DAS_ChangeSummary::isLogging' => ['bool'], +'SDO_DAS_DataFactory::addPropertyToType' => ['', 'parent_type_namespace_uri'=>'string', 'parent_type_name'=>'string', 'property_name'=>'string', 'type_namespace_uri'=>'string', 'type_name'=>'string', 'options='=>'array'], +'SDO_DAS_DataFactory::addType' => ['', 'type_namespace_uri'=>'string', 'type_name'=>'string', 'options='=>'array'], +'SDO_DAS_DataFactory::getDataFactory' => ['SDO_DAS_DataFactory'], +'SDO_DAS_DataObject::getChangeSummary' => ['SDO_DAS_ChangeSummary'], +'SDO_DAS_Relational::__construct' => ['void', 'database_metadata'=>'array', 'application_root_type='=>'string', 'sdo_containment_references_metadata='=>'array'], +'SDO_DAS_Relational::applyChanges' => ['', 'database_handle'=>'pdo', 'root_data_object'=>'sdodataobject'], +'SDO_DAS_Relational::createRootDataObject' => ['SDODataObject'], +'SDO_DAS_Relational::executePreparedQuery' => ['SDODataObject', 'database_handle'=>'pdo', 'prepared_statement'=>'pdostatement', 'value_list'=>'array', 'column_specifier='=>'array'], +'SDO_DAS_Relational::executeQuery' => ['SDODataObject', 'database_handle'=>'pdo', 'sql_statement'=>'string', 'column_specifier='=>'array'], +'SDO_DAS_Setting::getListIndex' => ['int'], +'SDO_DAS_Setting::getPropertyIndex' => ['int'], +'SDO_DAS_Setting::getPropertyName' => ['string'], +'SDO_DAS_Setting::getValue' => [''], +'SDO_DAS_Setting::isSet' => ['bool'], +'SDO_DAS_XML::addTypes' => ['', 'xsd_file'=>'string'], +'SDO_DAS_XML::create' => ['SDO_DAS_XML', 'xsd_file='=>'mixed', 'key='=>'string'], +'SDO_DAS_XML::createDataObject' => ['SDO_DataObject', 'namespace_uri'=>'string', 'type_name'=>'string'], +'SDO_DAS_XML::createDocument' => ['SDO_DAS_XML_Document', 'document_element_name'=>'string', 'document_element_namespace_uri'=>'string', 'dataobject='=>'sdo_dataobject'], +'SDO_DAS_XML::loadFile' => ['SDO_XMLDocument', 'xml_file'=>'string'], +'SDO_DAS_XML::loadString' => ['SDO_DAS_XML_Document', 'xml_string'=>'string'], +'SDO_DAS_XML::saveFile' => ['', 'xdoc'=>'sdo_xmldocument', 'xml_file'=>'string', 'indent='=>'int'], +'SDO_DAS_XML::saveString' => ['string', 'xdoc'=>'sdo_xmldocument', 'indent='=>'int'], +'SDO_DAS_XML_Document::getRootDataObject' => ['SDO_DataObject'], +'SDO_DAS_XML_Document::getRootElementName' => ['string'], +'SDO_DAS_XML_Document::getRootElementURI' => ['string'], +'SDO_DAS_XML_Document::setEncoding' => ['', 'encoding'=>'string'], +'SDO_DAS_XML_Document::setXMLDeclaration' => ['', 'xmldeclatation'=>'bool'], +'SDO_DAS_XML_Document::setXMLVersion' => ['', 'xmlversion'=>'string'], +'SDO_DataFactory::create' => ['void', 'type_namespace_uri'=>'string', 'type_name'=>'string'], +'SDO_DataObject::clear' => ['void'], +'SDO_DataObject::createDataObject' => ['SDO_DataObject', 'identifier'=>''], +'SDO_DataObject::getContainer' => ['SDO_DataObject'], +'SDO_DataObject::getSequence' => ['SDO_Sequence'], +'SDO_DataObject::getTypeName' => ['string'], +'SDO_DataObject::getTypeNamespaceURI' => ['string'], +'SDO_Exception::getCause' => [''], +'SDO_List::insert' => ['void', 'value'=>'mixed', 'index='=>'int'], +'SDO_Model_Property::getContainingType' => ['SDO_Model_Type'], +'SDO_Model_Property::getDefault' => [''], +'SDO_Model_Property::getName' => ['string'], +'SDO_Model_Property::getType' => ['SDO_Model_Type'], +'SDO_Model_Property::isContainment' => ['bool'], +'SDO_Model_Property::isMany' => ['bool'], +'SDO_Model_ReflectionDataObject::__construct' => ['void', 'data_object'=>'sdo_dataobject'], +'SDO_Model_ReflectionDataObject::export' => ['mixed', 'rdo'=>'sdo_model_reflectiondataobject', 'return='=>'bool'], +'SDO_Model_ReflectionDataObject::getContainmentProperty' => ['SDO_Model_Property'], +'SDO_Model_ReflectionDataObject::getInstanceProperties' => ['array'], +'SDO_Model_ReflectionDataObject::getType' => ['SDO_Model_Type'], +'SDO_Model_Type::getBaseType' => ['SDO_Model_Type'], +'SDO_Model_Type::getName' => ['string'], +'SDO_Model_Type::getNamespaceURI' => ['string'], +'SDO_Model_Type::getProperties' => ['array'], +'SDO_Model_Type::getProperty' => ['SDO_Model_Property', 'identifier'=>''], +'SDO_Model_Type::isAbstractType' => ['bool'], +'SDO_Model_Type::isDataType' => ['bool'], +'SDO_Model_Type::isInstance' => ['bool', 'data_object'=>'sdo_dataobject'], +'SDO_Model_Type::isOpenType' => ['bool'], +'SDO_Model_Type::isSequencedType' => ['bool'], +'SDO_Sequence::getProperty' => ['SDO_Model_Property', 'sequence_index'=>'int'], +'SDO_Sequence::insert' => ['void', 'value'=>'mixed', 'sequenceindex='=>'int', 'propertyidentifier='=>'mixed'], +'SDO_Sequence::move' => ['void', 'toindex'=>'int', 'fromindex'=>'int'], +'SeasLog::__destruct' => ['void'], +'SeasLog::alert' => ['bool', 'message'=>'string', 'content='=>'array', 'logger='=>'string'], +'SeasLog::analyzerCount' => ['mixed', 'level'=>'string', 'log_path='=>'string', 'key_word='=>'string'], +'SeasLog::analyzerDetail' => ['mixed', 'level'=>'string', 'log_path='=>'string', 'key_word='=>'string', 'start='=>'int', 'limit='=>'int', 'order='=>'int'], +'SeasLog::closeLoggerStream' => ['bool', 'model'=>'int', 'logger'=>'string'], +'SeasLog::critical' => ['bool', 'message'=>'string', 'content='=>'array', 'logger='=>'string'], +'SeasLog::debug' => ['bool', 'message'=>'string', 'content='=>'array', 'logger='=>'string'], +'SeasLog::emergency' => ['bool', 'message'=>'string', 'content='=>'array', 'logger='=>'string'], +'SeasLog::error' => ['bool', 'message'=>'string', 'content='=>'array', 'logger='=>'string'], +'SeasLog::flushBuffer' => ['bool'], +'SeasLog::getBasePath' => ['string'], +'SeasLog::getBuffer' => ['array'], +'SeasLog::getBufferEnabled' => ['bool'], +'SeasLog::getDatetimeFormat' => ['string'], +'SeasLog::getLastLogger' => ['string'], +'SeasLog::getRequestID' => ['string'], +'SeasLog::getRequestVariable' => ['bool', 'key'=>'int'], +'SeasLog::info' => ['bool', 'message'=>'string', 'content='=>'array', 'logger='=>'string'], +'SeasLog::log' => ['bool', 'level'=>'string', 'message='=>'string', 'content='=>'array', 'logger='=>'string'], +'SeasLog::notice' => ['bool', 'message'=>'string', 'content='=>'array', 'logger='=>'string'], +'SeasLog::setBasePath' => ['bool', 'base_path'=>'string'], +'SeasLog::setDatetimeFormat' => ['bool', 'format'=>'string'], +'SeasLog::setLogger' => ['bool', 'logger'=>'string'], +'SeasLog::setRequestID' => ['bool', 'request_id'=>'string'], +'SeasLog::setRequestVariable' => ['bool', 'key'=>'int', 'value'=>'string'], +'SeasLog::warning' => ['bool', 'message'=>'string', 'content='=>'array', 'logger='=>'string'], +'seaslog_get_author' => ['string'], +'seaslog_get_version' => ['string'], +'SeekableIterator::__construct' => ['void'], +'SeekableIterator::current' => ['mixed'], +'SeekableIterator::key' => ['int|string'], +'SeekableIterator::next' => ['void'], +'SeekableIterator::rewind' => ['void'], +'SeekableIterator::seek' => ['void', 'position'=>'int'], +'SeekableIterator::valid' => ['bool'], +'sem_acquire' => ['bool', 'sem_identifier'=>'resource', 'nowait='=>'bool'], +'sem_get' => ['resource|false', 'key'=>'int', 'max_acquire='=>'int', 'perm='=>'int', 'auto_release='=>'int'], +'sem_release' => ['bool', 'sem_identifier'=>'resource'], +'sem_remove' => ['bool', 'sem_identifier'=>'resource'], +'Serializable::__construct' => ['void'], +'Serializable::serialize' => ['?string'], +'Serializable::unserialize' => ['void', 'serialized'=>'string'], +'serialize' => ['string', 'variable'=>'mixed'], +'ServerRequest::withInput' => ['ServerRequest', 'input'=>'mixed'], +'ServerRequest::withoutParams' => ['ServerRequest', 'params'=>'int|string'], +'ServerRequest::withParam' => ['ServerRequest', 'key'=>'int|string', 'value'=>'mixed'], +'ServerRequest::withParams' => ['ServerRequest', 'params'=>'mixed'], +'ServerRequest::withUrl' => ['ServerRequest', 'url'=>'array'], +'ServerResponse::addHeader' => ['void', 'label'=>'string', 'value'=>'string'], +'ServerResponse::date' => ['string', 'date'=>'string|DateTimeInterface'], +'ServerResponse::getHeader' => ['string', 'label'=>'string'], +'ServerResponse::getHeaders' => ['string[]'], +'ServerResponse::getStatus' => ['int'], +'ServerResponse::getVersion' => ['string'], +'ServerResponse::setHeader' => ['void', 'label'=>'string', 'value'=>'string'], +'ServerResponse::setStatus' => ['void', 'status'=>'int'], +'ServerResponse::setVersion' => ['void', 'version'=>'string'], +'session_abort' => ['bool'], +'session_cache_expire' => ['int', 'new_cache_expire='=>'int'], +'session_cache_limiter' => ['string', 'new_cache_limiter='=>'string'], +'session_commit' => ['bool'], +'session_create_id' => ['string', 'prefix='=>'string'], +'session_decode' => ['bool', 'data'=>'string'], +'session_destroy' => ['bool'], +'session_encode' => ['string'], +'session_gc' => ['int|false'], +'session_get_cookie_params' => ['array'], +'session_id' => ['string', 'newid='=>'string'], +'session_is_registered' => ['bool', 'name'=>'string'], +'session_module_name' => ['string', 'newname='=>'string'], +'session_name' => ['string', 'newname='=>'string'], +'session_pgsql_add_error' => ['bool', 'error_level'=>'int', 'error_message='=>'string'], +'session_pgsql_get_error' => ['array', 'with_error_message='=>'bool'], +'session_pgsql_get_field' => ['string'], +'session_pgsql_reset' => ['bool'], +'session_pgsql_set_field' => ['bool', 'value'=>'string'], +'session_pgsql_status' => ['array'], +'session_regenerate_id' => ['bool', 'delete_old_session='=>'bool'], +'session_register' => ['bool', 'name'=>'mixed', '...args='=>'mixed'], +'session_register_shutdown' => ['void'], +'session_reset' => ['bool'], +'session_save_path' => ['string', 'newname='=>'string'], +'session_set_cookie_params' => ['bool', 'lifetime'=>'int', 'path='=>'string', 'domain='=>'?string', 'secure='=>'bool', 'httponly='=>'bool'], +'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:int,path?:string,domain?:?string,secure?:bool,httponly?:bool}'], +'session_set_save_handler' => ['bool', 'open'=>'callable(string,string):bool', 'close'=>'callable():bool', 'read'=>'callable(string):string', 'write'=>'callable(string,string):bool', 'destroy'=>'callable(string):bool', 'gc'=>'callable(string):bool', 'create_sid='=>'callable():string', 'validate_sid='=>'callable(string):bool', 'update_timestamp='=>'callable(string):bool'], +'session_set_save_handler\'1' => ['bool', 'sessionhandler'=>'SessionHandlerInterface', 'register_shutdown='=>'bool'], +'session_start' => ['bool', 'options='=>'array'], +'session_status' => ['int'], +'session_unregister' => ['bool', 'name'=>'string'], +'session_unset' => ['bool'], +'session_write_close' => ['bool'], +'SessionHandler::close' => ['bool'], +'SessionHandler::create_sid' => ['char'], +'SessionHandler::destroy' => ['bool', 'id'=>'string'], +'SessionHandler::gc' => ['bool', 'maxlifetime'=>'int'], +'SessionHandler::open' => ['bool', 'save_path'=>'string', 'session_name'=>'string'], +'SessionHandler::read' => ['string', 'id'=>'string'], +'SessionHandler::updateTimestamp' => ['bool', 'session_id'=>'string', 'session_data'=>'string'], +'SessionHandler::validateId' => ['bool', 'session_id'=>'string'], +'SessionHandler::write' => ['bool', 'id'=>'string', 'data'=>'string'], +'SessionHandlerInterface::close' => ['bool'], +'SessionHandlerInterface::destroy' => ['bool', 'session_id'=>'string'], +'SessionHandlerInterface::gc' => ['bool', 'maxlifetime'=>'int'], +'SessionHandlerInterface::open' => ['bool', 'save_path'=>'string', 'name'=>'string'], +'SessionHandlerInterface::read' => ['string', 'session_id'=>'string'], +'SessionHandlerInterface::write' => ['bool', 'session_id'=>'string', 'session_data'=>'string'], +'SessionIdInterface::create_sid' => ['string'], +'SessionUpdateTimestampHandler::updateTimestamp' => ['bool', 'id'=>'string', 'data'=>'string'], +'SessionUpdateTimestampHandler::validateId' => ['char', 'id'=>'string'], +'SessionUpdateTimestampHandlerInterface::updateTimestamp' => ['bool', 'key'=>'string', 'value'=>'string'], +'SessionUpdateTimestampHandlerInterface::validateId' => ['bool', 'key'=>'string'], +'set_error_handler' => ['null|callable(int,string,string=,int=,array=):bool', 'error_handler'=>'null|callable(int,string,string=,int=,array=):bool', 'error_types='=>'int'], +'set_exception_handler' => ['null|callable(Throwable):void', 'exception_handler'=>'null|callable(Throwable):void'], +'set_file_buffer' => ['int', 'fp'=>'resource', 'buffer'=>'int'], +'set_include_path' => ['string|false', 'new_include_path'=>'string'], +'set_magic_quotes_runtime' => ['bool', 'new_setting'=>'bool'], +'set_time_limit' => ['bool', 'seconds'=>'int'], +'setcookie' => ['bool', 'name'=>'string', 'value='=>'string', 'expires='=>'int', 'path='=>'string', 'domain='=>'string', 'secure='=>'bool', 'httponly='=>'bool', 'samesite='=>'string', 'url_encode='=>'int'], +'setcookie\'1' => ['bool', 'name'=>'string', 'value='=>'string', 'options='=>'array'], +'setLeftFill' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], +'setLine' => ['void', 'width'=>'int', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], +'setlocale' => ['string|false', 'category'=>'int', 'locale'=>'string|0|null', '...args='=>'string'], +'setlocale\'1' => ['string|false', 'category'=>'int', 'locale'=>'?array'], +'setproctitle' => ['void', 'title'=>'string'], +'setrawcookie' => ['bool', 'name'=>'string', 'value='=>'string', 'expires='=>'int', 'path='=>'string', 'domain='=>'string', 'secure='=>'bool', 'httponly='=>'bool'], +'setRightFill' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], +'setthreadtitle' => ['bool', 'title'=>'string'], +'settype' => ['bool', '&rw_var'=>'mixed', 'type'=>'string'], +'sha1' => ['string', 'string'=>'string', 'raw_output='=>'bool'], +'sha1_file' => ['string|false', 'filename'=>'string', 'raw_output='=>'bool'], +'sha256' => ['string', 'string'=>'string', 'raw_output='=>'bool'], +'sha256_file' => ['string', 'filename'=>'string', 'raw_output='=>'bool'], +'shapefileObj::__construct' => ['void', 'filename'=>'string', 'type'=>'int'], +'shapefileObj::addPoint' => ['int', 'point'=>'pointObj'], +'shapefileObj::addShape' => ['int', 'shape'=>'shapeObj'], +'shapefileObj::free' => ['void'], +'shapefileObj::getExtent' => ['rectObj', 'i'=>'int'], +'shapefileObj::getPoint' => ['shapeObj', 'i'=>'int'], +'shapefileObj::getShape' => ['shapeObj', 'i'=>'int'], +'shapefileObj::getTransformed' => ['shapeObj', 'map'=>'mapObj', 'i'=>'int'], +'shapefileObj::ms_newShapefileObj' => ['shapefileObj', 'filename'=>'string', 'type'=>'int'], +'shapeObj::__construct' => ['void', 'type'=>'int'], +'shapeObj::add' => ['int', 'line'=>'lineObj'], +'shapeObj::boundary' => ['shapeObj'], +'shapeObj::contains' => ['bool', 'point'=>'pointObj'], +'shapeObj::containsShape' => ['int', 'shape2'=>'shapeObj'], +'shapeObj::convexhull' => ['shapeObj'], +'shapeObj::crosses' => ['int', 'shape'=>'shapeObj'], +'shapeObj::difference' => ['shapeObj', 'shape'=>'shapeObj'], +'shapeObj::disjoint' => ['int', 'shape'=>'shapeObj'], +'shapeObj::draw' => ['int', 'map'=>'mapObj', 'layer'=>'layerObj', 'img'=>'imageObj'], +'shapeObj::equals' => ['int', 'shape'=>'shapeObj'], +'shapeObj::free' => ['void'], +'shapeObj::getArea' => ['float'], +'shapeObj::getCentroid' => ['pointObj'], +'shapeObj::getLabelPoint' => ['pointObj'], +'shapeObj::getLength' => ['float'], +'shapeObj::getPointUsingMeasure' => ['pointObj', 'm'=>'float'], +'shapeObj::getValue' => ['string', 'layer'=>'layerObj', 'filedname'=>'string'], +'shapeObj::intersection' => ['shapeObj', 'shape'=>'shapeObj'], +'shapeObj::intersects' => ['bool', 'shape'=>'shapeObj'], +'shapeObj::line' => ['lineObj', 'i'=>'int'], +'shapeObj::ms_shapeObjFromWkt' => ['shapeObj', 'wkt'=>'string'], +'shapeObj::overlaps' => ['int', 'shape'=>'shapeObj'], +'shapeObj::project' => ['int', 'in'=>'projectionObj', 'out'=>'projectionObj'], +'shapeObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'shapeObj::setBounds' => ['int'], +'shapeObj::simplify' => ['shapeObj|null', 'tolerance'=>'float'], +'shapeObj::symdifference' => ['shapeObj', 'shape'=>'shapeObj'], +'shapeObj::topologyPreservingSimplify' => ['shapeObj|null', 'tolerance'=>'float'], +'shapeObj::touches' => ['int', 'shape'=>'shapeObj'], +'shapeObj::toWkt' => ['string'], +'shapeObj::union' => ['shapeObj', 'shape'=>'shapeObj'], +'shapeObj::within' => ['int', 'shape2'=>'shapeObj'], +'shell_exec' => ['?string', 'cmd'=>'string'], +'shm_attach' => ['resource', 'key'=>'int', 'memsize='=>'int', 'perm='=>'int'], +'shm_detach' => ['bool', 'shm_identifier'=>'resource'], +'shm_get_var' => ['mixed', 'id'=>'resource', 'variable_key'=>'int'], +'shm_has_var' => ['bool', 'shm_identifier'=>'resource', 'variable_key'=>'int'], +'shm_put_var' => ['bool', 'shm_identifier'=>'resource', 'variable_key'=>'int', 'variable'=>'mixed'], +'shm_remove' => ['bool', 'shm_identifier'=>'resource'], +'shm_remove_var' => ['bool', 'shm_identifier'=>'resource', 'variable_key'=>'int'], +'shmop_close' => ['void', 'shmid'=>'resource'], +'shmop_delete' => ['bool', 'shmid'=>'resource'], +'shmop_open' => ['resource|false', 'key'=>'int', 'flags'=>'string', 'mode'=>'int', 'size'=>'int'], +'shmop_read' => ['string|false', 'shmid'=>'resource', 'start'=>'int', 'count'=>'int'], +'shmop_size' => ['int', 'shmid'=>'resource'], +'shmop_write' => ['int|false', 'shmid'=>'resource', 'data'=>'string', 'offset'=>'int'], +'show_source' => ['string|bool', 'file_name'=>'string', 'return='=>'bool'], +'shuffle' => ['bool', '&rw_array'=>'array'], +'signeurlpaiement' => ['string', 'clent'=>'string', 'data'=>'string'], +'similar_text' => ['int', 'string1'=>'string', 'string2'=>'string', '&w_percent='=>'float'], +'simplexml_import_dom' => ['SimpleXMLElement|false', 'node'=>'DOMNode', 'class_name='=>'string'], +'simplexml_load_file' => ['SimpleXMLElement|false', 'filename'=>'string', 'class_name='=>'string', 'options='=>'int', 'ns='=>'string', 'is_prefix='=>'bool'], +'simplexml_load_string' => ['SimpleXMLElement|false', 'data'=>'string', 'class_name='=>'string', 'options='=>'int', 'ns='=>'string', 'is_prefix='=>'bool'], +'SimpleXMLElement::__construct' => ['void', 'data'=>'string', 'options='=>'int', 'data_is_url='=>'bool', 'ns='=>'string', 'is_prefix='=>'bool'], +'SimpleXMLElement::__get' => ['SimpleXMLElement', 'name'=>'string'], +'SimpleXMLElement::__toString' => ['string'], +'SimpleXMLElement::addAttribute' => ['void', 'name'=>'string', 'value='=>'string', 'ns='=>'string'], +'SimpleXMLElement::addChild' => ['SimpleXMLElement', 'name'=>'string', 'value='=>'string', 'ns='=>'string'], +'SimpleXMLElement::asXML' => ['bool', 'filename'=>'string'], +'SimpleXMLElement::asXML\'1' => ['string|false'], +'SimpleXMLElement::attributes' => ['?SimpleXMLElement', 'ns='=>'string', 'is_prefix='=>'bool'], +'SimpleXMLElement::children' => ['SimpleXMLElement', 'ns='=>'string', 'is_prefix='=>'bool'], +'SimpleXMLElement::count' => ['int'], +'SimpleXMLElement::getDocNamespaces' => ['string[]', 'recursive='=>'bool', 'from_root='=>'bool'], +'SimpleXMLElement::getName' => ['string'], +'SimpleXMLElement::getNamespaces' => ['string[]', 'recursive='=>'bool'], +'SimpleXMLElement::offsetExists' => ['bool', 'offset'=>'int|string'], +'SimpleXMLElement::offsetGet' => ['SimpleXMLElement', 'offset'=>'int|string'], +'SimpleXMLElement::offsetSet' => ['void', 'offset'=>'int|string', 'value'=>'mixed'], +'SimpleXMLElement::offsetUnset' => ['void', 'offset'=>'int|string'], +'SimpleXMLElement::registerXPathNamespace' => ['bool', 'prefix'=>'string', 'ns'=>'string'], +'SimpleXMLElement::saveXML' => ['mixed', 'filename='=>'string'], +'SimpleXMLElement::xpath' => ['SimpleXMLElement[]|false', 'path'=>'string'], +'SimpleXMLIterator::__construct' => ['void', 'data'=>'string', 'options='=>'int', 'data_is_url='=>'bool', 'ns='=>'string', 'is_prefix='=>'bool'], +'SimpleXMLIterator::__toString' => ['string'], +'SimpleXMLIterator::addAttribute' => ['void', 'name'=>'string', 'value='=>'string', 'ns='=>'string'], +'SimpleXMLIterator::addChild' => ['SimpleXMLElement', 'name'=>'string', 'value='=>'string', 'ns='=>'string'], +'SimpleXMLIterator::asXML' => ['bool|string', 'filename='=>'string'], +'SimpleXMLIterator::attributes' => ['?SimpleXMLElement', 'ns='=>'string', 'is_prefix='=>'bool'], +'SimpleXMLIterator::children' => ['SimpleXMLElement', 'ns='=>'string', 'is_prefix='=>'bool'], +'SimpleXMLIterator::count' => ['int'], +'SimpleXMLIterator::current' => ['?SimpleXMLIterator'], +'SimpleXMLIterator::getChildren' => ['SimpleXMLIterator'], +'SimpleXMLIterator::getDocNamespaces' => ['string[]', 'recursive='=>'bool', 'from_root='=>'bool'], +'SimpleXMLIterator::getName' => ['string'], +'SimpleXMLIterator::getNamespaces' => ['string[]', 'recursive='=>'bool'], +'SimpleXMLIterator::hasChildren' => ['bool'], +'SimpleXMLIterator::key' => ['string|false'], +'SimpleXMLIterator::next' => ['void'], +'SimpleXMLIterator::registerXPathNamespace' => ['bool', 'prefix'=>'string', 'ns'=>'string'], +'SimpleXMLIterator::rewind' => ['void'], +'SimpleXMLIterator::saveXML' => ['mixed', 'filename='=>'string'], +'SimpleXMLIterator::valid' => ['bool'], +'SimpleXMLIterator::xpath' => ['SimpleXMLElement[]|false', 'path'=>'string'], +'sin' => ['float', 'number'=>'float'], +'sinh' => ['float', 'number'=>'float'], +'sizeof' => ['int', 'value'=>'Countable|array', 'mode='=>'int'], +'sleep' => ['int|false', 'seconds'=>'int'], +'snmp2_get' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmp2_getnext' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmp2_real_walk' => ['array|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmp2_set' => ['bool', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'type'=>'string', 'value'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmp2_walk' => ['array|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmp3_get' => ['string|false', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmp3_getnext' => ['string|false', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmp3_real_walk' => ['array|false', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmp3_set' => ['bool', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'type'=>'string', 'value'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmp3_walk' => ['array|false', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'SNMP::__construct' => ['void', 'version'=>'int', 'hostname'=>'string', 'community'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'SNMP::close' => ['bool'], +'SNMP::get' => ['array|string|false', 'object_id'=>'string|array', 'preserve_keys='=>'bool'], +'SNMP::getErrno' => ['int'], +'SNMP::getError' => ['string'], +'SNMP::getnext' => ['string|array|false', 'object_id'=>'string|array'], +'SNMP::set' => ['bool', 'object_id'=>'string|array', 'type'=>'string|array', 'value'=>'mixed'], +'SNMP::setSecurity' => ['bool', 'sec_level'=>'string', 'auth_protocol='=>'string', 'auth_passphrase='=>'string', 'priv_protocol='=>'string', 'priv_passphrase='=>'string', 'contextname='=>'string', 'contextengineid='=>'string'], +'SNMP::walk' => ['array|false', 'object_id'=>'string', 'suffix_as_key='=>'bool', 'non_repeaters='=>'int', 'max_repetitions='=>'int'], +'snmp_get_quick_print' => ['bool'], +'snmp_get_valueretrieval' => ['int'], +'snmp_read_mib' => ['bool', 'filename'=>'string'], +'snmp_set_enum_print' => ['bool', 'enum_print'=>'int'], +'snmp_set_oid_numeric_print' => ['void', 'oid_format'=>'int'], +'snmp_set_oid_output_format' => ['bool', 'oid_format'=>'int'], +'snmp_set_quick_print' => ['bool', 'quick_print'=>'bool'], +'snmp_set_valueretrieval' => ['bool', 'method='=>'int'], +'snmpget' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmpgetnext' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmprealwalk' => ['array|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmpset' => ['bool', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'type'=>'string', 'value'=>'mixed', 'timeout='=>'int', 'retries='=>'int'], +'snmpwalk' => ['array|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmpwalkoid' => ['array|false', 'hostname'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'SoapClient::__call' => ['', 'function_name'=>'string', 'arguments'=>'array'], +'SoapClient::__construct' => ['void', 'wsdl'=>'mixed', 'options='=>'array|null'], +'SoapClient::__doRequest' => ['string', 'request'=>'string', 'location'=>'string', 'action'=>'string', 'version'=>'int', 'one_way='=>'int'], +'SoapClient::__getCookies' => ['array'], +'SoapClient::__getFunctions' => ['array'], +'SoapClient::__getLastRequest' => ['string'], +'SoapClient::__getLastRequestHeaders' => ['string'], +'SoapClient::__getLastResponse' => ['string'], +'SoapClient::__getLastResponseHeaders' => ['string'], +'SoapClient::__getTypes' => ['array'], +'SoapClient::__setCookie' => ['', 'name'=>'string', 'value='=>'string'], +'SoapClient::__setLocation' => ['string', 'new_location='=>'string'], +'SoapClient::__setSoapHeaders' => ['bool', 'soapheaders='=>''], +'SoapClient::__soapCall' => ['', 'function_name'=>'string', 'arguments'=>'array', 'options='=>'array', 'input_headers='=>'SoapHeader|array', '&w_output_headers='=>'array'], +'SoapClient::SoapClient' => ['object', 'wsdl'=>'mixed', 'options='=>'array|null'], +'SoapFault::__clone' => ['void'], +'SoapFault::__construct' => ['void', 'faultcode'=>'string', 'faultstring'=>'string', 'faultactor='=>'string', 'detail='=>'string', 'faultname='=>'string', 'headerfault='=>'string'], +'SoapFault::__toString' => ['string'], +'SoapFault::__wakeup' => ['void'], +'SoapFault::getCode' => ['int'], +'SoapFault::getFile' => ['string'], +'SoapFault::getLine' => ['int'], +'SoapFault::getMessage' => ['string'], +'SoapFault::getPrevious' => ['?Exception|?Throwable'], +'SoapFault::getTrace' => ['list>'], +'SoapFault::getTraceAsString' => ['string'], +'SoapFault::SoapFault' => ['object', 'faultcode'=>'string', 'faultstring'=>'string', 'faultactor='=>'string', 'detail='=>'string', 'faultname='=>'string', 'headerfault='=>'string'], +'SoapHeader::__construct' => ['void', 'namespace'=>'string', 'name'=>'string', 'data='=>'mixed', 'mustunderstand='=>'bool', 'actor='=>'string'], +'SoapHeader::SoapHeader' => ['object', 'namespace'=>'string', 'name'=>'string', 'data='=>'mixed', 'mustunderstand='=>'bool', 'actor='=>'string'], +'SoapParam::__construct' => ['void', 'data'=>'mixed', 'name'=>'string'], +'SoapParam::SoapParam' => ['object', 'data'=>'mixed', 'name'=>'string'], +'SoapServer::__construct' => ['void', 'wsdl'=>'?string', 'options='=>'array'], +'SoapServer::addFunction' => ['void', 'functions'=>'mixed'], +'SoapServer::addSoapHeader' => ['void', 'object'=>'SoapHeader'], +'SoapServer::fault' => ['void', 'code'=>'string', 'string'=>'string', 'actor='=>'string', 'details='=>'string', 'name='=>'string'], +'SoapServer::getFunctions' => ['array'], +'SoapServer::handle' => ['void', 'soap_request='=>'string'], +'SoapServer::setClass' => ['void', 'class_name'=>'string', '...args='=>'mixed'], +'SoapServer::setObject' => ['void', 'object'=>'object'], +'SoapServer::setPersistence' => ['void', 'mode'=>'int'], +'SoapServer::SoapServer' => ['object', 'wsdl'=>'?string', 'options='=>'array'], +'SoapVar::__construct' => ['void', 'data'=>'mixed', 'encoding'=>'int', 'type_name='=>'string|null', 'type_namespace='=>'string|null', 'node_name='=>'string|null', 'node_namespace='=>'string|null'], +'SoapVar::SoapVar' => ['object', 'data'=>'mixed', 'encoding'=>'int', 'type_name='=>'string|null', 'type_namespace='=>'string|null', 'node_name='=>'string|null', 'node_namespace='=>'string|null'], +'socket_accept' => ['resource|false', 'socket'=>'resource'], +'socket_addrinfo_bind' => ['?resource', 'addrinfo'=>'resource'], +'socket_addrinfo_connect' => ['?resource', 'addrinfo'=>'resource'], +'socket_addrinfo_explain' => ['array', 'addrinfo'=>'resource'], +'socket_addrinfo_lookup' => ['AddressInfo[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'], +'socket_bind' => ['bool', 'socket'=>'resource', 'addr'=>'string', 'port='=>'int'], +'socket_clear_error' => ['void', 'socket='=>'resource'], +'socket_close' => ['void', 'socket'=>'resource'], +'socket_cmsg_space' => ['int', 'level'=>'int', 'type'=>'int'], +'socket_connect' => ['bool', 'socket'=>'resource', 'addr'=>'string', 'port='=>'int'], +'socket_create' => ['resource|false', 'domain'=>'int', 'type'=>'int', 'protocol'=>'int'], +'socket_create_listen' => ['resource|false', 'port'=>'int', 'backlog='=>'int'], +'socket_create_pair' => ['bool', 'domain'=>'int', 'type'=>'int', 'protocol'=>'int', '&w_fd'=>'resource[]'], +'socket_export_stream' => ['resource|false', 'socket'=>'resource'], +'socket_get_option' => ['mixed|false', 'socket'=>'resource', 'level'=>'int', 'optname'=>'int'], +'socket_get_status' => ['array', 'stream'=>'resource'], +'socket_getopt' => ['mixed', 'socket'=>'resource', 'level'=>'int', 'optname'=>'int'], +'socket_getpeername' => ['bool', 'socket'=>'resource', '&w_addr'=>'string', '&w_port='=>'int'], +'socket_getsockname' => ['bool', 'socket'=>'resource', '&w_addr'=>'string', '&w_port='=>'int'], +'socket_import_stream' => ['resource|false|null', 'stream'=>'resource'], +'socket_last_error' => ['int', 'socket='=>'resource'], +'socket_listen' => ['bool', 'socket'=>'resource', 'backlog='=>'int'], +'socket_read' => ['string|false', 'socket'=>'resource', 'length'=>'int', 'type='=>'int'], +'socket_recv' => ['int|false', 'socket'=>'resource', '&w_buf'=>'string', 'length'=>'int', 'flags'=>'int'], +'socket_recvfrom' => ['int|false', 'socket'=>'resource', '&w_buf'=>'string', 'length'=>'int', 'flags'=>'int', '&w_name'=>'string', '&w_port='=>'int'], +'socket_recvmsg' => ['int|false', 'socket'=>'resource', '&w_message'=>'string', 'flags='=>'int'], +'socket_select' => ['int|false', '&rw_read_fds'=>'resource[]|null', '&rw_write_fds'=>'resource[]|null', '&rw_except_fds'=>'resource[]|null', 'tv_sec'=>'int', 'tv_usec='=>'int'], +'socket_send' => ['int|false', 'socket'=>'resource', 'buf'=>'string', 'length'=>'int', 'flags'=>'int'], +'socket_sendmsg' => ['int|false', 'socket'=>'resource', 'message'=>'array', 'flags'=>'int'], +'socket_sendto' => ['int|false', 'socket'=>'resource', 'buf'=>'string', 'length'=>'int', 'flags'=>'int', 'addr'=>'string', 'port='=>'int'], +'socket_set_block' => ['bool', 'socket'=>'resource'], +'socket_set_blocking' => ['bool', 'socket'=>'resource', 'mode'=>'int'], +'socket_set_nonblock' => ['bool', 'socket'=>'resource'], +'socket_set_option' => ['bool', 'socket'=>'resource', 'level'=>'int', 'optname'=>'int', 'optval'=>'int|string|array'], +'socket_set_timeout' => ['bool', 'stream'=>'resource', 'seconds'=>'int', 'microseconds='=>'int'], +'socket_setopt' => ['void', 'socket'=>'resource', 'level'=>'int', 'optname'=>'int', 'optval'=>'int|string|array'], +'socket_shutdown' => ['bool', 'socket'=>'resource', 'how='=>'int'], +'socket_strerror' => ['string', 'errno'=>'int'], +'socket_write' => ['int|false', 'socket'=>'resource', 'buf'=>'string', 'length='=>'int'], +'socket_wsaprotocol_info_export' => ['string|false', 'stream'=>'resource', 'target_pid'=>'int'], +'socket_wsaprotocol_info_import' => ['resource|false', 'id'=>'string'], +'socket_wsaprotocol_info_release' => ['bool', 'id'=>'string'], +'Sodium\add' => ['void', '&left'=>'string', 'right'=>'string'], +'Sodium\bin2hex' => ['string', 'binary'=>'string'], +'Sodium\compare' => ['int', 'left'=>'string', 'right'=>'string'], +'Sodium\crypto_aead_aes256gcm_decrypt' => ['string|false', 'msg'=>'string', 'nonce'=>'string', 'key'=>'string', 'ad='=>'string'], +'Sodium\crypto_aead_aes256gcm_encrypt' => ['string', 'msg'=>'string', 'nonce'=>'string', 'key'=>'string', 'ad='=>'string'], +'Sodium\crypto_aead_aes256gcm_is_available' => ['bool'], +'Sodium\crypto_aead_chacha20poly1305_decrypt' => ['string', 'msg'=>'string', 'nonce'=>'string', 'key'=>'string', 'ad='=>'string'], +'Sodium\crypto_aead_chacha20poly1305_encrypt' => ['string', 'msg'=>'string', 'nonce'=>'string', 'key'=>'string', 'ad='=>'string'], +'Sodium\crypto_auth' => ['string', 'msg'=>'string', 'key'=>'string'], +'Sodium\crypto_auth_verify' => ['bool', 'mac'=>'string', 'msg'=>'string', 'key'=>'string'], +'Sodium\crypto_box' => ['string', 'msg'=>'string', 'nonce'=>'string', 'keypair'=>'string'], +'Sodium\crypto_box_keypair' => ['string'], +'Sodium\crypto_box_keypair_from_secretkey_and_publickey' => ['string', 'secretkey'=>'string', 'publickey'=>'string'], +'Sodium\crypto_box_open' => ['string', 'msg'=>'string', 'nonce'=>'string', 'keypair'=>'string'], +'Sodium\crypto_box_publickey' => ['string', 'keypair'=>'string'], +'Sodium\crypto_box_publickey_from_secretkey' => ['string', 'secretkey'=>'string'], +'Sodium\crypto_box_seal' => ['string', 'message'=>'string', 'publickey'=>'string'], +'Sodium\crypto_box_seal_open' => ['string', 'encrypted'=>'string', 'keypair'=>'string'], +'Sodium\crypto_box_secretkey' => ['string', 'keypair'=>'string'], +'Sodium\crypto_box_seed_keypair' => ['string', 'seed'=>'string'], +'Sodium\crypto_generichash' => ['string', 'input'=>'string', 'key='=>'string', 'length='=>'int'], +'Sodium\crypto_generichash_final' => ['string', 'state'=>'string', 'length='=>'int'], +'Sodium\crypto_generichash_init' => ['string', 'key='=>'string', 'length='=>'int'], +'Sodium\crypto_generichash_update' => ['bool', '&hashState'=>'string', 'append'=>'string'], +'Sodium\crypto_kx' => ['string', 'secretkey'=>'string', 'publickey'=>'string', 'client_publickey'=>'string', 'server_publickey'=>'string'], +'Sodium\crypto_pwhash' => ['string', 'out_len'=>'int', 'passwd'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], +'Sodium\crypto_pwhash_scryptsalsa208sha256' => ['string', 'out_len'=>'int', 'passwd'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], +'Sodium\crypto_pwhash_scryptsalsa208sha256_str' => ['string', 'passwd'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], +'Sodium\crypto_pwhash_scryptsalsa208sha256_str_verify' => ['bool', 'hash'=>'string', 'passwd'=>'string'], +'Sodium\crypto_pwhash_str' => ['string', 'passwd'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], +'Sodium\crypto_pwhash_str_verify' => ['bool', 'hash'=>'string', 'passwd'=>'string'], +'Sodium\crypto_scalarmult' => ['string', 'ecdhA'=>'string', 'ecdhB'=>'string'], +'Sodium\crypto_scalarmult_base' => ['string', 'sk'=>'string'], +'Sodium\crypto_secretbox' => ['string', 'plaintext'=>'string', 'nonce'=>'string', 'key'=>'string'], +'Sodium\crypto_secretbox_open' => ['string', 'ciphertext'=>'string', 'nonce'=>'string', 'key'=>'string'], +'Sodium\crypto_shorthash' => ['string', 'message'=>'string', 'key'=>'string'], +'Sodium\crypto_sign' => ['string', 'message'=>'string', 'secretkey'=>'string'], +'Sodium\crypto_sign_detached' => ['string', 'message'=>'string', 'secretkey'=>'string'], +'Sodium\crypto_sign_ed25519_pk_to_curve25519' => ['string', 'sign_pk'=>'string'], +'Sodium\crypto_sign_ed25519_sk_to_curve25519' => ['string', 'sign_sk'=>'string'], +'Sodium\crypto_sign_keypair' => ['string'], +'Sodium\crypto_sign_keypair_from_secretkey_and_publickey' => ['string', 'secretkey'=>'string', 'publickey'=>'string'], +'Sodium\crypto_sign_open' => ['string|false', 'signed_message'=>'string', 'publickey'=>'string'], +'Sodium\crypto_sign_publickey' => ['string', 'keypair'=>'string'], +'Sodium\crypto_sign_publickey_from_secretkey' => ['string', 'secretkey'=>'string'], +'Sodium\crypto_sign_secretkey' => ['string', 'keypair'=>'string'], +'Sodium\crypto_sign_seed_keypair' => ['string', 'seed'=>'string'], +'Sodium\crypto_sign_verify_detached' => ['bool', 'signature'=>'string', 'msg'=>'string', 'publickey'=>'string'], +'Sodium\crypto_stream' => ['string', 'length'=>'int', 'nonce'=>'string', 'key'=>'string'], +'Sodium\crypto_stream_xor' => ['string', 'plaintext'=>'string', 'nonce'=>'string', 'key'=>'string'], +'Sodium\hex2bin' => ['string', 'hex'=>'string'], +'Sodium\increment' => ['string', '&nonce'=>'string'], +'Sodium\library_version_major' => ['int'], +'Sodium\library_version_minor' => ['int'], +'Sodium\memcmp' => ['int', 'left'=>'string', 'right'=>'string'], +'Sodium\memzero' => ['void', '&target'=>'string'], +'Sodium\randombytes_buf' => ['string', 'length'=>'int'], +'Sodium\randombytes_random16' => ['int|string'], +'Sodium\randombytes_uniform' => ['int', 'upperBoundNonInclusive'=>'int'], +'Sodium\version_string' => ['string'], +'sodium_add' => ['string', 'string_1'=>'string', 'string_2'=>'string'], +'sodium_base642bin' => ['string', 'base64'=>'string', 'variant'=>'int', 'ignore='=>'string'], +'sodium_bin2base64' => ['string', 'binary'=>'string', 'variant'=>'int'], +'sodium_bin2hex' => ['string', 'binary'=>'string'], +'sodium_compare' => ['int', 'string_1'=>'string', 'string_2'=>'string'], +'sodium_crypto_aead_aes256gcm_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_aead_aes256gcm_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_aead_aes256gcm_is_available' => ['bool'], +'sodium_crypto_aead_aes256gcm_keygen' => ['string'], +'sodium_crypto_aead_chacha20poly1305_decrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_aead_chacha20poly1305_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['array|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_aead_chacha20poly1305_ietf_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_aead_chacha20poly1305_ietf_keygen' => ['string'], +'sodium_crypto_aead_chacha20poly1305_keygen' => ['string'], +'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_aead_xchacha20poly1305_ietf_keygen' => ['string'], +'sodium_crypto_auth' => ['string', 'message'=>'string', 'key'=>'string'], +'sodium_crypto_auth_keygen' => ['string'], +'sodium_crypto_auth_verify' => ['bool', 'mac'=>'string', 'message'=>'string', 'key'=>'string'], +'sodium_crypto_box' => ['string', 'string'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_box_keypair' => ['string'], +'sodium_crypto_box_keypair_from_secretkey_and_publickey' => ['string', 'secret_key'=>'string', 'public_key'=>'string'], +'sodium_crypto_box_open' => ['string|false', 'message'=>'string', 'nonce'=>'string', 'message_keypair'=>'string'], +'sodium_crypto_box_publickey' => ['string', 'keypair'=>'string'], +'sodium_crypto_box_publickey_from_secretkey' => ['string', 'secretkey'=>'string'], +'sodium_crypto_box_seal' => ['string', 'message'=>'string', 'publickey'=>'string'], +'sodium_crypto_box_seal_open' => ['string|false', 'message'=>'string', 'recipient_keypair'=>'string'], +'sodium_crypto_box_secretkey' => ['string', 'keypair'=>'string'], +'sodium_crypto_box_seed_keypair' => ['string', 'seed'=>'string'], +'sodium_crypto_generichash' => ['string', 'msg'=>'string', 'key='=>'?string', 'length='=>'?int'], +'sodium_crypto_generichash_final' => ['string', 'state'=>'string', 'length='=>'?int'], +'sodium_crypto_generichash_init' => ['string', 'key='=>'?string', 'length='=>'?int'], +'sodium_crypto_generichash_keygen' => ['string'], +'sodium_crypto_generichash_update' => ['bool', 'state'=>'string', 'string'=>'string'], +'sodium_crypto_kdf_derive_from_key' => ['string', 'subkey_len'=>'int', 'subkey_id'=>'int', 'context'=>'string', 'key'=>'string'], +'sodium_crypto_kdf_keygen' => ['string'], +'sodium_crypto_kx' => ['string', 'secretkey'=>'string', 'publickey'=>'string', 'client_publickey'=>'string', 'server_publickey'=>'string'], +'sodium_crypto_kx_client_session_keys' => ['array', 'client_keypair'=>'string', 'server_key'=>'string'], +'sodium_crypto_kx_keypair' => ['string'], +'sodium_crypto_kx_publickey' => ['string', 'keypair'=>'string'], +'sodium_crypto_kx_secretkey' => ['string', 'keypair'=>'string'], +'sodium_crypto_kx_seed_keypair' => ['string', 'seed'=>'string'], +'sodium_crypto_kx_server_session_keys' => ['array', 'server_keypair'=>'string', 'client_key'=>'string'], +'sodium_crypto_pwhash' => ['string', 'length'=>'int', 'password'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int', 'alg='=>'int'], +'sodium_crypto_pwhash_scryptsalsa208sha256' => ['string', 'length'=>'int', 'password'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], +'sodium_crypto_pwhash_scryptsalsa208sha256_str' => ['string', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], +'sodium_crypto_pwhash_scryptsalsa208sha256_str_verify' => ['bool', 'hash'=>'string', 'password'=>'string'], +'sodium_crypto_pwhash_str' => ['string', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], +'sodium_crypto_pwhash_str_needs_rehash' => ['bool', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], +'sodium_crypto_pwhash_str_verify' => ['bool', 'hash'=>'string', 'password'=>'string'], +'sodium_crypto_scalarmult' => ['string', 'string_1'=>'string', 'string_2'=>'string'], +'sodium_crypto_scalarmult_base' => ['string', 'secretkey'=>'string'], +'sodium_crypto_secretbox' => ['string', 'plaintext'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_secretbox_keygen' => ['string'], +'sodium_crypto_secretbox_open' => ['string|false', 'ciphertext'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_secretstream_xchacha20poly1305_init_pull' => ['string', 'header'=>'string', 'key'=>'string'], +'sodium_crypto_secretstream_xchacha20poly1305_init_push' => ['array', 'key'=>'string'], +'sodium_crypto_secretstream_xchacha20poly1305_keygen' => ['string'], +'sodium_crypto_secretstream_xchacha20poly1305_pull' => ['array', 'state'=>'string', 'c'=>'string', 'ad='=>'string'], +'sodium_crypto_secretstream_xchacha20poly1305_push' => ['string', 'state'=>'string', 'msg'=>'string', 'ad='=>'string', 'tag='=>'int'], +'sodium_crypto_secretstream_xchacha20poly1305_rekey' => ['void', 'state'=>'string'], +'sodium_crypto_shorthash' => ['string', 'message'=>'string', 'key'=>'string'], +'sodium_crypto_shorthash_keygen' => ['string'], +'sodium_crypto_sign' => ['string', 'message'=>'string', 'secretkey'=>'string'], +'sodium_crypto_sign_detached' => ['string', 'message'=>'string', 'secretkey'=>'string'], +'sodium_crypto_sign_ed25519_pk_to_curve25519' => ['string', 'ed25519pk'=>'string'], +'sodium_crypto_sign_ed25519_sk_to_curve25519' => ['string', 'ed25519sk'=>'string'], +'sodium_crypto_sign_keypair' => ['string'], +'sodium_crypto_sign_keypair_from_secretkey_and_publickey' => ['string', 'secret_key'=>'string', 'public_key'=>'string'], +'sodium_crypto_sign_open' => ['string|false', 'message'=>'string', 'publickey'=>'string'], +'sodium_crypto_sign_publickey' => ['string', 'keypair'=>'string'], +'sodium_crypto_sign_publickey_from_secretkey' => ['string', 'secretkey'=>'string'], +'sodium_crypto_sign_secretkey' => ['string', 'keypair'=>'string'], +'sodium_crypto_sign_seed_keypair' => ['string', 'seed'=>'string'], +'sodium_crypto_sign_verify_detached' => ['bool', 'signature'=>'string', 'message'=>'string', 'publickey'=>'string'], +'sodium_crypto_stream' => ['string', 'length'=>'int', 'nonce'=>'string', 'key'=>'string'], +'sodium_crypto_stream_keygen' => ['string'], +'sodium_crypto_stream_xor' => ['string', 'message'=>'string', 'nonce'=>'string', 'key'=>'string'], +'sodium_hex2bin' => ['string', 'hex'=>'string', 'ignore='=>'string'], +'sodium_increment' => ['string', '&binary_string'=>'string'], +'sodium_library_version_major' => ['int'], +'sodium_library_version_minor' => ['int'], +'sodium_memcmp' => ['int', 'string_1'=>'string', 'string_2'=>'string'], +'sodium_memzero' => ['void', '&secret'=>'string'], +'sodium_pad' => ['string', 'unpadded'=>'string', 'length'=>'int'], +'sodium_randombytes_buf' => ['string', 'length'=>'int'], +'sodium_randombytes_random16' => ['int|string'], +'sodium_randombytes_uniform' => ['int', 'upperBoundNonInclusive'=>'int'], +'sodium_unpad' => ['string', 'padded'=>'string', 'length'=>'int'], +'sodium_version_string' => ['string'], +'solid_fetch_prev' => ['bool', 'result_id'=>''], +'solr_get_version' => ['string|false'], +'SolrClient::__construct' => ['void', 'clientOptions'=>'array'], +'SolrClient::__destruct' => ['void'], +'SolrClient::addDocument' => ['SolrUpdateResponse', 'doc'=>'SolrInputDocument', 'allowdups='=>'bool', 'commitwithin='=>'int'], +'SolrClient::addDocuments' => ['SolrUpdateResponse', 'docs'=>'array', 'allowdups='=>'bool', 'commitwithin='=>'int'], +'SolrClient::commit' => ['SolrUpdateResponse', 'maxsegments='=>'int', 'waitflush='=>'bool', 'waitsearcher='=>'bool'], +'SolrClient::deleteById' => ['SolrUpdateResponse', 'id'=>'string'], +'SolrClient::deleteByIds' => ['SolrUpdateResponse', 'ids'=>'array'], +'SolrClient::deleteByQueries' => ['SolrUpdateResponse', 'queries'=>'array'], +'SolrClient::deleteByQuery' => ['SolrUpdateResponse', 'query'=>'string'], +'SolrClient::getById' => ['SolrQueryResponse', 'id'=>'string'], +'SolrClient::getByIds' => ['SolrQueryResponse', 'ids'=>'array'], +'SolrClient::getDebug' => ['string'], +'SolrClient::getOptions' => ['array'], +'SolrClient::optimize' => ['SolrUpdateResponse', 'maxsegments='=>'int', 'waitflush='=>'bool', 'waitsearcher='=>'bool'], +'SolrClient::ping' => ['SolrPingResponse'], +'SolrClient::query' => ['SolrQueryResponse', 'query'=>'SolrParams'], +'SolrClient::request' => ['SolrUpdateResponse', 'raw_request'=>'string'], +'SolrClient::rollback' => ['SolrUpdateResponse'], +'SolrClient::setResponseWriter' => ['void', 'responsewriter'=>'string'], +'SolrClient::setServlet' => ['bool', 'type'=>'int', 'value'=>'string'], +'SolrClient::system' => ['SolrGenericResponse'], +'SolrClient::threads' => ['SolrGenericResponse'], +'SolrClientException::__clone' => ['void'], +'SolrClientException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'SolrClientException::__toString' => ['string'], +'SolrClientException::__wakeup' => ['void'], +'SolrClientException::getCode' => ['int'], +'SolrClientException::getFile' => ['string'], +'SolrClientException::getInternalInfo' => ['array'], +'SolrClientException::getLine' => ['int'], +'SolrClientException::getMessage' => ['string'], +'SolrClientException::getPrevious' => ['?Exception|?Throwable'], +'SolrClientException::getTrace' => ['list>'], +'SolrClientException::getTraceAsString' => ['string'], +'SolrCollapseFunction::__construct' => ['void', 'field'=>'string'], +'SolrCollapseFunction::__toString' => ['string'], +'SolrCollapseFunction::getField' => ['string'], +'SolrCollapseFunction::getHint' => ['string'], +'SolrCollapseFunction::getMax' => ['string'], +'SolrCollapseFunction::getMin' => ['string'], +'SolrCollapseFunction::getNullPolicy' => ['string'], +'SolrCollapseFunction::getSize' => ['int'], +'SolrCollapseFunction::setField' => ['SolrCollapseFunction', 'fieldName'=>'string'], +'SolrCollapseFunction::setHint' => ['SolrCollapseFunction', 'hint'=>'string'], +'SolrCollapseFunction::setMax' => ['SolrCollapseFunction', 'max'=>'string'], +'SolrCollapseFunction::setMin' => ['SolrCollapseFunction', 'min'=>'string'], +'SolrCollapseFunction::setNullPolicy' => ['SolrCollapseFunction', 'nullPolicy'=>'string'], +'SolrCollapseFunction::setSize' => ['SolrCollapseFunction', 'size'=>'int'], +'SolrDisMaxQuery::__construct' => ['void', 'q='=>'string'], +'SolrDisMaxQuery::__destruct' => ['void'], +'SolrDisMaxQuery::add' => ['SolrParams', 'name'=>'string', 'value'=>'string'], +'SolrDisMaxQuery::addBigramPhraseField' => ['SolrDisMaxQuery', 'field'=>'string', 'boost'=>'string', 'slop='=>'string'], +'SolrDisMaxQuery::addBoostQuery' => ['SolrDisMaxQuery', 'field'=>'string', 'value'=>'string', 'boost='=>'string'], +'SolrDisMaxQuery::addExpandFilterQuery' => ['SolrQuery', 'fq'=>'string'], +'SolrDisMaxQuery::addExpandSortField' => ['SolrQuery', 'field'=>'string', 'order'=>'string'], +'SolrDisMaxQuery::addFacetDateField' => ['SolrQuery', 'dateField'=>'string'], +'SolrDisMaxQuery::addFacetDateOther' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::addFacetField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::addFacetQuery' => ['SolrQuery', 'facetQuery'=>'string'], +'SolrDisMaxQuery::addField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::addFilterQuery' => ['SolrQuery', 'fq'=>'string'], +'SolrDisMaxQuery::addGroupField' => ['SolrQuery', 'value'=>'string'], +'SolrDisMaxQuery::addGroupFunction' => ['SolrQuery', 'value'=>'string'], +'SolrDisMaxQuery::addGroupQuery' => ['SolrQuery', 'value'=>'string'], +'SolrDisMaxQuery::addGroupSortField' => ['SolrQuery', 'field'=>'string', 'order'=>'int'], +'SolrDisMaxQuery::addHighlightField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::addMltField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::addMltQueryField' => ['SolrQuery', 'field'=>'string', 'boost'=>'float'], +'SolrDisMaxQuery::addParam' => ['SolrParams', 'name'=>'string', 'value'=>'string'], +'SolrDisMaxQuery::addPhraseField' => ['SolrDisMaxQuery', 'field'=>'string', 'boost'=>'string', 'slop='=>'string'], +'SolrDisMaxQuery::addQueryField' => ['SolrDisMaxQuery', 'field'=>'string', 'boost='=>'string'], +'SolrDisMaxQuery::addSortField' => ['SolrQuery', 'field'=>'string', 'order='=>'int'], +'SolrDisMaxQuery::addStatsFacet' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::addStatsField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::addTrigramPhraseField' => ['SolrDisMaxQuery', 'field'=>'string', 'boost'=>'string', 'slop='=>'string'], +'SolrDisMaxQuery::addUserField' => ['SolrDisMaxQuery', 'field'=>'string'], +'SolrDisMaxQuery::collapse' => ['SolrQuery', 'collapseFunction'=>'SolrCollapseFunction'], +'SolrDisMaxQuery::get' => ['mixed', 'param_name'=>'string'], +'SolrDisMaxQuery::getExpand' => ['bool'], +'SolrDisMaxQuery::getExpandFilterQueries' => ['array'], +'SolrDisMaxQuery::getExpandQuery' => ['array'], +'SolrDisMaxQuery::getExpandRows' => ['int'], +'SolrDisMaxQuery::getExpandSortFields' => ['array'], +'SolrDisMaxQuery::getFacet' => ['bool'], +'SolrDisMaxQuery::getFacetDateEnd' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getFacetDateFields' => ['array'], +'SolrDisMaxQuery::getFacetDateGap' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getFacetDateHardEnd' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getFacetDateOther' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getFacetDateStart' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getFacetFields' => ['array'], +'SolrDisMaxQuery::getFacetLimit' => ['int', 'field_override'=>'string'], +'SolrDisMaxQuery::getFacetMethod' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getFacetMinCount' => ['int', 'field_override'=>'string'], +'SolrDisMaxQuery::getFacetMissing' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getFacetOffset' => ['int', 'field_override'=>'string'], +'SolrDisMaxQuery::getFacetPrefix' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getFacetQueries' => ['string'], +'SolrDisMaxQuery::getFacetSort' => ['int', 'field_override'=>'string'], +'SolrDisMaxQuery::getFields' => ['string'], +'SolrDisMaxQuery::getFilterQueries' => ['string'], +'SolrDisMaxQuery::getGroup' => ['bool'], +'SolrDisMaxQuery::getGroupCachePercent' => ['int'], +'SolrDisMaxQuery::getGroupFacet' => ['bool'], +'SolrDisMaxQuery::getGroupFields' => ['array'], +'SolrDisMaxQuery::getGroupFormat' => ['string'], +'SolrDisMaxQuery::getGroupFunctions' => ['array'], +'SolrDisMaxQuery::getGroupLimit' => ['int'], +'SolrDisMaxQuery::getGroupMain' => ['bool'], +'SolrDisMaxQuery::getGroupNGroups' => ['bool'], +'SolrDisMaxQuery::getGroupOffset' => ['bool'], +'SolrDisMaxQuery::getGroupQueries' => ['array'], +'SolrDisMaxQuery::getGroupSortFields' => ['array'], +'SolrDisMaxQuery::getGroupTruncate' => ['bool'], +'SolrDisMaxQuery::getHighlight' => ['bool'], +'SolrDisMaxQuery::getHighlightAlternateField' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getHighlightFields' => ['array'], +'SolrDisMaxQuery::getHighlightFormatter' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getHighlightFragmenter' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getHighlightFragsize' => ['int', 'field_override'=>'string'], +'SolrDisMaxQuery::getHighlightHighlightMultiTerm' => ['bool'], +'SolrDisMaxQuery::getHighlightMaxAlternateFieldLength' => ['int', 'field_override'=>'string'], +'SolrDisMaxQuery::getHighlightMaxAnalyzedChars' => ['int'], +'SolrDisMaxQuery::getHighlightMergeContiguous' => ['bool', 'field_override'=>'string'], +'SolrDisMaxQuery::getHighlightRegexMaxAnalyzedChars' => ['int'], +'SolrDisMaxQuery::getHighlightRegexPattern' => ['string'], +'SolrDisMaxQuery::getHighlightRegexSlop' => ['float'], +'SolrDisMaxQuery::getHighlightRequireFieldMatch' => ['bool'], +'SolrDisMaxQuery::getHighlightSimplePost' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getHighlightSimplePre' => ['string', 'field_override'=>'string'], +'SolrDisMaxQuery::getHighlightSnippets' => ['int', 'field_override'=>'string'], +'SolrDisMaxQuery::getHighlightUsePhraseHighlighter' => ['bool'], +'SolrDisMaxQuery::getMlt' => ['bool'], +'SolrDisMaxQuery::getMltBoost' => ['bool'], +'SolrDisMaxQuery::getMltCount' => ['int'], +'SolrDisMaxQuery::getMltFields' => ['array'], +'SolrDisMaxQuery::getMltMaxNumQueryTerms' => ['int'], +'SolrDisMaxQuery::getMltMaxNumTokens' => ['int'], +'SolrDisMaxQuery::getMltMaxWordLength' => ['int'], +'SolrDisMaxQuery::getMltMinDocFrequency' => ['int'], +'SolrDisMaxQuery::getMltMinTermFrequency' => ['int'], +'SolrDisMaxQuery::getMltMinWordLength' => ['int'], +'SolrDisMaxQuery::getMltQueryFields' => ['array'], +'SolrDisMaxQuery::getParam' => ['mixed', 'param_name'=>'string'], +'SolrDisMaxQuery::getParams' => ['array'], +'SolrDisMaxQuery::getPreparedParams' => ['array'], +'SolrDisMaxQuery::getQuery' => ['string'], +'SolrDisMaxQuery::getRows' => ['int'], +'SolrDisMaxQuery::getSortFields' => ['array'], +'SolrDisMaxQuery::getStart' => ['int'], +'SolrDisMaxQuery::getStats' => ['bool'], +'SolrDisMaxQuery::getStatsFacets' => ['array'], +'SolrDisMaxQuery::getStatsFields' => ['array'], +'SolrDisMaxQuery::getTerms' => ['bool'], +'SolrDisMaxQuery::getTermsField' => ['string'], +'SolrDisMaxQuery::getTermsIncludeLowerBound' => ['bool'], +'SolrDisMaxQuery::getTermsIncludeUpperBound' => ['bool'], +'SolrDisMaxQuery::getTermsLimit' => ['int'], +'SolrDisMaxQuery::getTermsLowerBound' => ['string'], +'SolrDisMaxQuery::getTermsMaxCount' => ['int'], +'SolrDisMaxQuery::getTermsMinCount' => ['int'], +'SolrDisMaxQuery::getTermsPrefix' => ['string'], +'SolrDisMaxQuery::getTermsReturnRaw' => ['bool'], +'SolrDisMaxQuery::getTermsSort' => ['int'], +'SolrDisMaxQuery::getTermsUpperBound' => ['string'], +'SolrDisMaxQuery::getTimeAllowed' => ['int'], +'SolrDisMaxQuery::removeBigramPhraseField' => ['SolrDisMaxQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeBoostQuery' => ['SolrDisMaxQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeExpandFilterQuery' => ['SolrQuery', 'fq'=>'string'], +'SolrDisMaxQuery::removeExpandSortField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeFacetDateField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeFacetDateOther' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::removeFacetField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeFacetQuery' => ['SolrQuery', 'value'=>'string'], +'SolrDisMaxQuery::removeField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeFilterQuery' => ['SolrQuery', 'fq'=>'string'], +'SolrDisMaxQuery::removeHighlightField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeMltField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeMltQueryField' => ['SolrQuery', 'queryField'=>'string'], +'SolrDisMaxQuery::removePhraseField' => ['SolrDisMaxQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeQueryField' => ['SolrDisMaxQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeSortField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeStatsFacet' => ['SolrQuery', 'value'=>'string'], +'SolrDisMaxQuery::removeStatsField' => ['SolrQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeTrigramPhraseField' => ['SolrDisMaxQuery', 'field'=>'string'], +'SolrDisMaxQuery::removeUserField' => ['SolrDisMaxQuery', 'field'=>'string'], +'SolrDisMaxQuery::serialize' => ['string'], +'SolrDisMaxQuery::set' => ['SolrParams', 'name'=>'string', 'value'=>''], +'SolrDisMaxQuery::setBigramPhraseFields' => ['SolrDisMaxQuery', 'fields'=>'string'], +'SolrDisMaxQuery::setBigramPhraseSlop' => ['SolrDisMaxQuery', 'slop'=>'string'], +'SolrDisMaxQuery::setBoostFunction' => ['SolrDisMaxQuery', 'function'=>'string'], +'SolrDisMaxQuery::setBoostQuery' => ['SolrDisMaxQuery', 'q'=>'string'], +'SolrDisMaxQuery::setEchoHandler' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setEchoParams' => ['SolrQuery', 'type'=>'string'], +'SolrDisMaxQuery::setExpand' => ['SolrQuery', 'value'=>'bool'], +'SolrDisMaxQuery::setExpandQuery' => ['SolrQuery', 'q'=>'string'], +'SolrDisMaxQuery::setExpandRows' => ['SolrQuery', 'value'=>'int'], +'SolrDisMaxQuery::setExplainOther' => ['SolrQuery', 'query'=>'string'], +'SolrDisMaxQuery::setFacet' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setFacetDateEnd' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setFacetDateGap' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setFacetDateHardEnd' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setFacetDateStart' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setFacetEnumCacheMinDefaultFrequency' => ['SolrQuery', 'frequency'=>'int', 'field_override'=>'string'], +'SolrDisMaxQuery::setFacetLimit' => ['SolrQuery', 'limit'=>'int', 'field_override'=>'string'], +'SolrDisMaxQuery::setFacetMethod' => ['SolrQuery', 'method'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setFacetMinCount' => ['SolrQuery', 'mincount'=>'int', 'field_override'=>'string'], +'SolrDisMaxQuery::setFacetMissing' => ['SolrQuery', 'flag'=>'bool', 'field_override'=>'string'], +'SolrDisMaxQuery::setFacetOffset' => ['SolrQuery', 'offset'=>'int', 'field_override'=>'string'], +'SolrDisMaxQuery::setFacetPrefix' => ['SolrQuery', 'prefix'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setFacetSort' => ['SolrQuery', 'facetSort'=>'int', 'field_override'=>'string'], +'SolrDisMaxQuery::setGroup' => ['SolrQuery', 'value'=>'bool'], +'SolrDisMaxQuery::setGroupCachePercent' => ['SolrQuery', 'percent'=>'int'], +'SolrDisMaxQuery::setGroupFacet' => ['SolrQuery', 'value'=>'bool'], +'SolrDisMaxQuery::setGroupFormat' => ['SolrQuery', 'value'=>'string'], +'SolrDisMaxQuery::setGroupLimit' => ['SolrQuery', 'value'=>'int'], +'SolrDisMaxQuery::setGroupMain' => ['SolrQuery', 'value'=>'string'], +'SolrDisMaxQuery::setGroupNGroups' => ['SolrQuery', 'value'=>'bool'], +'SolrDisMaxQuery::setGroupOffset' => ['SolrQuery', 'value'=>'int'], +'SolrDisMaxQuery::setGroupTruncate' => ['SolrQuery', 'value'=>'bool'], +'SolrDisMaxQuery::setHighlight' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setHighlightAlternateField' => ['SolrQuery', 'field'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setHighlightFormatter' => ['SolrQuery', 'formatter'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setHighlightFragmenter' => ['SolrQuery', 'fragmenter'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setHighlightFragsize' => ['SolrQuery', 'size'=>'int', 'field_override'=>'string'], +'SolrDisMaxQuery::setHighlightHighlightMultiTerm' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setHighlightMaxAlternateFieldLength' => ['SolrQuery', 'fieldLength'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setHighlightMaxAnalyzedChars' => ['SolrQuery', 'value'=>'int'], +'SolrDisMaxQuery::setHighlightMergeContiguous' => ['SolrQuery', 'flag'=>'bool', 'field_override'=>'string'], +'SolrDisMaxQuery::setHighlightRegexMaxAnalyzedChars' => ['SolrQuery', 'maxAnalyzedChars'=>'int'], +'SolrDisMaxQuery::setHighlightRegexPattern' => ['SolrQuery', 'value'=>'string'], +'SolrDisMaxQuery::setHighlightRegexSlop' => ['SolrQuery', 'factor'=>'float'], +'SolrDisMaxQuery::setHighlightRequireFieldMatch' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setHighlightSimplePost' => ['SolrQuery', 'simplePost'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setHighlightSimplePre' => ['SolrQuery', 'simplePre'=>'string', 'field_override'=>'string'], +'SolrDisMaxQuery::setHighlightSnippets' => ['SolrQuery', 'value'=>'int', 'field_override'=>'string'], +'SolrDisMaxQuery::setHighlightUsePhraseHighlighter' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setMinimumMatch' => ['SolrDisMaxQuery', 'value'=>'string'], +'SolrDisMaxQuery::setMlt' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setMltBoost' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setMltCount' => ['SolrQuery', 'count'=>'int'], +'SolrDisMaxQuery::setMltMaxNumQueryTerms' => ['SolrQuery', 'value'=>'int'], +'SolrDisMaxQuery::setMltMaxNumTokens' => ['SolrQuery', 'value'=>'int'], +'SolrDisMaxQuery::setMltMaxWordLength' => ['SolrQuery', 'maxWordLength'=>'int'], +'SolrDisMaxQuery::setMltMinDocFrequency' => ['SolrQuery', 'minDocFrequency'=>'int'], +'SolrDisMaxQuery::setMltMinTermFrequency' => ['SolrQuery', 'minTermFrequency'=>'int'], +'SolrDisMaxQuery::setMltMinWordLength' => ['SolrQuery', 'minWordLength'=>'int'], +'SolrDisMaxQuery::setOmitHeader' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setParam' => ['SolrParams', 'name'=>'string', 'value'=>''], +'SolrDisMaxQuery::setPhraseFields' => ['SolrDisMaxQuery', 'fields'=>'string'], +'SolrDisMaxQuery::setPhraseSlop' => ['SolrDisMaxQuery', 'slop'=>'string'], +'SolrDisMaxQuery::setQuery' => ['SolrQuery', 'query'=>'string'], +'SolrDisMaxQuery::setQueryAlt' => ['SolrDisMaxQuery', 'q'=>'string'], +'SolrDisMaxQuery::setQueryPhraseSlop' => ['SolrDisMaxQuery', 'slop'=>'string'], +'SolrDisMaxQuery::setRows' => ['SolrQuery', 'rows'=>'int'], +'SolrDisMaxQuery::setShowDebugInfo' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setStart' => ['SolrQuery', 'start'=>'int'], +'SolrDisMaxQuery::setStats' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setTerms' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setTermsField' => ['SolrQuery', 'fieldname'=>'string'], +'SolrDisMaxQuery::setTermsIncludeLowerBound' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setTermsIncludeUpperBound' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setTermsLimit' => ['SolrQuery', 'limit'=>'int'], +'SolrDisMaxQuery::setTermsLowerBound' => ['SolrQuery', 'lowerBound'=>'string'], +'SolrDisMaxQuery::setTermsMaxCount' => ['SolrQuery', 'frequency'=>'int'], +'SolrDisMaxQuery::setTermsMinCount' => ['SolrQuery', 'frequency'=>'int'], +'SolrDisMaxQuery::setTermsPrefix' => ['SolrQuery', 'prefix'=>'string'], +'SolrDisMaxQuery::setTermsReturnRaw' => ['SolrQuery', 'flag'=>'bool'], +'SolrDisMaxQuery::setTermsSort' => ['SolrQuery', 'sortType'=>'int'], +'SolrDisMaxQuery::setTermsUpperBound' => ['SolrQuery', 'upperBound'=>'string'], +'SolrDisMaxQuery::setTieBreaker' => ['SolrDisMaxQuery', 'tieBreaker'=>'string'], +'SolrDisMaxQuery::setTimeAllowed' => ['SolrQuery', 'timeAllowed'=>'int'], +'SolrDisMaxQuery::setTrigramPhraseFields' => ['SolrDisMaxQuery', 'fields'=>'string'], +'SolrDisMaxQuery::setTrigramPhraseSlop' => ['SolrDisMaxQuery', 'slop'=>'string'], +'SolrDisMaxQuery::setUserFields' => ['SolrDisMaxQuery', 'fields'=>'string'], +'SolrDisMaxQuery::toString' => ['string', 'url_encode='=>'bool'], +'SolrDisMaxQuery::unserialize' => ['void', 'serialized'=>'string'], +'SolrDisMaxQuery::useDisMaxQueryParser' => ['SolrDisMaxQuery'], +'SolrDisMaxQuery::useEDisMaxQueryParser' => ['SolrDisMaxQuery'], +'SolrDocument::__clone' => ['void'], +'SolrDocument::__construct' => ['void'], +'SolrDocument::__destruct' => ['void'], +'SolrDocument::__get' => ['SolrDocumentField', 'fieldname'=>'string'], +'SolrDocument::__isset' => ['bool', 'fieldname'=>'string'], +'SolrDocument::__set' => ['bool', 'fieldname'=>'string', 'fieldvalue'=>'string'], +'SolrDocument::__unset' => ['bool', 'fieldname'=>'string'], +'SolrDocument::addField' => ['bool', 'fieldname'=>'string', 'fieldvalue'=>'string'], +'SolrDocument::clear' => ['bool'], +'SolrDocument::current' => ['SolrDocumentField'], +'SolrDocument::deleteField' => ['bool', 'fieldname'=>'string'], +'SolrDocument::fieldExists' => ['bool', 'fieldname'=>'string'], +'SolrDocument::getChildDocuments' => ['SolrInputDocument[]'], +'SolrDocument::getChildDocumentsCount' => ['int'], +'SolrDocument::getField' => ['SolrDocumentField|false', 'fieldname'=>'string'], +'SolrDocument::getFieldCount' => ['int|false'], +'SolrDocument::getFieldNames' => ['array|false'], +'SolrDocument::getInputDocument' => ['SolrInputDocument'], +'SolrDocument::hasChildDocuments' => ['bool'], +'SolrDocument::key' => ['string'], +'SolrDocument::merge' => ['bool', 'sourcedoc'=>'solrdocument', 'overwrite='=>'bool'], +'SolrDocument::next' => ['void'], +'SolrDocument::offsetExists' => ['bool', 'fieldname'=>'string'], +'SolrDocument::offsetGet' => ['SolrDocumentField', 'fieldname'=>'string'], +'SolrDocument::offsetSet' => ['void', 'fieldname'=>'string', 'fieldvalue'=>'string'], +'SolrDocument::offsetUnset' => ['void', 'fieldname'=>'string'], +'SolrDocument::reset' => ['bool'], +'SolrDocument::rewind' => ['void'], +'SolrDocument::serialize' => ['string'], +'SolrDocument::sort' => ['bool', 'sortorderby'=>'int', 'sortdirection='=>'int'], +'SolrDocument::toArray' => ['array'], +'SolrDocument::unserialize' => ['void', 'serialized'=>'string'], +'SolrDocument::valid' => ['bool'], +'SolrDocumentField::__construct' => ['void'], +'SolrDocumentField::__destruct' => ['void'], +'SolrException::__clone' => ['void'], +'SolrException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'SolrException::__toString' => ['string'], +'SolrException::__wakeup' => ['void'], +'SolrException::getCode' => ['int'], +'SolrException::getFile' => ['string'], +'SolrException::getInternalInfo' => ['array'], +'SolrException::getLine' => ['int'], +'SolrException::getMessage' => ['string'], +'SolrException::getPrevious' => ['Exception|Throwable'], +'SolrException::getTrace' => ['list>'], +'SolrException::getTraceAsString' => ['string'], +'SolrGenericResponse::__construct' => ['void'], +'SolrGenericResponse::__destruct' => ['void'], +'SolrGenericResponse::getDigestedResponse' => ['string'], +'SolrGenericResponse::getHttpStatus' => ['int'], +'SolrGenericResponse::getHttpStatusMessage' => ['string'], +'SolrGenericResponse::getRawRequest' => ['string'], +'SolrGenericResponse::getRawRequestHeaders' => ['string'], +'SolrGenericResponse::getRawResponse' => ['string'], +'SolrGenericResponse::getRawResponseHeaders' => ['string'], +'SolrGenericResponse::getRequestUrl' => ['string'], +'SolrGenericResponse::getResponse' => ['SolrObject'], +'SolrGenericResponse::setParseMode' => ['bool', 'parser_mode='=>'int'], +'SolrGenericResponse::success' => ['bool'], +'SolrIllegalArgumentException::__clone' => ['void'], +'SolrIllegalArgumentException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'SolrIllegalArgumentException::__toString' => ['string'], +'SolrIllegalArgumentException::__wakeup' => ['void'], +'SolrIllegalArgumentException::getCode' => ['int'], +'SolrIllegalArgumentException::getFile' => ['string'], +'SolrIllegalArgumentException::getInternalInfo' => ['array'], +'SolrIllegalArgumentException::getLine' => ['int'], +'SolrIllegalArgumentException::getMessage' => ['string'], +'SolrIllegalArgumentException::getPrevious' => ['Exception|Throwable'], +'SolrIllegalArgumentException::getTrace' => ['list>'], +'SolrIllegalArgumentException::getTraceAsString' => ['string'], +'SolrIllegalOperationException::__clone' => ['void'], +'SolrIllegalOperationException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'SolrIllegalOperationException::__toString' => ['string'], +'SolrIllegalOperationException::__wakeup' => ['void'], +'SolrIllegalOperationException::getCode' => ['int'], +'SolrIllegalOperationException::getFile' => ['string'], +'SolrIllegalOperationException::getInternalInfo' => ['array'], +'SolrIllegalOperationException::getLine' => ['int'], +'SolrIllegalOperationException::getMessage' => ['string'], +'SolrIllegalOperationException::getPrevious' => ['Exception|Throwable'], +'SolrIllegalOperationException::getTrace' => ['list>'], +'SolrIllegalOperationException::getTraceAsString' => ['string'], +'SolrInputDocument::__clone' => ['void'], +'SolrInputDocument::__construct' => ['void'], +'SolrInputDocument::__destruct' => ['void'], +'SolrInputDocument::addChildDocument' => ['void', 'child'=>'SolrInputDocument'], +'SolrInputDocument::addChildDocuments' => ['void', 'docs'=>'array'], +'SolrInputDocument::addField' => ['bool', 'fieldname'=>'string', 'fieldvalue'=>'string', 'fieldboostvalue='=>'float'], +'SolrInputDocument::clear' => ['bool'], +'SolrInputDocument::deleteField' => ['bool', 'fieldname'=>'string'], +'SolrInputDocument::fieldExists' => ['bool', 'fieldname'=>'string'], +'SolrInputDocument::getBoost' => ['float|false'], +'SolrInputDocument::getChildDocuments' => ['SolrInputDocument[]'], +'SolrInputDocument::getChildDocumentsCount' => ['int'], +'SolrInputDocument::getField' => ['SolrDocumentField|false', 'fieldname'=>'string'], +'SolrInputDocument::getFieldBoost' => ['float|false', 'fieldname'=>'string'], +'SolrInputDocument::getFieldCount' => ['int|false'], +'SolrInputDocument::getFieldNames' => ['array|false'], +'SolrInputDocument::hasChildDocuments' => ['bool'], +'SolrInputDocument::merge' => ['bool', 'sourcedoc'=>'SolrInputDocument', 'overwrite='=>'bool'], +'SolrInputDocument::reset' => ['bool'], +'SolrInputDocument::setBoost' => ['bool', 'documentboostvalue'=>'float'], +'SolrInputDocument::setFieldBoost' => ['bool', 'fieldname'=>'string', 'fieldboostvalue'=>'float'], +'SolrInputDocument::sort' => ['bool', 'sortorderby'=>'int', 'sortdirection='=>'int'], +'SolrInputDocument::toArray' => ['array|false'], +'SolrModifiableParams::__construct' => ['void'], +'SolrModifiableParams::__destruct' => ['void'], +'SolrModifiableParams::add' => ['SolrParams', 'name'=>'string', 'value'=>'string'], +'SolrModifiableParams::addParam' => ['SolrParams', 'name'=>'string', 'value'=>'string'], +'SolrModifiableParams::get' => ['mixed', 'param_name'=>'string'], +'SolrModifiableParams::getParam' => ['mixed', 'param_name'=>'string'], +'SolrModifiableParams::getParams' => ['array'], +'SolrModifiableParams::getPreparedParams' => ['array'], +'SolrModifiableParams::serialize' => ['string'], +'SolrModifiableParams::set' => ['SolrParams', 'name'=>'string', 'value'=>''], +'SolrModifiableParams::setParam' => ['SolrParams', 'name'=>'string', 'value'=>''], +'SolrModifiableParams::toString' => ['string', 'url_encode='=>'bool'], +'SolrModifiableParams::unserialize' => ['void', 'serialized'=>'string'], +'SolrObject::__construct' => ['void'], +'SolrObject::__destruct' => ['void'], +'SolrObject::getPropertyNames' => ['array'], +'SolrObject::offsetExists' => ['bool', 'property_name'=>'string'], +'SolrObject::offsetGet' => ['SolrDocumentField', 'property_name'=>'string'], +'SolrObject::offsetSet' => ['void', 'property_name'=>'string', 'property_value'=>'string'], +'SolrObject::offsetUnset' => ['void', 'property_name'=>'string'], +'SolrParams::__construct' => ['void'], +'SolrParams::add' => ['SolrParams|false', 'name'=>'string', 'value'=>'string'], +'SolrParams::addParam' => ['SolrParams|false', 'name'=>'string', 'value'=>'string'], +'SolrParams::get' => ['mixed', 'param_name'=>'string'], +'SolrParams::getParam' => ['mixed', 'param_name='=>'string'], +'SolrParams::getParams' => ['array'], +'SolrParams::getPreparedParams' => ['array'], +'SolrParams::serialize' => ['string'], +'SolrParams::set' => ['SolrParams|false', 'name'=>'string', 'value'=>'string'], +'SolrParams::setParam' => ['SolrParams|false', 'name'=>'string', 'value'=>'string'], +'SolrParams::toString' => ['string|false', 'url_encode='=>'bool'], +'SolrParams::unserialize' => ['void', 'serialized'=>'string'], +'SolrPingResponse::__construct' => ['void'], +'SolrPingResponse::__destruct' => ['void'], +'SolrPingResponse::getDigestedResponse' => ['string'], +'SolrPingResponse::getHttpStatus' => ['int'], +'SolrPingResponse::getHttpStatusMessage' => ['string'], +'SolrPingResponse::getRawRequest' => ['string'], +'SolrPingResponse::getRawRequestHeaders' => ['string'], +'SolrPingResponse::getRawResponse' => ['string'], +'SolrPingResponse::getRawResponseHeaders' => ['string'], +'SolrPingResponse::getRequestUrl' => ['string'], +'SolrPingResponse::getResponse' => ['string'], +'SolrPingResponse::setParseMode' => ['bool', 'parser_mode='=>'int'], +'SolrPingResponse::success' => ['bool'], +'SolrQuery::__construct' => ['void', 'q='=>'string'], +'SolrQuery::__destruct' => ['void'], +'SolrQuery::add' => ['SolrParams', 'name'=>'string', 'value'=>'string'], +'SolrQuery::addExpandFilterQuery' => ['SolrQuery', 'fq'=>'string'], +'SolrQuery::addExpandSortField' => ['SolrQuery', 'field'=>'string', 'order='=>'string'], +'SolrQuery::addFacetDateField' => ['SolrQuery', 'datefield'=>'string'], +'SolrQuery::addFacetDateOther' => ['SolrQuery', 'value'=>'string', 'field_override='=>'string'], +'SolrQuery::addFacetField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::addFacetQuery' => ['SolrQuery', 'facetquery'=>'string'], +'SolrQuery::addField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::addFilterQuery' => ['SolrQuery', 'fq'=>'string'], +'SolrQuery::addGroupField' => ['SolrQuery', 'value'=>'string'], +'SolrQuery::addGroupFunction' => ['SolrQuery', 'value'=>'string'], +'SolrQuery::addGroupQuery' => ['SolrQuery', 'value'=>'string'], +'SolrQuery::addGroupSortField' => ['SolrQuery', 'field'=>'string', 'order='=>'int'], +'SolrQuery::addHighlightField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::addMltField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::addMltQueryField' => ['SolrQuery', 'field'=>'string', 'boost'=>'float'], +'SolrQuery::addParam' => ['SolrParams', 'name'=>'string', 'value'=>'string'], +'SolrQuery::addSortField' => ['SolrQuery', 'field'=>'string', 'order='=>'int'], +'SolrQuery::addStatsFacet' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::addStatsField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::collapse' => ['SolrQuery', 'collapseFunction'=>'SolrCollapseFunction'], +'SolrQuery::get' => ['mixed', 'param_name'=>'string'], +'SolrQuery::getExpand' => ['bool'], +'SolrQuery::getExpandFilterQueries' => ['array'], +'SolrQuery::getExpandQuery' => ['array'], +'SolrQuery::getExpandRows' => ['int'], +'SolrQuery::getExpandSortFields' => ['array'], +'SolrQuery::getFacet' => ['?bool'], +'SolrQuery::getFacetDateEnd' => ['?string', 'field_override='=>'string'], +'SolrQuery::getFacetDateFields' => ['array'], +'SolrQuery::getFacetDateGap' => ['?string', 'field_override='=>'string'], +'SolrQuery::getFacetDateHardEnd' => ['?string', 'field_override='=>'string'], +'SolrQuery::getFacetDateOther' => ['?string', 'field_override='=>'string'], +'SolrQuery::getFacetDateStart' => ['?string', 'field_override='=>'string'], +'SolrQuery::getFacetFields' => ['array'], +'SolrQuery::getFacetLimit' => ['?int', 'field_override='=>'string'], +'SolrQuery::getFacetMethod' => ['?string', 'field_override='=>'string'], +'SolrQuery::getFacetMinCount' => ['?int', 'field_override='=>'string'], +'SolrQuery::getFacetMissing' => ['?bool', 'field_override='=>'string'], +'SolrQuery::getFacetOffset' => ['?int', 'field_override='=>'string'], +'SolrQuery::getFacetPrefix' => ['?string', 'field_override='=>'string'], +'SolrQuery::getFacetQueries' => ['?array'], +'SolrQuery::getFacetSort' => ['int', 'field_override='=>'string'], +'SolrQuery::getFields' => ['?array'], +'SolrQuery::getFilterQueries' => ['?array'], +'SolrQuery::getGroup' => ['bool'], +'SolrQuery::getGroupCachePercent' => ['int'], +'SolrQuery::getGroupFacet' => ['bool'], +'SolrQuery::getGroupFields' => ['array'], +'SolrQuery::getGroupFormat' => ['string'], +'SolrQuery::getGroupFunctions' => ['array'], +'SolrQuery::getGroupLimit' => ['int'], +'SolrQuery::getGroupMain' => ['bool'], +'SolrQuery::getGroupNGroups' => ['bool'], +'SolrQuery::getGroupOffset' => ['int'], +'SolrQuery::getGroupQueries' => ['array'], +'SolrQuery::getGroupSortFields' => ['array'], +'SolrQuery::getGroupTruncate' => ['bool'], +'SolrQuery::getHighlight' => ['bool'], +'SolrQuery::getHighlightAlternateField' => ['?string', 'field_override='=>'string'], +'SolrQuery::getHighlightFields' => ['?array'], +'SolrQuery::getHighlightFormatter' => ['?string', 'field_override='=>'string'], +'SolrQuery::getHighlightFragmenter' => ['?string', 'field_override='=>'string'], +'SolrQuery::getHighlightFragsize' => ['?int', 'field_override='=>'string'], +'SolrQuery::getHighlightHighlightMultiTerm' => ['?bool'], +'SolrQuery::getHighlightMaxAlternateFieldLength' => ['?int', 'field_override='=>'string'], +'SolrQuery::getHighlightMaxAnalyzedChars' => ['?int'], +'SolrQuery::getHighlightMergeContiguous' => ['?bool', 'field_override='=>'string'], +'SolrQuery::getHighlightRegexMaxAnalyzedChars' => ['?int'], +'SolrQuery::getHighlightRegexPattern' => ['?string'], +'SolrQuery::getHighlightRegexSlop' => ['?float'], +'SolrQuery::getHighlightRequireFieldMatch' => ['?bool'], +'SolrQuery::getHighlightSimplePost' => ['?string', 'field_override='=>'string'], +'SolrQuery::getHighlightSimplePre' => ['?string', 'field_override='=>'string'], +'SolrQuery::getHighlightSnippets' => ['?int', 'field_override='=>'string'], +'SolrQuery::getHighlightUsePhraseHighlighter' => ['?bool'], +'SolrQuery::getMlt' => ['?bool'], +'SolrQuery::getMltBoost' => ['?bool'], +'SolrQuery::getMltCount' => ['?int'], +'SolrQuery::getMltFields' => ['?array'], +'SolrQuery::getMltMaxNumQueryTerms' => ['?int'], +'SolrQuery::getMltMaxNumTokens' => ['?int'], +'SolrQuery::getMltMaxWordLength' => ['?int'], +'SolrQuery::getMltMinDocFrequency' => ['?int'], +'SolrQuery::getMltMinTermFrequency' => ['?int'], +'SolrQuery::getMltMinWordLength' => ['?int'], +'SolrQuery::getMltQueryFields' => ['?array'], +'SolrQuery::getParam' => ['?mixed', 'param_name'=>'string'], +'SolrQuery::getParams' => ['?array'], +'SolrQuery::getPreparedParams' => ['?array'], +'SolrQuery::getQuery' => ['?string'], +'SolrQuery::getRows' => ['?int'], +'SolrQuery::getSortFields' => ['?array'], +'SolrQuery::getStart' => ['?int'], +'SolrQuery::getStats' => ['?bool'], +'SolrQuery::getStatsFacets' => ['?array'], +'SolrQuery::getStatsFields' => ['?array'], +'SolrQuery::getTerms' => ['?bool'], +'SolrQuery::getTermsField' => ['?string'], +'SolrQuery::getTermsIncludeLowerBound' => ['?bool'], +'SolrQuery::getTermsIncludeUpperBound' => ['?bool'], +'SolrQuery::getTermsLimit' => ['?int'], +'SolrQuery::getTermsLowerBound' => ['?string'], +'SolrQuery::getTermsMaxCount' => ['?int'], +'SolrQuery::getTermsMinCount' => ['?int'], +'SolrQuery::getTermsPrefix' => ['?string'], +'SolrQuery::getTermsReturnRaw' => ['?bool'], +'SolrQuery::getTermsSort' => ['?int'], +'SolrQuery::getTermsUpperBound' => ['?string'], +'SolrQuery::getTimeAllowed' => ['?int'], +'SolrQuery::removeExpandFilterQuery' => ['SolrQuery', 'fq'=>'string'], +'SolrQuery::removeExpandSortField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::removeFacetDateField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::removeFacetDateOther' => ['SolrQuery', 'value'=>'string', 'field_override='=>'string'], +'SolrQuery::removeFacetField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::removeFacetQuery' => ['SolrQuery', 'value'=>'string'], +'SolrQuery::removeField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::removeFilterQuery' => ['SolrQuery', 'fq'=>'string'], +'SolrQuery::removeHighlightField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::removeMltField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::removeMltQueryField' => ['SolrQuery', 'queryfield'=>'string'], +'SolrQuery::removeSortField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::removeStatsFacet' => ['SolrQuery', 'value'=>'string'], +'SolrQuery::removeStatsField' => ['SolrQuery', 'field'=>'string'], +'SolrQuery::serialize' => ['string'], +'SolrQuery::set' => ['SolrParams', 'name'=>'string', 'value'=>''], +'SolrQuery::setEchoHandler' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setEchoParams' => ['SolrQuery', 'type'=>'string'], +'SolrQuery::setExpand' => ['SolrQuery', 'value'=>'bool'], +'SolrQuery::setExpandQuery' => ['SolrQuery', 'q'=>'string'], +'SolrQuery::setExpandRows' => ['SolrQuery', 'value'=>'int'], +'SolrQuery::setExplainOther' => ['SolrQuery', 'query'=>'string'], +'SolrQuery::setFacet' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setFacetDateEnd' => ['SolrQuery', 'value'=>'string', 'field_override='=>'string'], +'SolrQuery::setFacetDateGap' => ['SolrQuery', 'value'=>'string', 'field_override='=>'string'], +'SolrQuery::setFacetDateHardEnd' => ['SolrQuery', 'value'=>'bool', 'field_override='=>'string'], +'SolrQuery::setFacetDateStart' => ['SolrQuery', 'value'=>'string', 'field_override='=>'string'], +'SolrQuery::setFacetEnumCacheMinDefaultFrequency' => ['SolrQuery', 'frequency'=>'int', 'field_override='=>'string'], +'SolrQuery::setFacetLimit' => ['SolrQuery', 'limit'=>'int', 'field_override='=>'string'], +'SolrQuery::setFacetMethod' => ['SolrQuery', 'method'=>'string', 'field_override='=>'string'], +'SolrQuery::setFacetMinCount' => ['SolrQuery', 'mincount'=>'int', 'field_override='=>'string'], +'SolrQuery::setFacetMissing' => ['SolrQuery', 'flag'=>'bool', 'field_override='=>'string'], +'SolrQuery::setFacetOffset' => ['SolrQuery', 'offset'=>'int', 'field_override='=>'string'], +'SolrQuery::setFacetPrefix' => ['SolrQuery', 'prefix'=>'string', 'field_override='=>'string'], +'SolrQuery::setFacetSort' => ['SolrQuery', 'facetsort'=>'int', 'field_override='=>'string'], +'SolrQuery::setGroup' => ['SolrQuery', 'value'=>'bool'], +'SolrQuery::setGroupCachePercent' => ['SolrQuery', 'percent'=>'int'], +'SolrQuery::setGroupFacet' => ['SolrQuery', 'value'=>'bool'], +'SolrQuery::setGroupFormat' => ['SolrQuery', 'value'=>'string'], +'SolrQuery::setGroupLimit' => ['SolrQuery', 'value'=>'int'], +'SolrQuery::setGroupMain' => ['SolrQuery', 'value'=>'string'], +'SolrQuery::setGroupNGroups' => ['SolrQuery', 'value'=>'bool'], +'SolrQuery::setGroupOffset' => ['SolrQuery', 'value'=>'int'], +'SolrQuery::setGroupTruncate' => ['SolrQuery', 'value'=>'bool'], +'SolrQuery::setHighlight' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setHighlightAlternateField' => ['SolrQuery', 'field'=>'string', 'field_override='=>'string'], +'SolrQuery::setHighlightFormatter' => ['SolrQuery', 'formatter'=>'string', 'field_override='=>'string'], +'SolrQuery::setHighlightFragmenter' => ['SolrQuery', 'fragmenter'=>'string', 'field_override='=>'string'], +'SolrQuery::setHighlightFragsize' => ['SolrQuery', 'size'=>'int', 'field_override='=>'string'], +'SolrQuery::setHighlightHighlightMultiTerm' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setHighlightMaxAlternateFieldLength' => ['SolrQuery', 'fieldlength'=>'int', 'field_override='=>'string'], +'SolrQuery::setHighlightMaxAnalyzedChars' => ['SolrQuery', 'value'=>'int'], +'SolrQuery::setHighlightMergeContiguous' => ['SolrQuery', 'flag'=>'bool', 'field_override='=>'string'], +'SolrQuery::setHighlightRegexMaxAnalyzedChars' => ['SolrQuery', 'maxanalyzedchars'=>'int'], +'SolrQuery::setHighlightRegexPattern' => ['SolrQuery', 'value'=>'string'], +'SolrQuery::setHighlightRegexSlop' => ['SolrQuery', 'factor'=>'float'], +'SolrQuery::setHighlightRequireFieldMatch' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setHighlightSimplePost' => ['SolrQuery', 'simplepost'=>'string', 'field_override='=>'string'], +'SolrQuery::setHighlightSimplePre' => ['SolrQuery', 'simplepre'=>'string', 'field_override='=>'string'], +'SolrQuery::setHighlightSnippets' => ['SolrQuery', 'value'=>'int', 'field_override='=>'string'], +'SolrQuery::setHighlightUsePhraseHighlighter' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setMlt' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setMltBoost' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setMltCount' => ['SolrQuery', 'count'=>'int'], +'SolrQuery::setMltMaxNumQueryTerms' => ['SolrQuery', 'value'=>'int'], +'SolrQuery::setMltMaxNumTokens' => ['SolrQuery', 'value'=>'int'], +'SolrQuery::setMltMaxWordLength' => ['SolrQuery', 'maxwordlength'=>'int'], +'SolrQuery::setMltMinDocFrequency' => ['SolrQuery', 'mindocfrequency'=>'int'], +'SolrQuery::setMltMinTermFrequency' => ['SolrQuery', 'mintermfrequency'=>'int'], +'SolrQuery::setMltMinWordLength' => ['SolrQuery', 'minwordlength'=>'int'], +'SolrQuery::setOmitHeader' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setParam' => ['SolrParams', 'name'=>'string', 'value'=>''], +'SolrQuery::setQuery' => ['SolrQuery', 'query'=>'string'], +'SolrQuery::setRows' => ['SolrQuery', 'rows'=>'int'], +'SolrQuery::setShowDebugInfo' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setStart' => ['SolrQuery', 'start'=>'int'], +'SolrQuery::setStats' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setTerms' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setTermsField' => ['SolrQuery', 'fieldname'=>'string'], +'SolrQuery::setTermsIncludeLowerBound' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setTermsIncludeUpperBound' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setTermsLimit' => ['SolrQuery', 'limit'=>'int'], +'SolrQuery::setTermsLowerBound' => ['SolrQuery', 'lowerbound'=>'string'], +'SolrQuery::setTermsMaxCount' => ['SolrQuery', 'frequency'=>'int'], +'SolrQuery::setTermsMinCount' => ['SolrQuery', 'frequency'=>'int'], +'SolrQuery::setTermsPrefix' => ['SolrQuery', 'prefix'=>'string'], +'SolrQuery::setTermsReturnRaw' => ['SolrQuery', 'flag'=>'bool'], +'SolrQuery::setTermsSort' => ['SolrQuery', 'sorttype'=>'int'], +'SolrQuery::setTermsUpperBound' => ['SolrQuery', 'upperbound'=>'string'], +'SolrQuery::setTimeAllowed' => ['SolrQuery', 'timeallowed'=>'int'], +'SolrQuery::toString' => ['string', 'url_encode='=>'bool'], +'SolrQuery::unserialize' => ['void', 'serialized'=>'string'], +'SolrQueryResponse::__construct' => ['void'], +'SolrQueryResponse::__destruct' => ['void'], +'SolrQueryResponse::getDigestedResponse' => ['string'], +'SolrQueryResponse::getHttpStatus' => ['int'], +'SolrQueryResponse::getHttpStatusMessage' => ['string'], +'SolrQueryResponse::getRawRequest' => ['string'], +'SolrQueryResponse::getRawRequestHeaders' => ['string'], +'SolrQueryResponse::getRawResponse' => ['string'], +'SolrQueryResponse::getRawResponseHeaders' => ['string'], +'SolrQueryResponse::getRequestUrl' => ['string'], +'SolrQueryResponse::getResponse' => ['SolrObject'], +'SolrQueryResponse::setParseMode' => ['bool', 'parser_mode='=>'int'], +'SolrQueryResponse::success' => ['bool'], +'SolrResponse::getDigestedResponse' => ['string'], +'SolrResponse::getHttpStatus' => ['int'], +'SolrResponse::getHttpStatusMessage' => ['string'], +'SolrResponse::getRawRequest' => ['string'], +'SolrResponse::getRawRequestHeaders' => ['string'], +'SolrResponse::getRawResponse' => ['string'], +'SolrResponse::getRawResponseHeaders' => ['string'], +'SolrResponse::getRequestUrl' => ['string'], +'SolrResponse::getResponse' => ['SolrObject'], +'SolrResponse::setParseMode' => ['bool', 'parser_mode='=>'int'], +'SolrResponse::success' => ['bool'], +'SolrServerException::__clone' => ['void'], +'SolrServerException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'SolrServerException::__toString' => ['string'], +'SolrServerException::__wakeup' => ['void'], +'SolrServerException::getCode' => ['int'], +'SolrServerException::getFile' => ['string'], +'SolrServerException::getInternalInfo' => ['array'], +'SolrServerException::getLine' => ['int'], +'SolrServerException::getMessage' => ['string'], +'SolrServerException::getPrevious' => ['Exception|Throwable'], +'SolrServerException::getTrace' => ['list>'], +'SolrServerException::getTraceAsString' => ['string'], +'SolrUpdateResponse::__construct' => ['void'], +'SolrUpdateResponse::__destruct' => ['void'], +'SolrUpdateResponse::getDigestedResponse' => ['string'], +'SolrUpdateResponse::getHttpStatus' => ['int'], +'SolrUpdateResponse::getHttpStatusMessage' => ['string'], +'SolrUpdateResponse::getRawRequest' => ['string'], +'SolrUpdateResponse::getRawRequestHeaders' => ['string'], +'SolrUpdateResponse::getRawResponse' => ['string'], +'SolrUpdateResponse::getRawResponseHeaders' => ['string'], +'SolrUpdateResponse::getRequestUrl' => ['string'], +'SolrUpdateResponse::getResponse' => ['SolrObject'], +'SolrUpdateResponse::setParseMode' => ['bool', 'parser_mode='=>'int'], +'SolrUpdateResponse::success' => ['bool'], +'SolrUtils::digestXmlResponse' => ['SolrObject', 'xmlresponse'=>'string', 'parse_mode='=>'int'], +'SolrUtils::escapeQueryChars' => ['string|false', 'string'=>'string'], +'SolrUtils::getSolrVersion' => ['string'], +'SolrUtils::queryPhrase' => ['string', 'string'=>'string'], +'sort' => ['bool', '&rw_array'=>'array', 'sort_flags='=>'int'], +'soundex' => ['string', 'string'=>'string'], +'SphinxClient::__construct' => ['void'], +'SphinxClient::addQuery' => ['int', 'query'=>'string', 'index='=>'string', 'comment='=>'string'], +'SphinxClient::buildExcerpts' => ['array', 'docs'=>'array', 'index'=>'string', 'words'=>'string', 'opts='=>'array'], +'SphinxClient::buildKeywords' => ['array', 'query'=>'string', 'index'=>'string', 'hits'=>'bool'], +'SphinxClient::close' => ['bool'], +'SphinxClient::escapeString' => ['string', 'string'=>'string'], +'SphinxClient::getLastError' => ['string'], +'SphinxClient::getLastWarning' => ['string'], +'SphinxClient::open' => ['bool'], +'SphinxClient::query' => ['array', 'query'=>'string', 'index='=>'string', 'comment='=>'string'], +'SphinxClient::resetFilters' => ['void'], +'SphinxClient::resetGroupBy' => ['void'], +'SphinxClient::runQueries' => ['array'], +'SphinxClient::setArrayResult' => ['bool', 'array_result'=>'bool'], +'SphinxClient::setConnectTimeout' => ['bool', 'timeout'=>'float'], +'SphinxClient::setFieldWeights' => ['bool', 'weights'=>'array'], +'SphinxClient::setFilter' => ['bool', 'attribute'=>'string', 'values'=>'array', 'exclude='=>'bool'], +'SphinxClient::setFilterFloatRange' => ['bool', 'attribute'=>'string', 'min'=>'float', 'max'=>'float', 'exclude='=>'bool'], +'SphinxClient::setFilterRange' => ['bool', 'attribute'=>'string', 'min'=>'int', 'max'=>'int', 'exclude='=>'bool'], +'SphinxClient::setGeoAnchor' => ['bool', 'attrlat'=>'string', 'attrlong'=>'string', 'latitude'=>'float', 'longitude'=>'float'], +'SphinxClient::setGroupBy' => ['bool', 'attribute'=>'string', 'func'=>'int', 'groupsort='=>'string'], +'SphinxClient::setGroupDistinct' => ['bool', 'attribute'=>'string'], +'SphinxClient::setIDRange' => ['bool', 'min'=>'int', 'max'=>'int'], +'SphinxClient::setIndexWeights' => ['bool', 'weights'=>'array'], +'SphinxClient::setLimits' => ['bool', 'offset'=>'int', 'limit'=>'int', 'max_matches='=>'int', 'cutoff='=>'int'], +'SphinxClient::setMatchMode' => ['bool', 'mode'=>'int'], +'SphinxClient::setMaxQueryTime' => ['bool', 'qtime'=>'int'], +'SphinxClient::setOverride' => ['bool', 'attribute'=>'string', 'type'=>'int', 'values'=>'array'], +'SphinxClient::setRankingMode' => ['bool', 'ranker'=>'int'], +'SphinxClient::setRetries' => ['bool', 'count'=>'int', 'delay='=>'int'], +'SphinxClient::setSelect' => ['bool', 'clause'=>'string'], +'SphinxClient::setServer' => ['bool', 'server'=>'string', 'port'=>'int'], +'SphinxClient::setSortMode' => ['bool', 'mode'=>'int', 'sortby='=>'string'], +'SphinxClient::status' => ['array'], +'SphinxClient::updateAttributes' => ['int', 'index'=>'string', 'attributes'=>'array', 'values'=>'array', 'mva='=>'bool'], +'spl_autoload' => ['void', 'class_name'=>'string', 'file_extensions='=>'string'], +'spl_autoload_call' => ['void', 'class_name'=>'string'], +'spl_autoload_extensions' => ['string', 'file_extensions='=>'string'], +'spl_autoload_functions' => ['false|array'], +'spl_autoload_register' => ['bool', 'autoload_function='=>'callable(string):void', 'throw='=>'bool', 'prepend='=>'bool'], +'spl_autoload_unregister' => ['bool', 'autoload_function'=>'mixed'], +'spl_classes' => ['array'], +'spl_object_hash' => ['string', 'object'=>'object'], +'spl_object_id' => ['int', 'object'=>'object'], +'SplDoublyLinkedList::__construct' => ['void'], +'SplDoublyLinkedList::add' => ['void', 'index'=>'mixed', 'newval'=>'mixed'], +'SplDoublyLinkedList::bottom' => ['mixed'], +'SplDoublyLinkedList::count' => ['int'], +'SplDoublyLinkedList::current' => ['mixed'], +'SplDoublyLinkedList::getIteratorMode' => ['int'], +'SplDoublyLinkedList::isEmpty' => ['bool'], +'SplDoublyLinkedList::key' => ['mixed'], +'SplDoublyLinkedList::next' => ['void'], +'SplDoublyLinkedList::offsetExists' => ['bool', 'index'=>'mixed'], +'SplDoublyLinkedList::offsetGet' => ['mixed', 'index'=>'mixed'], +'SplDoublyLinkedList::offsetSet' => ['void', 'index'=>'mixed', 'newval'=>'mixed'], +'SplDoublyLinkedList::offsetUnset' => ['void', 'index'=>'mixed'], +'SplDoublyLinkedList::pop' => ['mixed'], +'SplDoublyLinkedList::prev' => ['void'], +'SplDoublyLinkedList::push' => ['void', 'value'=>'mixed'], +'SplDoublyLinkedList::rewind' => ['void'], +'SplDoublyLinkedList::serialize' => ['string'], +'SplDoublyLinkedList::setIteratorMode' => ['void', 'flags'=>'int'], +'SplDoublyLinkedList::shift' => ['mixed'], +'SplDoublyLinkedList::top' => ['mixed'], +'SplDoublyLinkedList::unserialize' => ['void', 'serialized'=>'string'], +'SplDoublyLinkedList::unshift' => ['bool', 'value'=>'mixed'], +'SplDoublyLinkedList::valid' => ['bool'], +'SplEnum::__construct' => ['void', 'initial_value='=>'mixed', 'strict='=>'bool'], +'SplEnum::getConstList' => ['array', 'include_default='=>'bool'], +'SplFileInfo::__construct' => ['void', 'file_name'=>'string'], +'SplFileInfo::__toString' => ['string'], +'SplFileInfo::__wakeup' => ['void'], +'SplFileInfo::getATime' => ['int'], +'SplFileInfo::getBasename' => ['string', 'suffix='=>'string'], +'SplFileInfo::getCTime' => ['int'], +'SplFileInfo::getExtension' => ['string'], +'SplFileInfo::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], +'SplFileInfo::getFilename' => ['string'], +'SplFileInfo::getGroup' => ['int'], +'SplFileInfo::getInode' => ['int'], +'SplFileInfo::getLinkTarget' => ['string'], +'SplFileInfo::getMTime' => ['int'], +'SplFileInfo::getOwner' => ['int'], +'SplFileInfo::getPath' => ['string'], +'SplFileInfo::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], +'SplFileInfo::getPathname' => ['string'], +'SplFileInfo::getPerms' => ['int'], +'SplFileInfo::getRealPath' => ['string|false'], +'SplFileInfo::getSize' => ['int'], +'SplFileInfo::getType' => ['string'], +'SplFileInfo::isDir' => ['bool'], +'SplFileInfo::isExecutable' => ['bool'], +'SplFileInfo::isFile' => ['bool'], +'SplFileInfo::isLink' => ['bool'], +'SplFileInfo::isReadable' => ['bool'], +'SplFileInfo::isWritable' => ['bool'], +'SplFileInfo::openFile' => ['SplFileObject', 'mode='=>'string', 'use_include_path='=>'bool', 'context='=>'resource'], +'SplFileInfo::setFileClass' => ['void', 'class_name='=>'string'], +'SplFileInfo::setInfoClass' => ['void', 'class_name='=>'string'], +'SplFileObject::__construct' => ['void', 'filename'=>'string', 'mode='=>'string', 'use_include_path='=>'bool', 'context='=>''], +'SplFileObject::__toString' => ['string'], +'SplFileObject::current' => ['string|array|false'], +'SplFileObject::eof' => ['bool'], +'SplFileObject::fflush' => ['bool'], +'SplFileObject::fgetc' => ['string|false'], +'SplFileObject::fgetcsv' => ['list|array{0: null}|false|null', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], +'SplFileObject::fgets' => ['string|false'], +'SplFileObject::fgetss' => ['string|false', 'allowable_tags='=>'string'], +'SplFileObject::flock' => ['bool', 'operation'=>'int', '&w_wouldblock='=>'int'], +'SplFileObject::fpassthru' => ['int|false'], +'SplFileObject::fputcsv' => ['int|false', 'fields'=>'array', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], +'SplFileObject::fread' => ['string|false', 'length'=>'int'], +'SplFileObject::fscanf' => ['array|int', 'format'=>'string', '&...w_vars='=>'string|int|float'], +'SplFileObject::fseek' => ['int', 'pos'=>'int', 'whence='=>'int'], +'SplFileObject::fstat' => ['array|false'], +'SplFileObject::ftell' => ['int|false'], +'SplFileObject::ftruncate' => ['bool', 'size'=>'int'], +'SplFileObject::fwrite' => ['int', 'string'=>'string', 'length='=>'int'], +'SplFileObject::getATime' => ['int'], +'SplFileObject::getBasename' => ['string', 'suffix='=>'string'], +'SplFileObject::getChildren' => ['null'], +'SplFileObject::getCsvControl' => ['array'], +'SplFileObject::getCTime' => ['int'], +'SplFileObject::getCurrentLine' => ['string|false'], +'SplFileObject::getExtension' => ['string'], +'SplFileObject::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], +'SplFileObject::getFilename' => ['string'], +'SplFileObject::getFlags' => ['int'], +'SplFileObject::getGroup' => ['int'], +'SplFileObject::getInode' => ['int'], +'SplFileObject::getLinkTarget' => ['string'], +'SplFileObject::getMaxLineLen' => ['int'], +'SplFileObject::getMTime' => ['int'], +'SplFileObject::getOwner' => ['int'], +'SplFileObject::getPath' => ['string'], +'SplFileObject::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], +'SplFileObject::getPathname' => ['string'], +'SplFileObject::getPerms' => ['int'], +'SplFileObject::getRealPath' => ['false|string'], +'SplFileObject::getSize' => ['int'], +'SplFileObject::getType' => ['string'], +'SplFileObject::hasChildren' => ['false'], +'SplFileObject::isDir' => ['bool'], +'SplFileObject::isExecutable' => ['bool'], +'SplFileObject::isFile' => ['bool'], +'SplFileObject::isLink' => ['bool'], +'SplFileObject::isReadable' => ['bool'], +'SplFileObject::isWritable' => ['bool'], +'SplFileObject::key' => ['int'], +'SplFileObject::next' => ['void'], +'SplFileObject::openFile' => ['SplFileObject', 'mode='=>'string', 'use_include_path='=>'bool', 'context='=>'resource'], +'SplFileObject::rewind' => ['void'], +'SplFileObject::seek' => ['void', 'line_pos'=>'int'], +'SplFileObject::setCsvControl' => ['void', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], +'SplFileObject::setFileClass' => ['void', 'class_name='=>'string'], +'SplFileObject::setFlags' => ['void', 'flags'=>'int'], +'SplFileObject::setInfoClass' => ['void', 'class_name='=>'string'], +'SplFileObject::setMaxLineLen' => ['void', 'max_len'=>'int'], +'SplFileObject::valid' => ['bool'], +'SplFixedArray::__construct' => ['void', 'size='=>'int'], +'SplFixedArray::__wakeup' => ['void'], +'SplFixedArray::count' => ['int'], +'SplFixedArray::current' => ['mixed'], +'SplFixedArray::fromArray' => ['SplFixedArray', 'data'=>'array', 'save_indexes='=>'bool'], +'SplFixedArray::getSize' => ['int'], +'SplFixedArray::key' => ['int'], +'SplFixedArray::next' => ['void'], +'SplFixedArray::offsetExists' => ['bool', 'index'=>'int'], +'SplFixedArray::offsetGet' => ['mixed', 'index'=>'int'], +'SplFixedArray::offsetSet' => ['void', 'index'=>'int', 'newval'=>'mixed'], +'SplFixedArray::offsetUnset' => ['void', 'index'=>'int'], +'SplFixedArray::rewind' => ['void'], +'SplFixedArray::setSize' => ['bool', 'size'=>'int'], +'SplFixedArray::toArray' => ['array'], +'SplFixedArray::valid' => ['bool'], +'SplHeap::__construct' => ['void'], +'SplHeap::compare' => ['int', 'value1'=>'mixed', 'value2'=>'mixed'], +'SplHeap::count' => ['int'], +'SplHeap::current' => ['mixed'], +'SplHeap::extract' => ['mixed'], +'SplHeap::insert' => ['bool', 'value'=>'mixed'], +'SplHeap::isCorrupted' => ['bool'], +'SplHeap::isEmpty' => ['bool'], +'SplHeap::key' => ['int'], +'SplHeap::next' => ['void'], +'SplHeap::recoverFromCorruption' => ['int'], +'SplHeap::rewind' => ['void'], +'SplHeap::top' => ['mixed'], +'SplHeap::valid' => ['bool'], +'SplMaxHeap::__construct' => ['void'], +'SplMaxHeap::compare' => ['int', 'a'=>'mixed', 'b'=>'mixed'], +'SplMinHeap::compare' => ['int', 'a'=>'mixed', 'b'=>'mixed'], +'SplMinHeap::count' => ['int'], +'SplMinHeap::current' => ['mixed'], +'SplMinHeap::extract' => ['mixed'], +'SplMinHeap::insert' => ['void', 'value'=>'mixed'], +'SplMinHeap::isCorrupted' => ['int'], +'SplMinHeap::isEmpty' => ['bool'], +'SplMinHeap::key' => ['mixed'], +'SplMinHeap::next' => ['void'], +'SplMinHeap::recoverFromCorruption' => ['void'], +'SplMinHeap::rewind' => ['void'], +'SplMinHeap::top' => ['mixed'], +'SplMinHeap::valid' => ['bool'], +'SplObjectStorage::__construct' => ['void'], +'SplObjectStorage::addAll' => ['void', 'os'=>'splobjectstorage'], +'SplObjectStorage::attach' => ['void', 'object'=>'object', 'inf='=>'mixed'], +'SplObjectStorage::contains' => ['bool', 'object'=>'object'], +'SplObjectStorage::count' => ['int'], +'SplObjectStorage::current' => ['object'], +'SplObjectStorage::detach' => ['void', 'object'=>'object'], +'SplObjectStorage::getHash' => ['string', 'object'=>'object'], +'SplObjectStorage::getInfo' => ['mixed'], +'SplObjectStorage::key' => ['int'], +'SplObjectStorage::next' => ['void'], +'SplObjectStorage::offsetExists' => ['bool', 'object'=>'object'], +'SplObjectStorage::offsetGet' => ['mixed', 'object'=>'object'], +'SplObjectStorage::offsetSet' => ['object', 'object'=>'object', 'data='=>'mixed'], +'SplObjectStorage::offsetUnset' => ['object', 'object'=>'object'], +'SplObjectStorage::removeAll' => ['void', 'os'=>'splobjectstorage'], +'SplObjectStorage::removeAllExcept' => ['void', 'os'=>'splobjectstorage'], +'SplObjectStorage::rewind' => ['void'], +'SplObjectStorage::serialize' => ['string'], +'SplObjectStorage::setInfo' => ['void', 'inf'=>'mixed'], +'SplObjectStorage::unserialize' => ['void', 'serialized'=>'string'], +'SplObjectStorage::valid' => ['bool'], +'SplObserver::update' => ['void', 'subject'=>'SplSubject'], +'SplPriorityQueue::__construct' => ['void'], +'SplPriorityQueue::compare' => ['int', 'a'=>'mixed', 'b'=>'mixed'], +'SplPriorityQueue::count' => ['int'], +'SplPriorityQueue::current' => ['mixed'], +'SplPriorityQueue::extract' => ['mixed'], +'SplPriorityQueue::getExtractFlags' => ['int'], +'SplPriorityQueue::insert' => ['bool', 'value'=>'mixed', 'priority'=>'mixed'], +'SplPriorityQueue::isCorrupted' => ['bool'], +'SplPriorityQueue::isEmpty' => ['bool'], +'SplPriorityQueue::key' => ['mixed'], +'SplPriorityQueue::next' => ['void'], +'SplPriorityQueue::recoverFromCorruption' => ['void'], +'SplPriorityQueue::rewind' => ['void'], +'SplPriorityQueue::setExtractFlags' => ['void', 'flags'=>'int'], +'SplPriorityQueue::top' => ['mixed'], +'SplPriorityQueue::valid' => ['bool'], +'SplQueue::dequeue' => ['mixed'], +'SplQueue::enqueue' => ['void', 'value'=>'mixed'], +'SplQueue::getIteratorMode' => ['int'], +'SplQueue::isEmpty' => ['bool'], +'SplQueue::key' => ['mixed'], +'SplQueue::next' => ['void'], +'SplQueue::offsetExists' => ['bool', 'index'=>'mixed'], +'SplQueue::offsetGet' => ['mixed', 'index'=>'mixed'], +'SplQueue::offsetSet' => ['void', 'index'=>'mixed', 'newval'=>'mixed'], +'SplQueue::offsetUnset' => ['void', 'index'=>'mixed'], +'SplQueue::pop' => ['mixed'], +'SplQueue::prev' => ['void'], +'SplQueue::push' => ['void', 'value'=>'mixed'], +'SplQueue::rewind' => ['void'], +'SplQueue::serialize' => ['string'], +'SplQueue::setIteratorMode' => ['void', 'mode'=>'int'], +'SplQueue::shift' => ['mixed'], +'SplQueue::top' => ['mixed'], +'SplQueue::unserialize' => ['void', 'serialized'=>'string'], +'SplQueue::unshift' => ['bool', 'value'=>'mixed'], +'SplQueue::valid' => ['bool'], +'SplStack::__construct' => ['void'], +'SplStack::add' => ['void', 'index'=>'mixed', 'newval'=>'mixed'], +'SplStack::bottom' => ['mixed'], +'SplStack::count' => ['int'], +'SplStack::current' => ['mixed'], +'SplStack::getIteratorMode' => ['int'], +'SplStack::isEmpty' => ['bool'], +'SplStack::key' => ['mixed'], +'SplStack::next' => ['void'], +'SplStack::offsetExists' => ['bool', 'index'=>'mixed'], +'SplStack::offsetGet' => ['mixed', 'index'=>'mixed'], +'SplStack::offsetSet' => ['void', 'index'=>'mixed', 'newval'=>'mixed'], +'SplStack::offsetUnset' => ['void', 'index'=>'mixed'], +'SplStack::pop' => ['mixed'], +'SplStack::prev' => ['void'], +'SplStack::push' => ['void', 'value'=>'mixed'], +'SplStack::rewind' => ['void'], +'SplStack::serialize' => ['string'], +'SplStack::setIteratorMode' => ['void', 'mode'=>'int'], +'SplStack::shift' => ['mixed'], +'SplStack::top' => ['mixed'], +'SplStack::unserialize' => ['void', 'serialized'=>'string'], +'SplStack::unshift' => ['bool', 'value'=>'mixed'], +'SplStack::valid' => ['bool'], +'SplSubject::attach' => ['void', 'observer'=>'SplObserver'], +'SplSubject::detach' => ['void', 'observer'=>'SplObserver'], +'SplSubject::notify' => ['void'], +'SplTempFileObject::__construct' => ['void', 'max_memory='=>'int'], +'SplTempFileObject::__toString' => ['string'], +'SplTempFileObject::_bad_state_ex' => [''], +'SplTempFileObject::current' => ['array|false|string'], +'SplTempFileObject::eof' => ['bool'], +'SplTempFileObject::fflush' => ['bool'], +'SplTempFileObject::fgetc' => ['false|string'], +'SplTempFileObject::fgetcsv' => ['list|array{0: null}|false|null', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], +'SplTempFileObject::fgets' => ['string'], +'SplTempFileObject::fgetss' => ['string', 'allowable_tags='=>'string'], +'SplTempFileObject::flock' => ['bool', 'operation'=>'int', '&wouldblock='=>'int'], +'SplTempFileObject::fpassthru' => ['int|false'], +'SplTempFileObject::fputcsv' => ['false|int', 'fields'=>'array', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], +'SplTempFileObject::fread' => ['false|string', 'length'=>'int'], +'SplTempFileObject::fscanf' => ['bool', 'format'=>'string', '&...w_vars='=>'array|array|array'], +'SplTempFileObject::fseek' => ['int', 'pos'=>'int', 'whence='=>'int'], +'SplTempFileObject::fstat' => ['array|false'], +'SplTempFileObject::ftell' => ['int'], +'SplTempFileObject::ftruncate' => ['bool', 'size'=>'int'], +'SplTempFileObject::fwrite' => ['int', 'string'=>'string', 'length='=>'int'], +'SplTempFileObject::getATime' => ['int'], +'SplTempFileObject::getBasename' => ['string', 'suffix='=>'string'], +'SplTempFileObject::getChildren' => ['null'], +'SplTempFileObject::getCsvControl' => ['array'], +'SplTempFileObject::getCTime' => ['int'], +'SplTempFileObject::getCurrentLine' => ['string'], +'SplTempFileObject::getExtension' => ['string'], +'SplTempFileObject::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'], +'SplTempFileObject::getFilename' => ['string'], +'SplTempFileObject::getFlags' => ['int'], +'SplTempFileObject::getGroup' => ['int'], +'SplTempFileObject::getInode' => ['int'], +'SplTempFileObject::getLinkTarget' => ['string'], +'SplTempFileObject::getMaxLineLen' => ['int'], +'SplTempFileObject::getMTime' => ['int'], +'SplTempFileObject::getOwner' => ['int'], +'SplTempFileObject::getPath' => ['string'], +'SplTempFileObject::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], +'SplTempFileObject::getPathname' => ['string'], +'SplTempFileObject::getPerms' => ['int'], +'SplTempFileObject::getRealPath' => ['string'], +'SplTempFileObject::getSize' => ['int'], +'SplTempFileObject::getType' => ['string'], +'SplTempFileObject::hasChildren' => ['bool'], +'SplTempFileObject::isDir' => ['bool'], +'SplTempFileObject::isExecutable' => ['bool'], +'SplTempFileObject::isFile' => ['bool'], +'SplTempFileObject::isLink' => ['bool'], +'SplTempFileObject::isReadable' => ['bool'], +'SplTempFileObject::isWritable' => ['bool'], +'SplTempFileObject::key' => ['int'], +'SplTempFileObject::next' => ['void'], +'SplTempFileObject::openFile' => ['SplFileObject', 'mode='=>'string', 'use_include_path='=>'bool', 'context='=>'resource'], +'SplTempFileObject::rewind' => ['void'], +'SplTempFileObject::seek' => ['void', 'line_pos'=>'int'], +'SplTempFileObject::setCsvControl' => ['void', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], +'SplTempFileObject::setFileClass' => ['void', 'class_name='=>'string'], +'SplTempFileObject::setFlags' => ['void', 'flags'=>'int'], +'SplTempFileObject::setInfoClass' => ['void', 'class_name='=>'string'], +'SplTempFileObject::setMaxLineLen' => ['void', 'max_len'=>'int'], +'SplTempFileObject::valid' => ['bool'], +'SplType::__construct' => ['void', 'initial_value='=>'mixed', 'strict='=>'bool'], +'Spoofchecker::__construct' => ['void'], +'Spoofchecker::areConfusable' => ['bool', 's1'=>'string', 's2'=>'string', '&w_error='=>'string'], +'Spoofchecker::isSuspicious' => ['bool', 'text'=>'string', '&w_error='=>'string'], +'Spoofchecker::setAllowedLocales' => ['void', 'locale_list'=>'string'], +'Spoofchecker::setChecks' => ['void', 'checks'=>'long'], +'Spoofchecker::setRestrictionLevel' => ['void', 'restriction_level'=>'int'], +'sprintf' => ['string', 'format'=>'string', '...vars='=>'string|int|float'], +'SQLite3::__construct' => ['void', 'filename'=>'string', 'flags='=>'int', 'encryption_key='=>'?string'], +'SQLite3::busyTimeout' => ['bool', 'msecs'=>'int'], +'SQLite3::changes' => ['int'], +'SQLite3::close' => ['bool'], +'SQLite3::createAggregate' => ['bool', 'name'=>'string', 'step_callback'=>'callable', 'final_callback'=>'callable', 'argument_count='=>'int'], +'SQLite3::createCollation' => ['bool', 'name'=>'string', 'callback'=>'callable'], +'SQLite3::createFunction' => ['bool', 'name'=>'string', 'callback'=>'callable', 'argument_count='=>'int', 'flags='=>'int'], +'SQLite3::enableExceptions' => ['bool', 'enableexceptions='=>'bool'], +'SQLite3::escapeString' => ['string', 'value'=>'string'], +'SQLite3::exec' => ['bool', 'query'=>'string'], +'SQLite3::lastErrorCode' => ['int'], +'SQLite3::lastErrorMsg' => ['string'], +'SQLite3::lastInsertRowID' => ['int'], +'SQLite3::loadExtension' => ['bool', 'shared_library'=>'string'], +'SQLite3::open' => ['void', 'filename'=>'string', 'flags='=>'int', 'encryption_key='=>'?string'], +'SQLite3::openBlob' => ['resource|false', 'table'=>'string', 'column'=>'string', 'rowid'=>'int', 'dbname='=>'string', 'flags='=>'int'], +'SQLite3::prepare' => ['SQLite3Stmt|false', 'query'=>'string'], +'SQLite3::query' => ['SQLite3Result|false', 'query'=>'string'], +'SQLite3::querySingle' => ['array|int|string|bool|float|null|false', 'query'=>'string', 'entire_row='=>'bool'], +'SQLite3::version' => ['array'], +'SQLite3Result::__construct' => ['void'], +'SQLite3Result::columnName' => ['string', 'column_number'=>'int'], +'SQLite3Result::columnType' => ['int', 'column_number'=>'int'], +'SQLite3Result::fetchArray' => ['array|false', 'mode='=>'int'], +'SQLite3Result::finalize' => ['bool'], +'SQLite3Result::numColumns' => ['int'], +'SQLite3Result::reset' => ['bool'], +'SQLite3Stmt::__construct' => ['void', 'dbobject'=>'sqlite3', 'statement'=>'string'], +'SQLite3Stmt::bindParam' => ['bool', 'parameter_name_or_number'=>'string|int', '&rw_parameter'=>'mixed', 'type='=>'int'], +'SQLite3Stmt::bindValue' => ['bool', 'parameter_name_or_number'=>'string|int', 'parameter'=>'mixed', 'type='=>'int'], +'SQLite3Stmt::clear' => ['bool'], +'SQLite3Stmt::close' => ['bool'], +'SQLite3Stmt::execute' => ['false|SQLite3Result'], +'SQLite3Stmt::getSQL' => ['string', 'expanded='=>'bool'], +'SQLite3Stmt::paramCount' => ['int'], +'SQLite3Stmt::readOnly' => ['bool'], +'SQLite3Stmt::reset' => ['bool'], +'sqlite_array_query' => ['array|false', 'dbhandle'=>'resource', 'query'=>'string', 'result_type='=>'int', 'decode_binary='=>'bool'], +'sqlite_busy_timeout' => ['void', 'dbhandle'=>'resource', 'milliseconds'=>'int'], +'sqlite_changes' => ['int', 'dbhandle'=>'resource'], +'sqlite_close' => ['void', 'dbhandle'=>'resource'], +'sqlite_column' => ['mixed', 'result'=>'resource', 'index_or_name'=>'mixed', 'decode_binary='=>'bool'], +'sqlite_create_aggregate' => ['void', 'dbhandle'=>'resource', 'function_name'=>'string', 'step_func'=>'callable', 'finalize_func'=>'callable', 'num_args='=>'int'], +'sqlite_create_function' => ['void', 'dbhandle'=>'resource', 'function_name'=>'string', 'callback'=>'callable', 'num_args='=>'int'], +'sqlite_current' => ['array|false', 'result'=>'resource', 'result_type='=>'int', 'decode_binary='=>'bool'], +'sqlite_error_string' => ['string', 'error_code'=>'int'], +'sqlite_escape_string' => ['string', 'item'=>'string'], +'sqlite_exec' => ['bool', 'dbhandle'=>'resource', 'query'=>'string', 'error_msg='=>'string'], +'sqlite_factory' => ['SQLiteDatabase', 'filename'=>'string', 'mode='=>'int', 'error_message='=>'string'], +'sqlite_fetch_all' => ['array', 'result'=>'resource', 'result_type='=>'int', 'decode_binary='=>'bool'], +'sqlite_fetch_array' => ['array|false', 'result'=>'resource', 'result_type='=>'int', 'decode_binary='=>'bool'], +'sqlite_fetch_column_types' => ['array|false', 'table_name'=>'string', 'dbhandle'=>'resource', 'result_type='=>'int'], +'sqlite_fetch_object' => ['object', 'result'=>'resource', 'class_name='=>'string', 'ctor_params='=>'array', 'decode_binary='=>'bool'], +'sqlite_fetch_single' => ['string', 'result'=>'resource', 'decode_binary='=>'bool'], +'sqlite_fetch_string' => ['string', 'result'=>'resource', 'decode_binary'=>'bool'], +'sqlite_field_name' => ['string', 'result'=>'resource', 'field_index'=>'int'], +'sqlite_has_more' => ['bool', 'result'=>'resource'], +'sqlite_has_prev' => ['bool', 'result'=>'resource'], +'sqlite_key' => ['int', 'result'=>'resource'], +'sqlite_last_error' => ['int', 'dbhandle'=>'resource'], +'sqlite_last_insert_rowid' => ['int', 'dbhandle'=>'resource'], +'sqlite_libencoding' => ['string'], +'sqlite_libversion' => ['string'], +'sqlite_next' => ['bool', 'result'=>'resource'], +'sqlite_num_fields' => ['int', 'result'=>'resource'], +'sqlite_num_rows' => ['int', 'result'=>'resource'], +'sqlite_open' => ['resource|false', 'filename'=>'string', 'mode='=>'int', 'error_message='=>'string'], +'sqlite_popen' => ['resource|false', 'filename'=>'string', 'mode='=>'int', 'error_message='=>'string'], +'sqlite_prev' => ['bool', 'result'=>'resource'], +'sqlite_query' => ['resource|false', 'dbhandle'=>'resource', 'query'=>'resource|string', 'result_type='=>'int', 'error_msg='=>'string'], +'sqlite_rewind' => ['bool', 'result'=>'resource'], +'sqlite_seek' => ['bool', 'result'=>'resource', 'rownum'=>'int'], +'sqlite_single_query' => ['array', 'db'=>'resource', 'query'=>'string', 'first_row_only='=>'bool', 'decode_binary='=>'bool'], +'sqlite_udf_decode_binary' => ['string', 'data'=>'string'], +'sqlite_udf_encode_binary' => ['string', 'data'=>'string'], +'sqlite_unbuffered_query' => ['SQLiteUnbuffered|false', 'dbhandle'=>'resource', 'query'=>'string', 'result_type='=>'int', 'error_msg='=>'string'], +'sqlite_valid' => ['bool', 'result'=>'resource'], +'SQLiteDatabase::__construct' => ['void', 'filename'=>'', 'mode='=>'int|mixed', '&error_message'=>''], +'SQLiteDatabase::arrayQuery' => ['array', 'query'=>'string', 'result_type='=>'int', 'decode_binary='=>'bool'], +'SQLiteDatabase::busyTimeout' => ['int', 'milliseconds'=>'int'], +'SQLiteDatabase::changes' => ['int'], +'SQLiteDatabase::createAggregate' => ['', 'function_name'=>'string', 'step_func'=>'callable', 'finalize_func'=>'callable', 'num_args='=>'int'], +'SQLiteDatabase::createFunction' => ['', 'function_name'=>'string', 'callback'=>'callable', 'num_args='=>'int'], +'SQLiteDatabase::exec' => ['bool', 'query'=>'string', 'error_msg='=>'string'], +'SQLiteDatabase::fetchColumnTypes' => ['array', 'table_name'=>'string', 'result_type='=>'int'], +'SQLiteDatabase::lastError' => ['int'], +'SQLiteDatabase::lastInsertRowid' => ['int'], +'SQLiteDatabase::query' => ['SQLiteResult|false', 'query'=>'string', 'result_type='=>'int', 'error_msg='=>'string'], +'SQLiteDatabase::queryExec' => ['bool', 'query'=>'string', '&w_error_msg='=>'string'], +'SQLiteDatabase::singleQuery' => ['array', 'query'=>'string', 'first_row_only='=>'bool', 'decode_binary='=>'bool'], +'SQLiteDatabase::unbufferedQuery' => ['SQLiteUnbuffered|false', 'query'=>'string', 'result_type='=>'int', 'error_msg='=>'string'], +'SQLiteException::__clone' => ['void'], +'SQLiteException::__construct' => ['void', 'message'=>'', 'code'=>'', 'previous'=>''], +'SQLiteException::__toString' => ['string'], +'SQLiteException::__wakeup' => ['void'], +'SQLiteException::getCode' => ['int'], +'SQLiteException::getFile' => ['string'], +'SQLiteException::getLine' => ['int'], +'SQLiteException::getMessage' => ['string'], +'SQLiteException::getPrevious' => ['RuntimeException|Throwable|null'], +'SQLiteException::getTrace' => ['list>'], +'SQLiteException::getTraceAsString' => ['string'], +'SQLiteResult::__construct' => ['void'], +'SQLiteResult::column' => ['mixed', 'index_or_name'=>'', 'decode_binary='=>'bool'], +'SQLiteResult::count' => ['int'], +'SQLiteResult::current' => ['array', 'result_type='=>'int', 'decode_binary='=>'bool'], +'SQLiteResult::fetch' => ['array', 'result_type='=>'int', 'decode_binary='=>'bool'], +'SQLiteResult::fetchAll' => ['array', 'result_type='=>'int', 'decode_binary='=>'bool'], +'SQLiteResult::fetchObject' => ['object', 'class_name='=>'string', 'ctor_params='=>'array', 'decode_binary='=>'bool'], +'SQLiteResult::fetchSingle' => ['string', 'decode_binary='=>'bool'], +'SQLiteResult::fieldName' => ['string', 'field_index'=>'int'], +'SQLiteResult::hasPrev' => ['bool'], +'SQLiteResult::key' => ['mixed|null'], +'SQLiteResult::next' => ['bool'], +'SQLiteResult::numFields' => ['int'], +'SQLiteResult::numRows' => ['int'], +'SQLiteResult::prev' => ['bool'], +'SQLiteResult::rewind' => ['bool'], +'SQLiteResult::seek' => ['bool', 'rownum'=>'int'], +'SQLiteResult::valid' => ['bool'], +'SQLiteUnbuffered::column' => ['void', 'index_or_name'=>'', 'decode_binary='=>'bool'], +'SQLiteUnbuffered::current' => ['array', 'result_type='=>'int', 'decode_binary='=>'bool'], +'SQLiteUnbuffered::fetch' => ['array', 'result_type='=>'int', 'decode_binary='=>'bool'], +'SQLiteUnbuffered::fetchAll' => ['array', 'result_type='=>'int', 'decode_binary='=>'bool'], +'SQLiteUnbuffered::fetchObject' => ['object', 'class_name='=>'string', 'ctor_params='=>'array', 'decode_binary='=>'bool'], +'SQLiteUnbuffered::fetchSingle' => ['string', 'decode_binary='=>'bool'], +'SQLiteUnbuffered::fieldName' => ['string', 'field_index'=>'int'], +'SQLiteUnbuffered::next' => ['bool'], +'SQLiteUnbuffered::numFields' => ['int'], +'SQLiteUnbuffered::valid' => ['bool'], +'sqlsrv_begin_transaction' => ['bool', 'conn'=>'resource'], +'sqlsrv_cancel' => ['bool', 'stmt'=>'resource'], +'sqlsrv_client_info' => ['array|false', 'conn'=>'resource'], +'sqlsrv_close' => ['bool', 'conn'=>'?resource'], +'sqlsrv_commit' => ['bool', 'conn'=>'resource'], +'sqlsrv_configure' => ['bool', 'setting'=>'string', 'value'=>'mixed'], +'sqlsrv_connect' => ['resource|false', 'serverName'=>'string', 'connectionInfo='=>'array'], +'sqlsrv_errors' => ['?array', 'errorsOrWarnings='=>'int'], +'sqlsrv_execute' => ['bool', 'stmt'=>'resource'], +'sqlsrv_fetch' => ['?bool', 'stmt'=>'resource', 'row='=>'int', 'offset='=>'int'], +'sqlsrv_fetch_array' => ['array|null|false', 'stmt'=>'resource', 'fetchType='=>'int', 'row='=>'int', 'offset='=>'int'], +'sqlsrv_fetch_object' => ['object|null|false', 'stmt'=>'resource', 'className='=>'string', 'ctorParams='=>'array', 'row='=>'int', 'offset='=>'int'], +'sqlsrv_field_metadata' => ['array|false', 'stmt'=>'resource'], +'sqlsrv_free_stmt' => ['bool', 'stmt'=>'resource'], +'sqlsrv_get_config' => ['mixed', 'setting'=>'string'], +'sqlsrv_get_field' => ['mixed', 'stmt'=>'resource', 'fieldIndex'=>'int', 'getAsType='=>'int'], +'sqlsrv_has_rows' => ['bool', 'stmt'=>'resource'], +'sqlsrv_next_result' => ['?bool', 'stmt'=>'resource'], +'sqlsrv_num_fields' => ['int|false', 'stmt'=>'resource'], +'sqlsrv_num_rows' => ['int|false', 'stmt'=>'resource'], +'sqlsrv_prepare' => ['resource|false', 'conn'=>'resource', 'sql'=>'string', 'params='=>'array', 'options='=>'array'], +'sqlsrv_query' => ['resource|false', 'conn'=>'resource', 'sql'=>'string', 'params='=>'array', 'options='=>'array'], +'sqlsrv_rollback' => ['bool', 'conn'=>'resource'], +'sqlsrv_rows_affected' => ['int|false', 'stmt'=>'resource'], +'sqlsrv_send_stream_data' => ['bool', 'stmt'=>'resource'], +'sqlsrv_server_info' => ['array', 'conn'=>'resource'], +'sqrt' => ['float', 'number'=>'float'], +'srand' => ['void', 'seed='=>'int', 'mode='=>'int'], +'sscanf' => ['list|int', 'string'=>'string', 'format'=>'string', '&...w_vars='=>'string|int|float'], +'ssdeep_fuzzy_compare' => ['int', 'signature1'=>'string', 'signature2'=>'string'], +'ssdeep_fuzzy_hash' => ['string', 'to_hash'=>'string'], +'ssdeep_fuzzy_hash_filename' => ['string', 'file_name'=>'string'], +'ssh2_auth_agent' => ['bool', 'session'=>'resource', 'username'=>'string'], +'ssh2_auth_hostbased_file' => ['bool', 'session'=>'resource', 'username'=>'string', 'hostname'=>'string', 'pubkeyfile'=>'string', 'privkeyfile'=>'string', 'passphrase='=>'string', 'local_username='=>'string'], +'ssh2_auth_none' => ['bool|string[]', 'session'=>'resource', 'username'=>'string'], +'ssh2_auth_password' => ['bool', 'session'=>'resource', 'username'=>'string', 'password'=>'string'], +'ssh2_auth_pubkey_file' => ['bool', 'session'=>'resource', 'username'=>'string', 'pubkeyfile'=>'string', 'privkeyfile'=>'string', 'passphrase='=>'string'], +'ssh2_connect' => ['resource|false', 'host'=>'string', 'port='=>'int', 'methods='=>'array', 'callbacks='=>'array'], +'ssh2_disconnect' => ['bool', 'session'=>'resource'], +'ssh2_exec' => ['resource|false', 'session'=>'resource', 'command'=>'string', 'pty='=>'string', 'env='=>'array', 'width='=>'int', 'height='=>'int', 'width_height_type='=>'int'], +'ssh2_fetch_stream' => ['resource|false', 'channel'=>'resource', 'streamid'=>'int'], +'ssh2_fingerprint' => ['string|false', 'session'=>'resource', 'flags='=>'int'], +'ssh2_forward_accept' => ['resource|false', 'session'=>'resource'], +'ssh2_forward_listen' => ['resource|false', 'session'=>'resource', 'port'=>'int', 'host='=>'string', 'max_connections='=>'string'], +'ssh2_methods_negotiated' => ['array|false', 'session'=>'resource'], +'ssh2_poll' => ['int', '&polldes'=>'array', 'timeout='=>'int'], +'ssh2_publickey_add' => ['bool', 'pkey'=>'resource', 'algoname'=>'string', 'blob'=>'string', 'overwrite='=>'bool', 'attributes='=>'array'], +'ssh2_publickey_init' => ['resource|false', 'session'=>'resource'], +'ssh2_publickey_list' => ['array|false', 'pkey'=>'resource'], +'ssh2_publickey_remove' => ['bool', 'pkey'=>'resource', 'algoname'=>'string', 'blob'=>'string'], +'ssh2_scp_recv' => ['bool', 'session'=>'resource', 'remote_file'=>'string', 'local_file'=>'string'], +'ssh2_scp_send' => ['bool', 'session'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'create_mode='=>'int'], +'ssh2_sftp' => ['resource|false', 'session'=>'resource'], +'ssh2_sftp_chmod' => ['bool', 'sftp'=>'resource', 'filename'=>'string', 'mode'=>'int'], +'ssh2_sftp_lstat' => ['array|false', 'sftp'=>'resource', 'path'=>'string'], +'ssh2_sftp_mkdir' => ['bool', 'sftp'=>'resource', 'dirname'=>'string', 'mode='=>'int', 'recursive='=>'bool'], +'ssh2_sftp_readlink' => ['string|false', 'sftp'=>'resource', 'link'=>'string'], +'ssh2_sftp_realpath' => ['string|false', 'sftp'=>'resource', 'filename'=>'string'], +'ssh2_sftp_rename' => ['bool', 'sftp'=>'resource', 'from'=>'string', 'to'=>'string'], +'ssh2_sftp_rmdir' => ['bool', 'sftp'=>'resource', 'dirname'=>'string'], +'ssh2_sftp_stat' => ['array|false', 'sftp'=>'resource', 'path'=>'string'], +'ssh2_sftp_symlink' => ['bool', 'sftp'=>'resource', 'target'=>'string', 'link'=>'string'], +'ssh2_sftp_unlink' => ['bool', 'sftp'=>'resource', 'filename'=>'string'], +'ssh2_shell' => ['resource|false', 'session'=>'resource', 'term_type='=>'string', 'env='=>'array', 'width='=>'int', 'height='=>'int', 'width_height_type='=>'int'], +'ssh2_tunnel' => ['resource|false', 'session'=>'resource', 'host'=>'string', 'port'=>'int'], +'stat' => ['array|false', 'filename'=>'string'], +'stats_absolute_deviation' => ['float', 'a'=>'array'], +'stats_cdf_beta' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_binomial' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_cauchy' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_chisquare' => ['float', 'par1'=>'float', 'par2'=>'float', 'which'=>'int'], +'stats_cdf_exponential' => ['float', 'par1'=>'float', 'par2'=>'float', 'which'=>'int'], +'stats_cdf_f' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_gamma' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_laplace' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_logistic' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_negative_binomial' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_noncentral_chisquare' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_noncentral_f' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'par4'=>'float', 'which'=>'int'], +'stats_cdf_noncentral_t' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_normal' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_poisson' => ['float', 'par1'=>'float', 'par2'=>'float', 'which'=>'int'], +'stats_cdf_t' => ['float', 'par1'=>'float', 'par2'=>'float', 'which'=>'int'], +'stats_cdf_uniform' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_cdf_weibull' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_covariance' => ['float', 'a'=>'array', 'b'=>'array'], +'stats_den_uniform' => ['float', 'x'=>'float', 'a'=>'float', 'b'=>'float'], +'stats_dens_beta' => ['float', 'x'=>'float', 'a'=>'float', 'b'=>'float'], +'stats_dens_cauchy' => ['float', 'x'=>'float', 'ave'=>'float', 'stdev'=>'float'], +'stats_dens_chisquare' => ['float', 'x'=>'float', 'dfr'=>'float'], +'stats_dens_exponential' => ['float', 'x'=>'float', 'scale'=>'float'], +'stats_dens_f' => ['float', 'x'=>'float', 'dfr1'=>'float', 'dfr2'=>'float'], +'stats_dens_gamma' => ['float', 'x'=>'float', 'shape'=>'float', 'scale'=>'float'], +'stats_dens_laplace' => ['float', 'x'=>'float', 'ave'=>'float', 'stdev'=>'float'], +'stats_dens_logistic' => ['float', 'x'=>'float', 'ave'=>'float', 'stdev'=>'float'], +'stats_dens_negative_binomial' => ['float', 'x'=>'float', 'n'=>'float', 'pi'=>'float'], +'stats_dens_normal' => ['float', 'x'=>'float', 'ave'=>'float', 'stdev'=>'float'], +'stats_dens_pmf_binomial' => ['float', 'x'=>'float', 'n'=>'float', 'pi'=>'float'], +'stats_dens_pmf_hypergeometric' => ['float', 'n1'=>'float', 'n2'=>'float', 'N1'=>'float', 'N2'=>'float'], +'stats_dens_pmf_negative_binomial' => ['float', 'x'=>'float', 'n'=>'float', 'pi'=>'float'], +'stats_dens_pmf_poisson' => ['float', 'x'=>'float', 'lb'=>'float'], +'stats_dens_t' => ['float', 'x'=>'float', 'dfr'=>'float'], +'stats_dens_uniform' => ['float', 'x'=>'float', 'a'=>'float', 'b'=>'float'], +'stats_dens_weibull' => ['float', 'x'=>'float', 'a'=>'float', 'b'=>'float'], +'stats_harmonic_mean' => ['float', 'a'=>'array'], +'stats_kurtosis' => ['float', 'a'=>'array'], +'stats_rand_gen_beta' => ['float', 'a'=>'float', 'b'=>'float'], +'stats_rand_gen_chisquare' => ['float', 'df'=>'float'], +'stats_rand_gen_exponential' => ['float', 'av'=>'float'], +'stats_rand_gen_f' => ['float', 'dfn'=>'float', 'dfd'=>'float'], +'stats_rand_gen_funiform' => ['float', 'low'=>'float', 'high'=>'float'], +'stats_rand_gen_gamma' => ['float', 'a'=>'float', 'r'=>'float'], +'stats_rand_gen_ibinomial' => ['int', 'n'=>'int', 'pp'=>'float'], +'stats_rand_gen_ibinomial_negative' => ['int', 'n'=>'int', 'p'=>'float'], +'stats_rand_gen_int' => ['int'], +'stats_rand_gen_ipoisson' => ['int', 'mu'=>'float'], +'stats_rand_gen_iuniform' => ['int', 'low'=>'int', 'high'=>'int'], +'stats_rand_gen_noncenral_chisquare' => ['float', 'df'=>'float', 'xnonc'=>'float'], +'stats_rand_gen_noncentral_chisquare' => ['float', 'df'=>'float', 'xnonc'=>'float'], +'stats_rand_gen_noncentral_f' => ['float', 'dfn'=>'float', 'dfd'=>'float', 'xnonc'=>'float'], +'stats_rand_gen_noncentral_t' => ['float', 'df'=>'float', 'xnonc'=>'float'], +'stats_rand_gen_normal' => ['float', 'av'=>'float', 'sd'=>'float'], +'stats_rand_gen_t' => ['float', 'df'=>'float'], +'stats_rand_get_seeds' => ['array'], +'stats_rand_phrase_to_seeds' => ['array', 'phrase'=>'string'], +'stats_rand_ranf' => ['float'], +'stats_rand_setall' => ['void', 'iseed1'=>'int', 'iseed2'=>'int'], +'stats_skew' => ['float', 'a'=>'array'], +'stats_standard_deviation' => ['float', 'a'=>'array', 'sample='=>'bool'], +'stats_stat_binomial_coef' => ['float', 'x'=>'int', 'n'=>'int'], +'stats_stat_correlation' => ['float', 'array1'=>'array', 'array2'=>'array'], +'stats_stat_factorial' => ['float', 'n'=>'int'], +'stats_stat_gennch' => ['float', 'n'=>'int'], +'stats_stat_independent_t' => ['float', 'array1'=>'array', 'array2'=>'array'], +'stats_stat_innerproduct' => ['float', 'array1'=>'array', 'array2'=>'array'], +'stats_stat_noncentral_t' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'], +'stats_stat_paired_t' => ['float', 'array1'=>'array', 'array2'=>'array'], +'stats_stat_percentile' => ['float', 'arr'=>'array', 'perc'=>'float'], +'stats_stat_powersum' => ['float', 'arr'=>'array', 'power'=>'float'], +'stats_variance' => ['float', 'a'=>'array', 'sample='=>'bool'], +'Stomp::__construct' => ['void', 'broker='=>'string', 'username='=>'string', 'password='=>'string', 'headers='=>'array'], +'Stomp::__destruct' => ['bool', 'link'=>''], +'Stomp::abort' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''], +'Stomp::ack' => ['bool', 'msg'=>'', 'headers='=>'array', 'link='=>''], +'Stomp::begin' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''], +'Stomp::commit' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''], +'Stomp::error' => ['string', 'link'=>''], +'Stomp::getReadTimeout' => ['array', 'link'=>''], +'Stomp::getSessionId' => ['string', 'link'=>''], +'Stomp::hasFrame' => ['bool', 'link'=>''], +'Stomp::readFrame' => ['array', 'class_name='=>'string', 'link='=>''], +'Stomp::send' => ['bool', 'destination'=>'string', 'msg'=>'', 'headers='=>'array', 'link='=>''], +'Stomp::setReadTimeout' => ['', 'seconds'=>'int', 'microseconds='=>'int', 'link='=>''], +'Stomp::subscribe' => ['bool', 'destination'=>'string', 'headers='=>'array', 'link='=>''], +'Stomp::unsubscribe' => ['bool', 'destination'=>'string', 'headers='=>'array', 'link='=>''], +'stomp_abort' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''], +'stomp_ack' => ['bool', 'msg'=>'', 'headers='=>'array', 'link='=>''], +'stomp_begin' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''], +'stomp_close' => ['bool', 'link'=>''], +'stomp_commit' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''], +'stomp_connect' => ['resource', 'broker='=>'string', 'username='=>'string', 'password='=>'string', 'headers='=>'array'], +'stomp_connect_error' => ['string'], +'stomp_error' => ['string', 'link'=>''], +'stomp_get_read_timeout' => ['array', 'link'=>''], +'stomp_get_session_id' => ['string', 'link'=>''], +'stomp_has_frame' => ['bool', 'link'=>''], +'stomp_read_frame' => ['array', 'class_name='=>'string', 'link='=>''], +'stomp_send' => ['bool', 'destination'=>'string', 'msg'=>'', 'headers='=>'array', 'link='=>''], +'stomp_set_read_timeout' => ['', 'seconds'=>'int', 'microseconds='=>'int', 'link='=>''], +'stomp_subscribe' => ['bool', 'destination'=>'string', 'headers='=>'array', 'link='=>''], +'stomp_unsubscribe' => ['bool', 'destination'=>'string', 'headers='=>'array', 'link='=>''], +'stomp_version' => ['string'], +'StompException::getDetails' => ['string'], +'StompFrame::__construct' => ['void', 'command='=>'string', 'headers='=>'array', 'body='=>'string'], +'str_contains' => ['bool', 'haystack'=>'string', 'needle'=>'string'], +'str_ends_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], +'str_getcsv' => ['non-empty-list', 'input'=>'string', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], +'str_ireplace' => ['string|string[]', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_replace_count='=>'int'], +'str_pad' => ['string', 'input'=>'string', 'pad_length'=>'int', 'pad_string='=>'string', 'pad_type='=>'int'], +'str_repeat' => ['string', 'input'=>'string', 'multiplier'=>'int'], +'str_replace' => ['string|string[]', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_replace_count='=>'int'], +'str_rot13' => ['string', 'string'=>'string'], +'str_shuffle' => ['string', 'string'=>'string'], +'str_split' => ['list|false', 'str'=>'string', 'split_length='=>'int'], +'str_starts_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], +'str_word_count' => ['array|int', 'string'=>'string', 'format='=>'int', 'charlist='=>'string'], +'strcasecmp' => ['int', 'string1'=>'string', 'string2'=>'string'], +'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'mixed', 'before_needle='=>'bool'], +'strcmp' => ['int', 'string1'=>'string', 'string2'=>'string'], +'strcoll' => ['int', 'string1'=>'string', 'string2'=>'string'], +'strcspn' => ['int', 'string'=>'string', 'mask'=>'string', 'start='=>'int', 'length='=>'int'], +'stream_bucket_append' => ['void', 'brigade'=>'resource', 'bucket'=>'object'], +'stream_bucket_make_writeable' => ['object', 'brigade'=>'resource'], +'stream_bucket_new' => ['object|false', 'stream'=>'resource', 'buffer'=>'string'], +'stream_bucket_prepend' => ['void', 'brigade'=>'resource', 'bucket'=>'object'], +'stream_context_create' => ['resource', 'options='=>'array', 'params='=>'array'], +'stream_context_get_default' => ['resource', 'options='=>'array'], +'stream_context_get_options' => ['array', 'context'=>'resource'], +'stream_context_get_params' => ['array', 'context'=>'resource'], +'stream_context_set_default' => ['resource', 'options'=>'array'], +'stream_context_set_option' => ['bool', 'context'=>'', 'wrappername'=>'string', 'optionname'=>'string', 'value'=>''], +'stream_context_set_option\'1' => ['bool', 'context'=>'', 'options'=>'array'], +'stream_context_set_params' => ['bool', 'context'=>'resource', 'options'=>'array'], +'stream_copy_to_stream' => ['int|false', 'source'=>'resource', 'dest'=>'resource', 'maxlen='=>'int', 'pos='=>'int'], +'stream_encoding' => ['bool', 'stream'=>'resource', 'encoding='=>'string'], +'stream_filter_append' => ['resource|false', 'stream'=>'resource', 'filtername'=>'string', 'read_write='=>'int', 'filterparams='=>'mixed'], +'stream_filter_prepend' => ['resource|false', 'stream'=>'resource', 'filtername'=>'string', 'read_write='=>'int', 'filterparams='=>'mixed'], +'stream_filter_register' => ['bool', 'filtername'=>'string', 'classname'=>'string'], +'stream_filter_remove' => ['bool', 'stream_filter'=>'resource'], +'stream_get_contents' => ['string|false', 'source'=>'resource', 'maxlen='=>'int', 'offset='=>'int'], +'stream_get_filters' => ['array'], +'stream_get_line' => ['string|false', 'stream'=>'resource', 'maxlen'=>'int', 'ending='=>'string'], +'stream_get_meta_data' => ['array{timed_out:bool,blocked:bool,eof:bool,unread_bytes:int,stream_type:string,wrapper_type:string,wrapper_data:mixed,mode:string,seekable:bool,uri:string,mediatype:string}', 'fp'=>'resource'], +'stream_get_transports' => ['list'], +'stream_get_wrappers' => ['list'], +'stream_is_local' => ['bool', 'stream'=>'resource|string'], +'stream_isatty' => ['bool', 'stream'=>'resource'], +'stream_notification_callback' => ['callback', 'notification_code'=>'int', 'severity'=>'int', 'message'=>'string', 'message_code'=>'int', 'bytes_transferred'=>'int', 'bytes_max'=>'int'], +'stream_register_wrapper' => ['bool', 'protocol'=>'string', 'classname'=>'string', 'flags='=>'int'], +'stream_resolve_include_path' => ['string|false', 'filename'=>'string'], +'stream_select' => ['int|false', '&rw_read_streams'=>'resource[]', '&rw_write_streams'=>'?resource[]', '&rw_except_streams'=>'?resource[]', 'tv_sec'=>'?int', 'tv_usec='=>'?int'], +'stream_set_blocking' => ['bool', 'socket'=>'resource', 'mode'=>'bool'], +'stream_set_chunk_size' => ['int|false', 'fp'=>'resource', 'chunk_size'=>'int'], +'stream_set_read_buffer' => ['int', 'fp'=>'resource', 'buffer'=>'int'], +'stream_set_timeout' => ['bool', 'stream'=>'resource', 'seconds'=>'int', 'microseconds='=>'int'], +'stream_set_write_buffer' => ['int', 'fp'=>'resource', 'buffer'=>'int'], +'stream_socket_accept' => ['resource|false', 'serverstream'=>'resource', 'timeout='=>'float', '&w_peername='=>'string'], +'stream_socket_client' => ['resource|false', 'remoteaddress'=>'string', '&w_errcode='=>'int', '&w_errstring='=>'string', 'timeout='=>'float', 'flags='=>'int', 'context='=>'resource'], +'stream_socket_enable_crypto' => ['int|bool', 'stream'=>'resource', 'enable'=>'bool', 'cryptokind='=>'int', 'sessionstream='=>'resource'], +'stream_socket_get_name' => ['string', 'stream'=>'resource', 'want_peer'=>'bool'], +'stream_socket_pair' => ['resource[]|false', 'domain'=>'int', 'type'=>'int', 'protocol'=>'int'], +'stream_socket_recvfrom' => ['string', 'stream'=>'resource', 'amount'=>'int', 'flags='=>'int', '&w_remote_addr='=>'string'], +'stream_socket_sendto' => ['int', 'stream'=>'resource', 'data'=>'string', 'flags='=>'int', 'target_addr='=>'string'], +'stream_socket_server' => ['resource|false', 'localaddress'=>'string', '&w_errcode='=>'int', '&w_errstring='=>'string', 'flags='=>'int', 'context='=>'resource'], +'stream_socket_shutdown' => ['bool', 'stream'=>'resource', 'how'=>'int'], +'stream_supports_lock' => ['bool', 'stream'=>'resource'], +'stream_wrapper_register' => ['bool', 'protocol'=>'string', 'classname'=>'string', 'flags='=>'int'], +'stream_wrapper_restore' => ['bool', 'protocol'=>'string'], +'stream_wrapper_unregister' => ['bool', 'protocol'=>'string'], +'streamWrapper::__construct' => ['void'], +'streamWrapper::__destruct' => ['void'], +'streamWrapper::dir_closedir' => ['bool'], +'streamWrapper::dir_opendir' => ['bool', 'path'=>'string', 'options'=>'int'], +'streamWrapper::dir_readdir' => ['string'], +'streamWrapper::dir_rewinddir' => ['bool'], +'streamWrapper::mkdir' => ['bool', 'path'=>'string', 'mode'=>'int', 'options'=>'int'], +'streamWrapper::rename' => ['bool', 'path_from'=>'string', 'path_to'=>'string'], +'streamWrapper::rmdir' => ['bool', 'path'=>'string', 'options'=>'int'], +'streamWrapper::stream_cast' => ['resource', 'cast_as'=>'int'], +'streamWrapper::stream_close' => ['void'], +'streamWrapper::stream_eof' => ['bool'], +'streamWrapper::stream_flush' => ['bool'], +'streamWrapper::stream_lock' => ['bool', 'operation'=>'mode'], +'streamWrapper::stream_metadata' => ['bool', 'path'=>'string', 'option'=>'int', 'value'=>'mixed'], +'streamWrapper::stream_open' => ['bool', 'path'=>'string', 'mode'=>'string', 'options'=>'int', 'opened_path'=>'string'], +'streamWrapper::stream_read' => ['string', 'count'=>'int'], +'streamWrapper::stream_seek' => ['bool', 'offset'=>'int', 'whence'=>'int'], +'streamWrapper::stream_set_option' => ['bool', 'option'=>'int', 'arg1'=>'int', 'arg2'=>'int'], +'streamWrapper::stream_stat' => ['array'], +'streamWrapper::stream_tell' => ['int'], +'streamWrapper::stream_truncate' => ['bool', 'new_size'=>'int'], +'streamWrapper::stream_write' => ['int', 'data'=>'string'], +'streamWrapper::unlink' => ['bool', 'path'=>'string'], +'streamWrapper::url_stat' => ['array', 'path'=>'string', 'flags'=>'int'], +'strftime' => ['string', 'format'=>'string', 'timestamp='=>'int'], +'strip_tags' => ['string', 'string'=>'string', 'allowable_tags='=>'string'], +'stripcslashes' => ['string', 'string'=>'string'], +'stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], +'stripslashes' => ['string', 'string'=>'string'], +'stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'mixed', 'before_needle='=>'bool'], +'strlen' => ['int', 'string'=>'string'], +'strnatcasecmp' => ['int', 's1'=>'string', 's2'=>'string'], +'strnatcmp' => ['int', 's1'=>'string', 's2'=>'string'], +'strncasecmp' => ['int', 'string1'=>'string', 'string2'=>'string', 'length'=>'int'], +'strncmp' => ['int', 'string1'=>'string', 'string2'=>'string', 'length'=>'int'], +'strpbrk' => ['string|false', 'haystack'=>'string', 'char_list'=>'string'], +'strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], +'strptime' => ['array|false', 'datestr'=>'string', 'format'=>'string'], +'strrchr' => ['string|false', 'haystack'=>'string', 'needle'=>'mixed'], +'strrev' => ['string', 'string'=>'string'], +'strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], +'strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], +'strspn' => ['int', 'string'=>'string', 'mask'=>'string', 'start='=>'int', 'length='=>'int'], +'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'mixed', 'before_needle='=>'bool'], +'strtok' => ['string|false', 'string'=>'string', 'token'=>'string'], +'strtok\'1' => ['string|false', 'token'=>'string'], +'strtolower' => ['string', 'string'=>'string'], +'strtotime' => ['int|false', 'time'=>'string', 'now='=>'int'], +'strtoupper' => ['string', 'string'=>'string'], +'strtr' => ['string', 'string'=>'string', 'from'=>'string', 'to'=>'string'], +'strtr\'1' => ['string', 'string'=>'string', 'replace_pairs'=>'array'], +'strval' => ['string', 'value'=>'mixed'], +'styleObj::__construct' => ['void', 'label'=>'labelObj', 'style'=>'styleObj'], +'styleObj::convertToString' => ['string'], +'styleObj::free' => ['void'], +'styleObj::getBinding' => ['string', 'stylebinding'=>'mixed'], +'styleObj::getGeomTransform' => ['string'], +'styleObj::ms_newStyleObj' => ['styleObj', 'class'=>'classObj', 'style'=>'styleObj'], +'styleObj::removeBinding' => ['int', 'stylebinding'=>'mixed'], +'styleObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'styleObj::setBinding' => ['int', 'stylebinding'=>'mixed', 'value'=>'string'], +'styleObj::setGeomTransform' => ['int', 'value'=>'string'], +'styleObj::updateFromString' => ['int', 'snippet'=>'string'], +'substr' => ['string|false', 'string'=>'string', 'start'=>'int', 'length='=>'int'], +'substr_compare' => ['int|false', 'main_str'=>'string', 'string'=>'string', 'offset'=>'int', 'length='=>'int', 'case_sensitivity='=>'bool'], +'substr_count' => ['int', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'length='=>'int'], +'substr_replace' => ['string|string[]', 'string'=>'string|string[]', 'repl'=>'mixed', 'start'=>'mixed', 'length='=>'mixed'], +'suhosin_encrypt_cookie' => ['string|false', 'name'=>'string', 'value'=>'string'], +'suhosin_get_raw_cookies' => ['array'], +'SVM::__construct' => ['void'], +'svm::crossvalidate' => ['float', 'problem'=>'array', 'number_of_folds'=>'int'], +'SVM::getOptions' => ['array'], +'SVM::setOptions' => ['bool', 'params'=>'array'], +'svm::train' => ['SVMModel', 'problem'=>'array', 'weights='=>'array'], +'SVMModel::__construct' => ['void', 'filename='=>'string'], +'SVMModel::checkProbabilityModel' => ['bool'], +'SVMModel::getLabels' => ['array'], +'SVMModel::getNrClass' => ['int'], +'SVMModel::getSvmType' => ['int'], +'SVMModel::getSvrProbability' => ['float'], +'SVMModel::load' => ['bool', 'filename'=>'string'], +'SVMModel::predict' => ['float', 'data'=>'array'], +'SVMModel::predict_probability' => ['float', 'data'=>'array'], +'SVMModel::save' => ['bool', 'filename'=>'string'], +'svn_add' => ['bool', 'path'=>'string', 'recursive='=>'bool', 'force='=>'bool'], +'svn_auth_get_parameter' => ['?string', 'key'=>'string'], +'svn_auth_set_parameter' => ['void', 'key'=>'string', 'value'=>'string'], +'svn_blame' => ['array', 'repository_url'=>'string', 'revision_no='=>'int'], +'svn_cat' => ['string', 'repos_url'=>'string', 'revision_no='=>'int'], +'svn_checkout' => ['bool', 'repos'=>'string', 'targetpath'=>'string', 'revision='=>'int', 'flags='=>'int'], +'svn_cleanup' => ['bool', 'workingdir'=>'string'], +'svn_client_version' => ['string'], +'svn_commit' => ['array', 'log'=>'string', 'targets'=>'array', 'dontrecurse='=>'bool'], +'svn_delete' => ['bool', 'path'=>'string', 'force='=>'bool'], +'svn_diff' => ['array', 'path1'=>'string', 'rev1'=>'int', 'path2'=>'string', 'rev2'=>'int'], +'svn_export' => ['bool', 'frompath'=>'string', 'topath'=>'string', 'working_copy='=>'bool', 'revision_no='=>'int'], +'svn_fs_abort_txn' => ['bool', 'txn'=>'resource'], +'svn_fs_apply_text' => ['resource', 'root'=>'resource', 'path'=>'string'], +'svn_fs_begin_txn2' => ['resource', 'repos'=>'resource', 'rev'=>'int'], +'svn_fs_change_node_prop' => ['bool', 'root'=>'resource', 'path'=>'string', 'name'=>'string', 'value'=>'string'], +'svn_fs_check_path' => ['int', 'fsroot'=>'resource', 'path'=>'string'], +'svn_fs_contents_changed' => ['bool', 'root1'=>'resource', 'path1'=>'string', 'root2'=>'resource', 'path2'=>'string'], +'svn_fs_copy' => ['bool', 'from_root'=>'resource', 'from_path'=>'string', 'to_root'=>'resource', 'to_path'=>'string'], +'svn_fs_delete' => ['bool', 'root'=>'resource', 'path'=>'string'], +'svn_fs_dir_entries' => ['array', 'fsroot'=>'resource', 'path'=>'string'], +'svn_fs_file_contents' => ['resource', 'fsroot'=>'resource', 'path'=>'string'], +'svn_fs_file_length' => ['int', 'fsroot'=>'resource', 'path'=>'string'], +'svn_fs_is_dir' => ['bool', 'root'=>'resource', 'path'=>'string'], +'svn_fs_is_file' => ['bool', 'root'=>'resource', 'path'=>'string'], +'svn_fs_make_dir' => ['bool', 'root'=>'resource', 'path'=>'string'], +'svn_fs_make_file' => ['bool', 'root'=>'resource', 'path'=>'string'], +'svn_fs_node_created_rev' => ['int', 'fsroot'=>'resource', 'path'=>'string'], +'svn_fs_node_prop' => ['string', 'fsroot'=>'resource', 'path'=>'string', 'propname'=>'string'], +'svn_fs_props_changed' => ['bool', 'root1'=>'resource', 'path1'=>'string', 'root2'=>'resource', 'path2'=>'string'], +'svn_fs_revision_prop' => ['string', 'fs'=>'resource', 'revnum'=>'int', 'propname'=>'string'], +'svn_fs_revision_root' => ['resource', 'fs'=>'resource', 'revnum'=>'int'], +'svn_fs_txn_root' => ['resource', 'txn'=>'resource'], +'svn_fs_youngest_rev' => ['int', 'fs'=>'resource'], +'svn_import' => ['bool', 'path'=>'string', 'url'=>'string', 'nonrecursive'=>'bool'], +'svn_log' => ['array', 'repos_url'=>'string', 'start_revision='=>'int', 'end_revision='=>'int', 'limit='=>'int', 'flags='=>'int'], +'svn_ls' => ['array', 'repos_url'=>'string', 'revision_no='=>'int', 'recurse='=>'bool', 'peg='=>'bool'], +'svn_mkdir' => ['bool', 'path'=>'string', 'log_message='=>'string'], +'svn_move' => ['mixed', 'src_path'=>'string', 'dst_path'=>'string', 'force='=>'bool'], +'svn_propget' => ['mixed', 'path'=>'string', 'property_name'=>'string', 'recurse='=>'bool', 'revision'=>'int'], +'svn_proplist' => ['mixed', 'path'=>'string', 'recurse='=>'bool', 'revision'=>'int'], +'svn_repos_create' => ['resource', 'path'=>'string', 'config='=>'array', 'fsconfig='=>'array'], +'svn_repos_fs' => ['resource', 'repos'=>'resource'], +'svn_repos_fs_begin_txn_for_commit' => ['resource', 'repos'=>'resource', 'rev'=>'int', 'author'=>'string', 'log_msg'=>'string'], +'svn_repos_fs_commit_txn' => ['int', 'txn'=>'resource'], +'svn_repos_hotcopy' => ['bool', 'repospath'=>'string', 'destpath'=>'string', 'cleanlogs'=>'bool'], +'svn_repos_open' => ['resource', 'path'=>'string'], +'svn_repos_recover' => ['bool', 'path'=>'string'], +'svn_revert' => ['bool', 'path'=>'string', 'recursive='=>'bool'], +'svn_status' => ['array', 'path'=>'string', 'flags='=>'int'], +'svn_update' => ['int|false', 'path'=>'string', 'revno='=>'int', 'recurse='=>'bool'], +'swf_actiongeturl' => ['', 'url'=>'string', 'target'=>'string'], +'swf_actiongotoframe' => ['', 'framenumber'=>'int'], +'swf_actiongotolabel' => ['', 'label'=>'string'], +'swf_actionnextframe' => [''], +'swf_actionplay' => [''], +'swf_actionprevframe' => [''], +'swf_actionsettarget' => ['', 'target'=>'string'], +'swf_actionstop' => [''], +'swf_actiontogglequality' => [''], +'swf_actionwaitforframe' => ['', 'framenumber'=>'int', 'skipcount'=>'int'], +'swf_addbuttonrecord' => ['', 'states'=>'int', 'shapeid'=>'int', 'depth'=>'int'], +'swf_addcolor' => ['', 'r'=>'float', 'g'=>'float', 'b'=>'float', 'a'=>'float'], +'swf_closefile' => ['', 'return_file='=>'int'], +'swf_definebitmap' => ['', 'objid'=>'int', 'image_name'=>'string'], +'swf_definefont' => ['', 'fontid'=>'int', 'fontname'=>'string'], +'swf_defineline' => ['', 'objid'=>'int', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'width'=>'float'], +'swf_definepoly' => ['', 'objid'=>'int', 'coords'=>'array', 'npoints'=>'int', 'width'=>'float'], +'swf_definerect' => ['', 'objid'=>'int', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'width'=>'float'], +'swf_definetext' => ['', 'objid'=>'int', 'string'=>'string', 'docenter'=>'int'], +'swf_endbutton' => [''], +'swf_enddoaction' => [''], +'swf_endshape' => [''], +'swf_endsymbol' => [''], +'swf_fontsize' => ['', 'size'=>'float'], +'swf_fontslant' => ['', 'slant'=>'float'], +'swf_fonttracking' => ['', 'tracking'=>'float'], +'swf_getbitmapinfo' => ['array', 'bitmapid'=>'int'], +'swf_getfontinfo' => ['array'], +'swf_getframe' => ['int'], +'swf_labelframe' => ['', 'name'=>'string'], +'swf_lookat' => ['', 'view_x'=>'float', 'view_y'=>'float', 'view_z'=>'float', 'reference_x'=>'float', 'reference_y'=>'float', 'reference_z'=>'float', 'twist'=>'float'], +'swf_modifyobject' => ['', 'depth'=>'int', 'how'=>'int'], +'swf_mulcolor' => ['', 'r'=>'float', 'g'=>'float', 'b'=>'float', 'a'=>'float'], +'swf_nextid' => ['int'], +'swf_oncondition' => ['', 'transition'=>'int'], +'swf_openfile' => ['', 'filename'=>'string', 'width'=>'float', 'height'=>'float', 'framerate'=>'float', 'r'=>'float', 'g'=>'float', 'b'=>'float'], +'swf_ortho' => ['', 'xmin'=>'float', 'xmax'=>'float', 'ymin'=>'float', 'ymax'=>'float', 'zmin'=>'float', 'zmax'=>'float'], +'swf_ortho2' => ['', 'xmin'=>'float', 'xmax'=>'float', 'ymin'=>'float', 'ymax'=>'float'], +'swf_perspective' => ['', 'fovy'=>'float', 'aspect'=>'float', 'near'=>'float', 'far'=>'float'], +'swf_placeobject' => ['', 'objid'=>'int', 'depth'=>'int'], +'swf_polarview' => ['', 'dist'=>'float', 'azimuth'=>'float', 'incidence'=>'float', 'twist'=>'float'], +'swf_popmatrix' => [''], +'swf_posround' => ['', 'round'=>'int'], +'swf_pushmatrix' => [''], +'swf_removeobject' => ['', 'depth'=>'int'], +'swf_rotate' => ['', 'angle'=>'float', 'axis'=>'string'], +'swf_scale' => ['', 'x'=>'float', 'y'=>'float', 'z'=>'float'], +'swf_setfont' => ['', 'fontid'=>'int'], +'swf_setframe' => ['', 'framenumber'=>'int'], +'swf_shapearc' => ['', 'x'=>'float', 'y'=>'float', 'r'=>'float', 'ang1'=>'float', 'ang2'=>'float'], +'swf_shapecurveto' => ['', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float'], +'swf_shapecurveto3' => ['', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float'], +'swf_shapefillbitmapclip' => ['', 'bitmapid'=>'int'], +'swf_shapefillbitmaptile' => ['', 'bitmapid'=>'int'], +'swf_shapefilloff' => [''], +'swf_shapefillsolid' => ['', 'r'=>'float', 'g'=>'float', 'b'=>'float', 'a'=>'float'], +'swf_shapelinesolid' => ['', 'r'=>'float', 'g'=>'float', 'b'=>'float', 'a'=>'float', 'width'=>'float'], +'swf_shapelineto' => ['', 'x'=>'float', 'y'=>'float'], +'swf_shapemoveto' => ['', 'x'=>'float', 'y'=>'float'], +'swf_showframe' => [''], +'swf_startbutton' => ['', 'objid'=>'int', 'type'=>'int'], +'swf_startdoaction' => [''], +'swf_startshape' => ['', 'objid'=>'int'], +'swf_startsymbol' => ['', 'objid'=>'int'], +'swf_textwidth' => ['float', 'string'=>'string'], +'swf_translate' => ['', 'x'=>'float', 'y'=>'float', 'z'=>'float'], +'swf_viewport' => ['', 'xmin'=>'float', 'xmax'=>'float', 'ymin'=>'float', 'ymax'=>'float'], +'SWFAction::__construct' => ['void', 'script'=>'string'], +'SWFBitmap::__construct' => ['void', 'file'=>'', 'alphafile='=>''], +'SWFBitmap::getHeight' => ['float'], +'SWFBitmap::getWidth' => ['float'], +'SWFButton::__construct' => ['void'], +'SWFButton::addAction' => ['void', 'action'=>'swfaction', 'flags'=>'int'], +'SWFButton::addASound' => ['SWFSoundInstance', 'sound'=>'swfsound', 'flags'=>'int'], +'SWFButton::addShape' => ['void', 'shape'=>'swfshape', 'flags'=>'int'], +'SWFButton::setAction' => ['void', 'action'=>'swfaction'], +'SWFButton::setDown' => ['void', 'shape'=>'swfshape'], +'SWFButton::setHit' => ['void', 'shape'=>'swfshape'], +'SWFButton::setMenu' => ['void', 'flag'=>'int'], +'SWFButton::setOver' => ['void', 'shape'=>'swfshape'], +'SWFButton::setUp' => ['void', 'shape'=>'swfshape'], +'SWFDisplayItem::addAction' => ['void', 'action'=>'swfaction', 'flags'=>'int'], +'SWFDisplayItem::addColor' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], +'SWFDisplayItem::endMask' => ['void'], +'SWFDisplayItem::getRot' => ['float'], +'SWFDisplayItem::getX' => ['float'], +'SWFDisplayItem::getXScale' => ['float'], +'SWFDisplayItem::getXSkew' => ['float'], +'SWFDisplayItem::getY' => ['float'], +'SWFDisplayItem::getYScale' => ['float'], +'SWFDisplayItem::getYSkew' => ['float'], +'SWFDisplayItem::move' => ['void', 'dx'=>'float', 'dy'=>'float'], +'SWFDisplayItem::moveTo' => ['void', 'x'=>'float', 'y'=>'float'], +'SWFDisplayItem::multColor' => ['void', 'red'=>'float', 'green'=>'float', 'blue'=>'float', 'a='=>'float'], +'SWFDisplayItem::remove' => ['void'], +'SWFDisplayItem::rotate' => ['void', 'angle'=>'float'], +'SWFDisplayItem::rotateTo' => ['void', 'angle'=>'float'], +'SWFDisplayItem::scale' => ['void', 'dx'=>'float', 'dy'=>'float'], +'SWFDisplayItem::scaleTo' => ['void', 'x'=>'float', 'y='=>'float'], +'SWFDisplayItem::setDepth' => ['void', 'depth'=>'int'], +'SWFDisplayItem::setMaskLevel' => ['void', 'level'=>'int'], +'SWFDisplayItem::setMatrix' => ['void', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'x'=>'float', 'y'=>'float'], +'SWFDisplayItem::setName' => ['void', 'name'=>'string'], +'SWFDisplayItem::setRatio' => ['void', 'ratio'=>'float'], +'SWFDisplayItem::skewX' => ['void', 'ddegrees'=>'float'], +'SWFDisplayItem::skewXTo' => ['void', 'degrees'=>'float'], +'SWFDisplayItem::skewY' => ['void', 'ddegrees'=>'float'], +'SWFDisplayItem::skewYTo' => ['void', 'degrees'=>'float'], +'SWFFill::moveTo' => ['void', 'x'=>'float', 'y'=>'float'], +'SWFFill::rotateTo' => ['void', 'angle'=>'float'], +'SWFFill::scaleTo' => ['void', 'x'=>'float', 'y='=>'float'], +'SWFFill::skewXTo' => ['void', 'x'=>'float'], +'SWFFill::skewYTo' => ['void', 'y'=>'float'], +'SWFFont::__construct' => ['void', 'filename'=>'string'], +'SWFFont::getAscent' => ['float'], +'SWFFont::getDescent' => ['float'], +'SWFFont::getLeading' => ['float'], +'SWFFont::getShape' => ['string', 'code'=>'int'], +'SWFFont::getUTF8Width' => ['float', 'string'=>'string'], +'SWFFont::getWidth' => ['float', 'string'=>'string'], +'SWFFontChar::addChars' => ['void', 'char'=>'string'], +'SWFFontChar::addUTF8Chars' => ['void', 'char'=>'string'], +'SWFGradient::__construct' => ['void'], +'SWFGradient::addEntry' => ['void', 'ratio'=>'float', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha='=>'int'], +'SWFMorph::__construct' => ['void'], +'SWFMorph::getShape1' => ['SWFShape'], +'SWFMorph::getShape2' => ['SWFShape'], +'SWFMovie::__construct' => ['void', 'version='=>'int'], +'SWFMovie::add' => ['mixed', 'instance'=>'object'], +'SWFMovie::addExport' => ['void', 'char'=>'swfcharacter', 'name'=>'string'], +'SWFMovie::addFont' => ['mixed', 'font'=>'swffont'], +'SWFMovie::importChar' => ['SWFSprite', 'libswf'=>'string', 'name'=>'string'], +'SWFMovie::importFont' => ['SWFFontChar', 'libswf'=>'string', 'name'=>'string'], +'SWFMovie::labelFrame' => ['void', 'label'=>'string'], +'SWFMovie::namedAnchor' => [''], +'SWFMovie::nextFrame' => ['void'], +'SWFMovie::output' => ['int', 'compression='=>'int'], +'SWFMovie::protect' => [''], +'SWFMovie::remove' => ['void', 'instance'=>'object'], +'SWFMovie::save' => ['int', 'filename'=>'string', 'compression='=>'int'], +'SWFMovie::saveToFile' => ['int', 'x'=>'resource', 'compression='=>'int'], +'SWFMovie::setbackground' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], +'SWFMovie::setDimension' => ['void', 'width'=>'float', 'height'=>'float'], +'SWFMovie::setFrames' => ['void', 'number'=>'int'], +'SWFMovie::setRate' => ['void', 'rate'=>'float'], +'SWFMovie::startSound' => ['SWFSoundInstance', 'sound'=>'swfsound'], +'SWFMovie::stopSound' => ['void', 'sound'=>'swfsound'], +'SWFMovie::streamMP3' => ['int', 'mp3file'=>'mixed', 'skip='=>'float'], +'SWFMovie::writeExports' => ['void'], +'SWFPrebuiltClip::__construct' => ['void', 'file'=>''], +'SWFShape::__construct' => ['void'], +'SWFShape::addFill' => ['SWFFill', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha='=>'int', 'bitmap='=>'swfbitmap', 'flags='=>'int', 'gradient='=>'swfgradient'], +'SWFShape::addFill\'1' => ['SWFFill', 'bitmap'=>'SWFBitmap', 'flags='=>'int'], +'SWFShape::addFill\'2' => ['SWFFill', 'gradient'=>'SWFGradient', 'flags='=>'int'], +'SWFShape::drawArc' => ['void', 'r'=>'float', 'startangle'=>'float', 'endangle'=>'float'], +'SWFShape::drawCircle' => ['void', 'r'=>'float'], +'SWFShape::drawCubic' => ['int', 'bx'=>'float', 'by'=>'float', 'cx'=>'float', 'cy'=>'float', 'dx'=>'float', 'dy'=>'float'], +'SWFShape::drawCubicTo' => ['int', 'bx'=>'float', 'by'=>'float', 'cx'=>'float', 'cy'=>'float', 'dx'=>'float', 'dy'=>'float'], +'SWFShape::drawCurve' => ['int', 'controldx'=>'float', 'controldy'=>'float', 'anchordx'=>'float', 'anchordy'=>'float', 'targetdx='=>'float', 'targetdy='=>'float'], +'SWFShape::drawCurveTo' => ['int', 'controlx'=>'float', 'controly'=>'float', 'anchorx'=>'float', 'anchory'=>'float', 'targetx='=>'float', 'targety='=>'float'], +'SWFShape::drawGlyph' => ['void', 'font'=>'swffont', 'character'=>'string', 'size='=>'int'], +'SWFShape::drawLine' => ['void', 'dx'=>'float', 'dy'=>'float'], +'SWFShape::drawLineTo' => ['void', 'x'=>'float', 'y'=>'float'], +'SWFShape::movePen' => ['void', 'dx'=>'float', 'dy'=>'float'], +'SWFShape::movePenTo' => ['void', 'x'=>'float', 'y'=>'float'], +'SWFShape::setLeftFill' => ['', 'fill'=>'swfgradient', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], +'SWFShape::setLine' => ['', 'shape'=>'swfshape', 'width'=>'int', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], +'SWFShape::setRightFill' => ['', 'fill'=>'swfgradient', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], +'SWFSound' => ['SWFSound', 'filename'=>'string', 'flags='=>'int'], +'SWFSound::__construct' => ['void', 'filename'=>'string', 'flags='=>'int'], +'SWFSoundInstance::loopCount' => ['void', 'point'=>'int'], +'SWFSoundInstance::loopInPoint' => ['void', 'point'=>'int'], +'SWFSoundInstance::loopOutPoint' => ['void', 'point'=>'int'], +'SWFSoundInstance::noMultiple' => ['void'], +'SWFSprite::__construct' => ['void'], +'SWFSprite::add' => ['void', 'object'=>'object'], +'SWFSprite::labelFrame' => ['void', 'label'=>'string'], +'SWFSprite::nextFrame' => ['void'], +'SWFSprite::remove' => ['void', 'object'=>'object'], +'SWFSprite::setFrames' => ['void', 'number'=>'int'], +'SWFSprite::startSound' => ['SWFSoundInstance', 'sount'=>'swfsound'], +'SWFSprite::stopSound' => ['void', 'sount'=>'swfsound'], +'SWFText::__construct' => ['void'], +'SWFText::addString' => ['void', 'string'=>'string'], +'SWFText::addUTF8String' => ['void', 'text'=>'string'], +'SWFText::getAscent' => ['float'], +'SWFText::getDescent' => ['float'], +'SWFText::getLeading' => ['float'], +'SWFText::getUTF8Width' => ['float', 'string'=>'string'], +'SWFText::getWidth' => ['float', 'string'=>'string'], +'SWFText::moveTo' => ['void', 'x'=>'float', 'y'=>'float'], +'SWFText::setColor' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], +'SWFText::setFont' => ['void', 'font'=>'swffont'], +'SWFText::setHeight' => ['void', 'height'=>'float'], +'SWFText::setSpacing' => ['void', 'spacing'=>'float'], +'SWFTextField::__construct' => ['void', 'flags='=>'int'], +'SWFTextField::addChars' => ['void', 'chars'=>'string'], +'SWFTextField::addString' => ['void', 'string'=>'string'], +'SWFTextField::align' => ['void', 'alignement'=>'int'], +'SWFTextField::setBounds' => ['void', 'width'=>'float', 'height'=>'float'], +'SWFTextField::setColor' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], +'SWFTextField::setFont' => ['void', 'font'=>'swffont'], +'SWFTextField::setHeight' => ['void', 'height'=>'float'], +'SWFTextField::setIndentation' => ['void', 'width'=>'float'], +'SWFTextField::setLeftMargin' => ['void', 'width'=>'float'], +'SWFTextField::setLineSpacing' => ['void', 'height'=>'float'], +'SWFTextField::setMargins' => ['void', 'left'=>'float', 'right'=>'float'], +'SWFTextField::setName' => ['void', 'name'=>'string'], +'SWFTextField::setPadding' => ['void', 'padding'=>'float'], +'SWFTextField::setRightMargin' => ['void', 'width'=>'float'], +'SWFVideoStream::__construct' => ['void', 'file='=>'string'], +'SWFVideoStream::getNumFrames' => ['int'], +'SWFVideoStream::setDimension' => ['void', 'x'=>'int', 'y'=>'int'], +'Swish::__construct' => ['void', 'index_names'=>'string'], +'Swish::getMetaList' => ['array', 'index_name'=>'string'], +'Swish::getPropertyList' => ['array', 'index_name'=>'string'], +'Swish::prepare' => ['object', 'query='=>'string'], +'Swish::query' => ['object', 'query'=>'string'], +'SwishResult::getMetaList' => ['array'], +'SwishResult::stem' => ['array', 'word'=>'string'], +'SwishResults::getParsedWords' => ['array', 'index_name'=>'string'], +'SwishResults::getRemovedStopwords' => ['array', 'index_name'=>'string'], +'SwishResults::nextResult' => ['object'], +'SwishResults::seekResult' => ['int', 'position'=>'int'], +'SwishSearch::execute' => ['object', 'query='=>'string'], +'SwishSearch::resetLimit' => [''], +'SwishSearch::setLimit' => ['', 'property'=>'string', 'low'=>'string', 'high'=>'string'], +'SwishSearch::setPhraseDelimiter' => ['', 'delimiter'=>'string'], +'SwishSearch::setSort' => ['', 'sort'=>'string'], +'SwishSearch::setStructure' => ['', 'structure'=>'int'], +'swoole\async::dnsLookup' => ['void', 'hostname'=>'string', 'callback'=>'callable'], +'swoole\async::read' => ['bool', 'filename'=>'string', 'callback'=>'callable', 'chunk_size='=>'integer', 'offset='=>'integer'], +'swoole\async::readFile' => ['void', 'filename'=>'string', 'callback'=>'callable'], +'swoole\async::set' => ['void', 'settings'=>'array'], +'swoole\async::write' => ['void', 'filename'=>'string', 'content'=>'string', 'offset='=>'integer', 'callback='=>'callable'], +'swoole\async::writeFile' => ['void', 'filename'=>'string', 'content'=>'string', 'callback='=>'callable', 'flags='=>'string'], +'swoole\atomic::add' => ['integer', 'add_value='=>'integer'], +'swoole\atomic::cmpset' => ['integer', 'cmp_value'=>'integer', 'new_value'=>'integer'], +'swoole\atomic::get' => ['integer'], +'swoole\atomic::set' => ['integer', 'value'=>'integer'], +'swoole\atomic::sub' => ['integer', 'sub_value='=>'integer'], +'swoole\buffer::__destruct' => ['void'], +'swoole\buffer::__toString' => ['string'], +'swoole\buffer::append' => ['integer', 'data'=>'string'], +'swoole\buffer::clear' => ['void'], +'swoole\buffer::expand' => ['integer', 'size'=>'integer'], +'swoole\buffer::read' => ['string', 'offset'=>'integer', 'length'=>'integer'], +'swoole\buffer::recycle' => ['void'], +'swoole\buffer::substr' => ['string', 'offset'=>'integer', 'length='=>'integer', 'remove='=>'bool'], +'swoole\buffer::write' => ['void', 'offset'=>'integer', 'data'=>'string'], +'swoole\channel::__destruct' => ['void'], +'swoole\channel::pop' => ['mixed'], +'swoole\channel::push' => ['bool', 'data'=>'string'], +'swoole\channel::stats' => ['array'], +'swoole\client::__destruct' => ['void'], +'swoole\client::close' => ['bool', 'force='=>'bool'], +'swoole\client::connect' => ['bool', 'host'=>'string', 'port='=>'integer', 'timeout='=>'integer', 'flag='=>'integer'], +'swoole\client::getpeername' => ['array'], +'swoole\client::getsockname' => ['array'], +'swoole\client::isConnected' => ['bool'], +'swoole\client::on' => ['void', 'event'=>'string', 'callback'=>'callable'], +'swoole\client::pause' => ['void'], +'swoole\client::pipe' => ['void', 'socket'=>'string'], +'swoole\client::recv' => ['void', 'size='=>'string', 'flag='=>'string'], +'swoole\client::resume' => ['void'], +'swoole\client::send' => ['integer', 'data'=>'string', 'flag='=>'string'], +'swoole\client::sendfile' => ['bool', 'filename'=>'string', 'offset='=>'int'], +'swoole\client::sendto' => ['bool', 'ip'=>'string', 'port'=>'integer', 'data'=>'string'], +'swoole\client::set' => ['void', 'settings'=>'array'], +'swoole\client::sleep' => ['void'], +'swoole\client::wakeup' => ['void'], +'swoole\connection\iterator::count' => ['int'], +'swoole\connection\iterator::current' => ['Connection'], +'swoole\connection\iterator::key' => ['int'], +'swoole\connection\iterator::next' => ['Connection'], +'swoole\connection\iterator::offsetExists' => ['bool', 'index'=>'int'], +'swoole\connection\iterator::offsetGet' => ['Connection', 'index'=>'string'], +'swoole\connection\iterator::offsetSet' => ['void', 'offset'=>'int', 'connection'=>'mixed'], +'swoole\connection\iterator::offsetUnset' => ['void', 'offset'=>'int'], +'swoole\connection\iterator::rewind' => ['void'], +'swoole\connection\iterator::valid' => ['bool'], +'swoole\coroutine::call_user_func' => ['mixed', 'callback'=>'callable', 'parameter='=>'mixed', '...args='=>'mixed'], +'swoole\coroutine::call_user_func_array' => ['mixed', 'callback'=>'callable', 'param_array'=>'array'], +'swoole\coroutine::cli_wait' => ['ReturnType'], +'swoole\coroutine::create' => ['ReturnType'], +'swoole\coroutine::getuid' => ['ReturnType'], +'swoole\coroutine::resume' => ['ReturnType'], +'swoole\coroutine::suspend' => ['ReturnType'], +'swoole\coroutine\client::__destruct' => ['ReturnType'], +'swoole\coroutine\client::close' => ['ReturnType'], +'swoole\coroutine\client::connect' => ['ReturnType'], +'swoole\coroutine\client::getpeername' => ['ReturnType'], +'swoole\coroutine\client::getsockname' => ['ReturnType'], +'swoole\coroutine\client::isConnected' => ['ReturnType'], +'swoole\coroutine\client::recv' => ['ReturnType'], +'swoole\coroutine\client::send' => ['ReturnType'], +'swoole\coroutine\client::sendfile' => ['ReturnType'], +'swoole\coroutine\client::sendto' => ['ReturnType'], +'swoole\coroutine\client::set' => ['ReturnType'], +'swoole\coroutine\http\client::__destruct' => ['ReturnType'], +'swoole\coroutine\http\client::addFile' => ['ReturnType'], +'swoole\coroutine\http\client::close' => ['ReturnType'], +'swoole\coroutine\http\client::execute' => ['ReturnType'], +'swoole\coroutine\http\client::get' => ['ReturnType'], +'swoole\coroutine\http\client::getDefer' => ['ReturnType'], +'swoole\coroutine\http\client::isConnected' => ['ReturnType'], +'swoole\coroutine\http\client::post' => ['ReturnType'], +'swoole\coroutine\http\client::recv' => ['ReturnType'], +'swoole\coroutine\http\client::set' => ['ReturnType'], +'swoole\coroutine\http\client::setCookies' => ['ReturnType'], +'swoole\coroutine\http\client::setData' => ['ReturnType'], +'swoole\coroutine\http\client::setDefer' => ['ReturnType'], +'swoole\coroutine\http\client::setHeaders' => ['ReturnType'], +'swoole\coroutine\http\client::setMethod' => ['ReturnType'], +'swoole\coroutine\mysql::__destruct' => ['ReturnType'], +'swoole\coroutine\mysql::close' => ['ReturnType'], +'swoole\coroutine\mysql::connect' => ['ReturnType'], +'swoole\coroutine\mysql::getDefer' => ['ReturnType'], +'swoole\coroutine\mysql::query' => ['ReturnType'], +'swoole\coroutine\mysql::recv' => ['ReturnType'], +'swoole\coroutine\mysql::setDefer' => ['ReturnType'], +'swoole\event::add' => ['bool', 'fd'=>'int', 'read_callback'=>'callable', 'write_callback='=>'callable', 'events='=>'string'], +'swoole\event::defer' => ['void', 'callback'=>'mixed'], +'swoole\event::del' => ['bool', 'fd'=>'string'], +'swoole\event::exit' => ['void'], +'swoole\event::set' => ['bool', 'fd'=>'int', 'read_callback='=>'string', 'write_callback='=>'string', 'events='=>'string'], +'swoole\event::wait' => ['void'], +'swoole\event::write' => ['void', 'fd'=>'string', 'data'=>'string'], +'swoole\http\client::__destruct' => ['void'], +'swoole\http\client::addFile' => ['void', 'path'=>'string', 'name'=>'string', 'type='=>'string', 'filename='=>'string', 'offset='=>'string'], +'swoole\http\client::close' => ['void'], +'swoole\http\client::download' => ['void', 'path'=>'string', 'file'=>'string', 'callback'=>'callable', 'offset='=>'integer'], +'swoole\http\client::execute' => ['void', 'path'=>'string', 'callback'=>'string'], +'swoole\http\client::get' => ['void', 'path'=>'string', 'callback'=>'callable'], +'swoole\http\client::isConnected' => ['bool'], +'swoole\http\client::on' => ['void', 'event_name'=>'string', 'callback'=>'callable'], +'swoole\http\client::post' => ['void', 'path'=>'string', 'data'=>'string', 'callback'=>'callable'], +'swoole\http\client::push' => ['void', 'data'=>'string', 'opcode='=>'string', 'finish='=>'string'], +'swoole\http\client::set' => ['void', 'settings'=>'array'], +'swoole\http\client::setCookies' => ['void', 'cookies'=>'array'], +'swoole\http\client::setData' => ['ReturnType', 'data'=>'string'], +'swoole\http\client::setHeaders' => ['void', 'headers'=>'array'], +'swoole\http\client::setMethod' => ['void', 'method'=>'string'], +'swoole\http\client::upgrade' => ['void', 'path'=>'string', 'callback'=>'string'], +'swoole\http\request::__destruct' => ['void'], +'swoole\http\request::rawcontent' => ['string'], +'swoole\http\response::__destruct' => ['void'], +'swoole\http\response::cookie' => ['string', 'name'=>'string', 'value='=>'string', 'expires='=>'string', 'path='=>'string', 'domain='=>'string', 'secure='=>'string', 'httponly='=>'string'], +'swoole\http\response::end' => ['void', 'content='=>'string'], +'swoole\http\response::gzip' => ['ReturnType', 'compress_level='=>'string'], +'swoole\http\response::header' => ['void', 'key'=>'string', 'value'=>'string', 'ucwords='=>'string'], +'swoole\http\response::initHeader' => ['ReturnType'], +'swoole\http\response::rawcookie' => ['ReturnType', 'name'=>'string', 'value='=>'string', 'expires='=>'string', 'path='=>'string', 'domain='=>'string', 'secure='=>'string', 'httponly='=>'string'], +'swoole\http\response::sendfile' => ['ReturnType', 'filename'=>'string', 'offset='=>'int'], +'swoole\http\response::status' => ['ReturnType', 'http_code'=>'string'], +'swoole\http\response::write' => ['void', 'content'=>'string'], +'swoole\http\server::on' => ['void', 'event_name'=>'string', 'callback'=>'callable'], +'swoole\http\server::start' => ['void'], +'swoole\lock::__destruct' => ['void'], +'swoole\lock::lock' => ['void'], +'swoole\lock::lock_read' => ['void'], +'swoole\lock::trylock' => ['void'], +'swoole\lock::trylock_read' => ['void'], +'swoole\lock::unlock' => ['void'], +'swoole\mmap::open' => ['ReturnType', 'filename'=>'string', 'size='=>'string', 'offset='=>'string'], +'swoole\mysql::__destruct' => ['void'], +'swoole\mysql::close' => ['void'], +'swoole\mysql::connect' => ['void', 'server_config'=>'array', 'callback'=>'callable'], +'swoole\mysql::getBuffer' => ['ReturnType'], +'swoole\mysql::on' => ['void', 'event_name'=>'string', 'callback'=>'callable'], +'swoole\mysql::query' => ['ReturnType', 'sql'=>'string', 'callback'=>'callable'], +'swoole\process::__destruct' => ['void'], +'swoole\process::alarm' => ['void', 'interval_usec'=>'integer'], +'swoole\process::close' => ['void'], +'swoole\process::daemon' => ['void', 'nochdir='=>'bool', 'noclose='=>'bool'], +'swoole\process::exec' => ['ReturnType', 'exec_file'=>'string', 'args'=>'string'], +'swoole\process::exit' => ['void', 'exit_code='=>'string'], +'swoole\process::freeQueue' => ['void'], +'swoole\process::kill' => ['void', 'pid'=>'integer', 'signal_no='=>'string'], +'swoole\process::name' => ['void', 'process_name'=>'string'], +'swoole\process::pop' => ['mixed', 'maxsize='=>'integer'], +'swoole\process::push' => ['bool', 'data'=>'string'], +'swoole\process::read' => ['string', 'maxsize='=>'integer'], +'swoole\process::signal' => ['void', 'signal_no'=>'string', 'callback'=>'callable'], +'swoole\process::start' => ['void'], +'swoole\process::statQueue' => ['array'], +'swoole\process::useQueue' => ['bool', 'key'=>'integer', 'mode='=>'integer'], +'swoole\process::wait' => ['array', 'blocking='=>'bool'], +'swoole\process::write' => ['integer', 'data'=>'string'], +'swoole\redis\server::format' => ['ReturnType', 'type'=>'string', 'value='=>'string'], +'swoole\redis\server::setHandler' => ['ReturnType', 'command'=>'string', 'callback'=>'string', 'number_of_string_param='=>'string', 'type_of_array_param='=>'string'], +'swoole\redis\server::start' => ['ReturnType'], +'swoole\serialize::pack' => ['ReturnType', 'data'=>'string', 'is_fast='=>'int'], +'swoole\serialize::unpack' => ['ReturnType', 'data'=>'string', 'args='=>'string'], +'swoole\server::addlistener' => ['void', 'host'=>'string', 'port'=>'integer', 'socket_type'=>'string'], +'swoole\server::addProcess' => ['bool', 'process'=>'swoole_process'], +'swoole\server::after' => ['ReturnType', 'after_time_ms'=>'integer', 'callback'=>'callable', 'param='=>'string'], +'swoole\server::bind' => ['bool', 'fd'=>'integer', 'uid'=>'integer'], +'swoole\server::close' => ['bool', 'fd'=>'integer', 'reset='=>'bool'], +'swoole\server::confirm' => ['bool', 'fd'=>'integer'], +'swoole\server::connection_info' => ['array', 'fd'=>'integer', 'reactor_id='=>'integer'], +'swoole\server::connection_list' => ['array', 'start_fd'=>'integer', 'pagesize='=>'integer'], +'swoole\server::defer' => ['void', 'callback'=>'callable'], +'swoole\server::exist' => ['bool', 'fd'=>'integer'], +'swoole\server::finish' => ['void', 'data'=>'string'], +'swoole\server::getClientInfo' => ['ReturnType', 'fd'=>'integer', 'reactor_id='=>'integer'], +'swoole\server::getClientList' => ['array', 'start_fd'=>'integer', 'pagesize='=>'integer'], +'swoole\server::getLastError' => ['integer'], +'swoole\server::heartbeat' => ['mixed', 'if_close_connection'=>'bool'], +'swoole\server::listen' => ['bool', 'host'=>'string', 'port'=>'integer', 'socket_type'=>'string'], +'swoole\server::on' => ['void', 'event_name'=>'string', 'callback'=>'callable'], +'swoole\server::pause' => ['void', 'fd'=>'integer'], +'swoole\server::protect' => ['void', 'fd'=>'integer', 'is_protected='=>'bool'], +'swoole\server::reload' => ['bool'], +'swoole\server::resume' => ['void', 'fd'=>'integer'], +'swoole\server::send' => ['bool', 'fd'=>'integer', 'data'=>'string', 'reactor_id='=>'integer'], +'swoole\server::sendfile' => ['bool', 'fd'=>'integer', 'filename'=>'string', 'offset='=>'integer'], +'swoole\server::sendMessage' => ['bool', 'worker_id'=>'integer', 'data'=>'string'], +'swoole\server::sendto' => ['bool', 'ip'=>'string', 'port'=>'integer', 'data'=>'string', 'server_socket='=>'string'], +'swoole\server::sendwait' => ['bool', 'fd'=>'integer', 'data'=>'string'], +'swoole\server::set' => ['ReturnType', 'settings'=>'array'], +'swoole\server::shutdown' => ['void'], +'swoole\server::start' => ['void'], +'swoole\server::stats' => ['array'], +'swoole\server::stop' => ['bool', 'worker_id='=>'integer'], +'swoole\server::task' => ['mixed', 'data'=>'string', 'dst_worker_id='=>'integer', 'callback='=>'callable'], +'swoole\server::taskwait' => ['void', 'data'=>'string', 'timeout='=>'float', 'worker_id='=>'integer'], +'swoole\server::taskWaitMulti' => ['void', 'tasks'=>'array', 'timeout_ms='=>'double'], +'swoole\server::tick' => ['void', 'interval_ms'=>'integer', 'callback'=>'callable'], +'swoole\server\port::__destruct' => ['void'], +'swoole\server\port::on' => ['ReturnType', 'event_name'=>'string', 'callback'=>'callable'], +'swoole\server\port::set' => ['void', 'settings'=>'array'], +'swoole\table::column' => ['ReturnType', 'name'=>'string', 'type'=>'string', 'size='=>'integer'], +'swoole\table::count' => ['integer'], +'swoole\table::create' => ['void'], +'swoole\table::current' => ['array'], +'swoole\table::decr' => ['ReturnType', 'key'=>'string', 'column'=>'string', 'decrby='=>'integer'], +'swoole\table::del' => ['void', 'key'=>'string'], +'swoole\table::destroy' => ['void'], +'swoole\table::exist' => ['bool', 'key'=>'string'], +'swoole\table::get' => ['integer', 'row_key'=>'string', 'column_key'=>'string'], +'swoole\table::incr' => ['void', 'key'=>'string', 'column'=>'string', 'incrby='=>'integer'], +'swoole\table::key' => ['string'], +'swoole\table::next' => ['ReturnType'], +'swoole\table::rewind' => ['void'], +'swoole\table::set' => ['VOID', 'key'=>'string', 'value'=>'array'], +'swoole\table::valid' => ['bool'], +'swoole\timer::after' => ['void', 'after_time_ms'=>'int', 'callback'=>'callable'], +'swoole\timer::clear' => ['void', 'timer_id'=>'integer'], +'swoole\timer::exists' => ['bool', 'timer_id'=>'integer'], +'swoole\timer::tick' => ['void', 'interval_ms'=>'integer', 'callback'=>'callable', 'param='=>'string'], +'swoole\websocket\server::exist' => ['bool', 'fd'=>'integer'], +'swoole\websocket\server::on' => ['ReturnType', 'event_name'=>'string', 'callback'=>'callable'], +'swoole\websocket\server::pack' => ['binary', 'data'=>'string', 'opcode='=>'string', 'finish='=>'string', 'mask='=>'string'], +'swoole\websocket\server::push' => ['void', 'fd'=>'string', 'data'=>'string', 'opcode='=>'string', 'finish='=>'string'], +'swoole\websocket\server::unpack' => ['string', 'data'=>'binary'], +'swoole_async_dns_lookup' => ['bool', 'hostname'=>'string', 'callback'=>'callable'], +'swoole_async_read' => ['bool', 'filename'=>'string', 'callback'=>'callable', 'chunk_size='=>'int', 'offset='=>'int'], +'swoole_async_readfile' => ['bool', 'filename'=>'string', 'callback'=>'string'], +'swoole_async_set' => ['void', 'settings'=>'array'], +'swoole_async_write' => ['bool', 'filename'=>'string', 'content'=>'string', 'offset='=>'int', 'callback='=>'callable'], +'swoole_async_writefile' => ['bool', 'filename'=>'string', 'content'=>'string', 'callback='=>'callable', 'flags='=>'int'], +'swoole_client_select' => ['int', 'read_array'=>'array', 'write_array'=>'array', 'error_array'=>'array', 'timeout='=>'float'], +'swoole_cpu_num' => ['int'], +'swoole_errno' => ['int'], +'swoole_event_add' => ['int', 'fd'=>'int', 'read_callback='=>'callable', 'write_callback='=>'callable', 'events='=>'int'], +'swoole_event_defer' => ['bool', 'callback'=>'callable'], +'swoole_event_del' => ['bool', 'fd'=>'int'], +'swoole_event_exit' => ['void'], +'swoole_event_set' => ['bool', 'fd'=>'int', 'read_callback='=>'callable', 'write_callback='=>'callable', 'events='=>'int'], +'swoole_event_wait' => ['void'], +'swoole_event_write' => ['bool', 'fd'=>'int', 'data'=>'string'], +'swoole_get_local_ip' => ['array'], +'swoole_last_error' => ['int'], +'swoole_load_module' => ['mixed', 'filename'=>'string'], +'swoole_select' => ['int', 'read_array'=>'array', 'write_array'=>'array', 'error_array'=>'array', 'timeout='=>'float'], +'swoole_set_process_name' => ['void', 'process_name'=>'string', 'size='=>'int'], +'swoole_strerror' => ['string', 'errno'=>'int', 'error_type='=>'int'], +'swoole_timer_after' => ['int', 'ms'=>'int', 'callback'=>'callable', 'param='=>'mixed'], +'swoole_timer_exists' => ['bool', 'timer_id'=>'int'], +'swoole_timer_tick' => ['int', 'ms'=>'int', 'callback'=>'callable', 'param='=>'mixed'], +'swoole_version' => ['string'], +'symbolObj::__construct' => ['void', 'map'=>'mapObj', 'symbolname'=>'string'], +'symbolObj::free' => ['void'], +'symbolObj::getPatternArray' => ['array'], +'symbolObj::getPointsArray' => ['array'], +'symbolObj::ms_newSymbolObj' => ['int', 'map'=>'mapObj', 'symbolname'=>'string'], +'symbolObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'symbolObj::setImagePath' => ['int', 'filename'=>'string'], +'symbolObj::setPattern' => ['int', 'int'=>'array'], +'symbolObj::setPoints' => ['int', 'double'=>'array'], +'symlink' => ['bool', 'target'=>'string', 'link'=>'string'], +'SyncEvent::__construct' => ['void', 'name='=>'string', 'manual='=>'bool'], +'SyncEvent::fire' => ['bool'], +'SyncEvent::reset' => ['bool'], +'SyncEvent::wait' => ['bool', 'wait='=>'int'], +'SyncMutex::__construct' => ['void', 'name='=>'string'], +'SyncMutex::lock' => ['bool', 'wait='=>'int'], +'SyncMutex::unlock' => ['bool', 'all='=>'bool'], +'SyncReaderWriter::__construct' => ['void', 'name='=>'string', 'autounlock='=>'bool'], +'SyncReaderWriter::readlock' => ['bool', 'wait='=>'int'], +'SyncReaderWriter::readunlock' => ['bool'], +'SyncReaderWriter::writelock' => ['bool', 'wait='=>'int'], +'SyncReaderWriter::writeunlock' => ['bool'], +'SyncSemaphore::__construct' => ['void', 'name='=>'string', 'initialval='=>'int', 'autounlock='=>'bool'], +'SyncSemaphore::lock' => ['bool', 'wait='=>'int'], +'SyncSemaphore::unlock' => ['bool', '&w_prevcount='=>'int'], +'SyncSharedMemory::__construct' => ['void', 'name'=>'string', 'size'=>'int'], +'SyncSharedMemory::first' => ['bool'], +'SyncSharedMemory::read' => ['string', 'start='=>'int', 'length='=>'int'], +'SyncSharedMemory::size' => ['int'], +'SyncSharedMemory::write' => ['int', 'string='=>'string', 'start='=>'int'], +'sys_get_temp_dir' => ['string'], +'sys_getloadavg' => ['array'], +'syslog' => ['bool', 'priority'=>'int', 'message'=>'string'], +'system' => ['string|false', 'command'=>'string', '&w_return_value='=>'int'], +'taint' => ['bool', '&rw_string'=>'string', '&...w_other_strings='=>'string'], +'tan' => ['float', 'number'=>'float'], +'tanh' => ['float', 'number'=>'float'], +'tcpwrap_check' => ['bool', 'daemon'=>'string', 'address'=>'string', 'user='=>'string', 'nodns='=>'bool'], +'tempnam' => ['string|false', 'dir'=>'string', 'prefix'=>'string'], +'textdomain' => ['string', 'domain'=>'string'], +'Thread::__construct' => ['void'], +'Thread::addRef' => ['void'], +'Thread::chunk' => ['array', 'size'=>'int', 'preserve'=>'bool'], +'Thread::count' => ['int'], +'Thread::delRef' => ['void'], +'Thread::detach' => ['void'], +'Thread::extend' => ['bool', 'class'=>'string'], +'Thread::getCreatorId' => ['int'], +'Thread::getCurrentThread' => ['Thread'], +'Thread::getCurrentThreadId' => ['int'], +'Thread::getRefCount' => ['int'], +'Thread::getTerminationInfo' => ['array'], +'Thread::getThreadId' => ['int'], +'Thread::globally' => ['mixed'], +'Thread::isGarbage' => ['bool'], +'Thread::isJoined' => ['bool'], +'Thread::isRunning' => ['bool'], +'Thread::isStarted' => ['bool'], +'Thread::isTerminated' => ['bool'], +'Thread::isWaiting' => ['bool'], +'Thread::join' => ['bool'], +'Thread::kill' => ['void'], +'Thread::lock' => ['bool'], +'Thread::merge' => ['bool', 'from'=>'', 'overwrite='=>'mixed'], +'Thread::notify' => ['bool'], +'Thread::notifyOne' => ['bool'], +'Thread::offsetExists' => ['bool', 'offset'=>'mixed'], +'Thread::offsetGet' => ['mixed', 'offset'=>'mixed'], +'Thread::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'], +'Thread::offsetUnset' => ['void', 'offset'=>'mixed'], +'Thread::pop' => ['bool'], +'Thread::run' => ['void'], +'Thread::setGarbage' => ['void'], +'Thread::shift' => ['bool'], +'Thread::start' => ['bool', 'options='=>'int'], +'Thread::synchronized' => ['mixed', 'block'=>'Closure', '_='=>'mixed'], +'Thread::unlock' => ['bool'], +'Thread::wait' => ['bool', 'timeout='=>'int'], +'Threaded::__construct' => ['void'], +'Threaded::addRef' => ['void'], +'Threaded::chunk' => ['array', 'size'=>'int', 'preserve'=>'bool'], +'Threaded::count' => ['int'], +'Threaded::delRef' => ['void'], +'Threaded::extend' => ['bool', 'class'=>'string'], +'Threaded::from' => ['Threaded', 'run'=>'Closure', 'construct='=>'Closure', 'args='=>'array'], +'Threaded::getRefCount' => ['int'], +'Threaded::getTerminationInfo' => ['array'], +'Threaded::isGarbage' => ['bool'], +'Threaded::isRunning' => ['bool'], +'Threaded::isTerminated' => ['bool'], +'Threaded::isWaiting' => ['bool'], +'Threaded::lock' => ['bool'], +'Threaded::merge' => ['bool', 'from'=>'mixed', 'overwrite='=>'bool'], +'Threaded::notify' => ['bool'], +'Threaded::notifyOne' => ['bool'], +'Threaded::offsetExists' => ['bool', 'offset'=>'mixed'], +'Threaded::offsetGet' => ['mixed', 'offset'=>'mixed'], +'Threaded::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'], +'Threaded::offsetUnset' => ['void', 'offset'=>'mixed'], +'Threaded::pop' => ['bool'], +'Threaded::run' => ['void'], +'Threaded::setGarbage' => ['void'], +'Threaded::shift' => ['mixed'], +'Threaded::synchronized' => ['mixed', 'block'=>'Closure', '...args='=>'mixed'], +'Threaded::unlock' => ['bool'], +'Threaded::wait' => ['bool', 'timeout='=>'int'], +'Throwable::__toString' => ['string'], +'Throwable::getCode' => ['int|string'], +'Throwable::getFile' => ['string'], +'Throwable::getLine' => ['int'], +'Throwable::getMessage' => ['string'], +'Throwable::getPrevious' => ['?Throwable'], +'Throwable::getTrace' => ['list>'], +'Throwable::getTraceAsString' => ['string'], +'tidy::__construct' => ['void', 'filename='=>'string', 'config='=>'', 'encoding='=>'string', 'use_include_path='=>'bool'], +'tidy::body' => ['tidyNode'], +'tidy::cleanRepair' => ['bool'], +'tidy::diagnose' => ['bool'], +'tidy::getConfig' => ['array'], +'tidy::getHtmlVer' => ['int'], +'tidy::getOpt' => ['mixed', 'option'=>'string'], +'tidy::getOptDoc' => ['string', 'optname'=>'string'], +'tidy::getRelease' => ['string'], +'tidy::getStatus' => ['int'], +'tidy::head' => ['tidyNode'], +'tidy::html' => ['tidyNode'], +'tidy::htmlver' => ['int'], +'tidy::isXhtml' => ['bool'], +'tidy::isXml' => ['bool'], +'tidy::parseFile' => ['bool', 'filename'=>'string', 'config='=>'', 'encoding='=>'string', 'use_include_path='=>'bool'], +'tidy::parseString' => ['bool', 'input'=>'string', 'config='=>'', 'encoding='=>'string'], +'tidy::repairFile' => ['string', 'filename'=>'string', 'config='=>'', 'encoding='=>'string', 'use_include_path='=>'bool'], +'tidy::repairString' => ['string', 'data'=>'string', 'config='=>'', 'encoding='=>'string'], +'tidy::root' => ['tidyNode'], +'tidy_access_count' => ['int', 'object'=>'tidy'], +'tidy_clean_repair' => ['bool', 'object'=>'tidy'], +'tidy_config_count' => ['int', 'object'=>'tidy'], +'tidy_diagnose' => ['bool', 'object'=>'tidy'], +'tidy_error_count' => ['int', 'object'=>'tidy'], +'tidy_get_body' => ['tidyNode', 'object'=>'tidy'], +'tidy_get_config' => ['array', 'object'=>'tidy'], +'tidy_get_error_buffer' => ['string', 'object'=>'tidy'], +'tidy_get_head' => ['tidyNode', 'object'=>'tidy'], +'tidy_get_html' => ['tidyNode', 'object'=>'tidy'], +'tidy_get_html_ver' => ['int', 'object'=>'tidy'], +'tidy_get_opt_doc' => ['string', 'object'=>'tidy', 'optname'=>'string'], +'tidy_get_output' => ['string', 'object'=>'tidy'], +'tidy_get_release' => ['string'], +'tidy_get_root' => ['tidyNode', 'object'=>'tidy'], +'tidy_get_status' => ['int', 'object'=>'tidy'], +'tidy_getopt' => ['mixed', 'option'=>'string', 'object'=>'tidy'], +'tidy_is_xhtml' => ['bool', 'object'=>'tidy'], +'tidy_is_xml' => ['bool', 'object'=>'tidy'], +'tidy_load_config' => ['void', 'filename'=>'string', 'encoding'=>'string'], +'tidy_parse_file' => ['tidy', 'file'=>'string', 'config_options='=>'', 'encoding='=>'string', 'use_include_path='=>'bool'], +'tidy_parse_string' => ['tidy', 'input'=>'string', 'config_options='=>'', 'encoding='=>'string'], +'tidy_repair_file' => ['string', 'filename'=>'string', 'config_file='=>'', 'encoding='=>'string', 'use_include_path='=>'bool'], +'tidy_repair_string' => ['string', 'data'=>'string', 'config_file='=>'', 'encoding='=>'string'], +'tidy_reset_config' => ['bool'], +'tidy_save_config' => ['bool', 'filename'=>'string'], +'tidy_set_encoding' => ['bool', 'encoding'=>'string'], +'tidy_setopt' => ['bool', 'option'=>'string', 'value'=>'mixed'], +'tidy_warning_count' => ['int', 'object'=>'tidy'], +'tidyNode::__construct' => ['void'], +'tidyNode::getParent' => ['tidyNode'], +'tidyNode::hasChildren' => ['bool'], +'tidyNode::hasSiblings' => ['bool'], +'tidyNode::isAsp' => ['bool'], +'tidyNode::isComment' => ['bool'], +'tidyNode::isHtml' => ['bool'], +'tidyNode::isJste' => ['bool'], +'tidyNode::isPhp' => ['bool'], +'tidyNode::isText' => ['bool'], +'time' => ['int'], +'time_nanosleep' => ['array{0:int,1:int}|bool', 'seconds'=>'int', 'nanoseconds'=>'int'], +'time_sleep_until' => ['bool', 'timestamp'=>'float'], +'timezone_abbreviations_list' => ['array|false'], +'timezone_identifiers_list' => ['list|false', 'what='=>'int', 'country='=>'?string'], +'timezone_location_get' => ['array|false', 'object'=>'DateTimeZone'], +'timezone_name_from_abbr' => ['string|false', 'abbr'=>'string', 'gmtoffset='=>'int', 'isdst='=>'int'], +'timezone_name_get' => ['string', 'object'=>'DateTimeZone'], +'timezone_offset_get' => ['int|false', 'object'=>'DateTimeZone', 'datetime'=>'DateTimeInterface'], +'timezone_open' => ['DateTimeZone|false', 'timezone'=>'string'], +'timezone_transitions_get' => ['array|false', 'object'=>'DateTimeZone', 'timestamp_begin='=>'int', 'timestamp_end='=>'int'], +'timezone_version_get' => ['string'], +'tmpfile' => ['resource|false'], +'token_get_all' => ['list', 'source'=>'string', 'flags='=>'int'], +'token_name' => ['string', 'type'=>'int'], +'TokyoTyrant::__construct' => ['void', 'host='=>'string', 'port='=>'int', 'options='=>'array'], +'TokyoTyrant::add' => ['int|float', 'key'=>'string', 'increment'=>'float', 'type='=>'int'], +'TokyoTyrant::connect' => ['TokyoTyrant', 'host'=>'string', 'port='=>'int', 'options='=>'array'], +'TokyoTyrant::connectUri' => ['TokyoTyrant', 'uri'=>'string'], +'TokyoTyrant::copy' => ['TokyoTyrant', 'path'=>'string'], +'TokyoTyrant::ext' => ['string', 'name'=>'string', 'options'=>'int', 'key'=>'string', 'value'=>'string'], +'TokyoTyrant::fwmKeys' => ['array', 'prefix'=>'string', 'max_recs'=>'int'], +'TokyoTyrant::get' => ['array', 'keys'=>'mixed'], +'TokyoTyrant::getIterator' => ['TokyoTyrantIterator'], +'TokyoTyrant::num' => ['int'], +'TokyoTyrant::out' => ['string', 'keys'=>'mixed'], +'TokyoTyrant::put' => ['TokyoTyrant', 'keys'=>'mixed', 'value='=>'string'], +'TokyoTyrant::putCat' => ['TokyoTyrant', 'keys'=>'mixed', 'value='=>'string'], +'TokyoTyrant::putKeep' => ['TokyoTyrant', 'keys'=>'mixed', 'value='=>'string'], +'TokyoTyrant::putNr' => ['TokyoTyrant', 'keys'=>'mixed', 'value='=>'string'], +'TokyoTyrant::putShl' => ['mixed', 'key'=>'string', 'value'=>'string', 'width'=>'int'], +'TokyoTyrant::restore' => ['mixed', 'log_dir'=>'string', 'timestamp'=>'int', 'check_consistency='=>'bool'], +'TokyoTyrant::setMaster' => ['mixed', 'host'=>'string', 'port'=>'int', 'timestamp'=>'int', 'check_consistency='=>'bool'], +'TokyoTyrant::size' => ['int', 'key'=>'string'], +'TokyoTyrant::stat' => ['array'], +'TokyoTyrant::sync' => ['mixed'], +'TokyoTyrant::tune' => ['TokyoTyrant', 'timeout'=>'float', 'options='=>'int'], +'TokyoTyrant::vanish' => ['mixed'], +'TokyoTyrantIterator::__construct' => ['void', 'object'=>'mixed'], +'TokyoTyrantIterator::current' => ['mixed'], +'TokyoTyrantIterator::key' => ['mixed'], +'TokyoTyrantIterator::next' => ['mixed'], +'TokyoTyrantIterator::rewind' => ['void'], +'TokyoTyrantIterator::valid' => ['bool'], +'TokyoTyrantQuery::__construct' => ['void', 'table'=>'TokyoTyrantTable'], +'TokyoTyrantQuery::addCond' => ['mixed', 'name'=>'string', 'op'=>'int', 'expr'=>'string'], +'TokyoTyrantQuery::count' => ['int'], +'TokyoTyrantQuery::current' => ['array'], +'TokyoTyrantQuery::hint' => ['string'], +'TokyoTyrantQuery::key' => ['string'], +'TokyoTyrantQuery::metaSearch' => ['array', 'queries'=>'array', 'type'=>'int'], +'TokyoTyrantQuery::next' => ['array'], +'TokyoTyrantQuery::out' => ['TokyoTyrantQuery'], +'TokyoTyrantQuery::rewind' => ['bool'], +'TokyoTyrantQuery::search' => ['array'], +'TokyoTyrantQuery::setLimit' => ['mixed', 'max='=>'int', 'skip='=>'int'], +'TokyoTyrantQuery::setOrder' => ['mixed', 'name'=>'string', 'type'=>'int'], +'TokyoTyrantQuery::valid' => ['bool'], +'TokyoTyrantTable::add' => ['void', 'key'=>'string', 'increment'=>'mixed', 'type='=>'string'], +'TokyoTyrantTable::genUid' => ['int'], +'TokyoTyrantTable::get' => ['array', 'keys'=>'mixed'], +'TokyoTyrantTable::getIterator' => ['TokyoTyrantIterator'], +'TokyoTyrantTable::getQuery' => ['TokyoTyrantQuery'], +'TokyoTyrantTable::out' => ['void', 'keys'=>'mixed'], +'TokyoTyrantTable::put' => ['int', 'key'=>'string', 'columns'=>'array'], +'TokyoTyrantTable::putCat' => ['void', 'key'=>'string', 'columns'=>'array'], +'TokyoTyrantTable::putKeep' => ['void', 'key'=>'string', 'columns'=>'array'], +'TokyoTyrantTable::putNr' => ['void', 'keys'=>'mixed', 'value='=>'string'], +'TokyoTyrantTable::putShl' => ['void', 'key'=>'string', 'value'=>'string', 'width'=>'int'], +'TokyoTyrantTable::setIndex' => ['mixed', 'column'=>'string', 'type'=>'int'], +'touch' => ['bool', 'filename'=>'string', 'time='=>'int', 'atime='=>'int'], +'trader_acos' => ['array', 'real'=>'array'], +'trader_ad' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array'], +'trader_add' => ['array', 'real0'=>'array', 'real1'=>'array'], +'trader_adosc' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int'], +'trader_adx' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_adxr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_apo' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'mAType='=>'int'], +'trader_aroon' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], +'trader_aroonosc' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], +'trader_asin' => ['array', 'real'=>'array'], +'trader_atan' => ['array', 'real'=>'array'], +'trader_atr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_avgprice' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_bbands' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'nbDevUp='=>'float', 'nbDevDn='=>'float', 'mAType='=>'int'], +'trader_beta' => ['array', 'real0'=>'array', 'real1'=>'array', 'timePeriod='=>'int'], +'trader_bop' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cci' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_cdl2crows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3blackcrows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3inside' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3linestrike' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3outside' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3starsinsouth' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3whitesoldiers' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlabandonedbaby' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdladvanceblock' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlbelthold' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlbreakaway' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlclosingmarubozu' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlconcealbabyswall' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlcounterattack' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdldarkcloudcover' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdldoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdldojistar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdldragonflydoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlengulfing' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdleveningdojistar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdleveningstar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdlgapsidesidewhite' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlgravestonedoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhammer' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhangingman' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlharami' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlharamicross' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhighwave' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhikkake' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhikkakemod' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhomingpigeon' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlidentical3crows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlinneck' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlinvertedhammer' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlkicking' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlkickingbylength' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlladderbottom' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdllongleggeddoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdllongline' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlmarubozu' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlmatchinglow' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlmathold' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdlmorningdojistar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdlmorningstar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdlonneck' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlpiercing' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlrickshawman' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlrisefall3methods' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlseparatinglines' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlshootingstar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlshortline' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlspinningtop' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlstalledpattern' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlsticksandwich' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdltakuri' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdltasukigap' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlthrusting' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdltristar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlunique3river' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlupsidegap2crows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlxsidegap3methods' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_ceil' => ['array', 'real'=>'array'], +'trader_cmo' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_correl' => ['array', 'real0'=>'array', 'real1'=>'array', 'timePeriod='=>'int'], +'trader_cos' => ['array', 'real'=>'array'], +'trader_cosh' => ['array', 'real'=>'array'], +'trader_dema' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_div' => ['array', 'real0'=>'array', 'real1'=>'array'], +'trader_dx' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_ema' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_errno' => ['int'], +'trader_exp' => ['array', 'real'=>'array'], +'trader_floor' => ['array', 'real'=>'array'], +'trader_get_compat' => ['int'], +'trader_get_unstable_period' => ['int', 'functionId'=>'int'], +'trader_ht_dcperiod' => ['array', 'real'=>'array'], +'trader_ht_dcphase' => ['array', 'real'=>'array'], +'trader_ht_phasor' => ['array', 'real'=>'array'], +'trader_ht_sine' => ['array', 'real'=>'array'], +'trader_ht_trendline' => ['array', 'real'=>'array'], +'trader_ht_trendmode' => ['array', 'real'=>'array'], +'trader_kama' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_linearreg' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_linearreg_angle' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_linearreg_intercept' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_linearreg_slope' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_ln' => ['array', 'real'=>'array'], +'trader_log10' => ['array', 'real'=>'array'], +'trader_ma' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'mAType='=>'int'], +'trader_macd' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'signalPeriod='=>'int'], +'trader_macdext' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'fastMAType='=>'int', 'slowPeriod='=>'int', 'slowMAType='=>'int', 'signalPeriod='=>'int', 'signalMAType='=>'int'], +'trader_macdfix' => ['array', 'real'=>'array', 'signalPeriod='=>'int'], +'trader_mama' => ['array', 'real'=>'array', 'fastLimit='=>'float', 'slowLimit='=>'float'], +'trader_mavp' => ['array', 'real'=>'array', 'periods'=>'array', 'minPeriod='=>'int', 'maxPeriod='=>'int', 'mAType='=>'int'], +'trader_max' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_maxindex' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_medprice' => ['array', 'high'=>'array', 'low'=>'array'], +'trader_mfi' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array', 'timePeriod='=>'int'], +'trader_midpoint' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_midprice' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], +'trader_min' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_minindex' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_minmax' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_minmaxindex' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_minus_di' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_minus_dm' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], +'trader_mom' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_mult' => ['array', 'real0'=>'array', 'real1'=>'array'], +'trader_natr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_obv' => ['array', 'real'=>'array', 'volume'=>'array'], +'trader_plus_di' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_plus_dm' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], +'trader_ppo' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'mAType='=>'int'], +'trader_roc' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_rocp' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_rocr' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_rocr100' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_rsi' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_sar' => ['array', 'high'=>'array', 'low'=>'array', 'acceleration='=>'float', 'maximum='=>'float'], +'trader_sarext' => ['array', 'high'=>'array', 'low'=>'array', 'startValue='=>'float', 'offsetOnReverse='=>'float', 'accelerationInitLong='=>'float', 'accelerationLong='=>'float', 'accelerationMaxLong='=>'float', 'accelerationInitShort='=>'float', 'accelerationShort='=>'float', 'accelerationMaxShort='=>'float'], +'trader_set_compat' => ['void', 'compatId'=>'int'], +'trader_set_unstable_period' => ['void', 'functionId'=>'int', 'timePeriod'=>'int'], +'trader_sin' => ['array', 'real'=>'array'], +'trader_sinh' => ['array', 'real'=>'array'], +'trader_sma' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_sqrt' => ['array', 'real'=>'array'], +'trader_stddev' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'nbDev='=>'float'], +'trader_stoch' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'fastK_Period='=>'int', 'slowK_Period='=>'int', 'slowK_MAType='=>'int', 'slowD_Period='=>'int', 'slowD_MAType='=>'int'], +'trader_stochf' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'fastK_Period='=>'int', 'fastD_Period='=>'int', 'fastD_MAType='=>'int'], +'trader_stochrsi' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'fastK_Period='=>'int', 'fastD_Period='=>'int', 'fastD_MAType='=>'int'], +'trader_sub' => ['array', 'real0'=>'array', 'real1'=>'array'], +'trader_sum' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_t3' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'vFactor='=>'float'], +'trader_tan' => ['array', 'real'=>'array'], +'trader_tanh' => ['array', 'real'=>'array'], +'trader_tema' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_trange' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_trima' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_trix' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_tsf' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_typprice' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_ultosc' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod1='=>'int', 'timePeriod2='=>'int', 'timePeriod3='=>'int'], +'trader_var' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'nbDev='=>'float'], +'trader_wclprice' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_willr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_wma' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trait_exists' => ['?bool', 'traitname'=>'string', 'autoload='=>'bool'], +'Transliterator::create' => ['?Transliterator', 'id'=>'string', 'direction='=>'int'], +'Transliterator::createFromRules' => ['?Transliterator', 'rules'=>'string', 'direction='=>'int'], +'Transliterator::createInverse' => ['Transliterator'], +'Transliterator::getErrorCode' => ['int'], +'Transliterator::getErrorMessage' => ['string'], +'Transliterator::listIDs' => ['array'], +'Transliterator::transliterate' => ['string|false', 'subject'=>'string', 'start='=>'int', 'end='=>'int'], +'transliterator_create' => ['?Transliterator', 'id'=>'string', 'direction='=>'int'], +'transliterator_create_from_rules' => ['?Transliterator', 'rules'=>'string', 'direction='=>'int'], +'transliterator_create_inverse' => ['Transliterator', 'object'=>'Transliterator'], +'transliterator_get_error_code' => ['int', 'object'=>'Transliterator'], +'transliterator_get_error_message' => ['string', 'object'=>'Transliterator'], +'transliterator_list_ids' => ['array'], +'transliterator_transliterate' => ['string|false', 'object'=>'Transliterator|string', 'subject'=>'string', 'start='=>'int', 'end='=>'int'], +'trigger_error' => ['bool', 'message'=>'string', 'error_type='=>'256|512|1024|16384'], +'trim' => ['string', 'string'=>'string', 'character_mask='=>'string'], +'TypeError::__clone' => ['void'], +'TypeError::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?TypeError'], +'TypeError::__toString' => ['string'], +'TypeError::getCode' => ['int'], +'TypeError::getFile' => ['string'], +'TypeError::getLine' => ['int'], +'TypeError::getMessage' => ['string'], +'TypeError::getPrevious' => ['Throwable|TypeError|null'], +'TypeError::getTrace' => ['list>'], +'TypeError::getTraceAsString' => ['string'], +'uasort' => ['bool', '&rw_array'=>'array', 'cmp_function'=>'callable(mixed,mixed):int'], +'ucfirst' => ['string', 'string'=>'string'], +'UConverter::__construct' => ['void', 'destination_encoding='=>'string', 'source_encoding='=>'string'], +'UConverter::convert' => ['string', 'string'=>'string', 'reverse='=>'bool'], +'UConverter::fromUCallback' => ['mixed', 'reason'=>'int', 'source'=>'string', 'codePoint'=>'string', '&w_error'=>'int'], +'UConverter::getAliases' => ['array', 'name'=>'string'], +'UConverter::getAvailable' => ['array'], +'UConverter::getDestinationEncoding' => ['string'], +'UConverter::getDestinationType' => ['int'], +'UConverter::getErrorCode' => ['int'], +'UConverter::getErrorMessage' => ['string'], +'UConverter::getSourceEncoding' => ['string'], +'UConverter::getSourceType' => ['int'], +'UConverter::getStandards' => ['array'], +'UConverter::getSubstChars' => ['string'], +'UConverter::reasonText' => ['string', 'reason='=>'int'], +'UConverter::setDestinationEncoding' => ['bool', 'encoding'=>'string'], +'UConverter::setSourceEncoding' => ['bool', 'encoding'=>'string'], +'UConverter::setSubstChars' => ['bool', 'chars'=>'string'], +'UConverter::toUCallback' => ['mixed', 'reason'=>'int', 'source'=>'string', 'codeUnits'=>'string', '&w_error'=>'int'], +'UConverter::transcode' => ['string', 'string'=>'string', 'toEncoding'=>'string', 'fromEncoding'=>'string', 'options='=>'?array'], +'ucwords' => ['string', 'string'=>'string', 'delimiters='=>'string'], +'udm_add_search_limit' => ['bool', 'agent'=>'resource', 'var'=>'int', 'value'=>'string'], +'udm_alloc_agent' => ['resource', 'dbaddr'=>'string', 'dbmode='=>'string'], +'udm_alloc_agent_array' => ['resource', 'databases'=>'array'], +'udm_api_version' => ['int'], +'udm_cat_list' => ['array', 'agent'=>'resource', 'category'=>'string'], +'udm_cat_path' => ['array', 'agent'=>'resource', 'category'=>'string'], +'udm_check_charset' => ['bool', 'agent'=>'resource', 'charset'=>'string'], +'udm_check_stored' => ['int', 'agent'=>'', 'link'=>'int', 'doc_id'=>'string'], +'udm_clear_search_limits' => ['bool', 'agent'=>'resource'], +'udm_close_stored' => ['int', 'agent'=>'', 'link'=>'int'], +'udm_crc32' => ['int', 'agent'=>'resource', 'string'=>'string'], +'udm_errno' => ['int', 'agent'=>'resource'], +'udm_error' => ['string', 'agent'=>'resource'], +'udm_find' => ['resource', 'agent'=>'resource', 'query'=>'string'], +'udm_free_agent' => ['int', 'agent'=>'resource'], +'udm_free_ispell_data' => ['bool', 'agent'=>'int'], +'udm_free_res' => ['bool', 'res'=>'resource'], +'udm_get_doc_count' => ['int', 'agent'=>'resource'], +'udm_get_res_field' => ['string', 'res'=>'resource', 'row'=>'int', 'field'=>'int'], +'udm_get_res_param' => ['string', 'res'=>'resource', 'param'=>'int'], +'udm_hash32' => ['int', 'agent'=>'resource', 'string'=>'string'], +'udm_load_ispell_data' => ['bool', 'agent'=>'resource', 'var'=>'int', 'val1'=>'string', 'val2'=>'string', 'flag'=>'int'], +'udm_open_stored' => ['int', 'agent'=>'', 'storedaddr'=>'string'], +'udm_set_agent_param' => ['bool', 'agent'=>'resource', 'var'=>'int', 'val'=>'string'], +'ui\area::onDraw' => ['', 'pen'=>'UI\Draw\Pen', 'areaSize'=>'UI\Size', 'clipPoint'=>'UI\Point', 'clipSize'=>'UI\Size'], +'ui\area::onKey' => ['', 'key'=>'string', 'ext'=>'int', 'flags'=>'int'], +'ui\area::onMouse' => ['', 'areaPoint'=>'UI\Point', 'areaSize'=>'UI\Size', 'flags'=>'int'], +'ui\area::redraw' => [''], +'ui\area::scrollTo' => ['', 'point'=>'UI\Point', 'size'=>'UI\Size'], +'ui\area::setSize' => ['', 'size'=>'UI\Size'], +'ui\control::destroy' => [''], +'ui\control::disable' => [''], +'ui\control::enable' => [''], +'ui\control::getParent' => ['UI\Control'], +'ui\control::getTopLevel' => ['int'], +'ui\control::hide' => [''], +'ui\control::isEnabled' => ['bool'], +'ui\control::isVisible' => ['bool'], +'ui\control::setParent' => ['', 'parent'=>'UI\Control'], +'ui\control::show' => [''], +'ui\controls\box::append' => ['int', 'control'=>'Control', 'stretchy='=>'bool'], +'ui\controls\box::delete' => ['bool', 'index'=>'int'], +'ui\controls\box::getOrientation' => ['int'], +'ui\controls\box::isPadded' => ['bool'], +'ui\controls\box::setPadded' => ['', 'padded'=>'bool'], +'ui\controls\button::getText' => ['string'], +'ui\controls\button::onClick' => [''], +'ui\controls\button::setText' => ['', 'text'=>'string'], +'ui\controls\check::getText' => ['string'], +'ui\controls\check::isChecked' => ['bool'], +'ui\controls\check::onToggle' => [''], +'ui\controls\check::setChecked' => ['', 'checked'=>'bool'], +'ui\controls\check::setText' => ['', 'text'=>'string'], +'ui\controls\colorbutton::getColor' => ['UI\Color'], +'ui\controls\colorbutton::onChange' => [''], +'ui\controls\combo::append' => ['', 'text'=>'string'], +'ui\controls\combo::getSelected' => ['int'], +'ui\controls\combo::onSelected' => [''], +'ui\controls\combo::setSelected' => ['', 'index'=>'int'], +'ui\controls\editablecombo::append' => ['', 'text'=>'string'], +'ui\controls\editablecombo::getText' => ['string'], +'ui\controls\editablecombo::onChange' => [''], +'ui\controls\editablecombo::setText' => ['', 'text'=>'string'], +'ui\controls\entry::getText' => ['string'], +'ui\controls\entry::isReadOnly' => ['bool'], +'ui\controls\entry::onChange' => [''], +'ui\controls\entry::setReadOnly' => ['', 'readOnly'=>'bool'], +'ui\controls\entry::setText' => ['', 'text'=>'string'], +'ui\controls\form::append' => ['int', 'label'=>'string', 'control'=>'UI\Control', 'stretchy='=>'bool'], +'ui\controls\form::delete' => ['bool', 'index'=>'int'], +'ui\controls\form::isPadded' => ['bool'], +'ui\controls\form::setPadded' => ['', 'padded'=>'bool'], +'ui\controls\grid::append' => ['', 'control'=>'UI\Control', 'left'=>'int', 'top'=>'int', 'xspan'=>'int', 'yspan'=>'int', 'hexpand'=>'bool', 'halign'=>'int', 'vexpand'=>'bool', 'valign'=>'int'], +'ui\controls\grid::isPadded' => ['bool'], +'ui\controls\grid::setPadded' => ['', 'padding'=>'bool'], +'ui\controls\group::append' => ['', 'control'=>'UI\Control'], +'ui\controls\group::getTitle' => ['string'], +'ui\controls\group::hasMargin' => ['bool'], +'ui\controls\group::setMargin' => ['', 'margin'=>'bool'], +'ui\controls\group::setTitle' => ['', 'title'=>'string'], +'ui\controls\label::getText' => ['string'], +'ui\controls\label::setText' => ['', 'text'=>'string'], +'ui\controls\multilineentry::append' => ['', 'text'=>'string'], +'ui\controls\multilineentry::getText' => ['string'], +'ui\controls\multilineentry::isReadOnly' => ['bool'], +'ui\controls\multilineentry::onChange' => [''], +'ui\controls\multilineentry::setReadOnly' => ['', 'readOnly'=>'bool'], +'ui\controls\multilineentry::setText' => ['', 'text'=>'string'], +'ui\controls\progress::getValue' => ['int'], +'ui\controls\progress::setValue' => ['', 'value'=>'int'], +'ui\controls\radio::append' => ['', 'text'=>'string'], +'ui\controls\radio::getSelected' => ['int'], +'ui\controls\radio::onSelected' => [''], +'ui\controls\radio::setSelected' => ['', 'index'=>'int'], +'ui\controls\slider::getValue' => ['int'], +'ui\controls\slider::onChange' => [''], +'ui\controls\slider::setValue' => ['', 'value'=>'int'], +'ui\controls\spin::getValue' => ['int'], +'ui\controls\spin::onChange' => [''], +'ui\controls\spin::setValue' => ['', 'value'=>'int'], +'ui\controls\tab::append' => ['int', 'name'=>'string', 'control'=>'UI\Control'], +'ui\controls\tab::delete' => ['bool', 'index'=>'int'], +'ui\controls\tab::hasMargin' => ['bool', 'page'=>'int'], +'ui\controls\tab::insertAt' => ['', 'name'=>'string', 'page'=>'int', 'control'=>'UI\Control'], +'ui\controls\tab::pages' => ['int'], +'ui\controls\tab::setMargin' => ['', 'page'=>'int', 'margin'=>'bool'], +'ui\draw\brush::getColor' => ['UI\Draw\Color'], +'ui\draw\brush\gradient::delStop' => ['int', 'index'=>'int'], +'ui\draw\color::getChannel' => ['float', 'channel'=>'int'], +'ui\draw\color::setChannel' => ['void', 'channel'=>'int', 'value'=>'float'], +'ui\draw\matrix::invert' => [''], +'ui\draw\matrix::isInvertible' => ['bool'], +'ui\draw\matrix::multiply' => ['UI\Draw\Matrix', 'matrix'=>'UI\Draw\Matrix'], +'ui\draw\matrix::rotate' => ['', 'point'=>'UI\Point', 'amount'=>'float'], +'ui\draw\matrix::scale' => ['', 'center'=>'UI\Point', 'point'=>'UI\Point'], +'ui\draw\matrix::skew' => ['', 'point'=>'UI\Point', 'amount'=>'UI\Point'], +'ui\draw\matrix::translate' => ['', 'point'=>'UI\Point'], +'ui\draw\path::addRectangle' => ['', 'point'=>'UI\Point', 'size'=>'UI\Size'], +'ui\draw\path::arcTo' => ['', 'point'=>'UI\Point', 'radius'=>'float', 'angle'=>'float', 'sweep'=>'float', 'negative'=>'float'], +'ui\draw\path::bezierTo' => ['', 'point'=>'UI\Point', 'radius'=>'float', 'angle'=>'float', 'sweep'=>'float', 'negative'=>'float'], +'ui\draw\path::closeFigure' => [''], +'ui\draw\path::end' => [''], +'ui\draw\path::lineTo' => ['', 'point'=>'UI\Point', 'radius'=>'float', 'angle'=>'float', 'sweep'=>'float', 'negative'=>'float'], +'ui\draw\path::newFigure' => ['', 'point'=>'UI\Point'], +'ui\draw\path::newFigureWithArc' => ['', 'point'=>'UI\Point', 'radius'=>'float', 'angle'=>'float', 'sweep'=>'float', 'negative'=>'float'], +'ui\draw\pen::clip' => ['', 'path'=>'UI\Draw\Path'], +'ui\draw\pen::restore' => [''], +'ui\draw\pen::save' => [''], +'ui\draw\pen::transform' => ['', 'matrix'=>'UI\Draw\Matrix'], +'ui\draw\pen::write' => ['', 'point'=>'UI\Point', 'layout'=>'UI\Draw\Text\Layout'], +'ui\draw\stroke::getCap' => ['int'], +'ui\draw\stroke::getJoin' => ['int'], +'ui\draw\stroke::getMiterLimit' => ['float'], +'ui\draw\stroke::getThickness' => ['float'], +'ui\draw\stroke::setCap' => ['', 'cap'=>'int'], +'ui\draw\stroke::setJoin' => ['', 'join'=>'int'], +'ui\draw\stroke::setMiterLimit' => ['', 'limit'=>'float'], +'ui\draw\stroke::setThickness' => ['', 'thickness'=>'float'], +'ui\draw\text\font::getAscent' => ['float'], +'ui\draw\text\font::getDescent' => ['float'], +'ui\draw\text\font::getLeading' => ['float'], +'ui\draw\text\font::getUnderlinePosition' => ['float'], +'ui\draw\text\font::getUnderlineThickness' => ['float'], +'ui\draw\text\font\descriptor::getFamily' => ['string'], +'ui\draw\text\font\descriptor::getItalic' => ['int'], +'ui\draw\text\font\descriptor::getSize' => ['float'], +'ui\draw\text\font\descriptor::getStretch' => ['int'], +'ui\draw\text\font\descriptor::getWeight' => ['int'], +'ui\draw\text\font\fontfamilies' => ['array'], +'ui\draw\text\layout::setWidth' => ['', 'width'=>'float'], +'ui\executor::kill' => ['void'], +'ui\executor::onExecute' => ['void'], +'ui\menu::append' => ['UI\MenuItem', 'name'=>'string', 'type='=>'string'], +'ui\menu::appendAbout' => ['UI\MenuItem', 'type='=>'string'], +'ui\menu::appendCheck' => ['UI\MenuItem', 'name'=>'string', 'type='=>'string'], +'ui\menu::appendPreferences' => ['UI\MenuItem', 'type='=>'string'], +'ui\menu::appendQuit' => ['UI\MenuItem', 'type='=>'string'], +'ui\menu::appendSeparator' => [''], +'ui\menuitem::disable' => [''], +'ui\menuitem::enable' => [''], +'ui\menuitem::isChecked' => ['bool'], +'ui\menuitem::onClick' => [''], +'ui\menuitem::setChecked' => ['', 'checked'=>'bool'], +'ui\point::getX' => ['float'], +'ui\point::getY' => ['float'], +'ui\point::setX' => ['', 'point'=>'float'], +'ui\point::setY' => ['', 'point'=>'float'], +'ui\quit' => ['void'], +'ui\run' => ['void', 'flags='=>'int'], +'ui\size::getHeight' => ['float'], +'ui\size::getWidth' => ['float'], +'ui\size::setHeight' => ['', 'size'=>'float'], +'ui\size::setWidth' => ['', 'size'=>'float'], +'ui\window::add' => ['', 'control'=>'UI\Control'], +'ui\window::error' => ['', 'title'=>'string', 'msg'=>'string'], +'ui\window::getSize' => ['UI\Size'], +'ui\window::getTitle' => ['string'], +'ui\window::hasBorders' => ['bool'], +'ui\window::hasMargin' => ['bool'], +'ui\window::isFullScreen' => ['bool'], +'ui\window::msg' => ['', 'title'=>'string', 'msg'=>'string'], +'ui\window::onClosing' => ['int'], +'ui\window::open' => ['string'], +'ui\window::save' => ['string'], +'ui\window::setBorders' => ['', 'borders'=>'bool'], +'ui\window::setFullScreen' => ['', 'full'=>'bool'], +'ui\window::setMargin' => ['', 'margin'=>'bool'], +'ui\window::setSize' => ['', 'size'=>'UI\Size'], +'ui\window::setTitle' => ['', 'title'=>'string'], +'uksort' => ['bool', '&rw_array'=>'array', 'cmp_function'=>'callable(mixed,mixed):int'], +'umask' => ['int', 'mask='=>'int'], +'UnderflowException::__clone' => ['void'], +'UnderflowException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?UnderflowException'], +'UnderflowException::__toString' => ['string'], +'UnderflowException::getCode' => ['int'], +'UnderflowException::getFile' => ['string'], +'UnderflowException::getLine' => ['int'], +'UnderflowException::getMessage' => ['string'], +'UnderflowException::getPrevious' => ['Throwable|UnderflowException|null'], +'UnderflowException::getTrace' => ['list>'], +'UnderflowException::getTraceAsString' => ['string'], +'UnexpectedValueException::__clone' => ['void'], +'UnexpectedValueException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?UnexpectedValueException'], +'UnexpectedValueException::__toString' => ['string'], +'UnexpectedValueException::getCode' => ['int'], +'UnexpectedValueException::getFile' => ['string'], +'UnexpectedValueException::getLine' => ['int'], +'UnexpectedValueException::getMessage' => ['string'], +'UnexpectedValueException::getPrevious' => ['Throwable|UnexpectedValueException|null'], +'UnexpectedValueException::getTrace' => ['list>'], +'UnexpectedValueException::getTraceAsString' => ['string'], +'uniqid' => ['string', 'prefix='=>'string', 'more_entropy='=>'bool'], +'unixtojd' => ['int', 'timestamp='=>'int'], +'unlink' => ['bool', 'filename'=>'string', 'context='=>'resource'], +'unpack' => ['array|false', 'format'=>'string', 'data'=>'string', 'offset='=>'int'], +'unregister_tick_function' => ['void', 'function_name'=>'callable'], +'unserialize' => ['mixed', 'value'=>'string', 'options='=>'array{allowed_classes?:string[]|bool}'], +'unset' => ['void', 'var='=>'mixed', '...args='=>'mixed'], +'untaint' => ['bool', '&rw_string'=>'string', '&...rw_strings='=>'string'], +'uopz_allow_exit' => ['void', 'allow'=>'bool'], +'uopz_backup' => ['void', 'class'=>'string', 'function'=>'string'], +'uopz_backup\'1' => ['void', 'function'=>'string'], +'uopz_compose' => ['void', 'name'=>'string', 'classes'=>'array', 'methods='=>'array', 'properties='=>'array', 'flags='=>'int'], +'uopz_copy' => ['Closure', 'class'=>'string', 'function'=>'string'], +'uopz_copy\'1' => ['Closure', 'function'=>'string'], +'uopz_delete' => ['void', 'class'=>'string', 'function'=>'string'], +'uopz_delete\'1' => ['void', 'function'=>'string'], +'uopz_extend' => ['bool', 'class'=>'string', 'parent'=>'string'], +'uopz_flags' => ['int', 'class'=>'string', 'function'=>'string', 'flags'=>'int'], +'uopz_flags\'1' => ['int', 'function'=>'string', 'flags'=>'int'], +'uopz_function' => ['void', 'class'=>'string', 'function'=>'string', 'handler'=>'Closure', 'modifiers='=>'int'], +'uopz_function\'1' => ['void', 'function'=>'string', 'handler'=>'Closure', 'modifiers='=>'int'], +'uopz_get_exit_status' => ['?int'], +'uopz_get_hook' => ['?Closure', 'class'=>'string', 'function'=>'string'], +'uopz_get_hook\'1' => ['?Closure', 'function'=>'string'], +'uopz_get_mock' => ['string|object|null', 'class'=>'string'], +'uopz_get_property' => ['mixed', 'class'=>'object|string', 'property'=>'string'], +'uopz_get_return' => ['mixed', 'class='=>'string', 'function='=>'string'], +'uopz_get_static' => ['?array', 'class'=>'string', 'function'=>'string'], +'uopz_implement' => ['bool', 'class'=>'string', 'interface'=>'string'], +'uopz_overload' => ['void', 'opcode'=>'int', 'callable'=>'Callable'], +'uopz_redefine' => ['bool', 'class'=>'string', 'constant'=>'string', 'value'=>'mixed'], +'uopz_redefine\'1' => ['bool', 'constant'=>'string', 'value'=>'mixed'], +'uopz_rename' => ['void', 'class'=>'string', 'function'=>'string', 'rename'=>'string'], +'uopz_rename\'1' => ['void', 'function'=>'string', 'rename'=>'string'], +'uopz_restore' => ['void', 'class'=>'string', 'function'=>'string'], +'uopz_restore\'1' => ['void', 'function'=>'string'], +'uopz_set_hook' => ['bool', 'class'=>'string', 'function'=>'string', 'hook'=>'Closure'], +'uopz_set_hook\'1' => ['bool', 'function'=>'string', 'hook'=>'Closure'], +'uopz_set_mock' => ['void', 'class'=>'string', 'mock'=>'object|string'], +'uopz_set_property' => ['void', 'class'=>'object|string', 'property'=>'string', 'value'=>'mixed'], +'uopz_set_return' => ['bool', 'class'=>'string', 'function'=>'string', 'value'=>'mixed', 'execute='=>'bool'], +'uopz_set_return\'1' => ['bool', 'function'=>'string', 'value'=>'mixed', 'execute='=>'bool'], +'uopz_set_static' => ['void', 'class'=>'string', 'function'=>'string', 'static'=>'array'], +'uopz_undefine' => ['bool', 'class'=>'string', 'constant'=>'string'], +'uopz_undefine\'1' => ['bool', 'constant'=>'string'], +'uopz_unset_hook' => ['bool', 'class'=>'string', 'function'=>'string'], +'uopz_unset_hook\'1' => ['bool', 'function'=>'string'], +'uopz_unset_mock' => ['void', 'class'=>'string'], +'uopz_unset_return' => ['bool', 'class='=>'string', 'function='=>'string'], +'uopz_unset_return\'1' => ['bool', 'function'=>'string'], +'urldecode' => ['string', 'string'=>'string'], +'urlencode' => ['string', 'string'=>'string'], +'use_soap_error_handler' => ['bool', 'handler='=>'bool'], +'user_error' => ['void', 'message'=>'string', 'error_type='=>'int'], +'usleep' => ['void', 'microseconds'=>'int'], +'usort' => ['bool', '&rw_array'=>'array', 'cmp_function'=>'callable(mixed,mixed):int'], +'utf8_decode' => ['string', 'data'=>'string'], +'utf8_encode' => ['string', 'data'=>'string'], +'V8Js::__construct' => ['void', 'object_name='=>'string', 'variables='=>'array', 'extensions='=>'array', 'report_uncaught_exceptions='=>'bool', 'snapshot_blob='=>'string'], +'V8Js::clearPendingException' => [''], +'V8Js::compileString' => ['resource', 'script'=>'', 'identifier='=>'string'], +'V8Js::createSnapshot' => ['false|string', 'embed_source'=>'string'], +'V8Js::executeScript' => ['', 'script'=>'resource', 'flags='=>'int', 'time_limit='=>'int', 'memory_limit='=>'int'], +'V8Js::executeString' => ['mixed', 'script'=>'string', 'identifier='=>'string', 'flags='=>'int'], +'V8Js::getExtensions' => ['string[]'], +'V8Js::getPendingException' => ['?V8JsException'], +'V8Js::registerExtension' => ['bool', 'extension_name'=>'string', 'script'=>'string', 'dependencies='=>'array', 'auto_enable='=>'bool'], +'V8Js::setAverageObjectSize' => ['', 'average_object_size'=>'int'], +'V8Js::setMemoryLimit' => ['', 'limit'=>'int'], +'V8Js::setModuleLoader' => ['', 'loader'=>'callable'], +'V8Js::setModuleNormaliser' => ['', 'normaliser'=>'callable'], +'V8Js::setTimeLimit' => ['', 'limit'=>'int'], +'V8JsException::getJsFileName' => ['string'], +'V8JsException::getJsLineNumber' => ['int'], +'V8JsException::getJsSourceLine' => ['int'], +'V8JsException::getJsTrace' => ['string'], +'V8JsScriptException::__clone' => ['void'], +'V8JsScriptException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'V8JsScriptException::__toString' => ['string'], +'V8JsScriptException::__wakeup' => ['void'], +'V8JsScriptException::getCode' => ['int'], +'V8JsScriptException::getFile' => ['string'], +'V8JsScriptException::getJsEndColumn' => ['int'], +'V8JsScriptException::getJsFileName' => ['string'], +'V8JsScriptException::getJsLineNumber' => ['int'], +'V8JsScriptException::getJsSourceLine' => ['string'], +'V8JsScriptException::getJsStartColumn' => ['int'], +'V8JsScriptException::getJsTrace' => ['string'], +'V8JsScriptException::getLine' => ['int'], +'V8JsScriptException::getMessage' => ['string'], +'V8JsScriptException::getPrevious' => ['Exception|Throwable'], +'V8JsScriptException::getTrace' => ['list>'], +'V8JsScriptException::getTraceAsString' => ['string'], +'var_dump' => ['void', 'value'=>'mixed', '...args='=>'mixed'], +'var_export' => ['?string', 'value'=>'mixed', 'return='=>'bool'], +'VARIANT::__construct' => ['void', 'value='=>'mixed', 'type='=>'int', 'codepage='=>'int'], +'variant_abs' => ['mixed', 'left'=>'mixed'], +'variant_add' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_and' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_cast' => ['VARIANT', 'variant'=>'VARIANT', 'type'=>'int'], +'variant_cat' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_cmp' => ['int', 'left'=>'mixed', 'right'=>'mixed', 'lcid='=>'int', 'flags='=>'int'], +'variant_date_from_timestamp' => ['VARIANT', 'timestamp'=>'int'], +'variant_date_to_timestamp' => ['int', 'variant'=>'VARIANT'], +'variant_div' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_eqv' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_fix' => ['mixed', 'left'=>'mixed'], +'variant_get_type' => ['int', 'variant'=>'VARIANT'], +'variant_idiv' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_imp' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_int' => ['mixed', 'left'=>'mixed'], +'variant_mod' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_mul' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_neg' => ['mixed', 'left'=>'mixed'], +'variant_not' => ['mixed', 'left'=>'mixed'], +'variant_or' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_pow' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_round' => ['mixed', 'left'=>'mixed', 'decimals'=>'int'], +'variant_set' => ['void', 'variant'=>'object', 'value'=>'mixed'], +'variant_set_type' => ['void', 'variant'=>'object', 'type'=>'int'], +'variant_sub' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'variant_xor' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'], +'VarnishAdmin::__construct' => ['void', 'args='=>'array'], +'VarnishAdmin::auth' => ['bool'], +'VarnishAdmin::ban' => ['int', 'vcl_regex'=>'string'], +'VarnishAdmin::banUrl' => ['int', 'vcl_regex'=>'string'], +'VarnishAdmin::clearPanic' => ['int'], +'VarnishAdmin::connect' => ['bool'], +'VarnishAdmin::disconnect' => ['bool'], +'VarnishAdmin::getPanic' => ['string'], +'VarnishAdmin::getParams' => ['array'], +'VarnishAdmin::isRunning' => ['bool'], +'VarnishAdmin::setCompat' => ['void', 'compat'=>'int'], +'VarnishAdmin::setHost' => ['void', 'host'=>'string'], +'VarnishAdmin::setIdent' => ['void', 'ident'=>'string'], +'VarnishAdmin::setParam' => ['int', 'name'=>'string', 'value'=>'string|int'], +'VarnishAdmin::setPort' => ['void', 'port'=>'int'], +'VarnishAdmin::setSecret' => ['void', 'secret'=>'string'], +'VarnishAdmin::setTimeout' => ['void', 'timeout'=>'int'], +'VarnishAdmin::start' => ['int'], +'VarnishAdmin::stop' => ['int'], +'VarnishLog::__construct' => ['void', 'args='=>'array'], +'VarnishLog::getLine' => ['array'], +'VarnishLog::getTagName' => ['string', 'index'=>'int'], +'VarnishStat::__construct' => ['void', 'args='=>'array'], +'VarnishStat::getSnapshot' => ['array'], +'version_compare' => ['bool', 'ver1'=>'string', 'ver2'=>'string', 'oper'=>'\'<\'|\'lt\'|\'<=\'|\'le\'|\'>\'|\'gt\'|\'>=\'|\'ge\'|\'==\'|\'=\'|\'eq\'|\'!=\'|\'<>\'|\'ne\''], +'version_compare\'1' => ['int', 'ver1'=>'string', 'ver2'=>'string'], +'vfprintf' => ['int', 'stream'=>'resource', 'format'=>'string', 'args'=>'array'], +'virtual' => ['bool', 'uri'=>'string'], +'vpopmail_add_alias_domain' => ['bool', 'domain'=>'string', 'aliasdomain'=>'string'], +'vpopmail_add_alias_domain_ex' => ['bool', 'olddomain'=>'string', 'newdomain'=>'string'], +'vpopmail_add_domain' => ['bool', 'domain'=>'string', 'dir'=>'string', 'uid'=>'int', 'gid'=>'int'], +'vpopmail_add_domain_ex' => ['bool', 'domain'=>'string', 'passwd'=>'string', 'quota='=>'string', 'bounce='=>'string', 'apop='=>'bool'], +'vpopmail_add_user' => ['bool', 'user'=>'string', 'domain'=>'string', 'password'=>'string', 'gecos='=>'string', 'apop='=>'bool'], +'vpopmail_alias_add' => ['bool', 'user'=>'string', 'domain'=>'string', 'alias'=>'string'], +'vpopmail_alias_del' => ['bool', 'user'=>'string', 'domain'=>'string'], +'vpopmail_alias_del_domain' => ['bool', 'domain'=>'string'], +'vpopmail_alias_get' => ['array', 'alias'=>'string', 'domain'=>'string'], +'vpopmail_alias_get_all' => ['array', 'domain'=>'string'], +'vpopmail_auth_user' => ['bool', 'user'=>'string', 'domain'=>'string', 'password'=>'string', 'apop='=>'string'], +'vpopmail_del_domain' => ['bool', 'domain'=>'string'], +'vpopmail_del_domain_ex' => ['bool', 'domain'=>'string'], +'vpopmail_del_user' => ['bool', 'user'=>'string', 'domain'=>'string'], +'vpopmail_error' => ['string'], +'vpopmail_passwd' => ['bool', 'user'=>'string', 'domain'=>'string', 'password'=>'string', 'apop='=>'bool'], +'vpopmail_set_user_quota' => ['bool', 'user'=>'string', 'domain'=>'string', 'quota'=>'string'], +'vprintf' => ['int', 'format'=>'string', 'args'=>'array'], +'vsprintf' => ['string', 'format'=>'string', 'args'=>'array'], +'Vtiful\Kernel\Excel::__construct' => ['void', 'config'=>'array'], +'Vtiful\Kernel\Excel::addSheet' => ['', 'sheetName'=>'string'], +'Vtiful\Kernel\Excel::autoFilter' => ['', 'scope'=>'string'], +'Vtiful\Kernel\Excel::constMemory' => ['', 'fileName'=>'string', 'sheetName='=>'string'], +'Vtiful\Kernel\Excel::data' => ['', 'data'=>'array'], +'Vtiful\Kernel\Excel::fileName' => ['', 'fileName'=>'string', 'sheetName='=>'string'], +'Vtiful\Kernel\Excel::getHandle' => [''], +'Vtiful\Kernel\Excel::header' => ['', 'headerData'=>'array'], +'Vtiful\Kernel\Excel::insertFormula' => ['', 'row'=>'int', 'column'=>'int', 'formula'=>'string'], +'Vtiful\Kernel\Excel::insertImage' => ['', 'row'=>'int', 'column'=>'int', 'localImagePath'=>'string'], +'Vtiful\Kernel\Excel::insertText' => ['', 'row'=>'int', 'column'=>'int', 'data'=>'string', 'format='=>'string'], +'Vtiful\Kernel\Excel::mergeCells' => ['', 'scope'=>'string', 'data'=>'string'], +'Vtiful\Kernel\Excel::output' => [''], +'Vtiful\Kernel\Excel::setColumn' => ['', 'range'=>'string', 'width'=>'float', 'format='=>'resource'], +'Vtiful\Kernel\Excel::setRow' => ['', 'range'=>'string', 'height'=>'float', 'format='=>'resource'], +'Vtiful\Kernel\Format::align' => ['', 'handle'=>'resource', 'style'=>'int'], +'Vtiful\Kernel\Format::bold' => ['', 'handle'=>'resource'], +'Vtiful\Kernel\Format::italic' => ['', 'handle'=>'resource'], +'Vtiful\Kernel\Format::underline' => ['', 'handle'=>'resource', 'style'=>'int'], +'w32api_deftype' => ['bool', 'typename'=>'string', 'member1_type'=>'string', 'member1_name'=>'string', '...args='=>'string'], +'w32api_init_dtype' => ['resource', 'typename'=>'string', 'value'=>'', '...args='=>''], +'w32api_invoke_function' => ['', 'funcname'=>'string', 'argument'=>'', '...args='=>''], +'w32api_register_function' => ['bool', 'library'=>'string', 'function_name'=>'string', 'return_type'=>'string'], +'w32api_set_call_method' => ['', 'method'=>'int'], +'wddx_add_vars' => ['bool', 'packet_id'=>'resource', 'var_names'=>'mixed', '...vars='=>'mixed'], +'wddx_deserialize' => ['mixed', 'packet'=>'string'], +'wddx_packet_end' => ['string', 'packet_id'=>'resource'], +'wddx_packet_start' => ['resource|false', 'comment='=>'string'], +'wddx_serialize_value' => ['string|false', 'value'=>'mixed', 'comment='=>'string'], +'wddx_serialize_vars' => ['string|false', 'var_name'=>'mixed', '...vars='=>'mixed'], +'WeakMap::__construct' => ['void'], +'WeakMap::count' => ['int'], +'WeakMap::current' => ['mixed'], +'WeakMap::key' => ['object'], +'WeakMap::next' => ['void'], +'WeakMap::offsetExists' => ['bool', 'object'=>'object'], +'WeakMap::offsetGet' => ['mixed', 'object'=>'object'], +'WeakMap::offsetSet' => ['void', 'object'=>'object', 'value'=>'mixed'], +'WeakMap::offsetUnset' => ['void', 'object'=>'object'], +'WeakMap::rewind' => ['void'], +'WeakMap::valid' => ['bool'], +'Weakref::acquire' => ['bool'], +'Weakref::get' => ['object'], +'Weakref::release' => ['bool'], +'Weakref::valid' => ['bool'], +'webObj::convertToString' => ['string'], +'webObj::free' => ['void'], +'webObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], +'webObj::updateFromString' => ['int', 'snippet'=>'string'], +'win32_continue_service' => ['int|false', 'servicename'=>'string', 'machine='=>'string'], +'win32_create_service' => ['int|false', 'details'=>'array', 'machine='=>'string'], +'win32_delete_service' => ['int|false', 'servicename'=>'string', 'machine='=>'string'], +'win32_get_last_control_message' => ['int'], +'win32_pause_service' => ['int|false', 'servicename'=>'string', 'machine='=>'string'], +'win32_ps_list_procs' => ['array'], +'win32_ps_stat_mem' => ['array'], +'win32_ps_stat_proc' => ['array', 'pid='=>'int'], +'win32_query_service_status' => ['array|false|int', 'servicename'=>'string', 'machine='=>'string'], +'win32_send_custom_control' => ['int', 'servicename'=>'string', 'control'=>'int', 'machine='=>'string'], +'win32_set_service_exit_code' => ['int', 'exitCode='=>'int'], +'win32_set_service_exit_mode' => ['bool', 'gracefulMode='=>'bool'], +'win32_set_service_status' => ['bool|int', 'status'=>'int', 'checkpoint='=>'int'], +'win32_start_service' => ['int|false', 'servicename'=>'string', 'machine='=>'string'], +'win32_start_service_ctrl_dispatcher' => ['bool|int', 'name'=>'string'], +'win32_stop_service' => ['int|false', 'servicename'=>'string', 'machine='=>'string'], +'wincache_fcache_fileinfo' => ['array|false', 'summaryonly='=>'bool'], +'wincache_fcache_meminfo' => ['array|false'], +'wincache_lock' => ['bool', 'key'=>'string', 'isglobal='=>'bool'], +'wincache_ocache_fileinfo' => ['array|false', 'summaryonly='=>'bool'], +'wincache_ocache_meminfo' => ['array|false'], +'wincache_refresh_if_changed' => ['bool', 'files='=>'array'], +'wincache_rplist_fileinfo' => ['array|false', 'summaryonly='=>'bool'], +'wincache_rplist_meminfo' => ['array|false'], +'wincache_scache_info' => ['array|false', 'summaryonly='=>'bool'], +'wincache_scache_meminfo' => ['array|false'], +'wincache_ucache_add' => ['bool', 'key'=>'string', 'value'=>'mixed', 'ttl='=>'int'], +'wincache_ucache_add\'1' => ['bool', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'], +'wincache_ucache_cas' => ['bool', 'key'=>'string', 'old_value'=>'int', 'new_value'=>'int'], +'wincache_ucache_clear' => ['bool'], +'wincache_ucache_dec' => ['int|false', 'key'=>'string', 'dec_by='=>'int', 'success='=>'bool'], +'wincache_ucache_delete' => ['bool', 'key'=>'mixed'], +'wincache_ucache_exists' => ['bool', 'key'=>'string'], +'wincache_ucache_get' => ['mixed', 'key'=>'mixed', '&w_success='=>'bool'], +'wincache_ucache_inc' => ['int|false', 'key'=>'string', 'inc_by='=>'int', 'success='=>'bool'], +'wincache_ucache_info' => ['array|false', 'summaryonly='=>'bool', 'key='=>'string'], +'wincache_ucache_meminfo' => ['array|false'], +'wincache_ucache_set' => ['bool', 'key'=>'', 'value'=>'', 'ttl='=>'int'], +'wincache_ucache_set\'1' => ['bool', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'], +'wincache_unlock' => ['bool', 'key'=>'string'], +'wkhtmltox\image\converter::convert' => ['?string'], +'wkhtmltox\image\converter::getVersion' => ['string'], +'wkhtmltox\pdf\converter::add' => ['void', 'object'=>'wkhtmltox\PDF\Object'], +'wkhtmltox\pdf\converter::convert' => ['?string'], +'wkhtmltox\pdf\converter::getVersion' => ['string'], +'wordwrap' => ['string', 'string'=>'string', 'width='=>'int', 'break='=>'string', 'cut='=>'bool'], +'Worker::__construct' => ['void'], +'Worker::addRef' => ['void'], +'Worker::chunk' => ['array', 'size'=>'int', 'preserve'=>'bool'], +'Worker::collect' => ['int', 'collector='=>'Callable'], +'Worker::count' => ['int'], +'Worker::delRef' => ['void'], +'Worker::detach' => ['void'], +'Worker::extend' => ['bool', 'class'=>'string'], +'Worker::getCreatorId' => ['int'], +'Worker::getCurrentThread' => ['Thread'], +'Worker::getCurrentThreadId' => ['int'], +'Worker::getRefCount' => ['int'], +'Worker::getStacked' => ['int'], +'Worker::getTerminationInfo' => ['array'], +'Worker::getThreadId' => ['int'], +'Worker::globally' => ['mixed'], +'Worker::isGarbage' => ['bool'], +'Worker::isJoined' => ['bool'], +'Worker::isRunning' => ['bool'], +'Worker::isShutdown' => ['bool'], +'Worker::isStarted' => ['bool'], +'Worker::isTerminated' => ['bool'], +'Worker::isWaiting' => ['bool'], +'Worker::isWorking' => ['bool'], +'Worker::join' => ['bool'], +'Worker::kill' => ['bool'], +'Worker::lock' => ['bool'], +'Worker::merge' => ['bool', 'from'=>'', 'overwrite='=>'mixed'], +'Worker::notify' => ['bool'], +'Worker::notifyOne' => ['bool'], +'Worker::offsetExists' => ['bool', 'offset'=>'mixed'], +'Worker::offsetGet' => ['mixed', 'offset'=>'mixed'], +'Worker::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'], +'Worker::offsetUnset' => ['void', 'offset'=>'mixed'], +'Worker::pop' => ['bool'], +'Worker::run' => ['void'], +'Worker::setGarbage' => ['void'], +'Worker::shift' => ['bool'], +'Worker::shutdown' => ['bool'], +'Worker::stack' => ['int', '&rw_work'=>'Threaded'], +'Worker::start' => ['bool', 'options='=>'int'], +'Worker::synchronized' => ['mixed', 'block'=>'Closure', '_='=>'mixed'], +'Worker::unlock' => ['bool'], +'Worker::unstack' => ['int', '&rw_work='=>'Threaded'], +'Worker::wait' => ['bool', 'timeout='=>'int'], +'xattr_get' => ['string', 'filename'=>'string', 'name'=>'string', 'flags='=>'int'], +'xattr_list' => ['array', 'filename'=>'string', 'flags='=>'int'], +'xattr_remove' => ['bool', 'filename'=>'string', 'name'=>'string', 'flags='=>'int'], +'xattr_set' => ['bool', 'filename'=>'string', 'name'=>'string', 'value'=>'string', 'flags='=>'int'], +'xattr_supported' => ['bool', 'filename'=>'string', 'flags='=>'int'], +'xcache_asm' => ['string', 'filename'=>'string'], +'xcache_clear_cache' => ['void', 'type'=>'int', 'id='=>'int'], +'xcache_coredump' => ['string', 'op_type'=>'int'], +'xcache_count' => ['int', 'type'=>'int'], +'xcache_coverager_decode' => ['array', 'data'=>'string'], +'xcache_coverager_get' => ['array', 'clean='=>'bool'], +'xcache_coverager_start' => ['void', 'clean='=>'bool'], +'xcache_coverager_stop' => ['void', 'clean='=>'bool'], +'xcache_dasm_file' => ['string', 'filename'=>'string'], +'xcache_dasm_string' => ['string', 'code'=>'string'], +'xcache_dec' => ['int', 'name'=>'string', 'value='=>'int|mixed', 'ttl='=>'int'], +'xcache_decode' => ['bool', 'filename'=>'string'], +'xcache_encode' => ['string', 'filename'=>'string'], +'xcache_get' => ['mixed', 'name'=>'string'], +'xcache_get_data_type' => ['string', 'type'=>'int'], +'xcache_get_op_spec' => ['string', 'op_type'=>'int'], +'xcache_get_op_type' => ['string', 'op_type'=>'int'], +'xcache_get_opcode' => ['string', 'opcode'=>'int'], +'xcache_get_opcode_spec' => ['string', 'opcode'=>'int'], +'xcache_inc' => ['int', 'name'=>'string', 'value='=>'int|mixed', 'ttl='=>'int'], +'xcache_info' => ['array', 'type'=>'int', 'id'=>'int'], +'xcache_is_autoglobal' => ['string', 'name'=>'string'], +'xcache_isset' => ['bool', 'name'=>'string'], +'xcache_list' => ['array', 'type'=>'int', 'id'=>'int'], +'xcache_set' => ['bool', 'name'=>'string', 'value'=>'mixed', 'ttl='=>'int'], +'xcache_unset' => ['bool', 'name'=>'string'], +'xcache_unset_by_prefix' => ['bool', 'prefix'=>'string'], +'Xcom::__construct' => ['void', 'fabric_url='=>'string', 'fabric_token='=>'string', 'capability_token='=>'string'], +'Xcom::decode' => ['object', 'avro_msg'=>'string', 'json_schema'=>'string'], +'Xcom::encode' => ['string', 'data'=>'stdClass', 'avro_schema'=>'string'], +'Xcom::getDebugOutput' => ['string'], +'Xcom::getLastResponse' => ['string'], +'Xcom::getLastResponseInfo' => ['array'], +'Xcom::getOnboardingURL' => ['string', 'capability_name'=>'string', 'agreement_url'=>'string'], +'Xcom::send' => ['int', 'topic'=>'string', 'data'=>'mixed', 'json_schema='=>'string', 'http_headers='=>'array'], +'Xcom::sendAsync' => ['int', 'topic'=>'string', 'data'=>'mixed', 'json_schema='=>'string', 'http_headers='=>'array'], +'xdebug_break' => ['bool'], +'xdebug_call_class' => ['string', 'depth='=>'int'], +'xdebug_call_file' => ['string', 'depth='=>'int'], +'xdebug_call_function' => ['string', 'depth='=>'int'], +'xdebug_call_line' => ['int', 'depth='=>'int'], +'xdebug_clear_aggr_profiling_data' => ['bool'], +'xdebug_code_coverage_started' => ['bool'], +'xdebug_debug_zval' => ['void', '...varName'=>'string'], +'xdebug_debug_zval_stdout' => ['void', '...varName'=>'string'], +'xdebug_disable' => ['void'], +'xdebug_dump_aggr_profiling_data' => ['bool'], +'xdebug_dump_superglobals' => ['void'], +'xdebug_enable' => ['void'], +'xdebug_get_code_coverage' => ['array'], +'xdebug_get_collected_errors' => ['string', 'clean='=>'bool'], +'xdebug_get_declared_vars' => ['array'], +'xdebug_get_formatted_function_stack' => [''], +'xdebug_get_function_count' => ['int'], +'xdebug_get_function_stack' => ['array', 'message='=>'string', 'options='=>'int'], +'xdebug_get_headers' => ['array'], +'xdebug_get_monitored_functions' => ['array'], +'xdebug_get_profiler_filename' => ['string'], +'xdebug_get_stack_depth' => ['int'], +'xdebug_get_tracefile_name' => ['string'], +'xdebug_is_debugger_active' => ['bool'], +'xdebug_is_enabled' => ['bool'], +'xdebug_memory_usage' => ['int'], +'xdebug_peak_memory_usage' => ['int'], +'xdebug_print_function_stack' => ['array', 'message='=>'string', 'options='=>'int'], +'xdebug_set_filter' => ['void', 'group'=>'int', 'list_type'=>'int', 'configuration'=>'array'], +'xdebug_start_code_coverage' => ['void', 'options='=>'int'], +'xdebug_start_error_collection' => ['void'], +'xdebug_start_function_monitor' => ['void', 'list_of_functions_to_monitor'=>'string[]'], +'xdebug_start_trace' => ['void', 'trace_file'=>'', 'options='=>'int|mixed'], +'xdebug_stop_code_coverage' => ['void', 'cleanup='=>'bool'], +'xdebug_stop_error_collection' => ['void'], +'xdebug_stop_function_monitor' => ['void'], +'xdebug_stop_trace' => ['void'], +'xdebug_time_index' => ['float'], +'xdebug_var_dump' => ['void', '...var'=>''], +'xdiff_file_bdiff' => ['bool', 'old_file'=>'string', 'new_file'=>'string', 'dest'=>'string'], +'xdiff_file_bdiff_size' => ['int', 'file'=>'string'], +'xdiff_file_bpatch' => ['bool', 'file'=>'string', 'patch'=>'string', 'dest'=>'string'], +'xdiff_file_diff' => ['bool', 'old_file'=>'string', 'new_file'=>'string', 'dest'=>'string', 'context='=>'int', 'minimal='=>'bool'], +'xdiff_file_diff_binary' => ['bool', 'old_file'=>'string', 'new_file'=>'string', 'dest'=>'string'], +'xdiff_file_merge3' => ['mixed', 'old_file'=>'string', 'new_file1'=>'string', 'new_file2'=>'string', 'dest'=>'string'], +'xdiff_file_patch' => ['mixed', 'file'=>'string', 'patch'=>'string', 'dest'=>'string', 'flags='=>'int'], +'xdiff_file_patch_binary' => ['bool', 'file'=>'string', 'patch'=>'string', 'dest'=>'string'], +'xdiff_file_rabdiff' => ['bool', 'old_file'=>'string', 'new_file'=>'string', 'dest'=>'string'], +'xdiff_string_bdiff' => ['string', 'old_data'=>'string', 'new_data'=>'string'], +'xdiff_string_bdiff_size' => ['int', 'patch'=>'string'], +'xdiff_string_bpatch' => ['string', 'string'=>'string', 'patch'=>'string'], +'xdiff_string_diff' => ['string', 'old_data'=>'string', 'new_data'=>'string', 'context='=>'int', 'minimal='=>'bool'], +'xdiff_string_diff_binary' => ['string', 'old_data'=>'string', 'new_data'=>'string'], +'xdiff_string_merge3' => ['mixed', 'old_data'=>'string', 'new_data1'=>'string', 'new_data2'=>'string', 'error='=>'string'], +'xdiff_string_patch' => ['string', 'string'=>'string', 'patch'=>'string', 'flags='=>'int', '&w_error='=>'string'], +'xdiff_string_patch_binary' => ['string', 'string'=>'string', 'patch'=>'string'], +'xdiff_string_rabdiff' => ['string', 'old_data'=>'string', 'new_data'=>'string'], +'xhprof_disable' => ['array'], +'xhprof_enable' => ['void', 'flags='=>'int', 'options='=>'array'], +'xhprof_sample_disable' => ['array'], +'xhprof_sample_enable' => ['void'], +'xml_error_string' => ['string', 'code'=>'int'], +'xml_get_current_byte_index' => ['int|false', 'parser'=>'resource'], +'xml_get_current_column_number' => ['int|false', 'parser'=>'resource'], +'xml_get_current_line_number' => ['int|false', 'parser'=>'resource'], +'xml_get_error_code' => ['int|false', 'parser'=>'resource'], +'xml_parse' => ['int', 'parser'=>'resource', 'data'=>'string', 'isfinal='=>'bool'], +'xml_parse_into_struct' => ['int', 'parser'=>'resource', 'data'=>'string', '&w_values'=>'array', '&w_index='=>'array'], +'xml_parser_create' => ['resource', 'encoding='=>'string'], +'xml_parser_create_ns' => ['resource', 'encoding='=>'string', 'sep='=>'string'], +'xml_parser_free' => ['bool', 'parser'=>'resource'], +'xml_parser_get_option' => ['mixed|false', 'parser'=>'resource', 'option'=>'int'], +'xml_parser_set_option' => ['bool', 'parser'=>'resource', 'option'=>'int', 'value'=>'mixed'], +'xml_set_character_data_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'callable'], +'xml_set_default_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'callable'], +'xml_set_element_handler' => ['bool', 'parser'=>'resource', 'shdl'=>'callable', 'ehdl'=>'callable'], +'xml_set_end_namespace_decl_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'callable'], +'xml_set_external_entity_ref_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'callable'], +'xml_set_notation_decl_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'callable'], +'xml_set_object' => ['bool', 'parser'=>'resource', 'object'=>'object'], +'xml_set_processing_instruction_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'callable'], +'xml_set_start_namespace_decl_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'callable'], +'xml_set_unparsed_entity_decl_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'callable'], +'XMLDiff\Base::__construct' => ['void', 'nsname'=>'string'], +'XMLDiff\Base::diff' => ['mixed', 'from'=>'mixed', 'to'=>'mixed'], +'XMLDiff\Base::merge' => ['mixed', 'src'=>'mixed', 'diff'=>'mixed'], +'XMLDiff\DOM::diff' => ['DOMDocument', 'from'=>'DOMDocument', 'to'=>'DOMDocument'], +'XMLDiff\DOM::merge' => ['DOMDocument', 'src'=>'DOMDocument', 'diff'=>'DOMDocument'], +'XMLDiff\File::diff' => ['string', 'from'=>'string', 'to'=>'string'], +'XMLDiff\File::merge' => ['string', 'src'=>'string', 'diff'=>'string'], +'XMLDiff\Memory::diff' => ['string', 'from'=>'string', 'to'=>'string'], +'XMLDiff\Memory::merge' => ['string', 'src'=>'string', 'diff'=>'string'], +'XMLReader::close' => ['bool'], +'XMLReader::expand' => ['DOMNode|false', 'basenode='=>'DOMNode'], +'XMLReader::getAttribute' => ['?string', 'name'=>'string'], +'XMLReader::getAttributeNo' => ['?string', 'index'=>'int'], +'XMLReader::getAttributeNs' => ['?string', 'name'=>'string', 'namespaceuri'=>'string'], +'XMLReader::getParserProperty' => ['bool', 'property'=>'int'], +'XMLReader::isValid' => ['bool'], +'XMLReader::lookupNamespace' => ['?string', 'prefix'=>'string'], +'XMLReader::moveToAttribute' => ['bool', 'name'=>'string'], +'XMLReader::moveToAttributeNo' => ['bool', 'index'=>'int'], +'XMLReader::moveToAttributeNs' => ['bool', 'localname'=>'string', 'namespaceuri'=>'string'], +'XMLReader::moveToElement' => ['bool'], +'XMLReader::moveToFirstAttribute' => ['bool'], +'XMLReader::moveToNextAttribute' => ['bool'], +'XMLReader::next' => ['bool', 'localname='=>'string'], +'XMLReader::open' => ['bool', 'uri'=>'string', 'encoding='=>'?string', 'options='=>'int'], +'XMLReader::read' => ['bool'], +'XMLReader::readInnerXML' => ['string'], +'XMLReader::readOuterXML' => ['string'], +'XMLReader::readString' => ['string'], +'XMLReader::setParserProperty' => ['bool', 'property'=>'int', 'value'=>'bool'], +'XMLReader::setRelaxNGSchema' => ['bool', 'filename'=>'string'], +'XMLReader::setRelaxNGSchemaSource' => ['bool', 'source'=>'string'], +'XMLReader::setSchema' => ['bool', 'filename'=>'string'], +'XMLReader::XML' => ['bool', 'source'=>'string', 'encoding='=>'?string', 'options='=>'int'], +'xmlrpc_decode' => ['mixed', 'xml'=>'string', 'encoding='=>'string'], +'xmlrpc_decode_request' => ['?array', 'xml'=>'string', '&w_method'=>'string', 'encoding='=>'string'], +'xmlrpc_encode' => ['string', 'value'=>'mixed'], +'xmlrpc_encode_request' => ['string', 'method'=>'string', 'params'=>'mixed', 'output_options='=>'array'], +'xmlrpc_get_type' => ['string', 'value'=>'mixed'], +'xmlrpc_is_fault' => ['bool', 'arg'=>'array'], +'xmlrpc_parse_method_descriptions' => ['array', 'xml'=>'string'], +'xmlrpc_server_add_introspection_data' => ['int', 'server'=>'resource', 'desc'=>'array'], +'xmlrpc_server_call_method' => ['string', 'server'=>'resource', 'xml'=>'string', 'user_data'=>'mixed', 'output_options='=>'array'], +'xmlrpc_server_create' => ['resource'], +'xmlrpc_server_destroy' => ['int', 'server'=>'resource'], +'xmlrpc_server_register_introspection_callback' => ['bool', 'server'=>'resource', 'function'=>'string'], +'xmlrpc_server_register_method' => ['bool', 'server'=>'resource', 'method_name'=>'string', 'function'=>'string'], +'xmlrpc_set_type' => ['bool', '&rw_value'=>'string|DateTime', 'type'=>'string'], +'XMLWriter::endAttribute' => ['bool'], +'XMLWriter::endCData' => ['bool'], +'XMLWriter::endComment' => ['bool'], +'XMLWriter::endDocument' => ['bool'], +'XMLWriter::endDTD' => ['bool', 'xmlwriter='=>''], +'XMLWriter::endDTDAttlist' => ['bool'], +'XMLWriter::endDTDElement' => ['bool'], +'XMLWriter::endDTDEntity' => ['bool'], +'XMLWriter::endElement' => ['bool'], +'XMLWriter::endPI' => ['bool'], +'XMLWriter::flush' => ['', 'empty='=>'bool', 'xmlwriter='=>''], +'XMLWriter::fullEndElement' => ['bool'], +'XMLWriter::openMemory' => ['bool'], +'XMLWriter::openURI' => ['bool', 'uri'=>'string'], +'XMLWriter::outputMemory' => ['string', 'flush='=>'bool', 'xmlwriter='=>''], +'XMLWriter::setIndent' => ['bool', 'indent'=>'bool'], +'XMLWriter::setIndentString' => ['bool', 'indentstring'=>'string'], +'XMLWriter::startAttribute' => ['bool', 'name'=>'string'], +'XMLWriter::startAttributeNS' => ['bool', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], +'XMLWriter::startCData' => ['bool'], +'XMLWriter::startComment' => ['bool'], +'XMLWriter::startDocument' => ['bool', 'version='=>'string', 'encoding='=>'string', 'standalone='=>'string'], +'XMLWriter::startDTD' => ['bool', 'qualifiedname'=>'string', 'publicid='=>'string', 'systemid='=>'string'], +'XMLWriter::startDTDAttlist' => ['bool', 'name'=>'string'], +'XMLWriter::startDTDElement' => ['bool', 'qualifiedname'=>'string'], +'XMLWriter::startDTDEntity' => ['bool', 'name'=>'string', 'isparam'=>'bool'], +'XMLWriter::startElement' => ['bool', 'name'=>'string'], +'XMLWriter::startElementNS' => ['bool', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], +'XMLWriter::startPI' => ['bool', 'target'=>'string'], +'XMLWriter::text' => ['bool', 'content'=>'string'], +'XMLWriter::writeAttribute' => ['bool', 'name'=>'string', 'value'=>'string'], +'XMLWriter::writeAttributeNS' => ['bool', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], +'XMLWriter::writeCData' => ['bool', 'content'=>'string'], +'XMLWriter::writeComment' => ['bool', 'content'=>'string'], +'XMLWriter::writeDTD' => ['bool', 'name'=>'string', 'publicid='=>'string', 'systemid='=>'string', 'subset='=>'string'], +'XMLWriter::writeDTDAttlist' => ['bool', 'name'=>'string', 'content'=>'string'], +'XMLWriter::writeDTDElement' => ['bool', 'name'=>'string', 'content'=>'string'], +'XMLWriter::writeDTDEntity' => ['bool', 'name'=>'string', 'content'=>'string', 'pe'=>'bool', 'publicid'=>'string', 'sysid'=>'string', 'ndataid'=>'string'], +'XMLWriter::writeElement' => ['bool', 'name'=>'string', 'content='=>'?string'], +'XMLWriter::writeElementNS' => ['bool', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content='=>'?string'], +'XMLWriter::writePI' => ['bool', 'target'=>'string', 'content'=>'string'], +'XMLWriter::writeRaw' => ['bool', 'content'=>'string'], +'xmlwriter_end_attribute' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_cdata' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_comment' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_document' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_dtd' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_dtd_attlist' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_dtd_element' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_dtd_entity' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_element' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_pi' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_flush' => ['mixed', 'xmlwriter'=>'resource', 'empty='=>'bool'], +'xmlwriter_full_end_element' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_open_memory' => ['resource'], +'xmlwriter_open_uri' => ['resource', 'source'=>'string'], +'xmlwriter_output_memory' => ['string', 'xmlwriter'=>'resource', 'flush='=>'bool'], +'xmlwriter_set_indent' => ['bool', 'xmlwriter'=>'resource', 'indent'=>'bool'], +'xmlwriter_set_indent_string' => ['bool', 'xmlwriter'=>'resource', 'indentstring'=>'string'], +'xmlwriter_start_attribute' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], +'xmlwriter_start_attribute_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], +'xmlwriter_start_cdata' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_start_comment' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_start_document' => ['bool', 'xmlwriter'=>'resource', 'version='=>'string', 'encoding='=>'string', 'standalone='=>'string'], +'xmlwriter_start_dtd' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string'], +'xmlwriter_start_dtd_attlist' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], +'xmlwriter_start_dtd_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], +'xmlwriter_start_dtd_entity' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'isparam'=>'bool'], +'xmlwriter_start_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], +'xmlwriter_start_element_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], +'xmlwriter_start_pi' => ['bool', 'xmlwriter'=>'resource', 'target'=>'string'], +'xmlwriter_text' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], +'xmlwriter_write_attribute' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_attribute_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], +'xmlwriter_write_cdata' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], +'xmlwriter_write_comment' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], +'xmlwriter_write_dtd' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string', 'subset='=>'string'], +'xmlwriter_write_dtd_attlist' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_dtd_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_dtd_entity' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string', 'pe'=>'bool', 'publicid'=>'string', 'sysid'=>'string', 'ndataid'=>'string'], +'xmlwriter_write_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_element_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], +'xmlwriter_write_pi' => ['bool', 'xmlwriter'=>'resource', 'target'=>'string', 'content'=>'string'], +'xmlwriter_write_raw' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], +'xpath_new_context' => ['XPathContext', 'dom_document'=>'DOMDocument'], +'xpath_register_ns' => ['bool', 'xpath_context'=>'xpathcontext', 'prefix'=>'string', 'uri'=>'string'], +'xpath_register_ns_auto' => ['bool', 'xpath_context'=>'xpathcontext', 'context_node='=>'object'], +'xptr_new_context' => ['XPathContext'], +'xsl_xsltprocessor_get_parameter' => ['string', 'namespace'=>'string', 'name'=>'string'], +'xsl_xsltprocessor_get_security_prefs' => ['int'], +'xsl_xsltprocessor_has_exslt_support' => ['bool'], +'xsl_xsltprocessor_register_php_functions' => ['', 'restrict'=>''], +'xsl_xsltprocessor_remove_parameter' => ['bool', 'namespace'=>'string', 'name'=>'string'], +'xsl_xsltprocessor_set_parameter' => ['bool', 'namespace'=>'string', 'name'=>'', 'value'=>'string'], +'xsl_xsltprocessor_set_profiling' => ['bool', 'filename'=>'string'], +'xsl_xsltprocessor_set_security_prefs' => ['int', 'securityprefs'=>'int'], +'xsl_xsltprocessor_transform_to_uri' => ['int', 'doc'=>'DOMDocument', 'uri'=>'string'], +'xsl_xsltprocessor_transform_to_xml' => ['string', 'doc'=>'DOMDocument'], +'xslt_backend_info' => ['string'], +'xslt_backend_name' => ['string'], +'xslt_backend_version' => ['string'], +'xslt_create' => ['resource'], +'xslt_errno' => ['int', 'xh'=>''], +'xslt_error' => ['string', 'xh'=>''], +'xslt_free' => ['', 'xh'=>''], +'xslt_getopt' => ['int', 'processor'=>''], +'xslt_process' => ['', 'xh'=>'', 'xmlcontainer'=>'string', 'xslcontainer'=>'string', 'resultcontainer='=>'string', 'arguments='=>'array', 'parameters='=>'array'], +'xslt_set_base' => ['', 'xh'=>'', 'uri'=>'string'], +'xslt_set_encoding' => ['', 'xh'=>'', 'encoding'=>'string'], +'xslt_set_error_handler' => ['', 'xh'=>'', 'handler'=>''], +'xslt_set_log' => ['', 'xh'=>'', 'log='=>''], +'xslt_set_object' => ['bool', 'processor'=>'', 'object'=>'object'], +'xslt_set_sax_handler' => ['', 'xh'=>'', 'handlers'=>'array'], +'xslt_set_sax_handlers' => ['', 'processor'=>'', 'handlers'=>'array'], +'xslt_set_scheme_handler' => ['', 'xh'=>'', 'handlers'=>'array'], +'xslt_set_scheme_handlers' => ['', 'xh'=>'', 'handlers'=>'array'], +'xslt_setopt' => ['', 'processor'=>'', 'newmask'=>'int'], +'XSLTProcessor::getParameter' => ['string|false', 'namespaceuri'=>'string', 'localname'=>'string'], +'XsltProcessor::getSecurityPrefs' => ['int'], +'XSLTProcessor::hasExsltSupport' => ['bool'], +'XSLTProcessor::importStylesheet' => ['bool', 'stylesheet'=>'object'], +'XSLTProcessor::registerPHPFunctions' => ['void', 'restrict='=>'mixed'], +'XSLTProcessor::removeParameter' => ['bool', 'namespaceuri'=>'string', 'localname'=>'string'], +'XSLTProcessor::setParameter' => ['bool', 'namespace'=>'string', 'name'=>'string', 'value'=>'string'], +'XSLTProcessor::setParameter\'1' => ['bool', 'namespace'=>'string', 'options'=>'array'], +'XSLTProcessor::setProfiling' => ['bool', 'filename'=>'string'], +'XsltProcessor::setSecurityPrefs' => ['int', 'securityPrefs'=>'int'], +'XSLTProcessor::transformToDoc' => ['DOMDocument|false', 'doc'=>'DOMNode'], +'XSLTProcessor::transformToURI' => ['int', 'doc'=>'DOMDocument', 'uri'=>'string'], +'XSLTProcessor::transformToXML' => ['string|false', 'doc'=>'DOMDocument'], +'yac::__construct' => ['void', 'prefix='=>'string'], +'yac::__get' => ['mixed', 'key'=>'string'], +'yac::__set' => ['mixed', 'key'=>'string', 'value'=>'mixed'], +'yac::delete' => ['bool', 'keys'=>'string|array', 'ttl='=>'int'], +'yac::dump' => ['mixed', 'num'=>'int'], +'yac::flush' => ['bool'], +'yac::get' => ['mixed', 'key'=>'string|array', 'cas='=>'int'], +'yac::info' => ['array'], +'Yaconf::get' => ['mixed', 'name'=>'string', 'default_value='=>'mixed'], +'Yaconf::has' => ['bool', 'name'=>'string'], +'Yaf\Action_Abstract::__clone' => ['void'], +'Yaf\Action_Abstract::__construct' => ['void', 'request'=>'Yaf\Request_Abstract', 'response'=>'Yaf\Response_Abstract', 'view'=>'Yaf\View_Interface', 'invokeArgs='=>'?array'], +'Yaf\Action_Abstract::display' => ['bool', 'tpl'=>'string', 'parameters='=>'?array'], +'Yaf\Action_Abstract::execute' => ['mixed'], +'Yaf\Action_Abstract::forward' => ['bool', 'module'=>'string', 'controller='=>'string', 'action='=>'string', 'parameters='=>'?array'], +'Yaf\Action_Abstract::getController' => ['Yaf\Controller_Abstract'], +'Yaf\Action_Abstract::getInvokeArg' => ['mixed|null', 'name'=>'string'], +'Yaf\Action_Abstract::getInvokeArgs' => ['array'], +'Yaf\Action_Abstract::getModuleName' => ['string'], +'Yaf\Action_Abstract::getRequest' => ['Yaf\Request_Abstract'], +'Yaf\Action_Abstract::getResponse' => ['Yaf\Response_Abstract'], +'Yaf\Action_Abstract::getView' => ['Yaf\View_Interface'], +'Yaf\Action_Abstract::getViewpath' => ['string'], +'Yaf\Action_Abstract::init' => [''], +'Yaf\Action_Abstract::initView' => ['Yaf\Response_Abstract', 'options='=>'?array'], +'Yaf\Action_Abstract::redirect' => ['bool', 'url'=>'string'], +'Yaf\Action_Abstract::render' => ['string', 'tpl'=>'string', 'parameters='=>'?array'], +'Yaf\Action_Abstract::setViewpath' => ['bool', 'view_directory'=>'string'], +'Yaf\Application::__clone' => ['void'], +'Yaf\Application::__construct' => ['void', 'config'=>'array|string', 'envrion='=>'string'], +'Yaf\Application::__destruct' => ['void'], +'Yaf\Application::__sleep' => ['string[]'], +'Yaf\Application::__wakeup' => ['void'], +'Yaf\Application::app' => ['?Yaf\Application'], +'Yaf\Application::bootstrap' => ['Yaf\Application', 'bootstrap='=>'?Yaf\Bootstrap_Abstract'], +'Yaf\Application::clearLastError' => ['void'], +'Yaf\Application::environ' => ['string'], +'Yaf\Application::execute' => ['void', 'entry'=>'callable', '_='=>'string'], +'Yaf\Application::getAppDirectory' => ['string'], +'Yaf\Application::getConfig' => ['Yaf\Config_Abstract'], +'Yaf\Application::getDispatcher' => ['Yaf\Dispatcher'], +'Yaf\Application::getLastErrorMsg' => ['string'], +'Yaf\Application::getLastErrorNo' => ['int'], +'Yaf\Application::getModules' => ['array'], +'Yaf\Application::run' => ['void'], +'Yaf\Application::setAppDirectory' => ['Yaf\Application', 'directory'=>'string'], +'Yaf\Config\Ini::__construct' => ['void', 'config_file'=>'string', 'section='=>'string'], +'Yaf\Config\Ini::__get' => ['', 'name='=>'mixed'], +'Yaf\Config\Ini::__isset' => ['', 'name'=>'string'], +'Yaf\Config\Ini::__set' => ['void', 'name'=>'', 'value'=>''], +'Yaf\Config\Ini::count' => ['int'], +'Yaf\Config\Ini::current' => ['mixed'], +'Yaf\Config\Ini::get' => ['mixed', 'name='=>'mixed'], +'Yaf\Config\Ini::key' => ['int|string'], +'Yaf\Config\Ini::next' => ['void'], +'Yaf\Config\Ini::offsetExists' => ['bool', 'name'=>'mixed'], +'Yaf\Config\Ini::offsetGet' => ['mixed', 'name'=>'mixed'], +'Yaf\Config\Ini::offsetSet' => ['void', 'name'=>'mixed', 'value'=>'mixed'], +'Yaf\Config\Ini::offsetUnset' => ['void', 'name'=>'mixed'], +'Yaf\Config\Ini::readonly' => ['bool'], +'Yaf\Config\Ini::rewind' => ['void'], +'Yaf\Config\Ini::set' => ['Yaf\Config_Abstract', 'name'=>'string', 'value'=>'mixed'], +'Yaf\Config\Ini::toArray' => ['array'], +'Yaf\Config\Ini::valid' => ['bool'], +'Yaf\Config\Simple::__construct' => ['void', 'array'=>'array', 'readonly='=>'string'], +'Yaf\Config\Simple::__get' => ['', 'name='=>'mixed'], +'Yaf\Config\Simple::__isset' => ['', 'name'=>'string'], +'Yaf\Config\Simple::__set' => ['void', 'name'=>'', 'value'=>''], +'Yaf\Config\Simple::count' => ['int'], +'Yaf\Config\Simple::current' => ['mixed'], +'Yaf\Config\Simple::get' => ['mixed', 'name='=>'mixed'], +'Yaf\Config\Simple::key' => ['int|string'], +'Yaf\Config\Simple::next' => ['void'], +'Yaf\Config\Simple::offsetExists' => ['bool', 'name'=>'mixed'], +'Yaf\Config\Simple::offsetGet' => ['mixed', 'name'=>'mixed'], +'Yaf\Config\Simple::offsetSet' => ['void', 'name'=>'mixed', 'value'=>'mixed'], +'Yaf\Config\Simple::offsetUnset' => ['void', 'name'=>'mixed'], +'Yaf\Config\Simple::readonly' => ['bool'], +'Yaf\Config\Simple::rewind' => ['void'], +'Yaf\Config\Simple::set' => ['Yaf\Config_Abstract', 'name'=>'string', 'value'=>'mixed'], +'Yaf\Config\Simple::toArray' => ['array'], +'Yaf\Config\Simple::valid' => ['bool'], +'Yaf\Config_Abstract::__construct' => ['void'], +'Yaf\Config_Abstract::get' => ['mixed', 'name='=>'string'], +'Yaf\Config_Abstract::readonly' => ['bool'], +'Yaf\Config_Abstract::set' => ['Yaf\Config_Abstract', 'name'=>'string', 'value'=>'mixed'], +'Yaf\Config_Abstract::toArray' => ['array'], +'Yaf\Controller_Abstract::__clone' => ['void'], +'Yaf\Controller_Abstract::__construct' => ['void', 'request'=>'Yaf\Request_Abstract', 'response'=>'Yaf\Response_Abstract', 'view'=>'Yaf\View_Interface', 'invokeArgs='=>'?array'], +'Yaf\Controller_Abstract::display' => ['bool', 'tpl'=>'string', 'parameters='=>'?array'], +'Yaf\Controller_Abstract::forward' => ['bool', 'module'=>'string', 'controller='=>'string', 'action='=>'string', 'parameters='=>'?array'], +'Yaf\Controller_Abstract::getInvokeArg' => ['mixed|null', 'name'=>'string'], +'Yaf\Controller_Abstract::getInvokeArgs' => ['array'], +'Yaf\Controller_Abstract::getModuleName' => ['string'], +'Yaf\Controller_Abstract::getRequest' => ['Yaf\Request_Abstract'], +'Yaf\Controller_Abstract::getResponse' => ['Yaf\Response_Abstract'], +'Yaf\Controller_Abstract::getView' => ['Yaf\View_Interface'], +'Yaf\Controller_Abstract::getViewpath' => ['string'], +'Yaf\Controller_Abstract::init' => [''], +'Yaf\Controller_Abstract::initView' => ['Yaf\Response_Abstract', 'options='=>'?array'], +'Yaf\Controller_Abstract::redirect' => ['bool', 'url'=>'string'], +'Yaf\Controller_Abstract::render' => ['string', 'tpl'=>'string', 'parameters='=>'?array'], +'Yaf\Controller_Abstract::setViewpath' => ['bool', 'view_directory'=>'string'], +'Yaf\Dispatcher::__clone' => ['void'], +'Yaf\Dispatcher::__construct' => ['void'], +'Yaf\Dispatcher::__sleep' => ['list'], +'Yaf\Dispatcher::__wakeup' => ['void'], +'Yaf\Dispatcher::autoRender' => ['Yaf\Dispatcher', 'flag='=>'bool'], +'Yaf\Dispatcher::catchException' => ['Yaf\Dispatcher', 'flag='=>'bool'], +'Yaf\Dispatcher::disableView' => ['bool'], +'Yaf\Dispatcher::dispatch' => ['Yaf\Response_Abstract', 'request'=>'Yaf\Request_Abstract'], +'Yaf\Dispatcher::enableView' => ['Yaf\Dispatcher'], +'Yaf\Dispatcher::flushInstantly' => ['Yaf\Dispatcher', 'flag='=>'bool'], +'Yaf\Dispatcher::getApplication' => ['Yaf\Application'], +'Yaf\Dispatcher::getInstance' => ['Yaf\Dispatcher'], +'Yaf\Dispatcher::getRequest' => ['Yaf\Request_Abstract'], +'Yaf\Dispatcher::getRouter' => ['Yaf\Router'], +'Yaf\Dispatcher::initView' => ['Yaf\View_Interface', 'templates_dir'=>'string', 'options='=>'?array'], +'Yaf\Dispatcher::registerPlugin' => ['Yaf\Dispatcher', 'plugin'=>'Yaf\Plugin_Abstract'], +'Yaf\Dispatcher::returnResponse' => ['Yaf\Dispatcher', 'flag'=>'bool'], +'Yaf\Dispatcher::setDefaultAction' => ['Yaf\Dispatcher', 'action'=>'string'], +'Yaf\Dispatcher::setDefaultController' => ['Yaf\Dispatcher', 'controller'=>'string'], +'Yaf\Dispatcher::setDefaultModule' => ['Yaf\Dispatcher', 'module'=>'string'], +'Yaf\Dispatcher::setErrorHandler' => ['Yaf\Dispatcher', 'callback'=>'callable', 'error_types'=>'int'], +'Yaf\Dispatcher::setRequest' => ['Yaf\Dispatcher', 'request'=>'Yaf\Request_Abstract'], +'Yaf\Dispatcher::setView' => ['Yaf\Dispatcher', 'view'=>'Yaf\View_Interface'], +'Yaf\Dispatcher::throwException' => ['Yaf\Dispatcher', 'flag='=>'bool'], +'Yaf\Loader::__clone' => ['void'], +'Yaf\Loader::__construct' => ['void'], +'Yaf\Loader::__sleep' => ['list'], +'Yaf\Loader::__wakeup' => ['void'], +'Yaf\Loader::autoload' => ['bool', 'class_name'=>'string'], +'Yaf\Loader::clearLocalNamespace' => [''], +'Yaf\Loader::getInstance' => ['Yaf\Loader', 'local_library_path='=>'string', 'global_library_path='=>'string'], +'Yaf\Loader::getLibraryPath' => ['string', 'is_global='=>'bool'], +'Yaf\Loader::getLocalNamespace' => ['string'], +'Yaf\Loader::import' => ['bool', 'file'=>'string'], +'Yaf\Loader::isLocalName' => ['bool', 'class_name'=>'string'], +'Yaf\Loader::registerLocalNamespace' => ['bool', 'name_prefix'=>'string|string[]'], +'Yaf\Loader::setLibraryPath' => ['Yaf\Loader', 'directory'=>'string', 'global='=>'bool'], +'Yaf\Plugin_Abstract::dispatchLoopShutdown' => ['bool', 'request'=>'Yaf\Request_Abstract', 'response'=>'Yaf\Response_Abstract'], +'Yaf\Plugin_Abstract::dispatchLoopStartup' => ['bool', 'request'=>'Yaf\Request_Abstract', 'response'=>'Yaf\Response_Abstract'], +'Yaf\Plugin_Abstract::postDispatch' => ['bool', 'request'=>'Yaf\Request_Abstract', 'response'=>'Yaf\Response_Abstract'], +'Yaf\Plugin_Abstract::preDispatch' => ['bool', 'request'=>'Yaf\Request_Abstract', 'response'=>'Yaf\Response_Abstract'], +'Yaf\Plugin_Abstract::preResponse' => ['bool', 'request'=>'Yaf\Request_Abstract', 'response'=>'Yaf\Response_Abstract'], +'Yaf\Plugin_Abstract::routerShutdown' => ['bool', 'request'=>'Yaf\Request_Abstract', 'response'=>'Yaf\Response_Abstract'], +'Yaf\Plugin_Abstract::routerStartup' => ['bool', 'request'=>'Yaf\Request_Abstract', 'response'=>'Yaf\Response_Abstract'], +'Yaf\Registry::__clone' => ['void'], +'Yaf\Registry::__construct' => ['void'], +'Yaf\Registry::del' => ['bool|void', 'name'=>'string'], +'Yaf\Registry::get' => ['mixed', 'name'=>'string'], +'Yaf\Registry::has' => ['bool', 'name'=>'string'], +'Yaf\Registry::set' => ['bool', 'name'=>'string', 'value'=>'mixed'], +'Yaf\Request\Http::__clone' => ['void'], +'Yaf\Request\Http::__construct' => ['void', 'request_uri'=>'string', 'base_uri'=>'string'], +'Yaf\Request\Http::get' => ['mixed', 'name'=>'string', 'default='=>'string'], +'Yaf\Request\Http::getActionName' => ['string'], +'Yaf\Request\Http::getBaseUri' => ['string'], +'Yaf\Request\Http::getControllerName' => ['string'], +'Yaf\Request\Http::getCookie' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf\Request\Http::getEnv' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf\Request\Http::getException' => ['Yaf\Exception'], +'Yaf\Request\Http::getFiles' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf\Request\Http::getLanguage' => ['string'], +'Yaf\Request\Http::getMethod' => ['string'], +'Yaf\Request\Http::getModuleName' => ['string'], +'Yaf\Request\Http::getParam' => ['mixed', 'name'=>'string', 'default='=>'mixed'], +'Yaf\Request\Http::getParams' => ['array'], +'Yaf\Request\Http::getPost' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf\Request\Http::getQuery' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf\Request\Http::getRequest' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf\Request\Http::getRequestUri' => ['string'], +'Yaf\Request\Http::getServer' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf\Request\Http::isCli' => ['bool'], +'Yaf\Request\Http::isDispatched' => ['bool'], +'Yaf\Request\Http::isGet' => ['bool'], +'Yaf\Request\Http::isHead' => ['bool'], +'Yaf\Request\Http::isOptions' => ['bool'], +'Yaf\Request\Http::isPost' => ['bool'], +'Yaf\Request\Http::isPut' => ['bool'], +'Yaf\Request\Http::isRouted' => ['bool'], +'Yaf\Request\Http::isXmlHttpRequest' => ['bool'], +'Yaf\Request\Http::setActionName' => ['Yaf\Request_Abstract|bool', 'action'=>'string'], +'Yaf\Request\Http::setBaseUri' => ['bool', 'uri'=>'string'], +'Yaf\Request\Http::setControllerName' => ['Yaf\Request_Abstract|bool', 'controller'=>'string'], +'Yaf\Request\Http::setDispatched' => ['bool'], +'Yaf\Request\Http::setModuleName' => ['Yaf\Request_Abstract|bool', 'module'=>'string'], +'Yaf\Request\Http::setParam' => ['Yaf\Request_Abstract|bool', 'name'=>'array|string', 'value='=>'string'], +'Yaf\Request\Http::setRequestUri' => ['', 'uri'=>'string'], +'Yaf\Request\Http::setRouted' => ['Yaf\Request_Abstract|bool'], +'Yaf\Request\Simple::__clone' => ['void'], +'Yaf\Request\Simple::__construct' => ['void', 'method'=>'string', 'controller'=>'string', 'action'=>'string', 'params='=>'string'], +'Yaf\Request\Simple::get' => ['mixed', 'name'=>'string', 'default='=>'string'], +'Yaf\Request\Simple::getActionName' => ['string'], +'Yaf\Request\Simple::getBaseUri' => ['string'], +'Yaf\Request\Simple::getControllerName' => ['string'], +'Yaf\Request\Simple::getCookie' => ['mixed', 'name='=>'string', 'default='=>'string'], +'Yaf\Request\Simple::getEnv' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf\Request\Simple::getException' => ['Yaf\Exception'], +'Yaf\Request\Simple::getFiles' => ['array', 'name='=>'mixed', 'default='=>'null'], +'Yaf\Request\Simple::getLanguage' => ['string'], +'Yaf\Request\Simple::getMethod' => ['string'], +'Yaf\Request\Simple::getModuleName' => ['string'], +'Yaf\Request\Simple::getParam' => ['mixed', 'name'=>'string', 'default='=>'mixed'], +'Yaf\Request\Simple::getParams' => ['array'], +'Yaf\Request\Simple::getPost' => ['mixed', 'name='=>'string', 'default='=>'string'], +'Yaf\Request\Simple::getQuery' => ['mixed', 'name='=>'string', 'default='=>'string'], +'Yaf\Request\Simple::getRequest' => ['mixed', 'name='=>'string', 'default='=>'string'], +'Yaf\Request\Simple::getRequestUri' => ['string'], +'Yaf\Request\Simple::getServer' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf\Request\Simple::isCli' => ['bool'], +'Yaf\Request\Simple::isDispatched' => ['bool'], +'Yaf\Request\Simple::isGet' => ['bool'], +'Yaf\Request\Simple::isHead' => ['bool'], +'Yaf\Request\Simple::isOptions' => ['bool'], +'Yaf\Request\Simple::isPost' => ['bool'], +'Yaf\Request\Simple::isPut' => ['bool'], +'Yaf\Request\Simple::isRouted' => ['bool'], +'Yaf\Request\Simple::isXmlHttpRequest' => ['bool'], +'Yaf\Request\Simple::setActionName' => ['Yaf\Request_Abstract|bool', 'action'=>'string'], +'Yaf\Request\Simple::setBaseUri' => ['bool', 'uri'=>'string'], +'Yaf\Request\Simple::setControllerName' => ['Yaf\Request_Abstract|bool', 'controller'=>'string'], +'Yaf\Request\Simple::setDispatched' => ['bool'], +'Yaf\Request\Simple::setModuleName' => ['Yaf\Request_Abstract|bool', 'module'=>'string'], +'Yaf\Request\Simple::setParam' => ['Yaf\Request_Abstract|bool', 'name'=>'array|string', 'value='=>'string'], +'Yaf\Request\Simple::setRequestUri' => ['', 'uri'=>'string'], +'Yaf\Request\Simple::setRouted' => ['Yaf\Request_Abstract|bool'], +'Yaf\Request_Abstract::getActionName' => ['string'], +'Yaf\Request_Abstract::getBaseUri' => ['string'], +'Yaf\Request_Abstract::getControllerName' => ['string'], +'Yaf\Request_Abstract::getEnv' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf\Request_Abstract::getException' => ['Yaf\Exception'], +'Yaf\Request_Abstract::getLanguage' => ['string'], +'Yaf\Request_Abstract::getMethod' => ['string'], +'Yaf\Request_Abstract::getModuleName' => ['string'], +'Yaf\Request_Abstract::getParam' => ['mixed', 'name'=>'string', 'default='=>'mixed'], +'Yaf\Request_Abstract::getParams' => ['array'], +'Yaf\Request_Abstract::getRequestUri' => ['string'], +'Yaf\Request_Abstract::getServer' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf\Request_Abstract::isCli' => ['bool'], +'Yaf\Request_Abstract::isDispatched' => ['bool'], +'Yaf\Request_Abstract::isGet' => ['bool'], +'Yaf\Request_Abstract::isHead' => ['bool'], +'Yaf\Request_Abstract::isOptions' => ['bool'], +'Yaf\Request_Abstract::isPost' => ['bool'], +'Yaf\Request_Abstract::isPut' => ['bool'], +'Yaf\Request_Abstract::isRouted' => ['bool'], +'Yaf\Request_Abstract::isXmlHttpRequest' => ['bool'], +'Yaf\Request_Abstract::setActionName' => ['Yaf\Request_Abstract|bool', 'action'=>'string'], +'Yaf\Request_Abstract::setBaseUri' => ['bool', 'uri'=>'string'], +'Yaf\Request_Abstract::setControllerName' => ['Yaf\Request_Abstract|bool', 'controller'=>'string'], +'Yaf\Request_Abstract::setDispatched' => ['bool'], +'Yaf\Request_Abstract::setModuleName' => ['Yaf\Request_Abstract|bool', 'module'=>'string'], +'Yaf\Request_Abstract::setParam' => ['Yaf\Request_Abstract|bool', 'name'=>'array|string', 'value='=>'string'], +'Yaf\Request_Abstract::setRequestUri' => ['', 'uri'=>'string'], +'Yaf\Request_Abstract::setRouted' => ['Yaf\Request_Abstract|bool'], +'Yaf\Response\Cli::__clone' => ['void'], +'Yaf\Response\Cli::__construct' => ['void'], +'Yaf\Response\Cli::__destruct' => ['void'], +'Yaf\Response\Cli::__toString' => ['string'], +'Yaf\Response\Cli::appendBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf\Response\Cli::clearBody' => ['bool', 'key='=>'string'], +'Yaf\Response\Cli::getBody' => ['mixed', 'key='=>'?string'], +'Yaf\Response\Cli::prependBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf\Response\Cli::setBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf\Response\Http::__clone' => ['void'], +'Yaf\Response\Http::__construct' => ['void'], +'Yaf\Response\Http::__destruct' => ['void'], +'Yaf\Response\Http::__toString' => ['string'], +'Yaf\Response\Http::appendBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf\Response\Http::clearBody' => ['bool', 'key='=>'string'], +'Yaf\Response\Http::clearHeaders' => ['Yaf\Response_Abstract|false', 'name='=>'string'], +'Yaf\Response\Http::getBody' => ['mixed', 'key='=>'?string'], +'Yaf\Response\Http::getHeader' => ['mixed', 'name='=>'string'], +'Yaf\Response\Http::prependBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf\Response\Http::response' => ['bool'], +'Yaf\Response\Http::setAllHeaders' => ['bool', 'headers'=>'array'], +'Yaf\Response\Http::setBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf\Response\Http::setHeader' => ['bool', 'name'=>'string', 'value'=>'string', 'replace='=>'bool', 'response_code='=>'int'], +'Yaf\Response\Http::setRedirect' => ['bool', 'url'=>'string'], +'Yaf\Response_Abstract::__clone' => ['void'], +'Yaf\Response_Abstract::__construct' => ['void'], +'Yaf\Response_Abstract::__destruct' => ['void'], +'Yaf\Response_Abstract::__toString' => ['void'], +'Yaf\Response_Abstract::appendBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf\Response_Abstract::clearBody' => ['bool', 'key='=>'string'], +'Yaf\Response_Abstract::getBody' => ['mixed', 'key='=>'?string'], +'Yaf\Response_Abstract::prependBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf\Response_Abstract::setBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf\Route\Map::__construct' => ['void', 'controller_prefer='=>'bool', 'delimiter='=>'string'], +'Yaf\Route\Map::assemble' => ['bool', 'info'=>'array', 'query='=>'?array'], +'Yaf\Route\Map::route' => ['bool', 'request'=>'Yaf\Request_Abstract'], +'Yaf\Route\Regex::__construct' => ['void', 'match'=>'string', 'route'=>'array', 'map='=>'?array', 'verify='=>'?array', 'reverse='=>'string'], +'Yaf\Route\Regex::addConfig' => ['Yaf\Router|bool', 'config'=>'Yaf\Config_Abstract'], +'Yaf\Route\Regex::addRoute' => ['Yaf\Router|bool', 'name'=>'string', 'route'=>'Yaf\Route_Interface'], +'Yaf\Route\Regex::assemble' => ['bool', 'info'=>'array', 'query='=>'?array'], +'Yaf\Route\Regex::getCurrentRoute' => ['string'], +'Yaf\Route\Regex::getRoute' => ['Yaf\Route_Interface', 'name'=>'string'], +'Yaf\Route\Regex::getRoutes' => ['Yaf\Route_Interface[]'], +'Yaf\Route\Regex::route' => ['bool', 'request'=>'Yaf\Request_Abstract'], +'Yaf\Route\Rewrite::__construct' => ['void', 'match'=>'string', 'route'=>'array', 'verify='=>'?array', 'reverse='=>'string'], +'Yaf\Route\Rewrite::addConfig' => ['Yaf\Router|bool', 'config'=>'Yaf\Config_Abstract'], +'Yaf\Route\Rewrite::addRoute' => ['Yaf\Router|bool', 'name'=>'string', 'route'=>'Yaf\Route_Interface'], +'Yaf\Route\Rewrite::assemble' => ['bool', 'info'=>'array', 'query='=>'?array'], +'Yaf\Route\Rewrite::getCurrentRoute' => ['string'], +'Yaf\Route\Rewrite::getRoute' => ['Yaf\Route_Interface', 'name'=>'string'], +'Yaf\Route\Rewrite::getRoutes' => ['Yaf\Route_Interface[]'], +'Yaf\Route\Rewrite::route' => ['bool', 'request'=>'Yaf\Request_Abstract'], +'Yaf\Route\Simple::__construct' => ['void', 'module_name'=>'string', 'controller_name'=>'string', 'action_name'=>'string'], +'Yaf\Route\Simple::assemble' => ['bool', 'info'=>'array', 'query='=>'?array'], +'Yaf\Route\Simple::route' => ['bool', 'request'=>'Yaf\Request_Abstract'], +'Yaf\Route\Supervar::__construct' => ['void', 'supervar_name'=>'string'], +'Yaf\Route\Supervar::assemble' => ['bool', 'info'=>'array', 'query='=>'?array'], +'Yaf\Route\Supervar::route' => ['bool', 'request'=>'Yaf\Request_Abstract'], +'Yaf\Route_Interface::__construct' => ['Yaf\Route_Interface'], +'Yaf\Route_Interface::assemble' => ['bool', 'info'=>'array', 'query='=>'?array'], +'Yaf\Route_Interface::route' => ['bool', 'request'=>'Yaf\Request_Abstract'], +'Yaf\Route_Static::assemble' => ['bool', 'info'=>'array', 'query='=>'?array'], +'Yaf\Route_Static::match' => ['bool', 'uri'=>'string'], +'Yaf\Route_Static::route' => ['bool', 'request'=>'Yaf\Request_Abstract'], +'Yaf\Router::__construct' => ['void'], +'Yaf\Router::addConfig' => ['Yaf\Router|false', 'config'=>'Yaf\Config_Abstract'], +'Yaf\Router::addRoute' => ['Yaf\Router|false', 'name'=>'string', 'route'=>'Yaf\Route_Interface'], +'Yaf\Router::getCurrentRoute' => ['string'], +'Yaf\Router::getRoute' => ['Yaf\Route_Interface', 'name'=>'string'], +'Yaf\Router::getRoutes' => ['Yaf\Route_Interface[]'], +'Yaf\Router::route' => ['Yaf\Router|false', 'request'=>'Yaf\Request_Abstract'], +'Yaf\Session::__clone' => ['void'], +'Yaf\Session::__construct' => ['void'], +'Yaf\Session::__get' => ['void', 'name'=>''], +'Yaf\Session::__isset' => ['void', 'name'=>''], +'Yaf\Session::__set' => ['void', 'name'=>'', 'value'=>''], +'Yaf\Session::__sleep' => ['list'], +'Yaf\Session::__unset' => ['void', 'name'=>''], +'Yaf\Session::__wakeup' => ['void'], +'Yaf\Session::count' => ['int'], +'Yaf\Session::current' => ['mixed'], +'Yaf\Session::del' => ['Yaf\Session|false', 'name'=>'string'], +'Yaf\Session::get' => ['mixed', 'name'=>'string'], +'Yaf\Session::getInstance' => ['Yaf\Session'], +'Yaf\Session::has' => ['bool', 'name'=>'string'], +'Yaf\Session::key' => ['int|string'], +'Yaf\Session::next' => ['void'], +'Yaf\Session::offsetExists' => ['bool', 'name'=>'mixed'], +'Yaf\Session::offsetGet' => ['mixed', 'name'=>'mixed'], +'Yaf\Session::offsetSet' => ['void', 'name'=>'mixed', 'value'=>'mixed'], +'Yaf\Session::offsetUnset' => ['void', 'name'=>'mixed'], +'Yaf\Session::rewind' => ['void'], +'Yaf\Session::set' => ['Yaf\Session|false', 'name'=>'string', 'value'=>'mixed'], +'Yaf\Session::start' => ['Yaf\Session'], +'Yaf\Session::valid' => ['bool'], +'Yaf\View\Simple::__construct' => ['void', 'template_dir'=>'string', 'options='=>'?array'], +'Yaf\View\Simple::__get' => ['mixed', 'name='=>'null'], +'Yaf\View\Simple::__isset' => ['', 'name'=>'string'], +'Yaf\View\Simple::__set' => ['void', 'name'=>'string', 'value='=>'mixed'], +'Yaf\View\Simple::assign' => ['Yaf\View\Simple', 'name'=>'array|string', 'value='=>'mixed'], +'Yaf\View\Simple::assignRef' => ['Yaf\View\Simple', 'name'=>'string', '&value'=>'mixed'], +'Yaf\View\Simple::clear' => ['Yaf\View\Simple', 'name='=>'string'], +'Yaf\View\Simple::display' => ['bool', 'tpl'=>'string', 'tpl_vars='=>'?array'], +'Yaf\View\Simple::eval' => ['bool|void', 'tpl_str'=>'string', 'vars='=>'?array'], +'Yaf\View\Simple::getScriptPath' => ['string'], +'Yaf\View\Simple::render' => ['string|void', 'tpl'=>'string', 'tpl_vars='=>'?array'], +'Yaf\View\Simple::setScriptPath' => ['Yaf\View\Simple', 'template_dir'=>'string'], +'Yaf\View_Interface::assign' => ['bool', 'name'=>'array|string', 'value'=>'mixed'], +'Yaf\View_Interface::display' => ['bool', 'tpl'=>'string', 'tpl_vars='=>'?array'], +'Yaf\View_Interface::getScriptPath' => ['string'], +'Yaf\View_Interface::render' => ['string', 'tpl'=>'string', 'tpl_vars='=>'?array'], +'Yaf\View_Interface::setScriptPath' => ['void', 'template_dir'=>'string'], +'Yaf_Action_Abstract::__clone' => ['void'], +'Yaf_Action_Abstract::__construct' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract', 'view'=>'Yaf_View_Interface', 'invokeArgs='=>'?array'], +'Yaf_Action_Abstract::display' => ['bool', 'tpl'=>'string', 'parameters='=>'?array'], +'Yaf_Action_Abstract::execute' => ['mixed', 'arg='=>'mixed', '...args='=>'mixed'], +'Yaf_Action_Abstract::forward' => ['bool', 'module'=>'string', 'controller='=>'string', 'action='=>'string', 'parameters='=>'?array'], +'Yaf_Action_Abstract::getController' => ['Yaf_Controller_Abstract'], +'Yaf_Action_Abstract::getControllerName' => ['string'], +'Yaf_Action_Abstract::getInvokeArg' => ['mixed|null', 'name'=>'string'], +'Yaf_Action_Abstract::getInvokeArgs' => ['array'], +'Yaf_Action_Abstract::getModuleName' => ['string'], +'Yaf_Action_Abstract::getRequest' => ['Yaf_Request_Abstract'], +'Yaf_Action_Abstract::getResponse' => ['Yaf_Response_Abstract'], +'Yaf_Action_Abstract::getView' => ['Yaf_View_Interface'], +'Yaf_Action_Abstract::getViewpath' => ['string'], +'Yaf_Action_Abstract::init' => [''], +'Yaf_Action_Abstract::initView' => ['Yaf_Response_Abstract', 'options='=>'?array'], +'Yaf_Action_Abstract::redirect' => ['bool', 'url'=>'string'], +'Yaf_Action_Abstract::render' => ['string', 'tpl'=>'string', 'parameters='=>'?array'], +'Yaf_Action_Abstract::setViewpath' => ['bool', 'view_directory'=>'string'], +'Yaf_Application::__clone' => ['void'], +'Yaf_Application::__construct' => ['void', 'config'=>'mixed', 'envrion='=>'string'], +'Yaf_Application::__destruct' => ['void'], +'Yaf_Application::__sleep' => ['list'], +'Yaf_Application::__wakeup' => ['void'], +'Yaf_Application::app' => ['?Yaf_Application'], +'Yaf_Application::bootstrap' => ['Yaf_Application', 'bootstrap='=>'Yaf_Bootstrap_Abstract'], +'Yaf_Application::clearLastError' => ['Yaf_Application'], +'Yaf_Application::environ' => ['string'], +'Yaf_Application::execute' => ['void', 'entry'=>'callable', '...args'=>'string'], +'Yaf_Application::getAppDirectory' => ['Yaf_Application'], +'Yaf_Application::getConfig' => ['Yaf_Config_Abstract'], +'Yaf_Application::getDispatcher' => ['Yaf_Dispatcher'], +'Yaf_Application::getLastErrorMsg' => ['string'], +'Yaf_Application::getLastErrorNo' => ['int'], +'Yaf_Application::getModules' => ['array'], +'Yaf_Application::run' => ['void'], +'Yaf_Application::setAppDirectory' => ['Yaf_Application', 'directory'=>'string'], +'Yaf_Config_Abstract::__construct' => ['void'], +'Yaf_Config_Abstract::get' => ['mixed', 'name'=>'string', 'value'=>'mixed'], +'Yaf_Config_Abstract::readonly' => ['bool'], +'Yaf_Config_Abstract::set' => ['Yaf_Config_Abstract'], +'Yaf_Config_Abstract::toArray' => ['array'], +'Yaf_Config_Ini::__construct' => ['void', 'config_file'=>'string', 'section='=>'string'], +'Yaf_Config_Ini::__get' => ['void', 'name='=>'string'], +'Yaf_Config_Ini::__isset' => ['void', 'name'=>'string'], +'Yaf_Config_Ini::__set' => ['void', 'name'=>'string', 'value'=>'mixed'], +'Yaf_Config_Ini::count' => ['void'], +'Yaf_Config_Ini::current' => ['void'], +'Yaf_Config_Ini::get' => ['mixed', 'name='=>'mixed'], +'Yaf_Config_Ini::key' => ['void'], +'Yaf_Config_Ini::next' => ['void'], +'Yaf_Config_Ini::offsetExists' => ['void', 'name'=>'string'], +'Yaf_Config_Ini::offsetGet' => ['void', 'name'=>'string'], +'Yaf_Config_Ini::offsetSet' => ['void', 'name'=>'string', 'value'=>'string'], +'Yaf_Config_Ini::offsetUnset' => ['void', 'name'=>'string'], +'Yaf_Config_Ini::readonly' => ['void'], +'Yaf_Config_Ini::rewind' => ['void'], +'Yaf_Config_Ini::set' => ['Yaf_Config_Abstract', 'name'=>'string', 'value'=>'mixed'], +'Yaf_Config_Ini::toArray' => ['array'], +'Yaf_Config_Ini::valid' => ['void'], +'Yaf_Config_Simple::__construct' => ['void', 'config_file'=>'string', 'section='=>'string'], +'Yaf_Config_Simple::__get' => ['void', 'name='=>'string'], +'Yaf_Config_Simple::__isset' => ['void', 'name'=>'string'], +'Yaf_Config_Simple::__set' => ['void', 'name'=>'string', 'value'=>'string'], +'Yaf_Config_Simple::count' => ['void'], +'Yaf_Config_Simple::current' => ['void'], +'Yaf_Config_Simple::get' => ['mixed', 'name='=>'mixed'], +'Yaf_Config_Simple::key' => ['void'], +'Yaf_Config_Simple::next' => ['void'], +'Yaf_Config_Simple::offsetExists' => ['void', 'name'=>'string'], +'Yaf_Config_Simple::offsetGet' => ['void', 'name'=>'string'], +'Yaf_Config_Simple::offsetSet' => ['void', 'name'=>'string', 'value'=>'string'], +'Yaf_Config_Simple::offsetUnset' => ['void', 'name'=>'string'], +'Yaf_Config_Simple::readonly' => ['void'], +'Yaf_Config_Simple::rewind' => ['void'], +'Yaf_Config_Simple::set' => ['Yaf_Config_Abstract', 'name'=>'string', 'value'=>'mixed'], +'Yaf_Config_Simple::toArray' => ['array'], +'Yaf_Config_Simple::valid' => ['void'], +'Yaf_Controller_Abstract::__clone' => ['void'], +'Yaf_Controller_Abstract::__construct' => ['void'], +'Yaf_Controller_Abstract::display' => ['bool', 'tpl'=>'string', 'parameters='=>'array'], +'Yaf_Controller_Abstract::forward' => ['void', 'action'=>'string', 'parameters='=>'array'], +'Yaf_Controller_Abstract::forward\'1' => ['void', 'controller'=>'string', 'action'=>'string', 'parameters='=>'array'], +'Yaf_Controller_Abstract::forward\'2' => ['void', 'module'=>'string', 'controller'=>'string', 'action'=>'string', 'parameters='=>'array'], +'Yaf_Controller_Abstract::getInvokeArg' => ['void', 'name'=>'string'], +'Yaf_Controller_Abstract::getInvokeArgs' => ['void'], +'Yaf_Controller_Abstract::getModuleName' => ['string'], +'Yaf_Controller_Abstract::getName' => ['string'], +'Yaf_Controller_Abstract::getRequest' => ['Yaf_Request_Abstract'], +'Yaf_Controller_Abstract::getResponse' => ['Yaf_Response_Abstract'], +'Yaf_Controller_Abstract::getView' => ['Yaf_View_Interface'], +'Yaf_Controller_Abstract::getViewpath' => ['void'], +'Yaf_Controller_Abstract::init' => ['void'], +'Yaf_Controller_Abstract::initView' => ['void', 'options='=>'array'], +'Yaf_Controller_Abstract::redirect' => ['bool', 'url'=>'string'], +'Yaf_Controller_Abstract::render' => ['string', 'tpl'=>'string', 'parameters='=>'array'], +'Yaf_Controller_Abstract::setViewpath' => ['void', 'view_directory'=>'string'], +'Yaf_Dispatcher::__clone' => ['void'], +'Yaf_Dispatcher::__construct' => ['void'], +'Yaf_Dispatcher::__sleep' => ['list'], +'Yaf_Dispatcher::__wakeup' => ['void'], +'Yaf_Dispatcher::autoRender' => ['Yaf_Dispatcher', 'flag='=>'bool'], +'Yaf_Dispatcher::catchException' => ['Yaf_Dispatcher', 'flag='=>'bool'], +'Yaf_Dispatcher::disableView' => ['bool'], +'Yaf_Dispatcher::dispatch' => ['Yaf_Response_Abstract', 'request'=>'Yaf_Request_Abstract'], +'Yaf_Dispatcher::enableView' => ['Yaf_Dispatcher'], +'Yaf_Dispatcher::flushInstantly' => ['Yaf_Dispatcher', 'flag='=>'bool'], +'Yaf_Dispatcher::getApplication' => ['Yaf_Application'], +'Yaf_Dispatcher::getDefaultAction' => ['string'], +'Yaf_Dispatcher::getDefaultController' => ['string'], +'Yaf_Dispatcher::getDefaultModule' => ['string'], +'Yaf_Dispatcher::getInstance' => ['Yaf_Dispatcher'], +'Yaf_Dispatcher::getRequest' => ['Yaf_Request_Abstract'], +'Yaf_Dispatcher::getRouter' => ['Yaf_Router'], +'Yaf_Dispatcher::initView' => ['Yaf_View_Interface', 'templates_dir'=>'string', 'options='=>'array'], +'Yaf_Dispatcher::registerPlugin' => ['Yaf_Dispatcher', 'plugin'=>'Yaf_Plugin_Abstract'], +'Yaf_Dispatcher::returnResponse' => ['Yaf_Dispatcher', 'flag'=>'bool'], +'Yaf_Dispatcher::setDefaultAction' => ['Yaf_Dispatcher', 'action'=>'string'], +'Yaf_Dispatcher::setDefaultController' => ['Yaf_Dispatcher', 'controller'=>'string'], +'Yaf_Dispatcher::setDefaultModule' => ['Yaf_Dispatcher', 'module'=>'string'], +'Yaf_Dispatcher::setErrorHandler' => ['Yaf_Dispatcher', 'callback'=>'callable', 'error_types'=>'int'], +'Yaf_Dispatcher::setRequest' => ['Yaf_Dispatcher', 'request'=>'Yaf_Request_Abstract'], +'Yaf_Dispatcher::setView' => ['Yaf_Dispatcher', 'view'=>'Yaf_View_Interface'], +'Yaf_Dispatcher::throwException' => ['Yaf_Dispatcher', 'flag='=>'bool'], +'Yaf_Exception::__construct' => ['void'], +'Yaf_Exception::getPrevious' => ['void'], +'Yaf_Loader::__clone' => ['void'], +'Yaf_Loader::__construct' => ['void'], +'Yaf_Loader::__sleep' => ['list'], +'Yaf_Loader::__wakeup' => ['void'], +'Yaf_Loader::autoload' => ['void'], +'Yaf_Loader::clearLocalNamespace' => ['void'], +'Yaf_Loader::getInstance' => ['Yaf_Loader'], +'Yaf_Loader::getLibraryPath' => ['Yaf_Loader', 'is_global='=>'bool'], +'Yaf_Loader::getLocalNamespace' => ['void'], +'Yaf_Loader::getNamespacePath' => ['string', 'namespaces'=>'string'], +'Yaf_Loader::import' => ['bool'], +'Yaf_Loader::isLocalName' => ['bool'], +'Yaf_Loader::registerLocalNamespace' => ['void', 'prefix'=>'mixed'], +'Yaf_Loader::registerNamespace' => ['bool', 'namespaces'=>'string|array', 'path='=>'string'], +'Yaf_Loader::setLibraryPath' => ['Yaf_Loader', 'directory'=>'string', 'is_global='=>'bool'], +'Yaf_Plugin_Abstract::dispatchLoopShutdown' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'], +'Yaf_Plugin_Abstract::dispatchLoopStartup' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'], +'Yaf_Plugin_Abstract::postDispatch' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'], +'Yaf_Plugin_Abstract::preDispatch' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'], +'Yaf_Plugin_Abstract::preResponse' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'], +'Yaf_Plugin_Abstract::routerShutdown' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'], +'Yaf_Plugin_Abstract::routerStartup' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'], +'Yaf_Registry::__clone' => ['void'], +'Yaf_Registry::__construct' => ['void'], +'Yaf_Registry::del' => ['void', 'name'=>'string'], +'Yaf_Registry::get' => ['mixed', 'name'=>'string'], +'Yaf_Registry::has' => ['bool', 'name'=>'string'], +'Yaf_Registry::set' => ['bool', 'name'=>'string', 'value'=>'string'], +'Yaf_Request_Abstract::clearParams' => ['bool'], +'Yaf_Request_Abstract::getActionName' => ['void'], +'Yaf_Request_Abstract::getBaseUri' => ['void'], +'Yaf_Request_Abstract::getControllerName' => ['void'], +'Yaf_Request_Abstract::getEnv' => ['void', 'name'=>'string', 'default='=>'string'], +'Yaf_Request_Abstract::getException' => ['void'], +'Yaf_Request_Abstract::getLanguage' => ['void'], +'Yaf_Request_Abstract::getMethod' => ['void'], +'Yaf_Request_Abstract::getModuleName' => ['void'], +'Yaf_Request_Abstract::getParam' => ['void', 'name'=>'string', 'default='=>'string'], +'Yaf_Request_Abstract::getParams' => ['void'], +'Yaf_Request_Abstract::getRequestUri' => ['void'], +'Yaf_Request_Abstract::getServer' => ['void', 'name'=>'string', 'default='=>'string'], +'Yaf_Request_Abstract::isCli' => ['void'], +'Yaf_Request_Abstract::isDispatched' => ['void'], +'Yaf_Request_Abstract::isGet' => ['void'], +'Yaf_Request_Abstract::isHead' => ['void'], +'Yaf_Request_Abstract::isOptions' => ['void'], +'Yaf_Request_Abstract::isPost' => ['void'], +'Yaf_Request_Abstract::isPut' => ['void'], +'Yaf_Request_Abstract::isRouted' => ['void'], +'Yaf_Request_Abstract::isXmlHttpRequest' => ['void'], +'Yaf_Request_Abstract::setActionName' => ['void', 'action'=>'string'], +'Yaf_Request_Abstract::setBaseUri' => ['bool', 'uir'=>'string'], +'Yaf_Request_Abstract::setControllerName' => ['void', 'controller'=>'string'], +'Yaf_Request_Abstract::setDispatched' => ['void'], +'Yaf_Request_Abstract::setModuleName' => ['void', 'module'=>'string'], +'Yaf_Request_Abstract::setParam' => ['void', 'name'=>'string', 'value='=>'string'], +'Yaf_Request_Abstract::setRequestUri' => ['void', 'uir'=>'string'], +'Yaf_Request_Abstract::setRouted' => ['void', 'flag='=>'string'], +'Yaf_Request_Http::__clone' => ['void'], +'Yaf_Request_Http::__construct' => ['void'], +'Yaf_Request_Http::get' => ['mixed', 'name'=>'string', 'default='=>'string'], +'Yaf_Request_Http::getActionName' => ['string'], +'Yaf_Request_Http::getBaseUri' => ['string'], +'Yaf_Request_Http::getControllerName' => ['string'], +'Yaf_Request_Http::getCookie' => ['mixed', 'name'=>'string', 'default='=>'string'], +'Yaf_Request_Http::getEnv' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf_Request_Http::getException' => ['Yaf_Exception'], +'Yaf_Request_Http::getFiles' => ['void'], +'Yaf_Request_Http::getLanguage' => ['string'], +'Yaf_Request_Http::getMethod' => ['string'], +'Yaf_Request_Http::getModuleName' => ['string'], +'Yaf_Request_Http::getParam' => ['mixed', 'name'=>'string', 'default='=>'mixed'], +'Yaf_Request_Http::getParams' => ['array'], +'Yaf_Request_Http::getPost' => ['mixed', 'name'=>'string', 'default='=>'string'], +'Yaf_Request_Http::getQuery' => ['mixed', 'name'=>'string', 'default='=>'string'], +'Yaf_Request_Http::getRaw' => ['mixed'], +'Yaf_Request_Http::getRequest' => ['void'], +'Yaf_Request_Http::getRequestUri' => ['string'], +'Yaf_Request_Http::getServer' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf_Request_Http::isCli' => ['bool'], +'Yaf_Request_Http::isDispatched' => ['bool'], +'Yaf_Request_Http::isGet' => ['bool'], +'Yaf_Request_Http::isHead' => ['bool'], +'Yaf_Request_Http::isOptions' => ['bool'], +'Yaf_Request_Http::isPost' => ['bool'], +'Yaf_Request_Http::isPut' => ['bool'], +'Yaf_Request_Http::isRouted' => ['bool'], +'Yaf_Request_Http::isXmlHttpRequest' => ['bool'], +'Yaf_Request_Http::setActionName' => ['Yaf_Request_Abstract|bool', 'action'=>'string'], +'Yaf_Request_Http::setBaseUri' => ['bool', 'uri'=>'string'], +'Yaf_Request_Http::setControllerName' => ['Yaf_Request_Abstract|bool', 'controller'=>'string'], +'Yaf_Request_Http::setDispatched' => ['bool'], +'Yaf_Request_Http::setModuleName' => ['Yaf_Request_Abstract|bool', 'module'=>'string'], +'Yaf_Request_Http::setParam' => ['Yaf_Request_Abstract|bool', 'name'=>'array|string', 'value='=>'string'], +'Yaf_Request_Http::setRequestUri' => ['', 'uri'=>'string'], +'Yaf_Request_Http::setRouted' => ['Yaf_Request_Abstract|bool'], +'Yaf_Request_Simple::__clone' => ['void'], +'Yaf_Request_Simple::__construct' => ['void'], +'Yaf_Request_Simple::get' => ['void'], +'Yaf_Request_Simple::getActionName' => ['string'], +'Yaf_Request_Simple::getBaseUri' => ['string'], +'Yaf_Request_Simple::getControllerName' => ['string'], +'Yaf_Request_Simple::getCookie' => ['void'], +'Yaf_Request_Simple::getEnv' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf_Request_Simple::getException' => ['Yaf_Exception'], +'Yaf_Request_Simple::getFiles' => ['void'], +'Yaf_Request_Simple::getLanguage' => ['string'], +'Yaf_Request_Simple::getMethod' => ['string'], +'Yaf_Request_Simple::getModuleName' => ['string'], +'Yaf_Request_Simple::getParam' => ['mixed', 'name'=>'string', 'default='=>'mixed'], +'Yaf_Request_Simple::getParams' => ['array'], +'Yaf_Request_Simple::getPost' => ['void'], +'Yaf_Request_Simple::getQuery' => ['void'], +'Yaf_Request_Simple::getRequest' => ['void'], +'Yaf_Request_Simple::getRequestUri' => ['string'], +'Yaf_Request_Simple::getServer' => ['mixed', 'name='=>'string', 'default='=>'mixed'], +'Yaf_Request_Simple::isCli' => ['bool'], +'Yaf_Request_Simple::isDispatched' => ['bool'], +'Yaf_Request_Simple::isGet' => ['bool'], +'Yaf_Request_Simple::isHead' => ['bool'], +'Yaf_Request_Simple::isOptions' => ['bool'], +'Yaf_Request_Simple::isPost' => ['bool'], +'Yaf_Request_Simple::isPut' => ['bool'], +'Yaf_Request_Simple::isRouted' => ['bool'], +'Yaf_Request_Simple::isXmlHttpRequest' => ['void'], +'Yaf_Request_Simple::setActionName' => ['Yaf_Request_Abstract|bool', 'action'=>'string'], +'Yaf_Request_Simple::setBaseUri' => ['bool', 'uri'=>'string'], +'Yaf_Request_Simple::setControllerName' => ['Yaf_Request_Abstract|bool', 'controller'=>'string'], +'Yaf_Request_Simple::setDispatched' => ['bool'], +'Yaf_Request_Simple::setModuleName' => ['Yaf_Request_Abstract|bool', 'module'=>'string'], +'Yaf_Request_Simple::setParam' => ['Yaf_Request_Abstract|bool', 'name'=>'array|string', 'value='=>'string'], +'Yaf_Request_Simple::setRequestUri' => ['', 'uri'=>'string'], +'Yaf_Request_Simple::setRouted' => ['Yaf_Request_Abstract|bool'], +'Yaf_Response_Abstract::__clone' => ['void'], +'Yaf_Response_Abstract::__construct' => ['void'], +'Yaf_Response_Abstract::__destruct' => ['void'], +'Yaf_Response_Abstract::__toString' => ['string'], +'Yaf_Response_Abstract::appendBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf_Response_Abstract::clearBody' => ['bool', 'key='=>'string'], +'Yaf_Response_Abstract::clearHeaders' => ['void'], +'Yaf_Response_Abstract::getBody' => ['mixed', 'key='=>'string'], +'Yaf_Response_Abstract::getHeader' => ['void'], +'Yaf_Response_Abstract::prependBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf_Response_Abstract::response' => ['void'], +'Yaf_Response_Abstract::setAllHeaders' => ['void'], +'Yaf_Response_Abstract::setBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf_Response_Abstract::setHeader' => ['void'], +'Yaf_Response_Abstract::setRedirect' => ['void'], +'Yaf_Response_Cli::__clone' => ['void'], +'Yaf_Response_Cli::__construct' => ['void'], +'Yaf_Response_Cli::__destruct' => ['void'], +'Yaf_Response_Cli::__toString' => ['string'], +'Yaf_Response_Cli::appendBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf_Response_Cli::clearBody' => ['bool', 'key='=>'string'], +'Yaf_Response_Cli::getBody' => ['mixed', 'key='=>'?string'], +'Yaf_Response_Cli::prependBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf_Response_Cli::setBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf_Response_Http::__clone' => ['void'], +'Yaf_Response_Http::__construct' => ['void'], +'Yaf_Response_Http::__destruct' => ['void'], +'Yaf_Response_Http::__toString' => ['string'], +'Yaf_Response_Http::appendBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf_Response_Http::clearBody' => ['bool', 'key='=>'string'], +'Yaf_Response_Http::clearHeaders' => ['Yaf_Response_Abstract|false', 'name='=>'string'], +'Yaf_Response_Http::getBody' => ['mixed', 'key='=>'?string'], +'Yaf_Response_Http::getHeader' => ['mixed', 'name='=>'string'], +'Yaf_Response_Http::prependBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf_Response_Http::response' => ['bool'], +'Yaf_Response_Http::setAllHeaders' => ['bool', 'headers'=>'array'], +'Yaf_Response_Http::setBody' => ['bool', 'content'=>'string', 'key='=>'string'], +'Yaf_Response_Http::setHeader' => ['bool', 'name'=>'string', 'value'=>'string', 'replace='=>'bool', 'response_code='=>'int'], +'Yaf_Response_Http::setRedirect' => ['bool', 'url'=>'string'], +'Yaf_Route_Interface::__construct' => ['void'], +'Yaf_Route_Interface::assemble' => ['string', 'info'=>'array', 'query='=>'array'], +'Yaf_Route_Interface::route' => ['bool', 'request'=>'Yaf_Request_Abstract'], +'Yaf_Route_Map::__construct' => ['void', 'controller_prefer='=>'string', 'delimiter='=>'string'], +'Yaf_Route_Map::assemble' => ['string', 'info'=>'array', 'query='=>'array'], +'Yaf_Route_Map::route' => ['bool', 'request'=>'Yaf_Request_Abstract'], +'Yaf_Route_Regex::__construct' => ['void', 'match'=>'string', 'route'=>'array', 'map='=>'array', 'verify='=>'array', 'reverse='=>'string'], +'Yaf_Route_Regex::addConfig' => ['Yaf_Router|bool', 'config'=>'Yaf_Config_Abstract'], +'Yaf_Route_Regex::addRoute' => ['Yaf_Router|bool', 'name'=>'string', 'route'=>'Yaf_Route_Interface'], +'Yaf_Route_Regex::assemble' => ['string', 'info'=>'array', 'query='=>'array'], +'Yaf_Route_Regex::getCurrentRoute' => ['string'], +'Yaf_Route_Regex::getRoute' => ['Yaf_Route_Interface', 'name'=>'string'], +'Yaf_Route_Regex::getRoutes' => ['Yaf_Route_Interface[]'], +'Yaf_Route_Regex::route' => ['bool', 'request'=>'Yaf_Request_Abstract'], +'Yaf_Route_Rewrite::__construct' => ['void', 'match'=>'string', 'route'=>'array', 'verify='=>'array'], +'Yaf_Route_Rewrite::addConfig' => ['Yaf_Router|bool', 'config'=>'Yaf_Config_Abstract'], +'Yaf_Route_Rewrite::addRoute' => ['Yaf_Router|bool', 'name'=>'string', 'route'=>'Yaf_Route_Interface'], +'Yaf_Route_Rewrite::assemble' => ['string', 'info'=>'array', 'query='=>'array'], +'Yaf_Route_Rewrite::getCurrentRoute' => ['string'], +'Yaf_Route_Rewrite::getRoute' => ['Yaf_Route_Interface', 'name'=>'string'], +'Yaf_Route_Rewrite::getRoutes' => ['Yaf_Route_Interface[]'], +'Yaf_Route_Rewrite::route' => ['bool', 'request'=>'Yaf_Request_Abstract'], +'Yaf_Route_Simple::__construct' => ['void', 'module_name'=>'string', 'controller_name'=>'string', 'action_name'=>'string'], +'Yaf_Route_Simple::assemble' => ['string', 'info'=>'array', 'query='=>'array'], +'Yaf_Route_Simple::route' => ['bool', 'request'=>'Yaf_Request_Abstract'], +'Yaf_Route_Static::assemble' => ['string', 'info'=>'array', 'query='=>'array'], +'Yaf_Route_Static::match' => ['void', 'uri'=>'string'], +'Yaf_Route_Static::route' => ['bool', 'request'=>'Yaf_Request_Abstract'], +'Yaf_Route_Supervar::__construct' => ['void', 'supervar_name'=>'string'], +'Yaf_Route_Supervar::assemble' => ['string', 'info'=>'array', 'query='=>'array'], +'Yaf_Route_Supervar::route' => ['bool', 'request'=>'Yaf_Request_Abstract'], +'Yaf_Router::__construct' => ['void'], +'Yaf_Router::addConfig' => ['bool', 'config'=>'Yaf_Config_Abstract'], +'Yaf_Router::addRoute' => ['bool', 'name'=>'string', 'route'=>'Yaf_Route_Interface'], +'Yaf_Router::getCurrentRoute' => ['string'], +'Yaf_Router::getRoute' => ['Yaf_Route_Interface', 'name'=>'string'], +'Yaf_Router::getRoutes' => ['mixed'], +'Yaf_Router::route' => ['bool', 'request'=>'Yaf_Request_Abstract'], +'Yaf_Session::__clone' => ['void'], +'Yaf_Session::__construct' => ['void'], +'Yaf_Session::__get' => ['void', 'name'=>'string'], +'Yaf_Session::__isset' => ['void', 'name'=>'string'], +'Yaf_Session::__set' => ['void', 'name'=>'string', 'value'=>'string'], +'Yaf_Session::__sleep' => ['list'], +'Yaf_Session::__unset' => ['void', 'name'=>'string'], +'Yaf_Session::__wakeup' => ['void'], +'Yaf_Session::count' => ['void'], +'Yaf_Session::current' => ['void'], +'Yaf_Session::del' => ['void', 'name'=>'string'], +'Yaf_Session::get' => ['mixed', 'name'=>'string'], +'Yaf_Session::getInstance' => ['void'], +'Yaf_Session::has' => ['void', 'name'=>'string'], +'Yaf_Session::key' => ['void'], +'Yaf_Session::next' => ['void'], +'Yaf_Session::offsetExists' => ['void', 'name'=>'string'], +'Yaf_Session::offsetGet' => ['void', 'name'=>'string'], +'Yaf_Session::offsetSet' => ['void', 'name'=>'string', 'value'=>'string'], +'Yaf_Session::offsetUnset' => ['void', 'name'=>'string'], +'Yaf_Session::rewind' => ['void'], +'Yaf_Session::set' => ['Yaf_Session|bool', 'name'=>'string', 'value'=>'mixed'], +'Yaf_Session::start' => ['void'], +'Yaf_Session::valid' => ['void'], +'Yaf_View_Interface::assign' => ['bool', 'name'=>'string', 'value='=>'string'], +'Yaf_View_Interface::display' => ['bool', 'tpl'=>'string', 'tpl_vars='=>'array'], +'Yaf_View_Interface::getScriptPath' => ['string'], +'Yaf_View_Interface::render' => ['string', 'tpl'=>'string', 'tpl_vars='=>'array'], +'Yaf_View_Interface::setScriptPath' => ['void', 'template_dir'=>'string'], +'Yaf_View_Simple::__construct' => ['void', 'tempalte_dir'=>'string', 'options='=>'array'], +'Yaf_View_Simple::__get' => ['void', 'name='=>'string'], +'Yaf_View_Simple::__isset' => ['void', 'name'=>'string'], +'Yaf_View_Simple::__set' => ['void', 'name'=>'string', 'value'=>'mixed'], +'Yaf_View_Simple::assign' => ['bool', 'name'=>'string', 'value='=>'mixed'], +'Yaf_View_Simple::assignRef' => ['bool', 'name'=>'string', '&rw_value'=>'mixed'], +'Yaf_View_Simple::clear' => ['bool', 'name='=>'string'], +'Yaf_View_Simple::display' => ['bool', 'tpl'=>'string', 'tpl_vars='=>'array'], +'Yaf_View_Simple::eval' => ['string', 'tpl_content'=>'string', 'tpl_vars='=>'array'], +'Yaf_View_Simple::getScriptPath' => ['string'], +'Yaf_View_Simple::render' => ['string', 'tpl'=>'string', 'tpl_vars='=>'array'], +'Yaf_View_Simple::setScriptPath' => ['bool', 'template_dir'=>'string'], +'yaml_emit' => ['string', 'data'=>'mixed', 'encoding='=>'int', 'linebreak='=>'int'], +'yaml_emit_file' => ['bool', 'filename'=>'string', 'data'=>'mixed', 'encoding='=>'int', 'linebreak='=>'int'], +'yaml_parse' => ['mixed|false', 'input'=>'string', 'pos='=>'int', '&w_ndocs='=>'int', 'callbacks='=>'array'], +'yaml_parse_file' => ['mixed|false', 'filename'=>'string', 'pos='=>'int', '&w_ndocs='=>'int', 'callbacks='=>'array'], +'yaml_parse_url' => ['mixed|false', 'url'=>'string', 'pos='=>'int', '&w_ndocs='=>'int', 'callbacks='=>'array'], +'Yar_Client::__call' => ['void', 'method'=>'string', 'parameters'=>'array'], +'Yar_Client::__construct' => ['void', 'url'=>'string'], +'Yar_Client::setOpt' => ['Yar_Client|false', 'name'=>'int', 'value'=>'mixed'], +'Yar_Client_Exception::__clone' => ['void'], +'Yar_Client_Exception::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'Yar_Client_Exception::__toString' => ['string'], +'Yar_Client_Exception::__wakeup' => ['void'], +'Yar_Client_Exception::getCode' => ['int'], +'Yar_Client_Exception::getFile' => ['string'], +'Yar_Client_Exception::getLine' => ['int'], +'Yar_Client_Exception::getMessage' => ['string'], +'Yar_Client_Exception::getPrevious' => ['?Exception|?Throwable'], +'Yar_Client_Exception::getTrace' => ['list>'], +'Yar_Client_Exception::getTraceAsString' => ['string'], +'Yar_Client_Exception::getType' => ['string'], +'Yar_Concurrent_Client::call' => ['int', 'uri'=>'string', 'method'=>'string', 'parameters'=>'array', 'callback='=>'callable'], +'Yar_Concurrent_Client::loop' => ['bool', 'callback='=>'callable', 'error_callback='=>'callable'], +'Yar_Concurrent_Client::reset' => ['bool'], +'Yar_Server::__construct' => ['void', 'object'=>'Object'], +'Yar_Server::handle' => ['bool'], +'Yar_Server_Exception::__clone' => ['void'], +'Yar_Server_Exception::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Exception|?Throwable'], +'Yar_Server_Exception::__toString' => ['string'], +'Yar_Server_Exception::__wakeup' => ['void'], +'Yar_Server_Exception::getCode' => ['int'], +'Yar_Server_Exception::getFile' => ['string'], +'Yar_Server_Exception::getLine' => ['int'], +'Yar_Server_Exception::getMessage' => ['string'], +'Yar_Server_Exception::getPrevious' => ['?Exception|?Throwable'], +'Yar_Server_Exception::getTrace' => ['list>'], +'Yar_Server_Exception::getTraceAsString' => ['string'], +'Yar_Server_Exception::getType' => ['string'], +'yaz_addinfo' => ['string', 'id'=>'resource'], +'yaz_ccl_conf' => ['void', 'id'=>'resource', 'config'=>'array'], +'yaz_ccl_parse' => ['bool', 'id'=>'resource', 'query'=>'string', '&w_result'=>'array'], +'yaz_close' => ['bool', 'id'=>'resource'], +'yaz_connect' => ['mixed', 'zurl'=>'string', 'options='=>'mixed'], +'yaz_database' => ['bool', 'id'=>'resource', 'databases'=>'string'], +'yaz_element' => ['bool', 'id'=>'resource', 'elementset'=>'string'], +'yaz_errno' => ['int', 'id'=>'resource'], +'yaz_error' => ['string', 'id'=>'resource'], +'yaz_es' => ['void', 'id'=>'resource', 'type'=>'string', 'args'=>'array'], +'yaz_es_result' => ['array', 'id'=>'resource'], +'yaz_get_option' => ['string', 'id'=>'resource', 'name'=>'string'], +'yaz_hits' => ['int', 'id'=>'resource', 'searchresult='=>'array'], +'yaz_itemorder' => ['void', 'id'=>'resource', 'args'=>'array'], +'yaz_present' => ['bool', 'id'=>'resource'], +'yaz_range' => ['void', 'id'=>'resource', 'start'=>'int', 'number'=>'int'], +'yaz_record' => ['string', 'id'=>'resource', 'pos'=>'int', 'type'=>'string'], +'yaz_scan' => ['void', 'id'=>'resource', 'type'=>'string', 'startterm'=>'string', 'flags='=>'array'], +'yaz_scan_result' => ['array', 'id'=>'resource', 'result='=>'array'], +'yaz_schema' => ['void', 'id'=>'resource', 'schema'=>'string'], +'yaz_search' => ['bool', 'id'=>'resource', 'type'=>'string', 'query'=>'string'], +'yaz_set_option' => ['', 'id'=>'', 'name'=>'string', 'value'=>'string', 'options'=>'array'], +'yaz_sort' => ['void', 'id'=>'resource', 'criteria'=>'string'], +'yaz_syntax' => ['void', 'id'=>'resource', 'syntax'=>'string'], +'yaz_wait' => ['mixed', '&rw_options='=>'array'], +'yp_all' => ['void', 'domain'=>'string', 'map'=>'string', 'callback'=>'string'], +'yp_cat' => ['array', 'domain'=>'string', 'map'=>'string'], +'yp_err_string' => ['string', 'errorcode'=>'int'], +'yp_errno' => ['int'], +'yp_first' => ['array', 'domain'=>'string', 'map'=>'string'], +'yp_get_default_domain' => ['string'], +'yp_master' => ['string', 'domain'=>'string', 'map'=>'string'], +'yp_match' => ['string', 'domain'=>'string', 'map'=>'string', 'key'=>'string'], +'yp_next' => ['array', 'domain'=>'string', 'map'=>'string', 'key'=>'string'], +'yp_order' => ['int', 'domain'=>'string', 'map'=>'string'], +'zem_get_extension_info_by_id' => [''], +'zem_get_extension_info_by_name' => [''], +'zem_get_extensions_info' => [''], +'zem_get_license_info' => [''], +'zend_current_obfuscation_level' => ['int'], +'zend_disk_cache_clear' => ['bool', 'namespace='=>'mixed|string'], +'zend_disk_cache_delete' => ['mixed|null', 'key'=>'string'], +'zend_disk_cache_fetch' => ['mixed|null', 'key'=>'string'], +'zend_disk_cache_store' => ['bool', 'key'=>'', 'value'=>'', 'ttl='=>'int|mixed'], +'zend_get_id' => ['array', 'all_ids='=>'all_ids|false'], +'zend_is_configuration_changed' => [''], +'zend_loader_current_file' => ['string'], +'zend_loader_enabled' => ['bool'], +'zend_loader_file_encoded' => ['bool'], +'zend_loader_file_licensed' => ['array'], +'zend_loader_install_license' => ['bool', 'license_file'=>'string', 'override'=>'bool'], +'zend_logo_guid' => ['string'], +'zend_obfuscate_class_name' => ['string', 'class_name'=>'string'], +'zend_obfuscate_function_name' => ['string', 'function_name'=>'string'], +'zend_optimizer_version' => ['string'], +'zend_runtime_obfuscate' => ['void'], +'zend_send_buffer' => ['null|false', 'buffer'=>'string', 'mime_type='=>'string', 'custom_headers='=>'string'], +'zend_send_file' => ['null|false', 'filename'=>'string', 'mime_type='=>'string', 'custom_headers='=>'string'], +'zend_set_configuration_changed' => [''], +'zend_shm_cache_clear' => ['bool', 'namespace='=>'mixed|string'], +'zend_shm_cache_delete' => ['mixed|null', 'key'=>'string'], +'zend_shm_cache_fetch' => ['mixed|null', 'key'=>'string'], +'zend_shm_cache_store' => ['bool', 'key'=>'', 'value'=>'', 'ttl='=>'int|mixed'], +'zend_thread_id' => ['int'], +'zend_version' => ['string'], +'ZendAPI_Job::addJobToQueue' => ['int', 'jobqueue_url'=>'string', 'password'=>'string'], +'ZendAPI_Job::getApplicationID' => [''], +'ZendAPI_Job::getEndTime' => [''], +'ZendAPI_Job::getGlobalVariables' => [''], +'ZendAPI_Job::getHost' => [''], +'ZendAPI_Job::getID' => [''], +'ZendAPI_Job::getInterval' => [''], +'ZendAPI_Job::getJobDependency' => [''], +'ZendAPI_Job::getJobName' => [''], +'ZendAPI_Job::getJobPriority' => [''], +'ZendAPI_Job::getJobStatus' => ['int'], +'ZendAPI_Job::getLastPerformedStatus' => ['int'], +'ZendAPI_Job::getOutput' => ['An'], +'ZendAPI_Job::getPreserved' => [''], +'ZendAPI_Job::getProperties' => ['array'], +'ZendAPI_Job::getScheduledTime' => [''], +'ZendAPI_Job::getScript' => [''], +'ZendAPI_Job::getTimeToNextRepeat' => ['int'], +'ZendAPI_Job::getUserVariables' => [''], +'ZendAPI_Job::setApplicationID' => ['', 'app_id'=>''], +'ZendAPI_Job::setGlobalVariables' => ['', 'vars'=>''], +'ZendAPI_Job::setJobDependency' => ['', 'job_id'=>''], +'ZendAPI_Job::setJobName' => ['', 'name'=>''], +'ZendAPI_Job::setJobPriority' => ['', 'priority'=>'int'], +'ZendAPI_Job::setPreserved' => ['', 'preserved'=>''], +'ZendAPI_Job::setRecurrenceData' => ['', 'interval'=>'', 'end_time='=>'mixed'], +'ZendAPI_Job::setScheduledTime' => ['', 'timestamp'=>''], +'ZendAPI_Job::setScript' => ['', 'script'=>''], +'ZendAPI_Job::setUserVariables' => ['', 'vars'=>''], +'ZendAPI_Job::ZendAPI_Job' => ['Job', 'script'=>'script'], +'ZendAPI_Queue::addJob' => ['int', '&job'=>'Job'], +'ZendAPI_Queue::getAllApplicationIDs' => ['array'], +'ZendAPI_Queue::getAllhosts' => ['array'], +'ZendAPI_Queue::getHistoricJobs' => ['array', 'status'=>'int', 'start_time'=>'', 'end_time'=>'', 'index'=>'int', 'count'=>'int', '&total'=>'int'], +'ZendAPI_Queue::getJob' => ['Job', 'job_id'=>'int'], +'ZendAPI_Queue::getJobsInQueue' => ['array', 'filter_options='=>'array', 'max_jobs='=>'int', 'with_globals_and_output='=>'bool'], +'ZendAPI_Queue::getLastError' => ['string'], +'ZendAPI_Queue::getNumOfJobsInQueue' => ['int', 'filter_options='=>'array'], +'ZendAPI_Queue::getStatistics' => ['array'], +'ZendAPI_Queue::isScriptExists' => ['bool', 'path'=>'string'], +'ZendAPI_Queue::isSuspend' => ['bool'], +'ZendAPI_Queue::login' => ['bool', 'password'=>'string', 'application_id='=>'int'], +'ZendAPI_Queue::removeJob' => ['bool', 'job_id'=>'array|int'], +'ZendAPI_Queue::requeueJob' => ['bool', 'job'=>'Job'], +'ZendAPI_Queue::resumeJob' => ['bool', 'job_id'=>'array|int'], +'ZendAPI_Queue::resumeQueue' => ['bool'], +'ZendAPI_Queue::setMaxHistoryTime' => ['bool'], +'ZendAPI_Queue::suspendJob' => ['bool', 'job_id'=>'array|int'], +'ZendAPI_Queue::suspendQueue' => ['bool'], +'ZendAPI_Queue::updateJob' => ['int', '&job'=>'Job'], +'ZendAPI_Queue::zendapi_queue' => ['ZendAPI_Queue', 'queue_url'=>'string'], +'zip_close' => ['void', 'zip'=>'resource'], +'zip_entry_close' => ['bool', 'zip_ent'=>'resource'], +'zip_entry_compressedsize' => ['int', 'zip_entry'=>'resource'], +'zip_entry_compressionmethod' => ['string', 'zip_entry'=>'resource'], +'zip_entry_filesize' => ['int', 'zip_entry'=>'resource'], +'zip_entry_name' => ['string', 'zip_entry'=>'resource'], +'zip_entry_open' => ['bool', 'zip_dp'=>'resource', 'zip_entry'=>'resource', 'mode='=>'string'], +'zip_entry_read' => ['string|false', 'zip_entry'=>'resource', 'length='=>'int'], +'zip_open' => ['resource|int|false', 'filename'=>'string'], +'zip_read' => ['resource', 'zip'=>'resource'], +'ZipArchive::addEmptyDir' => ['bool', 'dirname'=>'string'], +'ZipArchive::addFile' => ['bool', 'filepath'=>'string', 'entryname='=>'string', 'start='=>'int', 'length='=>'int'], +'ZipArchive::addFromString' => ['bool', 'entryname'=>'string', 'content'=>'string'], +'ZipArchive::addGlob' => ['bool', 'pattern'=>'string', 'flags='=>'int', 'options='=>'array'], +'ZipArchive::addPattern' => ['bool', 'pattern'=>'string', 'path='=>'string', 'options='=>'array'], +'ZipArchive::close' => ['bool'], +'ZipArchive::count' => ['int'], +'ZipArchive::createEmptyDir' => ['bool', 'dirname'=>'string'], +'ZipArchive::deleteIndex' => ['bool', 'index'=>'int'], +'ZipArchive::deleteName' => ['bool', 'name'=>'string'], +'ZipArchive::extractTo' => ['bool', 'pathto'=>'string', 'files='=>'string[]|string'], +'ZipArchive::getArchiveComment' => ['string|false', 'flags='=>'int'], +'ZipArchive::getCommentIndex' => ['string|false', 'index'=>'int', 'flags='=>'int'], +'ZipArchive::getCommentName' => ['string|false', 'name'=>'string', 'flags='=>'int'], +'ZipArchive::getExternalAttributesIndex' => ['bool', 'index'=>'int', '&w_opsys'=>'int', '&w_attr'=>'int', 'flags='=>'int'], +'ZipArchive::getExternalAttributesName' => ['bool', 'name'=>'string', '&w_opsys'=>'int', '&w_attr'=>'int', 'flags='=>'int'], +'ZipArchive::getFromIndex' => ['string|false', 'index'=>'int', 'length='=>'int', 'flags='=>'int'], +'ZipArchive::getFromName' => ['string|false', 'entryname'=>'string', 'length='=>'int', 'flags='=>'int'], +'ZipArchive::getNameIndex' => ['string|false', 'index'=>'int', 'flags='=>'int'], +'ZipArchive::getStatusString' => ['string|false'], +'ZipArchive::getStream' => ['resource|false', 'entryname'=>'string'], +'ZipArchive::isCompressionMethodSupported' => ['bool', 'method'=>'int', 'encode='=>'bool'], +'ZipArchive::isEncryptionMethodSupported' => ['bool', 'method'=>'int', 'encode='=>'bool'], +'ZipArchive::locateName' => ['int|false', 'filename'=>'string', 'flags='=>'int'], +'ZipArchive::open' => ['int|bool', 'source'=>'string', 'flags='=>'int'], +'ZipArchive::registerCancelCallback' => ['bool', 'callback'=>'callable'], +'ZipArchive::registerProgressCallback' => ['bool', 'rate'=>'float', 'callback'=>'callable'], +'ZipArchive::renameIndex' => ['bool', 'index'=>'int', 'new_name'=>'string'], +'ZipArchive::renameName' => ['bool', 'name'=>'string', 'new_name'=>'string'], +'ZipArchive::replaceFile' => ['bool', 'filename'=>'string', 'index'=>'int', 'start='=>'int', 'length='=>'int', 'flags='=>'int'], +'ZipArchive::setArchiveComment' => ['bool', 'comment'=>'string'], +'ZipArchive::setCommentIndex' => ['bool', 'index'=>'int', 'comment'=>'string'], +'ZipArchive::setCommentName' => ['bool', 'name'=>'string', 'comment'=>'string'], +'ZipArchive::setCompressionIndex' => ['bool', 'index'=>'int', 'comp_method'=>'int', 'comp_flags='=>'int'], +'ZipArchive::setCompressionName' => ['bool', 'name'=>'string', 'comp_method'=>'int', 'comp_flags='=>'int'], +'ZipArchive::setEncryptionIndex' => ['bool', 'index'=>'int', 'method'=>'string', 'password='=>'string'], +'ZipArchive::setEncryptionName' => ['bool', 'name'=>'string', 'method'=>'int', 'password='=>'string'], +'ZipArchive::setExternalAttributesIndex' => ['bool', 'index'=>'int', 'opsys'=>'int', 'attr'=>'int', 'flags='=>'int'], +'ZipArchive::setExternalAttributesName' => ['bool', 'name'=>'string', 'opsys'=>'int', 'attr'=>'int', 'flags='=>'int'], +'ZipArchive::setMtimeIndex' => ['bool', 'index'=>'int', 'timestamp'=>'int', 'flags='=>'int'], +'ZipArchive::setMtimeName' => ['bool', 'name'=>'string', 'timestamp'=>'int', 'flags='=>'int'], +'ZipArchive::setPassword' => ['bool', 'password'=>'string'], +'ZipArchive::statIndex' => ['array|false', 'index'=>'int', 'flags='=>'int'], +'ZipArchive::statName' => ['array|false', 'filename'=>'string', 'flags='=>'int'], +'ZipArchive::unchangeAll' => ['bool'], +'ZipArchive::unchangeArchive' => ['bool'], +'ZipArchive::unchangeIndex' => ['bool', 'index'=>'int'], +'ZipArchive::unchangeName' => ['bool', 'name'=>'string'], +'zlib_decode' => ['string|false', 'data'=>'string', 'max_decoded_len='=>'int'], +'zlib_encode' => ['string', 'data'=>'string', 'encoding'=>'int', 'level='=>'string|int'], +'zlib_get_coding_type' => ['string|false'], +'ZMQ::__construct' => ['void'], +'ZMQContext::__construct' => ['void', 'io_threads='=>'int', 'is_persistent='=>'bool'], +'ZMQContext::getOpt' => ['int|string', 'key'=>'string'], +'ZMQContext::getSocket' => ['ZMQSocket', 'type'=>'int', 'persistent_id='=>'string', 'on_new_socket='=>'callable'], +'ZMQContext::isPersistent' => ['bool'], +'ZMQContext::setOpt' => ['ZMQContext', 'key'=>'int', 'value'=>'mixed'], +'ZMQDevice::__construct' => ['void', 'frontend'=>'ZMQSocket', 'backend'=>'ZMQSocket', 'listener='=>'ZMQSocket'], +'ZMQDevice::getIdleTimeout' => ['ZMQDevice'], +'ZMQDevice::getTimerTimeout' => ['ZMQDevice'], +'ZMQDevice::run' => ['void'], +'ZMQDevice::setIdleCallback' => ['ZMQDevice', 'cb_func'=>'callable', 'timeout'=>'int', 'user_data='=>'mixed'], +'ZMQDevice::setIdleTimeout' => ['ZMQDevice', 'timeout'=>'int'], +'ZMQDevice::setTimerCallback' => ['ZMQDevice', 'cb_func'=>'callable', 'timeout'=>'int', 'user_data='=>'mixed'], +'ZMQDevice::setTimerTimeout' => ['ZMQDevice', 'timeout'=>'int'], +'ZMQPoll::add' => ['string', 'entry'=>'mixed', 'type'=>'int'], +'ZMQPoll::clear' => ['ZMQPoll'], +'ZMQPoll::count' => ['int'], +'ZMQPoll::getLastErrors' => ['array'], +'ZMQPoll::poll' => ['int', '&w_readable'=>'array', '&w_writable'=>'array', 'timeout='=>'int'], +'ZMQPoll::remove' => ['bool', 'item'=>'mixed'], +'ZMQSocket::__construct' => ['void', 'context'=>'ZMQContext', 'type'=>'int', 'persistent_id='=>'string', 'on_new_socket='=>'callable'], +'ZMQSocket::bind' => ['ZMQSocket', 'dsn'=>'string', 'force='=>'bool'], +'ZMQSocket::connect' => ['ZMQSocket', 'dsn'=>'string', 'force='=>'bool'], +'ZMQSocket::disconnect' => ['ZMQSocket', 'dsn'=>'string'], +'ZMQSocket::getEndpoints' => ['array'], +'ZMQSocket::getPersistentId' => ['?string'], +'ZMQSocket::getSocketType' => ['int'], +'ZMQSocket::getSockOpt' => ['int|string', 'key'=>'string'], +'ZMQSocket::isPersistent' => ['bool'], +'ZMQSocket::recv' => ['string', 'mode='=>'int'], +'ZMQSocket::recvMulti' => ['string[]', 'mode='=>'int'], +'ZMQSocket::send' => ['ZMQSocket', 'message'=>'array', 'mode='=>'int'], +'ZMQSocket::send\'1' => ['ZMQSocket', 'message'=>'string', 'mode='=>'int'], +'ZMQSocket::sendmulti' => ['ZMQSocket', 'message'=>'array', 'mode='=>'int'], +'ZMQSocket::setSockOpt' => ['ZMQSocket', 'key'=>'int', 'value'=>'mixed'], +'ZMQSocket::unbind' => ['ZMQSocket', 'dsn'=>'string'], +'Zookeeper::addAuth' => ['bool', 'scheme'=>'string', 'cert'=>'string', 'completion_cb='=>'callable'], +'Zookeeper::close' => ['void'], +'Zookeeper::connect' => ['void', 'host'=>'string', 'watcher_cb='=>'callable', 'recv_timeout='=>'int'], +'Zookeeper::create' => ['string', 'path'=>'string', 'value'=>'string', 'acls'=>'array', 'flags='=>'int'], +'Zookeeper::delete' => ['bool', 'path'=>'string', 'version='=>'int'], +'Zookeeper::exists' => ['bool', 'path'=>'string', 'watcher_cb='=>'callable'], +'Zookeeper::get' => ['string', 'path'=>'string', 'watcher_cb='=>'callable', 'stat='=>'array', 'max_size='=>'int'], +'Zookeeper::getAcl' => ['array', 'path'=>'string'], +'Zookeeper::getChildren' => ['array|false', 'path'=>'string', 'watcher_cb='=>'callable'], +'Zookeeper::getClientId' => ['int'], +'Zookeeper::getConfig' => ['ZookeeperConfig'], +'Zookeeper::getRecvTimeout' => ['int'], +'Zookeeper::getState' => ['int'], +'Zookeeper::isRecoverable' => ['bool'], +'Zookeeper::set' => ['bool', 'path'=>'string', 'value'=>'string', 'version='=>'int', 'stat='=>'array'], +'Zookeeper::setAcl' => ['bool', 'path'=>'string', 'version'=>'int', 'acl'=>'array'], +'Zookeeper::setDebugLevel' => ['bool', 'logLevel'=>'int'], +'Zookeeper::setDeterministicConnOrder' => ['bool', 'yesOrNo'=>'bool'], +'Zookeeper::setLogStream' => ['bool', 'stream'=>'resource'], +'Zookeeper::setWatcher' => ['bool', 'watcher_cb'=>'callable'], +'zookeeper_dispatch' => ['void'], +'ZookeeperConfig::add' => ['void', 'members'=>'string', 'version='=>'int', 'stat='=>'array'], +'ZookeeperConfig::get' => ['string', 'watcher_cb='=>'callable', 'stat='=>'array'], +'ZookeeperConfig::remove' => ['void', 'id_list'=>'string', 'version='=>'int', 'stat='=>'array'], +'ZookeeperConfig::set' => ['void', 'members'=>'string', 'version='=>'int', 'stat='=>'array'], +]; diff --git a/lib/composer/vimeo/psalm/dictionaries/CallMap_71_delta.php b/lib/composer/vimeo/psalm/dictionaries/CallMap_71_delta.php new file mode 100644 index 0000000000000000000000000000000000000000..fd94d3c224b674b5666c783c36807b38984fbf90 --- /dev/null +++ b/lib/composer/vimeo/psalm/dictionaries/CallMap_71_delta.php @@ -0,0 +1,51 @@ + [ + 'Closure::fromCallable' => ['Closure', 'callable'=>'callable'], + 'SQLite3::createFunction' => ['bool', 'name'=>'string', 'callback'=>'callable', 'argument_count='=>'int', 'flags='=>'int'], + 'curl_multi_errno' => ['int', 'mh'=>'resource'], + 'curl_share_errno' => ['int', 'sh'=>'resource'], + 'curl_share_strerror' => ['string', 'code'=>'int'], + 'get_headers' => ['array|false', 'url'=>'string', 'format='=>'int', 'context='=>'resource'], + 'getenv\'1' => ['array'], + 'getopt' => ['array|array|array>', 'options'=>'string', 'longopts='=>'array', '&w_optind='=>'int'], + 'hash_hkdf' => ['string', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], + 'is_iterable' => ['bool', 'var'=>'mixed'], + 'openssl_get_curve_names' => ['array'], + 'pcntl_async_signals' => ['bool', 'on='=>'bool'], + 'pcntl_signal_get_handler' => ['int|string', 'signo'=>'int'], + 'pg_fetch_all' => ['array', 'result'=>'resource', 'result_type='=>'int'], + 'pg_last_error' => ['string', 'connection='=>'resource', 'operation='=>'int'], + 'pg_select' => ['mixed', 'db'=>'resource', 'table'=>'string', 'ids'=>'array', 'options='=>'int', 'result_type='=>'int'], + 'sapi_windows_cp_conv' => ['string', 'in_codepage'=>'int|string', 'out_codepage'=>'int|string', 'subject'=>'string'], + 'sapi_windows_cp_get' => ['int'], + 'sapi_windows_cp_is_utf8' => ['bool'], + 'sapi_windows_cp_set' => ['bool', 'code_page'=>'int'], + 'session_create_id' => ['string', 'prefix='=>'string'], + 'session_gc' => ['int'], + 'unpack' => ['array', 'format'=>'string', 'data'=>'string', 'offset='=>'int'], +], +'old' => [ + 'SQLite3::createFunction' => ['bool', 'name'=>'string', 'callback'=>'callable', 'argument_count='=>'int'], + 'get_headers' => ['array|false', 'url'=>'string', 'format='=>'int'], + 'getopt' => ['array|array|array>', 'options'=>'string', 'longopts='=>'array'], + 'pg_fetch_all' => ['array', 'result'=>'resource'], + 'pg_last_error' => ['string', 'connection='=>'resource'], + 'pg_select' => ['mixed', 'db'=>'resource', 'table'=>'string', 'ids'=>'array', 'options='=>'int'], + 'unpack' => ['array', 'format'=>'string', 'data'=>'string'], +], +]; diff --git a/lib/composer/vimeo/psalm/dictionaries/CallMap_72_delta.php b/lib/composer/vimeo/psalm/dictionaries/CallMap_72_delta.php new file mode 100644 index 0000000000000000000000000000000000000000..c555bb33255cc57fc3cd3f6a2dce5319e142d52a --- /dev/null +++ b/lib/composer/vimeo/psalm/dictionaries/CallMap_72_delta.php @@ -0,0 +1,150 @@ + [ + 'DOMNodeList::count' => ['int'], + 'ftp_append' => ['bool', 'ftp'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'int'], + 'hash_copy' => ['HashContext', 'context'=>'HashContext'], + 'hash_final' => ['string', 'context'=>'HashContext', 'raw_output='=>'bool'], + 'hash_hmac_algos' => ['array'], + 'hash_init' => ['HashContext', 'algo'=>'string', 'options='=>'int', 'key='=>'string'], + 'hash_update' => ['bool', 'context'=>'HashContext', 'data'=>'string'], + 'hash_update_file' => ['bool', 'context='=>'HashContext', 'filename'=>'string', 'scontext='=>'?HashContext'], + 'hash_update_stream' => ['int', 'context'=>'HashContext', 'handle'=>'', 'length='=>'int'], + 'imagebmp' => ['bool', 'image'=>'resource', 'to='=>'mixed', 'compressed='=>'bool'], + 'imagecreatefrombmp' => ['resource', 'filename'=>'string'], + 'imageopenpolygon' => ['bool', 'image'=>'resource', 'points'=>'array', 'num_points'=>'int', 'color'=>'int'], + 'imageresolution' => ['mixed', 'image'=>'resource', 'res_x='=>'int', 'res_y='=>'int'], + 'imagesetclip' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int'], + 'ldap_exop' => ['mixed', 'link'=>'resource', 'reqoid'=>'string', 'reqdata='=>'string', 'servercontrols='=>'array', 'retdata='=>'string', 'retoid='=>'string'], + 'ldap_exop_passwd' => ['mixed', 'link'=>'resource', 'user='=>'string', 'oldpw='=>'string', 'newpw='=>'string', 'serverctrls='=>'array'], + 'ldap_exop_refresh' => ['int', 'link'=>'resource', 'dn'=>'string', 'ttl'=>'int'], + 'ldap_exop_whoami' => ['string', 'link'=>'resource'], + 'ldap_parse_exop' => ['bool', 'link'=>'resource', 'result'=>'resource', 'retdata='=>'string', 'retoid='=>'string'], + 'mb_chr' => ['string', 'cp'=>'int', 'encoding='=>'string'], + 'mb_ord' => ['int', 'str'=>'string', 'enc='=>'string'], + 'mb_scrub' => ['string', 'str'=>'string', 'enc='=>'string'], + 'oci_register_taf_callback' => ['bool', 'connection'=>'resource', 'callback='=>'callable'], + 'oci_unregister_taf_callback' => ['bool', 'connection'=>'resource'], + 'ReflectionClass::isIterable' => ['bool'], + 'SQLite3::openBlob' => ['resource', 'table'=>'string', 'column'=>'string', 'rowid'=>'int', 'dbname'=>'string', 'flags='=>'int'], + 'sapi_windows_vt100_support' => ['bool', 'stream'=>'resource', 'enable='=>'bool'], + 'socket_addrinfo_bind' => ['resource', 'addrinfo'=>'resource'], + 'socket_addrinfo_connect' => ['resource', 'addrinfo'=>'resource'], + 'socket_addrinfo_explain' => ['array', 'addrinfo'=>'resource'], + 'socket_addrinfo_lookup' => ['resource[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'], + 'sodium_add' => ['string', 'string_1'=>'string', 'string_2'=>'string'], + 'sodium_base642bin' => ['string', 'base64'=>'string', 'variant'=>'int', 'ignore='=>'string'], + 'sodium_bin2base64' => ['string', 'binary'=>'string', 'variant'=>'int'], + 'sodium_bin2hex' => ['string', 'binary'=>'string'], + 'sodium_compare' => ['int', 'string_1'=>'string', 'string_2'=>'string'], + 'sodium_crypto_aead_aes256gcm_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_aead_aes256gcm_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_aead_aes256gcm_is_available' => ['bool'], + 'sodium_crypto_aead_aes256gcm_keygen' => ['string'], + 'sodium_crypto_aead_chacha20poly1305_decrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_aead_chacha20poly1305_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_aead_chacha20poly1305_ietf_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_aead_chacha20poly1305_ietf_keygen' => ['string'], + 'sodium_crypto_aead_chacha20poly1305_keygen' => ['string'], + 'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_aead_xchacha20poly1305_ietf_keygen' => ['string'], + 'sodium_crypto_auth' => ['string', 'message'=>'string', 'key'=>'string'], + 'sodium_crypto_auth_keygen' => ['string'], + 'sodium_crypto_auth_verify' => ['bool', 'mac'=>'string', 'message'=>'string', 'key'=>'string'], + 'sodium_crypto_box' => ['string', 'string'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_box_keypair' => ['string'], + 'sodium_crypto_box_keypair_from_secretkey_and_publickey' => ['string', 'secret_key'=>'string', 'public_key'=>'string'], + 'sodium_crypto_box_open' => ['string|false', 'message'=>'string', 'nonce'=>'string', 'message_keypair'=>'string'], + 'sodium_crypto_box_publickey' => ['string', 'keypair'=>'string'], + 'sodium_crypto_box_publickey_from_secretkey' => ['string', 'secretkey'=>'string'], + 'sodium_crypto_box_seal' => ['string', 'message'=>'string', 'publickey'=>'string'], + 'sodium_crypto_box_seal_open' => ['string|false', 'message'=>'string', 'recipient_keypair'=>'string'], + 'sodium_crypto_box_secretkey' => ['string', 'keypair'=>'string'], + 'sodium_crypto_box_seed_keypair' => ['string', 'seed'=>'string'], + 'sodium_crypto_generichash' => ['string', 'msg'=>'string', 'key='=>'?string', 'length='=>'?int'], + 'sodium_crypto_generichash_final' => ['string', 'state'=>'string', 'length='=>'?int'], + 'sodium_crypto_generichash_init' => ['string', 'key='=>'?string', 'length='=>'?int'], + 'sodium_crypto_generichash_keygen' => ['string'], + 'sodium_crypto_generichash_update' => ['bool', 'state'=>'string', 'string'=>'string'], + 'sodium_crypto_kdf_derive_from_key' => ['string', 'subkey_len'=>'int', 'subkey_id'=>'int', 'context'=>'string', 'key'=>'string'], + 'sodium_crypto_kdf_keygen' => ['string'], + 'sodium_crypto_kx_client_session_keys' => ['string', 'client_keypair'=>'string', 'server_key'=>'string'], + 'sodium_crypto_kx_keypair' => ['string'], + 'sodium_crypto_kx_publickey' => ['string', 'keypair'=>'string'], + 'sodium_crypto_kx_secretkey' => ['string', 'keypair'=>'string'], + 'sodium_crypto_kx_seed_keypair' => ['string', 'seed'=>'string'], + 'sodium_crypto_kx_server_session_keys' => ['string', 'server_keypair'=>'string', 'client_key'=>'string'], + 'sodium_crypto_pwhash' => ['string', 'length'=>'int', 'password'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int', 'alg='=>'int'], + 'sodium_crypto_pwhash_scryptsalsa208sha256' => ['string', 'length'=>'int', 'password'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], + 'sodium_crypto_pwhash_scryptsalsa208sha256_str' => ['string', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], + 'sodium_crypto_pwhash_scryptsalsa208sha256_str_verify' => ['bool', 'hash'=>'string', 'password'=>'string'], + 'sodium_crypto_pwhash_str' => ['string', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], + 'sodium_crypto_pwhash_str_needs_rehash' => ['bool', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'], + 'sodium_crypto_pwhash_str_verify' => ['bool', 'hash'=>'string', 'password'=>'string'], + 'sodium_crypto_scalarmult' => ['string', 'string_1'=>'string', 'string_2'=>'string'], + 'sodium_crypto_scalarmult_base' => ['string', 'secretkey'=>'string'], + 'sodium_crypto_secretbox' => ['string', 'plaintext'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_secretbox_keygen' => ['string'], + 'sodium_crypto_secretbox_open' => ['string|false', 'ciphertext'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_secretstream_xchacha20poly1305_init_pull' => ['string', 'header'=>'string', 'key'=>'string'], + 'sodium_crypto_secretstream_xchacha20poly1305_init_push' => ['array', 'key'=>'string'], + 'sodium_crypto_secretstream_xchacha20poly1305_keygen' => ['string'], + 'sodium_crypto_secretstream_xchacha20poly1305_pull' => ['array', 'state'=>'string', 'c'=>'string', 'ad='=>'string'], + 'sodium_crypto_secretstream_xchacha20poly1305_push' => ['string', 'state'=>'string', 'msg'=>'string', 'ad='=>'string', 'tag='=>'int'], + 'sodium_crypto_secretstream_xchacha20poly1305_rekey' => ['void', 'state'=>'string'], + 'sodium_crypto_shorthash' => ['string', 'message'=>'string', 'key'=>'string'], + 'sodium_crypto_shorthash_keygen' => ['string'], + 'sodium_crypto_sign' => ['string', 'message'=>'string', 'secretkey'=>'string'], + 'sodium_crypto_sign_detached' => ['string', 'message'=>'string', 'secretkey'=>'string'], + 'sodium_crypto_sign_ed25519_pk_to_curve25519' => ['string', 'ed25519pk'=>'string'], + 'sodium_crypto_sign_ed25519_sk_to_curve25519' => ['string', 'ed25519sk'=>'string'], + 'sodium_crypto_sign_keypair' => ['string'], + 'sodium_crypto_sign_keypair_from_secretkey_and_publickey' => ['string', 'secret_key'=>'string', 'public_key'=>'string'], + 'sodium_crypto_sign_open' => ['string|false', 'message'=>'string', 'publickey'=>'string'], + 'sodium_crypto_sign_publickey' => ['string', 'keypair'=>'string'], + 'sodium_crypto_sign_publickey_from_secretkey' => ['string', 'secretkey'=>'string'], + 'sodium_crypto_sign_secretkey' => ['string', 'keypair'=>'string'], + 'sodium_crypto_sign_seed_keypair' => ['string', 'seed'=>'string'], + 'sodium_crypto_sign_verify_detached' => ['bool', 'signature'=>'string', 'message'=>'string', 'publickey'=>'string'], + 'sodium_crypto_stream' => ['string', 'length'=>'int', 'nonce'=>'string', 'key'=>'string'], + 'sodium_crypto_stream_keygen' => ['string'], + 'sodium_crypto_stream_xor' => ['string', 'message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'sodium_hex2bin' => ['string', 'hex'=>'string', 'ignore='=>'string'], + 'sodium_increment' => ['string', '&binary_string'=>'string'], + 'sodium_memcmp' => ['int', 'string_1'=>'string', 'string_2'=>'string'], + 'sodium_memzero' => ['void', '&secret'=>'string'], + 'sodium_pad' => ['string', 'unpadded'=>'string', 'length'=>'int'], + 'sodium_unpad' => ['string', 'padded'=>'string', 'length'=>'int'], + 'stream_isatty' => ['bool', 'stream'=>'resource'], + 'ZipArchive::count' => ['int'], + 'ZipArchive::setEncryptionIndex' => ['bool', 'index'=>'int', 'method'=>'string', 'password='=>'string'], + 'ZipArchive::setEncryptionName' => ['bool', 'name'=>'string', 'method'=>'int', 'password='=>'string'], +], +'old' => [ + 'hash_copy' => ['resource', 'context'=>'resource'], + 'hash_final' => ['string', 'context'=>'resource', 'raw_output='=>'bool'], + 'hash_hkdf' => ['string', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], + 'hash_init' => ['resource', 'algo'=>'string', 'options='=>'int', 'key='=>'string'], + 'hash_pbkdf2' => ['string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'], + 'hash_update' => ['bool', 'context'=>'resource', 'data'=>'string'], + 'hash_update_file' => ['bool', 'hcontext'=>'resource', 'filename'=>'string', 'scontext='=>'?resource'], + 'hash_update_stream' => ['int', 'context'=>'resource', 'handle'=>'resource', 'length='=>'int'], + 'SQLite3::openBlob' => ['resource', 'table'=>'string', 'column'=>'string', 'rowid'=>'int', 'dbname'=>'string'], +] +]; diff --git a/lib/composer/vimeo/psalm/dictionaries/CallMap_73_delta.php b/lib/composer/vimeo/psalm/dictionaries/CallMap_73_delta.php new file mode 100644 index 0000000000000000000000000000000000000000..7f738f17a5df31327c04830b48ba2d8dc470c612 --- /dev/null +++ b/lib/composer/vimeo/psalm/dictionaries/CallMap_73_delta.php @@ -0,0 +1,57 @@ + [ + 'array_key_first' => ['int|string|null', 'array'=>'array'], + 'array_key_last' => ['int|string|null', 'array'=>'array'], + 'DateTime::createFromImmutable' => ['static', 'datetime'=>'DateTimeImmutable'], + 'fpm_get_status' => ['array'], + 'gmp_binomial' => ['GMP', 'n'=>'GMP|string|int', 'k'=>'GMP|string|int'], + 'gmp_lcm' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], + 'gmp_perfect_power' => ['GMP', 'a'=>'GMP|string|int'], + 'gmp_kronecker' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'], + 'JsonException::__clone' => [''], + 'JsonException::__construct' => [''], + 'JsonException::__toString' => [''], + 'JsonException::__wakeup' => [''], + 'JsonException::getCode' => [''], + 'JsonException::getFile' => [''], + 'JsonException::getLine' => [''], + 'JsonException::getMessage' => [''], + 'JsonException::getPrevious' => [''], + 'JsonException::getTrace' => [''], + 'JsonException::getTraceAsString' => [''], + 'net_get_interfaces' => ['array>|false'], + 'openssl_pkey_derive' => ['string|false', 'peer_pub_key'=>'mixed', 'priv_key'=>'mixed', 'keylen='=>'?int'], + 'gc_status' => ['array{runs:int,collected:int,threshold:int,roots:int}'], + 'hrtime' => ['array{0:int,1:int}|false', 'get_as_number='=>'false'], + 'hrtime\'1' => ['int|float|false', 'get_as_number='=>'true'], + 'is_countable' => ['bool', 'var'=>'mixed'], + 'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:int,path?:string,domain?:?string,secure?:bool,httponly?:bool}'], + 'setcookie' => ['bool', 'name'=>'string', 'value='=>'string', 'options='=>'array'], + 'setrawcookie' => ['bool', 'name'=>'string', 'value='=>'string', 'options='=>'array'], + 'socket_wsaprotocol_info_export' => ['string|false', 'sock='=>'resource','pid'=>'int'], + 'socket_wsaprotocol_info_import' => ['resource|false', 'id'=>'string'], + 'socket_wsaprotocol_info_release' => ['bool', 'id'=>'string'], + 'SplPriorityQueue::isCorrupted' => ['bool'], +], +'old' => [ + 'setcookie' => ['bool', 'name'=>'string', 'value='=>'string', 'expires='=>'int', 'path='=>'string', 'domain='=>'string', 'secure='=>'bool', 'httponly='=>'bool'], + 'setrawcookie' => ['bool', 'name'=>'string', 'value='=>'string', 'expires='=>'int', 'path='=>'string', 'domain='=>'string', 'secure='=>'bool', 'httponly='=>'bool'], +] +]; diff --git a/lib/composer/vimeo/psalm/dictionaries/CallMap_74_delta.php b/lib/composer/vimeo/psalm/dictionaries/CallMap_74_delta.php new file mode 100644 index 0000000000000000000000000000000000000000..d280425f02cad37a4116dde32937c2941cb58375 --- /dev/null +++ b/lib/composer/vimeo/psalm/dictionaries/CallMap_74_delta.php @@ -0,0 +1,28 @@ + [ + 'password_hash' => ['string|null', 'password'=>'string', 'algo'=>'int|string|null', 'options='=>'array'], + 'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'int|string|null', 'options='=>'array'], + 'proc_open' => ['resource|false', 'command'=>'string|array', 'descriptorspec'=>'array', '&w_pipes'=>'resource[]', 'cwd='=>'?string', 'env='=>'?array', 'other_options='=>'array'], + 'ReflectionProperty::getType' => ['?ReflectionType'], +], +'old' => [ + 'password_hash' => ['string|false', 'password'=>'string', 'algo'=>'int', 'options='=>'array'], + 'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'int', 'options='=>'array'], + 'proc_open' => ['resource|false', 'command'=>'string', 'descriptorspec'=>'array', '&w_pipes'=>'resource[]', 'cwd='=>'?string', 'env='=>'?array', 'other_options='=>'array'], +] +]; diff --git a/lib/composer/vimeo/psalm/dictionaries/CallMap_80_delta.php b/lib/composer/vimeo/psalm/dictionaries/CallMap_80_delta.php new file mode 100644 index 0000000000000000000000000000000000000000..8aad52884eb1308669ffcf5bb0476ea33e513b52 --- /dev/null +++ b/lib/composer/vimeo/psalm/dictionaries/CallMap_80_delta.php @@ -0,0 +1,390 @@ + [ +'array_combine' => ['associative-array', 'keys'=>'string[]|int[]', 'values'=>'array'], +'bcdiv' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], +'bcmod' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], +'bcpowmod' => ['string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], +'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'true'], +'count_chars' => ['array|string', 'input'=>'string', 'mode='=>'int'], +'curl_close' => ['void', 'ch'=>'CurlHandle'], +'curl_copy_handle' => ['CurlHandle', 'ch'=>'CurlHandle'], +'curl_errno' => ['int', 'ch'=>'CurlHandle'], +'curl_error' => ['string', 'ch'=>'CurlHandle'], +'curl_escape' => ['string|false', 'ch'=>'CurlHandle', 'string'=>'string'], +'curl_exec' => ['bool|string', 'ch'=>'CurlHandle'], +'curl_getinfo' => ['mixed', 'ch'=>'CurlHandle', 'option='=>'int'], +'curl_init' => ['CurlHandle|false', 'url='=>'string'], +'curl_multi_add_handle' => ['int', 'mh'=>'CurlMultiHandle', 'ch'=>'CurlHandle'], +'curl_multi_close' => ['void', 'mh'=>'CurlMultiHandle'], +'curl_multi_errno' => ['int', 'mh'=>'CurlMultiHandle'], +'curl_multi_exec' => ['int', 'mh'=>'CurlMultiHandle', '&w_still_running'=>'int'], +'curl_multi_getcontent' => ['string', 'ch'=>'CurlMultiHandle'], +'curl_multi_info_read' => ['array|false', 'mh'=>'CurlMultiHandle', '&w_msgs_in_queue='=>'int'], +'curl_multi_init' => ['CurlMultiHandle|false'], +'curl_multi_remove_handle' => ['int', 'mh'=>'CurlMultiHandle', 'ch'=>'CurlHandle'], +'curl_multi_select' => ['int', 'mh'=>'CurlMultiHandle', 'timeout='=>'float'], +'curl_multi_setopt' => ['bool', 'mh'=>'CurlMultiHandle', 'option'=>'int', 'value'=>'mixed'], +'curl_pause' => ['int', 'ch'=>'CurlHandle', 'bitmask'=>'int'], +'curl_reset' => ['void', 'ch'=>'CurlHandle'], +'curl_setopt' => ['bool', 'ch'=>'CurlHandle', 'option'=>'int', 'value'=>'callable|mixed'], +'curl_setopt_array' => ['bool', 'ch'=>'CurlHandle', 'options'=>'array'], +'curl_share_close' => ['void', 'sh'=>'CurlShareHandle'], +'curl_share_errno' => ['int', 'sh'=>'CurlShareHandle'], +'curl_share_init' => ['CurlShareHandle'], +'curl_share_setopt' => ['bool', 'sh'=>'CurlShareHandle', 'option'=>'int', 'value'=>'mixed'], +'curl_unescape' => ['string|false', 'ch'=>'CurlShareHandle', 'string'=>'string'], +'date_add' => ['DateTime', 'object'=>'DateTime', 'interval'=>'DateInterval'], +'date_date_set' => ['DateTime', 'object'=>'DateTime', 'year'=>'int', 'month'=>'int', 'day'=>'int'], +'date_diff' => ['DateInterval', 'obj1'=>'DateTimeInterface', 'obj2'=>'DateTimeInterface', 'absolute='=>'bool'], +'date_format' => ['string', 'object'=>'DateTimeInterface', 'format'=>'string'], +'date_isodate_set' => ['DateTime', 'object'=>'DateTime', 'year'=>'int', 'week'=>'int', 'day='=>'int|mixed'], +'date_parse' => ['array', 'date'=>'string'], +'date_sub' => ['DateTime', 'object'=>'DateTime', 'interval'=>'DateInterval'], +'date_sun_info' => ['array', 'time'=>'int', 'latitude'=>'float', 'longitude'=>'float'], +'date_time_set' => ['DateTime', 'object'=>'DateTime', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], +'date_timestamp_set' => ['DateTime', 'object'=>'DateTime', 'unixtimestamp'=>'int'], +'date_timezone_set' => ['DateTime', 'object'=>'DateTime', 'timezone'=>'DateTimeZone'], +'explode' => ['array', 'separator'=>'string', 'str'=>'string', 'limit='=>'int'], +'fdiv' => ['float', 'dividend'=>'float', 'divisor'=>'float'], +'get_debug_type' => ['string', 'var'=>'mixed'], +'get_resource_id' => ['int', 'res'=>'resource'], +'gmdate' => ['string', 'format'=>'string', 'timestamp='=>'int'], +'gmmktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], +'hash_hkdf' => ['string', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], +'imageaffine' => ['false|GdImage', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'], +'imagecreate' => ['false|GdImage', 'x_size'=>'int', 'y_size'=>'int'], +'imagecreatefrombmp' => ['false|GdImage', 'filename'=>'string'], +'imagecreatefromgd' => ['false|GdImage', 'filename'=>'string'], +'imagecreatefromgd2' => ['false|GdImage', 'filename'=>'string'], +'imagecreatefromgd2part' => ['false|GdImage', 'filename'=>'string', 'srcx'=>'int', 'srcy'=>'int', 'width'=>'int', 'height'=>'int'], +'imagecreatefromgif' => ['false|GdImage', 'filename'=>'string'], +'imagecreatefromjpeg' => ['false|GdImage', 'filename'=>'string'], +'imagecreatefrompng' => ['false|GdImage', 'filename'=>'string'], +'imagecreatefromstring' => ['false|GdImage', 'image'=>'string'], +'imagecreatefromwbmp' => ['false|GdImage', 'filename'=>'string'], +'imagecreatefromwebp' => ['false|GdImage', 'filename'=>'string'], +'imagecreatefromxbm' => ['false|GdImage', 'filename'=>'string'], +'imagecreatefromxpm' => ['false|GdImage', 'filename'=>'string'], +'imagecreatetruecolor' => ['false|GdImage', 'x_size'=>'int', 'y_size'=>'int'], +'imagecrop' => ['false|GdImage', 'im'=>'resource', 'rect'=>'array'], +'imagecropauto' => ['false|GdImage', 'im'=>'resource', 'mode'=>'int', 'threshold'=>'float', 'color'=>'int'], +'imagegetclip' => ['array', 'im'=>'resource'], +'imagegrabscreen' => ['false|GdImage'], +'imagegrabwindow' => ['false|GdImage', 'window_handle'=>'int', 'client_area='=>'int'], +'imagerotate' => ['false|GdImage', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], +'imagescale' => ['false|GdImage', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], +'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], +'mktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], +'parse_str' => ['void', 'encoded_string'=>'string', '&w_result'=>'array'], +'password_hash' => ['string', 'password'=>'string', 'algo'=>'?string|?int', 'options='=>'array'], +'PhpToken::getAll' => ['list', 'code'=>'string', 'flags='=>'int'], +'PhpToken::is' => ['bool', 'kind'=>'string|int|string[]|int[]'], +'PhpToken::isIgnorable' => ['bool'], +'PhpToken::getTokenName' => ['string'], +'proc_get_status' => ['array', 'process'=>'resource'], +'socket_accept' => ['Socket|false', 'socket'=>'Socket'], +'socket_addrinfo_bind' => ['?Socket', 'addrinfo'=>'AddressInfo'], +'socket_addrinfo_connect' => ['?Socket', 'addrinfo'=>'AddressInfo'], +'socket_addrinfo_explain' => ['array', 'addrinfo'=>'AddressInfo'], +'socket_addrinfo_lookup' => ['AddressInfo[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'], +'socket_bind' => ['bool', 'socket'=>'Socket', 'addr'=>'string', 'port='=>'int'], +'socket_clear_error' => ['void', 'socket='=>'Socket'], +'socket_close' => ['void', 'socket'=>'Socket'], +'socket_connect' => ['bool', 'socket'=>'Socket', 'addr'=>'string', 'port='=>'int'], +'socket_create' => ['Socket|false', 'domain'=>'int', 'type'=>'int', 'protocol'=>'int'], +'socket_create_listen' => ['Socket|false', 'port'=>'int', 'backlog='=>'int'], +'socket_create_pair' => ['bool', 'domain'=>'int', 'type'=>'int', 'protocol'=>'int', '&w_fd'=>'Socket[]'], +'socket_export_stream' => ['resource|false', 'socket'=>'Socket'], +'socket_get_option' => ['mixed|false', 'socket'=>'Socket', 'level'=>'int', 'optname'=>'int'], +'socket_get_status' => ['array', 'stream'=>'Socket'], +'socket_getopt' => ['mixed', 'socket'=>'Socket', 'level'=>'int', 'optname'=>'int'], +'socket_getpeername' => ['bool', 'socket'=>'Socket', '&w_addr'=>'string', '&w_port='=>'int'], +'socket_getsockname' => ['bool', 'socket'=>'Socket', '&w_addr'=>'string', '&w_port='=>'int'], +'socket_import_stream' => ['Socket|false|null', 'stream'=>'resource'], +'socket_last_error' => ['int', 'socket='=>'Socket'], +'socket_listen' => ['bool', 'socket'=>'Socket', 'backlog='=>'int'], +'socket_read' => ['string|false', 'socket'=>'Socket', 'length'=>'int', 'type='=>'int'], +'socket_recv' => ['int|false', 'socket'=>'Socket', '&w_buf'=>'string', 'length'=>'int', 'flags'=>'int'], +'socket_recvfrom' => ['int|false', 'socket'=>'Socket', '&w_buf'=>'string', 'length'=>'int', 'flags'=>'int', '&w_name'=>'string', '&w_port='=>'int'], +'socket_recvmsg' => ['int|false', 'socket'=>'Socket', '&w_message'=>'string', 'flags='=>'int'], +'socket_select' => ['int|false', '&rw_read_fds'=>'Socket[]|null', '&rw_write_fds'=>'Socket[]|null', '&rw_except_fds'=>'Socket[]|null', 'tv_sec'=>'int', 'tv_usec='=>'int'], +'socket_send' => ['int|false', 'socket'=>'Socket', 'buf'=>'string', 'length'=>'int', 'flags'=>'int'], +'socket_sendmsg' => ['int|false', 'socket'=>'Socket', 'message'=>'array', 'flags'=>'int'], +'socket_sendto' => ['int|false', 'socket'=>'Socket', 'buf'=>'string', 'length'=>'int', 'flags'=>'int', 'addr'=>'string', 'port='=>'int'], +'socket_set_block' => ['bool', 'socket'=>'Socket'], +'socket_set_blocking' => ['bool', 'socket'=>'Socket', 'mode'=>'int'], +'socket_set_nonblock' => ['bool', 'socket'=>'Socket'], +'socket_set_option' => ['bool', 'socket'=>'Socket', 'level'=>'int', 'optname'=>'int', 'optval'=>'int|string|array'], +'socket_set_timeout' => ['bool', 'stream'=>'resource', 'seconds'=>'int', 'microseconds='=>'int'], +'socket_setopt' => ['void', 'socket'=>'Socket', 'level'=>'int', 'optname'=>'int', 'optval'=>'int|string|array'], +'socket_shutdown' => ['bool', 'socket'=>'Socket', 'how='=>'int'], +'socket_strerror' => ['string', 'errno'=>'int'], +'socket_write' => ['int|false', 'socket'=>'Socket', 'buf'=>'string', 'length='=>'int'], +'socket_wsaprotocol_info_export' => ['string|false', 'stream'=>'resource', 'target_pid'=>'int'], +'socket_wsaprotocol_info_import' => ['Socket|false', 'id'=>'string'], +'socket_wsaprotocol_info_release' => ['bool', 'id'=>'string'], +'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], +'str_contains' => ['bool', 'haystack'=>'string', 'needle'=>'string'], +'str_ends_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], +'str_starts_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], +'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], +'stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], +'stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], +'strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], +'strrchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string'], +'strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], +'strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], +'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], +'xml_parser_create' => ['XMLParser', 'encoding='=>'string'], +'xml_parser_create_ns' => ['XMLParser', 'encoding='=>'string', 'sep='=>'string'], +'xml_parser_free' => ['bool', 'parser'=>'XMLParser'], +'xml_parser_get_option' => ['mixed|false', 'parser'=>'XMLParser', 'option'=>'int'], +'xml_parser_set_option' => ['bool', 'parser'=>'XMLParser', 'option'=>'int', 'value'=>'mixed'], +'xmlwriter_end_attribute' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_end_cdata' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_end_comment' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_end_document' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_end_dtd' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_end_dtd_attlist' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_end_dtd_element' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_end_dtd_entity' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_end_element' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_end_pi' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_flush' => ['mixed', 'xmlwriter'=>'XMLWriter', 'empty='=>'bool'], +'xmlwriter_full_end_element' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_open_memory' => ['XMLWriter'], +'xmlwriter_open_uri' => ['XMLWriter', 'source'=>'string'], +'xmlwriter_output_memory' => ['string', 'xmlwriter'=>'XMLWriter', 'flush='=>'bool'], +'xmlwriter_set_indent' => ['bool', 'xmlwriter'=>'XMLWriter', 'indent'=>'bool'], +'xmlwriter_set_indent_string' => ['bool', 'xmlwriter'=>'XMLWriter', 'indentstring'=>'string'], +'xmlwriter_start_attribute' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], +'xmlwriter_start_attribute_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], +'xmlwriter_start_cdata' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_start_comment' => ['bool', 'xmlwriter'=>'XMLWriter'], +'xmlwriter_start_document' => ['bool', 'xmlwriter'=>'XMLWriter', 'version='=>'string', 'encoding='=>'string', 'standalone='=>'string'], +'xmlwriter_start_dtd' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string'], +'xmlwriter_start_dtd_attlist' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], +'xmlwriter_start_dtd_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], +'xmlwriter_start_dtd_entity' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'isparam'=>'bool'], +'xmlwriter_start_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], +'xmlwriter_start_element_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], +'xmlwriter_start_pi' => ['bool', 'xmlwriter'=>'XMLWriter', 'target'=>'string'], +'xmlwriter_text' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], +'xmlwriter_write_attribute' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_attribute_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], +'xmlwriter_write_cdata' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], +'xmlwriter_write_comment' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], +'xmlwriter_write_dtd' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string', 'subset='=>'string'], +'xmlwriter_write_dtd_attlist' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_dtd_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_dtd_entity' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string', 'pe'=>'bool', 'publicid'=>'string', 'sysid'=>'string', 'ndataid'=>'string'], +'xmlwriter_write_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_element_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], +'xmlwriter_write_pi' => ['bool', 'xmlwriter'=>'XMLWriter', 'target'=>'string', 'content'=>'string'], +'xmlwriter_write_raw' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], +], +'old' => [ + +'array_combine' => ['associative-array|false', 'keys'=>'string[]|int[]', 'values'=>'array'], +'bcdiv' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], +'bcmod' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], +'bcpowmod' => ['?string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], +'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'bool'], +'count_chars' => ['array|false|string', 'input'=>'string', 'mode='=>'int'], +'create_function' => ['string', 'args'=>'string', 'code'=>'string'], +'curl_close' => ['void', 'ch'=>'resource'], +'curl_copy_handle' => ['resource', 'ch'=>'resource'], +'curl_errno' => ['int', 'ch'=>'resource'], +'curl_error' => ['string', 'ch'=>'resource'], +'curl_escape' => ['string|false', 'ch'=>'resource', 'string'=>'string'], +'curl_exec' => ['bool|string', 'ch'=>'resource'], +'curl_file_create' => ['CURLFile', 'filename'=>'string', 'mimetype='=>'string', 'postfilename='=>'string'], +'curl_getinfo' => ['mixed', 'ch'=>'resource', 'option='=>'int'], +'curl_init' => ['resource|false', 'url='=>'string'], +'curl_multi_add_handle' => ['int', 'mh'=>'resource', 'ch'=>'resource'], +'curl_multi_close' => ['void', 'mh'=>'resource'], +'curl_multi_errno' => ['int', 'mh'=>'resource'], +'curl_multi_exec' => ['int', 'mh'=>'resource', '&w_still_running'=>'int'], +'curl_multi_getcontent' => ['string', 'ch'=>'resource'], +'curl_multi_info_read' => ['array|false', 'mh'=>'resource', '&w_msgs_in_queue='=>'int'], +'curl_multi_init' => ['resource|false'], +'curl_multi_remove_handle' => ['int', 'mh'=>'resource', 'ch'=>'resource'], +'curl_multi_select' => ['int', 'mh'=>'resource', 'timeout='=>'float'], +'curl_multi_setopt' => ['bool', 'mh'=>'resource', 'option'=>'int', 'value'=>'mixed'], +'curl_pause' => ['int', 'ch'=>'resource', 'bitmask'=>'int'], +'curl_reset' => ['void', 'ch'=>'resource'], +'curl_setopt' => ['bool', 'ch'=>'resource', 'option'=>'int', 'value'=>'callable|mixed'], +'curl_setopt_array' => ['bool', 'ch'=>'resource', 'options'=>'array'], +'curl_share_close' => ['void', 'sh'=>'resource'], +'curl_share_errno' => ['int', 'sh'=>'resource'], +'curl_share_init' => ['resource'], +'curl_share_setopt' => ['bool', 'sh'=>'resource', 'option'=>'int', 'value'=>'mixed'], +'curl_unescape' => ['string|false', 'ch'=>'resource', 'string'=>'string'], +'date_add' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'], +'date_date_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'month'=>'int', 'day'=>'int'], +'date_diff' => ['DateInterval|false', 'obj1'=>'DateTimeInterface', 'obj2'=>'DateTimeInterface', 'absolute='=>'bool'], +'date_format' => ['string|false', 'object'=>'DateTimeInterface', 'format'=>'string'], +'date_isodate_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'week'=>'int', 'day='=>'int|mixed'], +'date_parse' => ['array|false', 'date'=>'string'], +'date_sub' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'], +'date_sun_info' => ['array|false', 'time'=>'int', 'latitude'=>'float', 'longitude'=>'float'], +'date_time_set' => ['DateTime|false', 'object'=>'DateTime', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], +'date_timestamp_set' => ['DateTime|false', 'object'=>'DateTime', 'unixtimestamp'=>'int'], +'date_timezone_set' => ['DateTime|false', 'object'=>'DateTime', 'timezone'=>'DateTimeZone'], +'each' => ['array{0:int|string,key:int|string,1:mixed,value:mixed}', '&r_arr'=>'array'], +'explode' => ['list', 'separator'=>'string', 'str'=>'string', 'limit='=>'int'], +'gmdate' => ['string|false', 'format'=>'string', 'timestamp='=>'int'], +'gmmktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], +'gmp_random' => ['GMP', 'limiter='=>'int'], +'gzgetss' => ['string|false', 'zp'=>'resource', 'length'=>'int', 'allowable_tags='=>'string'], +'hash_hkdf' => ['string|false', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], +'image2wbmp' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'threshold='=>'int'], +'imageaffine' => ['resource|false', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'], +'imagecreate' => ['resource|false', 'x_size'=>'int', 'y_size'=>'int'], +'imagecreatefrombmp' => ['resource|false', 'filename'=>'string'], +'imagecreatefromgd' => ['resource|false', 'filename'=>'string'], +'imagecreatefromgd2' => ['resource|false', 'filename'=>'string'], +'imagecreatefromgd2part' => ['resource|false', 'filename'=>'string', 'srcx'=>'int', 'srcy'=>'int', 'width'=>'int', 'height'=>'int'], +'imagecreatefromgif' => ['resource|false', 'filename'=>'string'], +'imagecreatefromjpeg' => ['resource|false', 'filename'=>'string'], +'imagecreatefrompng' => ['resource|false', 'filename'=>'string'], +'imagecreatefromstring' => ['resource|false', 'image'=>'string'], +'imagecreatefromwbmp' => ['resource|false', 'filename'=>'string'], +'imagecreatefromwebp' => ['resource|false', 'filename'=>'string'], +'imagecreatefromxbm' => ['resource|false', 'filename'=>'string'], +'imagecreatefromxpm' => ['resource|false', 'filename'=>'string'], +'imagecreatetruecolor' => ['resource|false', 'x_size'=>'int', 'y_size'=>'int'], +'imagecrop' => ['resource|false', 'im'=>'resource', 'rect'=>'array'], +'imagecropauto' => ['resource|false', 'im'=>'resource', 'mode'=>'int', 'threshold'=>'float', 'color'=>'int'], +'imagegetclip' => ['array|false', 'im'=>'resource'], +'imagegrabscreen' => ['false|resource'], +'imagegrabwindow' => ['false|resource', 'window_handle'=>'int', 'client_area='=>'int'], +'imagerotate' => ['resource|false', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], +'imagescale' => ['resource|false', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], +'jpeg2wbmp' => ['bool', 'jpegname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'], +'ldap_sort' => ['bool', 'link_identifier'=>'resource', 'result_identifier'=>'resource', 'sortfilter'=>'string'], +'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string', 'is_hex='=>'bool'], +'mktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], +'parse_str' => ['void', 'encoded_string'=>'string', '&w_result='=>'array'], +'password_hash' => ['string|false|null', 'password'=>'string', 'algo'=>'?string|?int', 'options='=>'array'], +'png2wbmp' => ['bool', 'pngname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'], +'proc_get_status' => ['array|false', 'process'=>'resource'], +'read_exif_data' => ['array', 'filename'=>'string', 'sections_needed='=>'string', 'sub_arrays='=>'bool', 'read_thumbnail='=>'bool'], +'socket_addrinfo_lookup' => ['resource[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'], +'socket_accept' => ['resource|false', 'socket'=>'resource'], +'socket_addrinfo_bind' => ['?resource', 'addrinfo'=>'resource'], +'socket_addrinfo_connect' => ['?resource', 'addrinfo'=>'resource'], +'socket_addrinfo_explain' => ['array', 'addrinfo'=>'resource'], +'socket_addrinfo_lookup' => ['resource[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'], +'socket_bind' => ['bool', 'socket'=>'resource', 'addr'=>'string', 'port='=>'int'], +'socket_clear_error' => ['void', 'socket='=>'resource'], +'socket_close' => ['void', 'socket'=>'resource'], +'socket_connect' => ['bool', 'socket'=>'resource', 'addr'=>'string', 'port='=>'int'], +'socket_create' => ['resource|false', 'domain'=>'int', 'type'=>'int', 'protocol'=>'int'], +'socket_create_listen' => ['resource|false', 'port'=>'int', 'backlog='=>'int'], +'socket_create_pair' => ['bool', 'domain'=>'int', 'type'=>'int', 'protocol'=>'int', '&w_fd'=>'resource[]'], +'socket_export_stream' => ['resource|false', 'socket'=>'resource'], +'socket_get_option' => ['mixed|false', 'socket'=>'resource', 'level'=>'int', 'optname'=>'int'], +'socket_get_status' => ['array', 'stream'=>'resource'], +'socket_getopt' => ['mixed', 'socket'=>'resource', 'level'=>'int', 'optname'=>'int'], +'socket_getpeername' => ['bool', 'socket'=>'resource', '&w_addr'=>'string', '&w_port='=>'int'], +'socket_getsockname' => ['bool', 'socket'=>'resource', '&w_addr'=>'string', '&w_port='=>'int'], +'socket_import_stream' => ['resource|false|null', 'stream'=>'resource'], +'socket_last_error' => ['int', 'socket='=>'resource'], +'socket_listen' => ['bool', 'socket'=>'resource', 'backlog='=>'int'], +'socket_read' => ['string|false', 'socket'=>'resource', 'length'=>'int', 'type='=>'int'], +'socket_recv' => ['int|false', 'socket'=>'resource', '&w_buf'=>'string', 'length'=>'int', 'flags'=>'int'], +'socket_recvfrom' => ['int|false', 'socket'=>'resource', '&w_buf'=>'string', 'length'=>'int', 'flags'=>'int', '&w_name'=>'string', '&w_port='=>'int'], +'socket_recvmsg' => ['int|false', 'socket'=>'resource', '&w_message'=>'string', 'flags='=>'int'], +'socket_select' => ['int|false', '&rw_read_fds'=>'resource[]|null', '&rw_write_fds'=>'resource[]|null', '&rw_except_fds'=>'resource[]|null', 'tv_sec'=>'int', 'tv_usec='=>'int'], +'socket_send' => ['int|false', 'socket'=>'resource', 'buf'=>'string', 'length'=>'int', 'flags'=>'int'], +'socket_sendmsg' => ['int|false', 'socket'=>'resource', 'message'=>'array', 'flags'=>'int'], +'socket_sendto' => ['int|false', 'socket'=>'resource', 'buf'=>'string', 'length'=>'int', 'flags'=>'int', 'addr'=>'string', 'port='=>'int'], +'socket_set_block' => ['bool', 'socket'=>'resource'], +'socket_set_blocking' => ['bool', 'socket'=>'resource', 'mode'=>'int'], +'socket_set_nonblock' => ['bool', 'socket'=>'resource'], +'socket_set_option' => ['bool', 'socket'=>'resource', 'level'=>'int', 'optname'=>'int', 'optval'=>'int|string|array'], +'socket_set_timeout' => ['bool', 'stream'=>'resource', 'seconds'=>'int', 'microseconds='=>'int'], +'socket_setopt' => ['void', 'socket'=>'resource', 'level'=>'int', 'optname'=>'int', 'optval'=>'int|string|array'], +'socket_shutdown' => ['bool', 'socket'=>'resource', 'how='=>'int'], +'socket_strerror' => ['string', 'errno'=>'int'], +'socket_write' => ['int|false', 'socket'=>'resource', 'buf'=>'string', 'length='=>'int'], +'socket_wsaprotocol_info_export' => ['string|false', 'stream'=>'resource', 'target_pid'=>'int'], +'socket_wsaprotocol_info_import' => ['resource|false', 'id'=>'string'], +'socket_wsaprotocol_info_release' => ['bool', 'id'=>'string'], +'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['?string|?false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], +'SplFileObject::fgetss' => ['string|false', 'allowable_tags='=>'string'], +'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'], +'stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], +'stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'], +'strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], +'strrchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int'], +'strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], +'strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], +'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'], +'xml_parser_create' => ['resource', 'encoding='=>'string'], +'xml_parser_create_ns' => ['resource', 'encoding='=>'string', 'sep='=>'string'], +'xml_parser_free' => ['bool', 'parser'=>'resource'], +'xml_parser_get_option' => ['mixed|false', 'parser'=>'resource', 'option'=>'int'], +'xml_parser_set_option' => ['bool', 'parser'=>'resource', 'option'=>'int', 'value'=>'mixed'], +'xmlwriter_end_attribute' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_cdata' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_comment' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_document' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_dtd' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_dtd_attlist' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_dtd_element' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_dtd_entity' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_element' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_end_pi' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_flush' => ['mixed', 'xmlwriter'=>'resource', 'empty='=>'bool'], +'xmlwriter_full_end_element' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_open_memory' => ['resource'], +'xmlwriter_open_uri' => ['resource', 'source'=>'string'], +'xmlwriter_output_memory' => ['string', 'xmlwriter'=>'resource', 'flush='=>'bool'], +'xmlwriter_set_indent' => ['bool', 'xmlwriter'=>'resource', 'indent'=>'bool'], +'xmlwriter_set_indent_string' => ['bool', 'xmlwriter'=>'resource', 'indentstring'=>'string'], +'xmlwriter_start_attribute' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], +'xmlwriter_start_attribute_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], +'xmlwriter_start_cdata' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_start_comment' => ['bool', 'xmlwriter'=>'resource'], +'xmlwriter_start_document' => ['bool', 'xmlwriter'=>'resource', 'version='=>'string', 'encoding='=>'string', 'standalone='=>'string'], +'xmlwriter_start_dtd' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string'], +'xmlwriter_start_dtd_attlist' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], +'xmlwriter_start_dtd_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], +'xmlwriter_start_dtd_entity' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'isparam'=>'bool'], +'xmlwriter_start_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], +'xmlwriter_start_element_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], +'xmlwriter_start_pi' => ['bool', 'xmlwriter'=>'resource', 'target'=>'string'], +'xmlwriter_text' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], +'xmlwriter_write_attribute' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_attribute_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], +'xmlwriter_write_cdata' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], +'xmlwriter_write_comment' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], +'xmlwriter_write_dtd' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string', 'subset='=>'string'], +'xmlwriter_write_dtd_attlist' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_dtd_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_dtd_entity' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string', 'pe'=>'bool', 'publicid'=>'string', 'sysid'=>'string', 'ndataid'=>'string'], +'xmlwriter_write_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], +'xmlwriter_write_element_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], +'xmlwriter_write_pi' => ['bool', 'xmlwriter'=>'resource', 'target'=>'string', 'content'=>'string'], +'xmlwriter_write_raw' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], +] +]; diff --git a/lib/composer/vimeo/psalm/dictionaries/InternalTaintSinkMap.php b/lib/composer/vimeo/psalm/dictionaries/InternalTaintSinkMap.php new file mode 100644 index 0000000000000000000000000000000000000000..d6451202be15001b868c4cbe95e5ea65b160d021 --- /dev/null +++ b/lib/composer/vimeo/psalm/dictionaries/InternalTaintSinkMap.php @@ -0,0 +1,42 @@ + [['shell']], +'create_function' => [['text'], ['text']], +'file_get_contents' => [['text']], +'file_put_contents' => [['shell']], +'fopen' => [['shell']], +'header' => [['text']], +'igbinary_unserialize' => [['text']], +'ldap_search' => [['text']], +'mysqli_query' => [[], ['sql']], +'mysqli::query' => [['sql']], +'mysqli_real_query' => [[], ['sql']], +'mysqli::real_query' => [['sql']], +'mysqli_multi_query' => [[], ['sql']], +'mysqli::multi_query' => [['sql']], +'mysqli_prepare' => [[], ['sql']], +'mysqli::prepare' => [['sql']], +'mysqli_stmt::__construct' => [[], ['sql']], +'mysqli_stmt_prepare' => [[], ['sql']], +'mysqli_stmt::prepare' => [['sql']], +'passthru' => [['shell']], +'pcntl_exec' => [['shell']], +'PDO::prepare' => [['sql']], +'PDO::query' => [['sql']], +'PDO::exec' => [['sql']], +'pg_exec' => [[], ['sql']], +'pg_prepare' => [[], [], ['sql']], +'pg_put_line' => [[], ['sql']], +'pg_query' => [[], ['sql']], +'pg_query_params' => [[], ['sql']], +'pg_send_prepare' => [[], [], ['sql']], +'pg_send_query' => [[], ['sql']], +'pg_send_query_params' => [[], ['sql'], []], +'setcookie' => [['text'], ['text']], +'shell_exec' => [['shell']], +'system' => [['shell']], +'unserialize' => [['text']], +]; diff --git a/lib/composer/vimeo/psalm/dictionaries/PropertyMap.php b/lib/composer/vimeo/psalm/dictionaries/PropertyMap.php new file mode 100644 index 0000000000000000000000000000000000000000..e3ea9afd05c272b1ca4d29ffca0f16b828b81085 --- /dev/null +++ b/lib/composer/vimeo/psalm/dictionaries/PropertyMap.php @@ -0,0 +1,524 @@ + [ + 'name' => 'string', + ], + 'limititerator' => [ + 'name' => 'string', + ], + 'solrdocumentfield' => [ + 'name' => 'string', + 'boost' => 'float', + 'values' => 'array', + ], + 'domprocessinginstruction' => [ + 'target' => 'string', + 'data' => 'string', + ], + 'recursivearrayiterator' => [ + 'name' => 'string', + ], + 'eventbuffer' => [ + 'length' => 'int', + 'contiguous-space' => 'int', + ], + 'mongocursor' => [ + 'slaveokay' => 'boolean', + 'timeout' => 'integer', + ], + 'domxpath' => [ + 'document' => 'DOMDocument', + ], + 'domentity' => [ + 'publicId' => 'string', + 'systemId' => 'string', + 'notationName' => 'string', + 'actualEncoding' => 'string', + 'encoding' => 'string', + 'version' => 'string', + ], + 'splminheap' => [ + 'name' => 'string', + ], + 'mongodb-driver-exception-writeexception' => [ + 'writeresult' => 'MongoDBDriverWriteResult', + ], + 'ziparchive' => [ + 'status' => 'int', + 'statusSys' => 'int', + 'numFiles' => 'int', + 'filename' => 'string', + 'comment' => 'string', + ], + 'solrexception' => [ + 'sourceline' => 'integer', + 'sourcefile' => 'string', + 'zif-name' => 'string', + ], + 'arrayiterator' => [ + 'name' => 'string', + ], + 'mongoid' => [ + 'id' => 'string', + ], + 'dateinterval' => [ + 'y' => 'integer', + 'm' => 'integer', + 'd' => 'integer', + 'h' => 'integer', + 'i' => 'integer', + 's' => 'integer', + 'f' => 'float', // only present from 7.1 onwards + 'invert' => 'integer', + 'days' => 'false|int', + ], + 'tokyotyrantexception' => [ + 'code' => 'int', + ], + 'tidy' => [ + 'errorbuffer' => 'string', + ], + 'filteriterator' => [ + 'name' => 'string', + ], + 'parentiterator' => [ + 'name' => 'string', + ], + 'recursiveregexiterator' => [ + 'name' => 'string', + ], + 'error' => [ + 'message' => 'string', + 'code' => 'int', + 'file' => 'string', + 'line' => 'int', + ], + 'domexception' => [ + 'code' => 'int', + ], + 'domentityreference' => [ + 'name' => 'string', + ], + 'spldoublylinkedlist' => [ + 'name' => 'string', + ], + 'domdocumentfragment' => [ + 'name' => 'string', + ], + 'collator' => [ + 'name' => 'string', + ], + 'streamwrapper' => [ + 'context' => 'resource', + ], + 'pdostatement' => [ + 'querystring' => 'string', + ], + 'domnotation' => [ + 'publicId' => 'string', + 'systemId' => 'string', + ], + 'snmpexception' => [ + 'code' => 'string', + ], + 'directoryiterator' => [ + 'name' => 'string', + ], + 'splqueue' => [ + 'name' => 'string', + ], + 'locale' => [ + 'name' => 'string', + ], + 'directory' => [ + 'path' => 'string', + 'handle' => 'resource', + ], + 'splheap' => [ + 'name' => 'string', + ], + 'domnodelist' => [ + 'length' => 'int', + ], + 'mongodb' => [ + 'w' => 'integer', + 'wtimeout' => 'integer', + ], + 'splpriorityqueue' => [ + 'name' => 'string', + ], + 'mongoclient' => [ + 'connected' => 'boolean', + 'status' => 'string', + ], + 'domdocument' => [ + 'actualEncoding' => 'string', + 'config' => 'null', + 'doctype' => 'DOMDocumentType', + 'documentElement' => 'DOMElement', + 'documentURI' => 'string', + 'encoding' => 'string', + 'formatOutput' => 'bool', + 'implementation' => 'DOMImplementation', + 'preserveWhiteSpace' => 'bool', + 'recover' => 'bool', + 'resolveExternals' => 'bool', + 'standalone' => 'bool', + 'strictErrorChecking' => 'bool', + 'substituteEntities' => 'bool', + 'validateOnParse' => 'bool', + 'version' => 'string', + 'xmlEncoding' => 'string', + 'xmlStandalone' => 'bool', + 'xmlVersion' => 'string', + 'ownerDocument' => 'null', + 'parentNode' => 'null', + ], + 'libxmlerror' => [ + 'level' => 'int', + 'code' => 'int', + 'column' => 'int', + 'message' => 'string', + 'file' => 'string', + 'line' => 'int', + ], + 'domimplementation' => [ + 'name' => 'string', + ], + 'normalizer' => [ + 'name' => 'string', + ], + 'norewinditerator' => [ + 'name' => 'string', + ], + 'event' => [ + 'pending' => 'bool', + ], + 'domdocumenttype' => [ + 'publicId' => 'string', + 'systemId' => 'string', + 'name' => 'string', + 'entities' => 'DOMNamedNodeMap', + 'notations' => 'DOMNamedNodeMap', + 'internalSubset' => 'string', + ], + 'errorexception' => [ + 'severity' => 'int', + ], + 'recursivedirectoryiterator' => [ + 'name' => 'string', + ], + 'domcharacterdata' => [ + 'data' => 'string', + 'length' => 'int', + ], + 'mongocollection' => [ + 'db' => 'MongoDB', + 'w' => 'integer', + 'wtimeout' => 'integer', + ], + 'mongoint64' => [ + 'value' => 'string', + ], + 'mysqli' => [ + 'affected_rows' => 'int', + 'client_info' => 'string', + 'client_version' => 'int', + 'connect_errno' => 'int', + 'connect_error' => 'string', + 'errno' => 'int', + 'error' => 'string', + 'error_list' => 'array', + 'field_count' => 'int', + 'host_info' => 'string', + 'info' => 'string', + 'insert_id' => 'int', + 'protocol_version' => 'string', + 'server_info' => 'string', + 'server_version' => 'int', + 'sqlstate' => 'string', + 'thread_id' => 'int', + 'warning_count' => 'int', + ], + 'mysqli_driver' => [ + 'client_info' => 'string', + 'client_version' => 'string', + 'driver_version' => 'string', + 'embedded' => 'string', + 'reconnect' => 'bool', + 'report_mode' => 'int' + ], + 'mysqli_result' => [ + 'current_field' => 'int', + 'field_count' => 'int', + 'lengths' => 'array', + 'num_rows' => 'int', + 'type' => 'mixed', + ], + 'mysqli_sql_exception' => [ + 'sqlstate' => 'string' + ], + 'mysqli_stmt' => [ + 'affected_rows' => 'int', + 'errno' => 'int', + 'error' => 'string', + 'error_list' => 'array', + 'field_count' => 'int', + 'id' => 'mixed', + 'insert_id' => 'int', + 'num_rows' => 'int', + 'param_count' => 'int', + 'sqlstate' => 'string', + ], + 'mysqli_warning' => [ + 'errno' => 'int', + 'message' => 'string', + 'sqlstate' => 'string', + ], + 'eventlistener' => [ + 'fd' => 'int', + ], + 'splmaxheap' => [ + 'name' => 'string', + ], + 'regexiterator' => [ + 'name' => 'string', + ], + 'domelement' => [ + 'schemaTypeInfo' => 'bool', + 'tagName' => 'string', + 'attributes' => 'DOMNamedNodeMap', + ], + 'tidynode' => [ + 'value' => 'string', + 'name' => 'string', + 'type' => 'int', + 'line' => 'int', + 'column' => 'int', + 'proprietary' => 'bool', + 'id' => 'int', + 'attribute' => 'array', + 'child' => '?array', + ], + 'recursivecachingiterator' => [ + 'name' => 'string', + ], + 'solrresponse' => [ + 'http-status' => 'integer', + 'parser-mode' => 'integer', + 'success' => 'bool', + 'http-status-message' => 'string', + 'http-request-url' => 'string', + 'http-raw-request-headers' => 'string', + 'http-raw-request' => 'string', + 'http-raw-response-headers' => 'string', + 'http-raw-response' => 'string', + 'http-digested-response' => 'string', + ], + 'domnamednodemap' => [ + 'length' => 'int', + ], + 'splstack' => [ + 'name' => 'string', + ], + 'numberformatter' => [ + 'name' => 'string', + ], + 'eventsslcontext' => [ + 'local-cert' => 'string', + 'local-pk' => 'string', + ], + 'pdoexception' => [ + 'errorinfo' => 'array', + 'code' => 'string', + ], + 'domnode' => [ + 'nodeName' => 'string', + 'nodeValue' => 'string', + 'nodeType' => 'int', + 'parentNode' => 'DOMNode|null', + 'childNodes' => 'DOMNodeList', + 'firstChild' => 'DOMNode|null', + 'lastChild' => 'DOMNode|null', + 'previousSibling' => 'DOMNode|null', + 'nextSibling' => 'DOMNode|null', + 'attributes' => 'null', + 'ownerDocument' => 'DOMDocument|null', + 'namespaceURI' => 'string|null', + 'prefix' => 'string', + 'localName' => 'string', + 'baseURI' => 'string|null', + 'textContent' => 'string', + ], + 'domattr' => [ + 'name' => 'string', + 'ownerElement' => 'DOMElement', + 'schemaTypeInfo' => 'bool', + 'specified' => 'bool', + 'value' => 'string', + ], + 'simplexmliterator' => [ + 'name' => 'string', + ], + 'snmp' => [ + 'max-oids' => 'int', + 'valueretrieval' => 'int', + 'quick-print' => 'bool', + 'enum-print' => 'bool', + 'oid-output-format' => 'int', + 'oid-increasing-check' => 'bool', + 'exceptions-enabled' => 'int', + 'info' => 'array', + ], + 'mongoint32' => [ + 'value' => 'string', + ], + 'xmlreader' => [ + 'attributeCount' => 'int', + 'baseURI' => 'string', + 'depth' => 'int', + 'hasAttributes' => 'bool', + 'hasValue' => 'bool', + 'isDefault' => 'bool', + 'isEmptyElement' => 'bool', + 'localName' => 'string', + 'name' => 'string', + 'namespaceURI' => 'string', + 'nodeType' => 'int', + 'prefix' => 'string', + 'value' => 'string', + 'xmlLang' => 'string', + ], + 'eventbufferevent' => [ + 'fd' => 'integer', + 'priority' => 'integer', + 'input' => 'EventBuffer', + 'output' => 'EventBuffer', + ], + 'domtext' => [ + 'wholeText' => 'string', + ], + 'exception' => [ + 'message' => 'string', + 'code' => 'int', + 'file' => 'string', + 'line' => 'int', + ], + 'reflectionclass' => [ + 'name' => 'string', + ], + 'reflectionmethod' => [ + 'class' => 'string', + 'name' => 'string', + ], + 'reflectionparameter' => [ + 'name' => 'string', + ], + 'phpparser\\node\\expr\\funccall' => [ + 'args' => 'array', + ], + 'phpparser\\node\\expr\\new_' => [ + 'args' => 'array', + ], + 'phpparser\\node\\expr\\array_' => [ + 'items' => 'array', + ], + 'phpparser\\node\\expr\\list_' => [ + 'items' => 'array', + ], + 'phpparser\\node\\expr\\methodcall' => [ + 'args' => 'array', + ], + 'phpparser\\node\\expr\\staticcall' => [ + 'args' => 'array', + ], + 'phpparser\\node\\name' => [ + 'parts' => 'non-empty-array', + ], + 'phpparser\\node\\stmt\\namespace_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\if_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\elseif_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\else_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\for_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\foreach_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\trycatch' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\catch_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\finally_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\case_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\while_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\do_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\class_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\trait_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\stmt\\interface_' => [ + 'stmts' => 'array', + ], + 'phpparser\\node\\matcharm' => [ + 'conds' => 'null|non-empty-list', + ], + 'rdkafka\\message' => [ + 'err' => 'int', + 'topic_name' => 'string', + 'partition' => 'int', + 'payload' => 'string', + 'key' => 'string|null', + 'offset' => 'int', + 'timestamp' => 'int', + 'headers' => 'array|null', + ], +]; diff --git a/lib/composer/vimeo/psalm/dictionaries/scripts/update_signaturemap_from_other_tool.php b/lib/composer/vimeo/psalm/dictionaries/scripts/update_signaturemap_from_other_tool.php new file mode 100644 index 0000000000000000000000000000000000000000..e2547a587577740b3d440cceb78ea5ae5221f2ea --- /dev/null +++ b/lib/composer/vimeo/psalm/dictionaries/scripts/update_signaturemap_from_other_tool.php @@ -0,0 +1,66 @@ + strtolower($a) <=> strtolower($b)); + +foreach ($new_local as $name => $data) { + if (!is_array($data)) { + throw new \UnexpectedValueException('bad data for ' . $name); + } + $return_type = array_shift($data); + echo '\'' . str_replace("'", "\'", $name) . '\' => [\'' . str_replace("'", "\'", $return_type) . '\''; + + if ($data) { + $signature = []; + + foreach ($data as $param_name => $type) { + $signature[] = '\'' . str_replace("'", "\'", $param_name) . '\'=>\'' . str_replace("'", "\'", $type) . '\''; + } + + echo ', ' . implode(', ', $signature); + } + + echo '],' . "\n"; +} + + +function get_changed_functions(array $a, array $b) { + $changed_functions = []; + + foreach (array_intersect_key($a, $b) as $function_name => $a_data) { + if (json_encode($b[$function_name]) !== json_encode($a_data)) { + $changed_functions[$function_name] = $b[$function_name]; + } + } + + return $changed_functions; +} diff --git a/lib/composer/vimeo/psalm/docs/README.md b/lib/composer/vimeo/psalm/docs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2d7171d4f347995b390d1130ba9cccb4d625a5f1 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/README.md @@ -0,0 +1,68 @@ +# About Psalm + +Psalm is a static analysis tool that attempts to dig into your program and find as many type-related bugs as possible. + +It has a few features that go further than other similar tools: + +- **Mixed type warnings**
+ If Psalm cannot infer a type for an expression then it uses a `mixed` placeholder type. `mixed` types can sometimes mask bugs, so keeping track of them helps you avoid a number of common pitfalls. + +- **Intelligent logic checks**
+ Psalm keeps track of logical assertions made about your code, so `if ($a && $a) {}` and `if ($a && !$a) {}` are both treated as issues. Psalm also keeps track of logical assertions made in prior code paths, preventing issues like `if ($a) {} elseif ($a) {}`. + +- **Property initialisation checks**
+ Psalm checks that all properties of a given object have values after the constructor is called. + +Psalm also has a few features to make it perform as well as possible on large codebases: + +- **Multi-threaded mode**
+ Wherever possible Psalm will run its analysis in parallel to save time. Useful for large codebases, it has a massive impact on performance. + +- **Incremental checks**
+ By default Psalm only analyses files that have changed and files that reference those changed files. + +## Example output + +Given a file `implode_strings.php`: + +```php + ./vendor/bin/psalm implode_strings.php +ERROR: InvalidArgument - somefile.php:3:14 - Argument 1 of implode expects `string`, `array` provided (see https://psalm.dev/004) +``` + +## Inspirations + +There are two main inspirations for Psalm: + +- Etsy's [Phan](https://github.com/etsy/phan), which uses nikic's [php-ast](https://github.com/nikic/php-ast) extension to create an abstract syntax tree +- Facebook's [Hack](http://hacklang.org/), a PHP-like language that supports many advanced typing features natively, so docblocks aren't necessary. + +## Index + +- Running Psalm: + - [Installation](running_psalm/installation.md) + - [Configuration](running_psalm/configuration.md) + - Plugins + - [Using plugins](running_psalm/plugins/using_plugins.md) + - [Authoring plugins](running_psalm/plugins/authoring_plugins.md) + - [How Psalm represents types](running_psalm/plugins/plugins_type_system.md) + - [Command line usage](running_psalm/command_line_usage.md) + - [IDE support](running_psalm/language_server.md) + - Handling errors: + - [Dealing with code issues](running_psalm/dealing_with_code_issues.md) + - [Issue Types](running_psalm/issues.md) + - [Checking non-PHP files](running_psalm/checking_non_php_files.md) +- Annotating code: + - [Typing in Psalm](annotating_code/typing_in_psalm.md) + - [Supported Annotations](annotating_code/supported_annotations.md) + - [Template Annotations](annotating_code/templated_annotations.md) +- Manipulating code: + - [Fixing code](manipulating_code/fixing.md) + - [Refactoring code](manipulating_code/refactoring.md) + diff --git a/lib/composer/vimeo/psalm/docs/annotating_code/adding_assertions.md b/lib/composer/vimeo/psalm/docs/annotating_code/adding_assertions.md new file mode 100644 index 0000000000000000000000000000000000000000..a8f7fd5d61233945fb56efd7b38cf58277fc5334 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/annotating_code/adding_assertions.md @@ -0,0 +1,110 @@ +# Adding assertions + +Psalm has three docblock annotations that allow you to specify that a function verifies facts about variables and properties: + +- `@psalm-assert` (used when throwing an exception) +- `@psalm-assert-if-true`/`@psalm-assert-if-false` (used when returning a `bool`) + +A list of acceptable assertions [can be found here](assertion_syntax.md). + +## Examples + +If you have a class that verified its input is an array of strings, you can make that clear to Psalm: + +```php +isValid(); +} + +/** + * @psalm-assert-if-false B $a + */ +function isInvalidB(A $a) : bool { + return $a instanceof B || !$a->isValid(); +} + +function takesA(A $a) : void { + if (isValidB($a)) { + $a->bar(); + } + + if (isInvalidB($a)) { + // do something + } else { + $a->bar(); + } + + $a->bar(); //error +} +``` + +As well as getting Psalm to understand that the given data must be a certain type, you can also show that a variable must be not null: + +```php + $foo` + +## Negated assertions + +Any assertion above can be negated: + +This asserts that `$foo` is not an `int`: + +```php +property`. If used above an assignment, Psalm checks whether the `VariableReference` matches the variable being assigned. If they differ, Psalm will assign the `Type` to `VariableReference` and use it in the expression below. + +If no `VariableReference` is given, the annotation tells Psalm that the right hand side of the expression, whether an assignment or a return, is of type `Type`. + +```php + 1 ? new Foo() : null; +} + +takesFoo(getFoo()); +``` + +### `@psalm-ignore-falsable-return` + +This provides the same, but for `false`. Psalm uses this internally for functions like `preg_replace`, which can return false if the given input has encoding errors, but where 99.9% of the time the function operates as expected. + +### `@psalm-seal-properties` + +If you have a magic property getter/setter, you can use `@psalm-seal-properties` to instruct Psalm to disallow getting and setting any properties not contained in a list of `@property` (or `@property-read`/`@property-write`) annotations. + +```php +bar = 5; // this call fails +``` + +### `@psalm-internal` + +Used to mark a class, property or function as internal to a given namespace. Psalm treats this slightly differently to +the PHPDoc `@internal` tag. For `@internal`, an issue is raised if the calling code is in a namespace completely +unrelated to the namespace of the calling code, i.e. not sharing the first element of the namespace. + +In contrast for `@psalm-internal`, the docbloc line must specify a namespace. An issue is raised if the calling code +is not within the given namespace. + +```php +s = $s; + } +} + +$b = new B("hello"); +echo $b->s; +$b->s = "boo"; // disallowed +``` + +### `@psalm-mutation-free` + +Used to annotate a class method that does not mutate state, either internally or externally of the class's scope. + +```php +s = $s; + } + + /** + * @psalm-mutation-free + */ + public function getShort() : string { + return substr($this->s, 0, 5); + } + + /** + * @psalm-mutation-free + */ + public function getShortMutating() : string { + $this->s .= "hello"; // this is a bug + return substr($this->s, 0, 5); + } +} +``` + +### `@psalm-external-mutation-free` + +Used to annotate a class method that does not mutate state externally of the class's scope. + +```php +s = $s; + } + + /** + * @psalm-external-mutation-free + */ + public function getShortMutating() : string { + $this->s .= "hello"; // this is fine + return substr($this->s, 0, 5); + } + + /** + * @psalm-external-mutation-free + */ + public function save() : void { + file_put_contents("foo.txt", $this->s); // this is a bug + } +} +``` + +### `@psalm-immutable` + +Used to annotate a class where every property is treated by consumers as `@psalm-readonly` and every instance method is treated as `@psalm-mutation-free`. + +```php +baz = $baz; + } + + public function bar(): int + { + return 0; + } +} + +$anonymous = new /** @psalm-immutable */ class extends Foo +{ + public string $baz = "B"; + + public function bar(): int + { + return 1; + } +}; +``` + +### `@psalm-pure` + +Used to annotate a [pure function](https://en.wikipedia.org/wiki/Pure_function) - one whose output is just a function of its input. + +```php + random_int(1, 2) +); +``` + +### `@psalm-allow-private-mutation` + +Used to annotate readonly properties that can be mutated in a private context. With this, public properties can be read from another class but only be mutated within a method of its own class. + +```php +count++; + } +} + +$counter = new Counter(); +echo $counter->count; // outputs 0 +$counter->increment(); // Method can mutate property +echo $counter->count; // outputs 1 +$counter->count = 5; // This will fail, as it's mutating a property directly +``` + +### `@psalm-readonly-allow-private-mutation` + +This is a shorthand for the property annotations `@readonly` and `@psalm-allow-private-mutation`. + +```php +count++; + } +} + +$counter = new Counter(); +echo $counter->count; // outputs 0 +$counter->increment(); // Method can mutate property +echo $counter->count; // outputs 1 +$counter->count = 5; // This will fail, as it's mutating a property directly +``` + +### `@psalm-trace` + +You can use this annotation to trace inferred type (applied to the *next* statement). + +```php + "Nokia"]; + } +} +``` + +### `@psalm-import-type` + +You can use this annotation to import a type defined with [`@psalm-type`](#psalm-type) if it was defined somewhere else. + +```php +toArray()); + } +} +``` + +You can also alias a type when you import it: + +```php +toArray()); + } +} +``` + +## Type Syntax + +Psalm supports PHPDoc’s [type syntax](https://docs.phpdoc.org/latest/guides/types.html), and also the [proposed PHPDoc PSR type syntax](https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#appendix-a-types). + +A detailed write-up is found in [Typing in Psalm](typing_in_psalm.md) diff --git a/lib/composer/vimeo/psalm/docs/annotating_code/templated_annotations.md b/lib/composer/vimeo/psalm/docs/annotating_code/templated_annotations.md new file mode 100644 index 0000000000000000000000000000000000000000..f0b152480995d85e99efd05d43789b2824dfe463 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/annotating_code/templated_annotations.md @@ -0,0 +1,447 @@ +# Templating + +Docblocks allow you to tell Psalm some simple information about how your code works. For example `@return int` in a function return type tells Psalm that a function should return an `int` and `@return MyContainer` tells Psalm that a function should return an instance of a user-defined class `MyContainer`. In either case, Psalm can check that the function actually returns those types _and_ that anything calling that function uses its returned value properly. + +Templated types allow you to tell Psalm even more information about how your code works. + +Let's look at a simple class `MyContainer`: + +```php +value = $value; + } + + public function getValue() { + return $this->value; + } +} +``` + +When Psalm handles the return type of `$my_container->getValue()` it doesn't know what it's getting out, because the value can be arbitrary. + +Templated annotations provide us with a workaround - we can define a generic/templated param `T` that is a placeholder for the value inside `MyContainer`: + +```php +value = $value; + } + + /** @return T */ + public function getValue() { + return $this->value; + } +} +``` + +Now we can substitute values for that templated param when we reference `MyContainer` in docblocks e.g. `@return MyContainer`. This tells Psalm to substitute `T` for `int` when evaluating that return type, effectively treating it as a class that looks like + +```php +value = $value; + } + + /** @return int */ + public function getValue() { + return $this->value; + } +} +``` + +This pattern can be used in large number of different situations like mocking, collections, iterators and loading arbitrary objects. Psalm has a large number of annotations to make it easy to use templated types in your codebase. + +## `@template` + +The `@template` tag allows classes and functions to declare a generic type parameter. + +As a very simple example, this function returns whatever is passed in: + +```php + $arr + * @param array $arr2 + * @return array + */ +function array_combine(array $arr, array $arr2) {} +``` + +### Notes +- `@template` tag order matters for class docblocks, as they dictate the order in which those generic parameters are referenced in docblocks. +- The names of your templated types (e.g. `TKey`, `TValue` don't matter outside the scope of the class or function in which they're declared. + +## @param class-string<T> + +Psalm also allows you to parameterize class types + +```php + $class + * @return T + */ +function instantiator(string $class) { + return new $class(); +} + +class Foo {} + +$a = instantiator(Foo::class); // Psalm knows the result is an object of type Foo +``` + +## Template inheritance + +Psalm allows you to extend templated classes with `@extends`/`@template-extends`: + +```php + + */ +class ChildClass extends ParentClass {} +``` + +similarly you can implement interfaces with `@implements`/`@template-implements` + +```php + + */ +class Foo implements IFoo {} +``` + +and import traits with `@use`/`@template-use` + +```php + + */ + use MyTrait; +} +``` + +You can also extend one templated class with another, e.g. + +```php + + */ +class ChildClass extends ParentClass {} +``` + +## Template constraints + +You can use `@template of ` to restrict input. For example, to restrict to a given class you can use + +```php + + */ +function makeArray($t) { + return [$t]; +} +$a = makeArray(new Foo()); // typed as array +$b = makeArray(new FooChild()); // typed as array +$c = makeArray(new stdClass()); // type error +``` + +Templated types aren't limited to key-value pairs, and you can re-use templates across multiple arguments of a template-supporting type: +```php + + */ +abstract class Foo implements IteratorAggregate { + /** + * @var int + */ + protected $rand_min; + + /** + * @var int + */ + protected $rand_max; + + public function __construct(int $rand_min, int $rand_max) { + $this->rand_min = $rand_min; + $this->rand_max = $rand_max; + } + + /** + * @return Generator + */ + public function getIterator() : Generator { + $j = random_int($this->rand_min, $this->rand_max); + for($i = $this->rand_min; $i <= $j; $i += 1) { + yield $this->getFuzzyType($i) => $i ** $i; + } + + return $this->getFuzzyType($j); + } + + /** + * @return T0 + */ + abstract protected function getFuzzyType(int $i); +} + +/** + * @template-extends Foo + */ +class Bar extends Foo { + protected function getFuzzyType(int $i) : int { + return $i; + } +} + +/** + * @template-extends Foo + */ +class Baz extends Foo { + protected function getFuzzyType(int $i) : string { + return static::class . '[' . $i . ']'; + } +} +``` + +## Template covariance + +Imagine you have code like this: + +```php + + */ + public array $list; + + /** + * @param array $list + */ + public function __construct(array $list) { + $this->list = $list; + } + + /** + * @param T $t + */ + public function add($t) : void { + $this->list[] = $t; + } +} + +/** + * @param Collection $collection + */ +function addAnimal(Collection $collection) : void { + $collection->add(new Cat()); +} + +/** + * @param Collection $dog_collection + */ +function takesDogList(Collection $dog_collection) : void { + addAnimal($dog_collection); +} +``` + +That last call `addAnimal($dog_collection)` breaks the type of the collection – suddenly a collection of dogs becomes a collection of dogs _or_ cats. That is bad. + +To prevent this, Psalm emits an error when calling `addAnimal($dog_collection)` saying "addAnimal expects a `Collection`, but `Collection` was passed". If you haven't encountered this rule before it's probably confusing to you – any function that accepted an `Animal` would be happy to accept a subtype thereof. But as we see in the example above, doing so can lead to problems. + +But there are also times where it's perfectly safe to pass template param subtypes: + +```php + */ + public array $list = []; +} + +/** + * @param Collection $collection + */ +function getNoises(Collection $collection) : void { + foreach ($collection->list as $animal) { + echo $animal->getNoise(); + } +} + +/** + * @param Collection $dog_collection + */ +function takesDogList(Collection $dog_collection) : void { + getNoises($dog_collection); +} +``` + +Here we're not doing anything bad – we're just iterating over an array of objects. But Psalm still gives that same basic error – "getNoises expects a `Collection`, but `Collection` was passed". + +We can tell Psalm that it's safe to pass subtypes for the templated param `T` by using the annotation `@template-covariant T`: + +```php + */ + public array $list = []; +} +``` + +Doing this for the above example produces no errors: [https://psalm.dev/r/5254af7a8b](https://psalm.dev/r/5254af7a8b) + +But `@template-covariant` doesn't get rid of _all_ errors – if you add it to the first example, you get a new error – [https://psalm.dev/r/0fcd699231](https://psalm.dev/r/0fcd699231) – complaining that you're attempting to use a covariant template parameter for function input. That’s no good, as it means you're likely altering the collection somehow (which is, again, a violation). + +### But what about immutability? + +Psalm has [comprehensive support for declaring functional immutability](https://psalm.dev/articles/immutability-and-beyond). + +If we make sure that the class is immutable, we can declare a class with an `add` method that still takes a covariant param as input, but which does not modify the collection at all, instead returning a new one: + +```php + + */ + public array $list = []; + + /** + * @param array $list + */ + public function __construct(array $list) { + $this->list = $list; + } + + /** + * @param T $t + * @return Collection + */ + public function add($t) : Collection { + return new Collection(array_merge($this->list, [$t])); + } +} +``` + +This is perfectly valid, and Psalm won't complain. + +## Builtin templated classes and interfaces + +Psalm has support for a number of builtin classes and interfaces that you can extend/implement in your own code. + +- `interface Traversable` +- `interface ArrayAccess` +- `interface IteratorAggregate extends Traversable` +- `interface Iterator extends Traversable` +- `interface SeekableIterator extends Iterator` + +- `class Generator extends Traversable` +- `class ArrayObject implements IteratorAggregate, ArrayAccess` +- `class ArrayIterator implements SeekableIterator, ArrayAccess` +- `class DOMNodeList implements Traversable` +- `class SplDoublyLinkedList implements Iterator, ArrayAccess` +- `class SplQueue extends SplDoublyLinkedList` diff --git a/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/array_types.md b/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/array_types.md new file mode 100644 index 0000000000000000000000000000000000000000..6793a70eb7d5de62f905fcf7bb81b159848a557d --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/array_types.md @@ -0,0 +1,135 @@ +# Array types + +In PHP, the `array` type is commonly used to represent three different data structures: + +[List](https://en.wikipedia.org/wiki/List_(abstract_data_type)): +```php + 'hello', 5 => 'goodbye']; +$b = ['a' => 'AA', 'b' => 'BB', 'c' => 'CC'] +``` + +Makeshift [Structs](https://en.wikipedia.org/wiki/Struct_(C_programming_language)): +```php + 'Psalm', 'type' => 'tool']; +``` + +PHP treats all these arrays the same, essentially (though there are some optimisations under the hood for the first case). + +Psalm has a few different ways to represent arrays in its type system: + +## Generic arrays + +Psalm uses a syntax [borrowed from Java](https://en.wikipedia.org/wiki/Generics_in_Java) that allows you denote the types of both keys *and* values: +```php +/** @return array */ +``` + +You can also specify that an array is non-empty with the special type `non-empty-array`. + +### PHPDoc syntax + +PHPDoc [allows you to specify](https://phpdoc.org/docs/latest/references/phpdoc/types.html#arrays) the type of values a generic array holds with the annotation: +```php +/** @return ValueType[] */ +``` + +In Psalm this annotation is equivalent to `@psalm-return array`. + +Generic arrays encompass both _associative arrays_ and _lists_. + +## Lists + +(Psalm 3.6+) + +Psalm supports a `list` type that represents continuous, integer-indexed arrays like `["red", "yellow", "blue"]` . + +These arrays have the property `$arr === array_values($arr)`, and represent a large percentage of all array usage in PHP applications. + +A `list` type is of the form `list`, where `SomeType` is any permitted [union type](union_types.md) supported by Psalm. + +- `list` is a subtype of `array` +- `list` is a subtype of `array`. + +List types show their value in a few ways: + +```php + $arr + */ +function takesArray(array $arr) : void { + if ($arr) { + // this index may not be set + echo $arr[0]; + } +} + +/** + * @psalm-param list $arr + */ +function takesList(array $arr) : void { + if ($arr) { + // list indexes always start from zero, + // so a non-empty list will have an element here + echo $arr[0]; + } +} + +takesArray(["hello"]); // this is fine +takesArray([1 => "hello"]); // would trigger bug, without warning + +takesList(["hello"]); // this is fine +takesList([1 => "hello"]); // triggers warning in Psalm +``` + +## Object-like arrays + +Psalm supports a special format for arrays where the key offsets are known: object-like arrays. + +Given an array + +```php + new stdClass, 28 => false]; +``` + +Psalm will type it internally as: + +``` +array{0: string, 1: string, foo: stdClass, 28: false} +``` + +You can specify types in that format yourself, e.g. + +```php +/** @return array{foo: string, bar: int} */ +``` + +Optional keys can be denoted by a trailing `?`, e.g.: + +```php +/** @return array{optional?: string, bar: int} */ +``` + +## Callable arrays + +a array holding a callable, like phps native `call_user_func()` and friends supports it: + +```php +](object_types.md) +- [Generator](object_types.md) + +## [Array types](array_types.md) + +- [array & non-empty-array](array_types.md) +- [array\](array_types.md#generic-arrays) +- [string\[\]](array_types.md#phpdoc-syntax) +- [list & non-empty-list](array_types.md#lists) +- [list\](array_types.md#lists) +- [array{foo: int, bar: string}](array_types.md#object-like-arrays) +- [callable-array](array_types.md#callable-array) + +## [Callable types](callable_types.md) + +- [callable, Closure and callable(Foo, Bar):Baz](callable_types.md) + +## [Value types](value_types.md) + +- [null](value_types.md#null) +- [true, false](value_types.md#true-false) +- [6, 7.0, "forty-two" and 'forty two'](value_types.md#some_string-4-314) +- [Foo\Bar::MY_SCALAR_CONST](value_types.md#regular-class-constants) + +## Magical types + +- [(T is true ? string : bool)](conditional_types.md) +- `key-of` +- `value-of` +- `T[K]` + +## Other + +- `iterable` - represents the [iterable pseudo-type](https://php.net/manual/en/language.types.iterable.php). Like arrays, iterables can have type parameters e.g. `iterable`. +- `void` - can be used in a return type when a function does not return a value. +- `empty` - a type that represents a lack of type - not just a lack of type information (that's where [mixed](#mixed) is useful) but where there can be no type. A good example is the type of the empty array `[]`. Psalm types this as `array`. +- `mixed` represents a lack of type information. Psalm warns about mixed when the `totallyTyped` flag is turned on. +- `resource` represents a [PHP resource](https://www.php.net/manual/en/language.types.resource.php). +- `no-return` is the 'return type' for a function that can never actually return, such as `die()`, `exit()`, or a function that + always throws an exception. It may also be written as `never-return` or `never-returns`, and is also known as the _bottom type_. diff --git a/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/callable_types.md b/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/callable_types.md new file mode 100644 index 0000000000000000000000000000000000000000..16050bcbb725d3d9a06566a328ce8d5bd7ae80b1 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/callable_types.md @@ -0,0 +1,27 @@ +# Callable types + +Psalm supports a special format for `callable`s of the form. It can also be used for annotating `Closure`. + +``` +callable(Type1, OptionalType2=, SpreadType3...):ReturnType +``` + +Adding `=` after the type implies it is optional, and suffixing with `...` implies the use of the spread operator. + +Using this annotation you can specify that a given function return a `Closure` e.g. + +```php + is ? : )` + +All conditional types must be wrapped inside brackets e.g. `(...)` + +Conditional types are dependent on [template parameters](../templated_annotations.md), so you can only use them in a function where template parameters are defined. + +## Example application + +Let's suppose we want to make a userland implementation of PHP's numeric addition (but please never do this). You could type this with a conditional return type: + +```php +createMock(Hare::class); +``` +`$hare` will be an instance of a class that extends `Hare`, and implements `\PHPUnit\Framework\MockObject\MockObject`. So +`$hare` is typed as `Hare&\PHPUnit\Framework\MockObject\MockObject`. You can use this syntax whenever a value is +required to implement multiple interfaces. + +Another use case is being able to merge object-like arrays: +```php +/** + * @psalm-type A=array{a: int} + * @psalm-type B=array{b: int} + * + * @param A $a + * @param B $b + * + * @return A&B + */ +function foo($a, $b) { + return $a + $b; +} +``` +The returned type will contain the properties of both `A` and `B`. In other words, it will be `{a: int, b: int}`. + +Intersections are only valid for lists of only *object types* and lists of only *object-like arrays*. diff --git a/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/object_types.md b/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/object_types.md new file mode 100644 index 0000000000000000000000000000000000000000..00af6c79e82a8e871726b69b4b88260c8723f550 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/object_types.md @@ -0,0 +1,20 @@ +# Object types + +`object`, `stdClass`, `Foo`, `Bar\Baz` etc. are examples of object types. These types are also valid types in PHP. + +#### Generic object types + +Psalm supports using generic object types like `ArrayObject`. Any generic object should be typehinted with appropriate [`@template` tags](../templated_annotations.md). + +#### Generators + +Generator types support up to four parameters, e.g. `Generator`: + +1. `TKey`, the type of the `yield` key - default: `mixed` +2. `TValue`, the type of the `yield` value - default: `mixed` +3. `TSend`, the type of the `send()` method's parameter - default: `mixed` +4. `TReturn`, the return type of the `getReturn()` method - default: `mixed` + +`Generator` is a shorthand for `Generator`. + +`Generator` is a shorthand for `Generator`. diff --git a/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/scalar_types.md b/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/scalar_types.md new file mode 100644 index 0000000000000000000000000000000000000000..f92aa6b73fddf42b7017db4035ce7b0513d35013 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/scalar_types.md @@ -0,0 +1,59 @@ +# Scalar types + +`int`, `bool`, `float`, `string` are examples of scalar types. Scalar types represent scalar values in PHP. These types are also valid types in PHP 7. + +### scalar + +The type `scalar` is the supertype of all scalar types. + +### array-key + +`array-key` is the supertype (but not a union) of `int` and `string`. + +### positive-int + +`positive-int` allows only positive integers + +### numeric + +`numeric` is a supertype of `int` or `float` and [`numeric-string`](#numeric-string). + +### class-string, interface-string + +Psalm supports a special meta-type for `MyClass::class` constants, `class-string`, which can be used everywhere `string` can. + +For example, given a function with a `string` parameter `$class_name`, you can use the annotation `@param class-string $class_name` to tell Psalm make sure that the function is always called with a `::class` constant in that position: + +```php +`](value_types.md#regular-class-constants). This tells Psalm that any matching type must either be a class string of `Foo` or one of its descendants. + +### trait-string + +Psalm also supports a `trait-string` annotation denote a trait that exists. + +### callable-string + +`callable-string` denotes a string value that has passed an `is_callable` check. + +### numeric-string + +`numeric-string` denotes a string value that has passed an `is_numeric` check. + +### lowercase-string, non-empty-string, non-empty-lowercase-string + +an empty string, lowercased or both at once. + +### html-escaped-string + +A string which can safely be used in a html context diff --git a/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/union_types.md b/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/union_types.md new file mode 100644 index 0000000000000000000000000000000000000000..7f01c11ea708513d86eceade8a78b3d4ebbef236 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/annotating_code/type_syntax/union_types.md @@ -0,0 +1,16 @@ +# Union Types + +An annotation of the form `Type1|Type2|Type3` is a _Union Type_. `Type1`, `Type2` and `Type3` are all acceptable possible types of that union type. + +`Type1`, `Type2` and `Type3` are each [atomic types](atomic_types.md). + +Union types can be generated in a number of different ways, for example in ternary expressions: + +```php + $foo_class`. If you only want the param to accept that exact class string, you can use the annotation `Foo::class`: + +```php +|class-string $s + */ +function foo(string $s) : void {} + +/** + * @param A::class|B::class $s + */ +function bar(string $s) : void {} + +foo(A::class); // works +foo(AChild::class); // works +foo(B::class); // works +foo(BChild::class); // works +bar(A::class); // works +bar(AChild::class); // fails +bar(B::class); // works +bar(BChild::class); // fails +``` diff --git a/lib/composer/vimeo/psalm/docs/annotating_code/typing_in_psalm.md b/lib/composer/vimeo/psalm/docs/annotating_code/typing_in_psalm.md new file mode 100644 index 0000000000000000000000000000000000000000..b2c8ee0ae3fed2471ada09bb28f6836ff735d51e --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/annotating_code/typing_in_psalm.md @@ -0,0 +1,173 @@ +# Typing in Psalm + +Psalm is able to interpret all PHPDoc type annotations, and use them to further understand the codebase. + +Types are used to describe acceptable values for properties, variables, function parameters and `return $x`. + +## Docblock Type Syntax + +Psalm allows you to express a lot of complicated type information in docblocks. + +All docblock types are either [atomic types](type_syntax/atomic_types.md), [union types](type_syntax/union_types.md) or [intersection types](type_syntax/intersection_types.md). + +Additionally Psalm supports PHPDoc’s [type syntax](https://docs.phpdoc.org/guides/types.html), and also the [proposed PHPDoc PSR type syntax](https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#appendix-a-types). + +## Property declaration types vs Assignment typehints + +You can use the `/** @var Type */` docblock to annotate both [property declarations](http://php.net/manual/en/language.oop5.properties.php) and to help Psalm understand variable assignment. + +### Property declaration types + +You can specify a particular type for a class property declaration in Psalm by using the `@var` declaration: + +```php +foo = $some_variable;`, Psalm will check to see whether `$some_variable` is either `string` or `null` and, if neither, emit an issue. + +If you leave off the property type docblock, Psalm will emit a `MissingPropertyType` issue. + +### Assignment typehints + +Consider the following code: + +```php + `src/FileName.php` mapping it uses reflection for project files and the Composer classmap for vendor files. + +### Storing data from scanning + +For each file that `ReflectorVisitor` visits, Psalm creates a [`FileStorage`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Storage/FileStorage.php) instance, along with [`ClassLikeStorage`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Storage/ClassLikeStorage.php) and [`FunctionLikeStorage`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Storage/FunctionLikeStorage.php) instances depending on the file contents. + +Once we have a set of all files and their classes and function signatures, we calculate inheritance for everything in the [`Populator`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Codebase/Populator.php) class and then move onto analysis. + +At the end of the scanning step we have populated all the necessary information in [`ClassLikes`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Codebase/ClassLikes.php), [`Functions`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Codebase/Functions.php) and [`Methods`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Codebase/Methods.php) classes, and created a complete list of `FileStorage` and `ClassLikeStorage` objects (in [`FileStorageProvider`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Provider/FileStorageProvider.php) and [`ClassLikeStorageProvider`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php) respectively) for all classes and files used in our project. + +## Analysis + +We analyse files in [`FileAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/FileAnalyzer.php) + +The `FileAnalyzer` takes a given file and looks for a set of top-level components: classes, traits, interfaces, functions. It can look inside namespaces and extract the classes, interfaces, traits and functions in them as well. + +It delegates the analysis of those components to [`ClassAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/ClassAnalyzer.php), [`InterfaceAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php) and [`FunctionAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php). + +Because it’s the most basic use case for the line-by-line analysis (no class inheritance to worry about), let’s drill down into `FunctionAnalyzer`. + +### Function Analysis + +`FunctionAnalyzer::analyze` is defined in [`FunctionLikeAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php). That method first gets the `FunctionLikeStorage` object for the given function that we created in our scanning step. That `FunctionLikeStorage` object has information about function parameters, which we then feed into a [`Context`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Context.php) object. The `Context` contains all the type information we know about variables & properties (stored in `Context::$vars_in_scope`) and also a whole bunch of other information that can change depending on assignments and assertions. + +Somewhere in `FunctionLikeAnalyzer::analyze` we create a new [`StatementsAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php) and then call its `analyze()` method, passing in a set of PhpParser nodes. `StatementAnalyzer::analyze` passes off to a number of different checkers (`IfAnalyzer`, `ForeachAnalyzer`, `ExpressionAnalyzer` etc.) for more thorough analysis. + +At each line the `Context` object may or may not be manipulated. At branching points (if statements, loops, ternary etc) the `Context` object is cloned and then, at the end of the branch, Psalm figures out how to resolve the changes and update the uncloned `Context` object. + +Each PhpParser node is then abused, adding a property called `inferredType` which Psalm uses for type analysis. + +After all the statements have been analysed we gather up all the return types and compare to the given return type. + +### Type Reconciliation + +While some updates to the `Context` object are straightforward, others are not. Updating the `Context` object in the light of new type information happens in [`Reconciler`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Type/Reconciler.php), which takes an array assertions e.g. `[“$a” => “!null”]` and a list of existing type information e.g. `$a => string|null` and return a set of updated information e.g. `$a => string` diff --git a/lib/composer/vimeo/psalm/docs/manipulating_code/fixing.md b/lib/composer/vimeo/psalm/docs/manipulating_code/fixing.md new file mode 100644 index 0000000000000000000000000000000000000000..a4c56d181209350c013fa8bf33e87c22afbfd65e --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/manipulating_code/fixing.md @@ -0,0 +1,615 @@ +# Fixing Code + +Psalm is good at finding potential issues in large codebases, but once found, it can be something of a gargantuan task to fix all the issues. + +It comes with a tool, called Psalter, that helps you fix code. + +You can either run it via its binary + +``` +vendor/bin/psalter [args] +``` + +or via Psalm's binary: + +``` +vendor/bin/psalm --alter [args] +``` + +## Safety features + +Updating code is inherently risky, doing so automatically is even more so. I've added a few features to make it a little more reassuring: + +- To see what changes Psalter will make ahead of time, you can run it with `--dry-run`. +- You can target particular versions of PHP via `--php-version`, so that (for example) you don't add nullable typehints to PHP 7.0 code, or any typehints at all to PHP 5.6 code. `--php-version` defaults to your current version. +- it has a `--safe-types` mode that will only update PHP 7 return typehints with information Psalm has gathered from non-docblock sources of type information (e.g. typehinted params, `instanceof` checks, other return typehints etc.) +- using `--allow-backwards-incompatible-changes=false` you can make sure to not create backwards incompatible changes + + +## Plugins + +You can pass in your own manipulation plugins e.g. +```bash +vendor/bin/psalter --plugin=vendor/vimeo/psalm/examples/plugins/ClassUnqualifier.php --dry-run +``` + +The above example plugin converts all unnecessarily qualified classnames in your code to shorter aliased versions. + +## Supported fixes + +This initial release provides support for the following alterations, corresponding to the names of issues Psalm finds. +To fix all of these at once, run `vendor/bin/psalter --issues=all` + +### MissingReturnType + +Running `vendor/bin/psalter --issues=MissingReturnType --php-version=7.0` on + +```php +foo = 5; + } else { + $this->foo = "hello"; + } + + $this->bar = "baz"; + } + + public function setBaz() { + $this->baz = [1, 2, 3]; + } +} +``` + +gives + +```php +|null + * @psalm-var non-empty-list|null + */ + public $baz; + + public function __construct() + { + if (rand(0, 1)) { + $this->foo = 5; + } else { + $this->foo = "hello"; + } + + $this->bar = "baz"; + } + + public function setBaz() { + $this->baz = [1, 2, 3]; + } +} +``` + +### MismatchingDocblockParamType + +Given + +```php + + + + +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/command_line_usage.md b/lib/composer/vimeo/psalm/docs/running_psalm/command_line_usage.md new file mode 100644 index 0000000000000000000000000000000000000000..e2f97a4e6eb333e7e8f91ea3a1687ab9d7f6de0e --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/command_line_usage.md @@ -0,0 +1,40 @@ +# Running Psalm + +Once you've set up your config file, you can run Psalm from your project's root directory with +```bash +./vendor/bin/psalm +``` + +and Psalm will scan all files in the project referenced by ``. + +If you want to run on specific files, use +```bash +./vendor/bin/psalm file1.php [file2.php...] +``` + +## Command-line options + +Run with `--help` to see a list of options that Psalm supports. + +## Shepherd + +Psalm currently offers some GitHub integration with public projects. + +Add `--shepherd` to send information about your build to https://shepherd.dev. + +Currently, Shepherd tracks type coverage (the percentage of types Psalm can infer) on `master` branches. + +## Running Psalm faster + +Psalm has a couple of command-line options that will result in faster builds: + +- `--threads=[n]` to run Psalm’s analysis in a number of threads +- `--diff` which only checks files you’ve updated since the last run (and their dependents). + +In Psalm 4 `--diff` is turned on by default (you can disable it with `--no-diff`). + +Data from the last run is stored in the *cache directory*, which may be set in [configuration](./configuration.md). +If you are running Psalm on a build server, you may want to configure the server to ensure that the cache directory +is preserved between runs. + +Running them together (e.g. `--threads=8 --diff`) will result in the fastest possible Psalm run. diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/configuration.md b/lib/composer/vimeo/psalm/docs/running_psalm/configuration.md new file mode 100644 index 0000000000000000000000000000000000000000..58bae2583dc71de8155a2bf145858931add5d936 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/configuration.md @@ -0,0 +1,418 @@ +# Configuration + +Psalm uses an XML config file (by default, `psalm.xml`). A barebones example looks like this: + +```xml + + + + + + +``` + +Configuration file may be split into several files using [XInclude](https://www.w3.org/TR/xinclude/) tags (c.f. previous example): +#### psalm.xml +```xml + + + + +``` +#### files.xml +```xml + + + + + +``` + + +## Optional <psalm /> attributes + +### Coding style + +#### errorLevel + +```xml + +``` +This corresponds to Psalm‘s [error-detection level](error_levels.md). + +#### reportMixedIssues + +```xml + +``` +Setting this to `"false"` hides all issues with `Mixed` types in Psalm’s output. If not given, this defaults to `"false"` when `errorLevel` is 3 or higher, and `"true"` when the error level is 1 or 2. + +#### totallyTyped + +```xml + +``` + +\(Deprecated\) Setting `totallyTyped` to `"true"` is equivalent to setting `errorLevel` to `"1"`. Setting `totallyTyped` to `"false"` is equivalent to setting `errorLevel` to `"2"` and `reportMixedIssues` to `"false"` + + + +#### resolveFromConfigFile + +```xml + +``` +If this is enabled, relative directories mentioned in the config file will be resolved relative to the location +of the config file. If it is disabled, or absent they will be resolved relative to the working directory of the Psalm process. + +New versions of Psalm enable this option when generating config files. Older versions did not include it. + +#### useDocblockTypes + +```xml + +``` +Whether or not to use types as defined in docblocks. Defaults to `true`. + +#### useDocblockPropertyTypes + +```xml + +``` +If not using all docblock types, you can still use docblock property types. Defaults to `false` (though only relevant if `useDocblockTypes` is `false`. + +#### usePhpDocMethodsWithoutMagicCall + +```xml + +``` +The PHPDoc `@method` annotation normally only applies to classes with a `__call` method. Setting this to `true` allows you to use the `@method` annotation to override inherited method return types. Defaults to `false`. + +#### usePhpDocPropertiesWithoutMagicCall + +```xml + +``` +The PHPDoc `@property`, `@property-read` and `@property-write` annotations normally only apply to classes with `__get`/`__set` methods. Setting this to `true` allows you to use the `@property`, `@property-read` and `@property-write` annotations to override property existence checks and resulting property types. Defaults to `false`. + +#### strictBinaryOperands + +```xml + +``` +If true we force strict typing on numerical and string operations (see https://github.com/vimeo/psalm/issues/24). Defaults to `false`. + +#### rememberPropertyAssignmentsAfterCall + +```xml + +``` +Setting this to `false` means that any function calls will cause Psalm to forget anything it knew about object properties within the scope of the function it's currently analysing. This duplicates functionality that Hack has. Defaults to `true`. + +#### allowPhpStormGenerics + +```xml + +``` +Allows you to specify whether or not to use the typed iterator docblock format supported by PHP Storm e.g. `ArrayIterator|string[]`, which Psalm transforms to `ArrayIterator`. Defaults to `false`. + +#### allowStringToStandInForClass + +```xml + +``` +When `true`, strings can be used as classes, meaning `$some_string::someMethod()` is allowed. If `false`, only class constant strings (of the form `Foo\Bar::class`) can stand in for classes, otherwise an `InvalidStringClass` issue is emitted. Defaults to `false`. + +#### memoizeMethodCallResults + +```xml + +``` +When `true`, the results of method calls without arguments passed arguments are remembered between repeated calls of that method on a given object. Defaults to `false`. + +#### hoistConstants + +```xml + +``` +When `true`, constants defined in a function in a file are assumed to be available when requiring that file, and not just when calling that function. Defaults to `false` (i.e. constants defined in functions will *only* be available for use when that function is called) + +#### addParamDefaultToDocblockType + +```xml + +``` +Occasionally a param default will not match up with the docblock type. By default, Psalm emits an issue. Setting this flag to `true` causes it to expand the param type to include the param default. Defaults to `false`. + +#### checkForThrowsDocblock +```xml + +``` +When `true`, Psalm will check that the developer has supplied `@throws` docblocks for every exception thrown in a given function or method. Defaults to `false`. + +#### checkForThrowsInGlobalScope +```xml + +``` +When `true`, Psalm will check that the developer has caught every exception in global scope. Defaults to `false`. + +#### ignoreInternalFunctionFalseReturn + +```xml + +``` +When `true`, Psalm ignores possibly-false issues stemming from return values of internal functions (like `preg_split`) that may return false, but do so rarely. Defaults to `true`. + +#### ignoreInternalFunctionNullReturn + +```xml + +``` +When `true`, Psalm ignores possibly-null issues stemming from return values of internal array functions (like `current`) that may return null, but do so rarely. Defaults to `true`. + +#### findUnusedVariablesAndParams +```xml + +``` +When `true`, Psalm will attempt to find all unused variables, the equivalent of running with `--find-unused-variables`. Defaults to `false`. + +#### findUnusedCode +```xml + +``` +When `true`, Psalm will attempt to find all unused code (including unused variables), the equivalent of running with `--find-unused-code`. Defaults to `false`. + +#### findUnusedPsalmSuppress +```xml + +``` +When `true`, Psalm will report all `@psalm-suppress` annotations that aren't used, the equivalent of running with `--find-unused-psalm-suppress`. Defaults to `false`. + +#### loadXdebugStub +```xml + +``` +If not present, Psalm will only load the Xdebug stub if Psalm has unloaded the extension. +When `true`, Psalm will load the Xdebug extension stub (as the extension is unloaded when Psalm runs). +Setting to `false` prevents the stub from loading. + +#### ensureArrayStringOffsetsExist +```xml + +``` +When `true`, Psalm will complain when referencing an explicit string offset on an array e.g. `$arr['foo']` without a user first asserting that it exists (either via an `isset` check or via an object-like array). Defaults to `false`. + +#### ensureArrayIntOffsetsExist +```xml + +``` +When `true`, Psalm will complain when referencing an explicit integer offset on an array e.g. `$arr[7]` without a user first asserting that it exists (either via an `isset` check or via an object-like array). Defaults to `false`. + +#### phpVersion +```xml + +``` +Set the php version Psalm should assume when checking and/or fixing the project. If this attribute is not set, Psalm uses the declaration in `composer.json` if one is present. It will check against the earliest version of PHP that satisfies the declared `php` dependency + +This can be overridden on the command-line using the `--php-version=` flag which takes the highest precedence over both the `phpVersion` setting and the version derived from `composer.json`. + +#### skipChecksOnUnresolvableIncludes +```xml + +``` + +When `true`, Psalm will skip checking classes, variables and functions after it comes across an `include` or `require` it cannot resolve. This allows code to reference functions and classes unknown to Psalm. + +This defaults to `false`. + +#### sealAllMethods + +```xml + +``` + +When `true`, Psalm will treat all classes as if they had sealed methods, meaning that if you implement the magic method `__call`, you also have to add `@method` for each magic method. Defaults to false. + +#### runTaintAnalysis + +```xml + +``` + +When `true`, Psalm will run [Taint Analysis](../security_analysis/index.md) on your codebase. This config is the same as if you were running Psalm with `--taint-analysis`. + +#### reportInfo + +```xml + +``` + +When `false`, Psalm will not consider issue at lower level than `errorLevel` as `info` (they will be suppressed instead). This can be a big improvement in analysis time for big projects. However, this config will prevent Psalm to count or suggest fixes for suppressed issue + +### Running Psalm + +#### autoloader +```xml + +``` +If your application registers one or more custom autoloaders, and/or declares universal constants/functions, this autoloader script will be executed by Psalm before scanning starts. Psalm always registers composer's autoloader by default. + +#### throwExceptionOnError +```xml + +``` +Useful in testing, this makes Psalm throw a regular-old exception when it encounters an error. Defaults to `false`. + +#### hideExternalErrors +```xml + +``` +Whether or not to show issues in files that are used by your project files, but which are not included in ``. Defaults to `false`. + +#### cacheDirectory +```xml + +``` +The directory used to store Psalm's cache data - if you specify one (and it does not already exist), its parent directory must already exist, otherwise Psalm will throw an error. + +Defaults to `$XDG_CACHE_HOME/psalm`. If `$XDG_CACHE_HOME` is either not set or empty, a default equal to `$HOME/.cache/psalm` is used or `sys_get_temp_dir() . '/psalm'` when not defined. + +#### allowFileIncludes +```xml + +``` +Whether or not to allow `require`/`include` calls in your PHP. Defaults to `true`. + +#### serializer +```xml + +``` +Allows you to hard-code a serializer for Psalm to use when caching data. By default, Psalm uses `ext-igbinary` *if* the version is greater than or equal to 2.0.5, otherwise it defaults to PHP's built-in serializer. + + +## Project settings + +#### <projectFiles> +Contains a list of all the directories that Psalm should inspect. You can also specify a set of files and folders to ignore with the `` directive, e.g. +```xml + + + + + + +``` + +#### <extraFiles> +Optional. Same format as ``. Directories Psalm should load but not inspect. + +#### <fileExtensions> +Optional. A list of extensions to search over. See [Checking non-PHP files](checking_non_php_files.md) to understand how to extend this. + +#### <plugins> +Optional. A list of `` entries. See the [Plugins](plugins/using_plugins.md) section for more information. + +#### <issueHandlers> +Optional. If you don't want Psalm to complain about every single issue it finds, the issueHandler tag allows you to configure that. [Dealing with code issues](dealing_with_code_issues.md) tells you more. + +#### <mockClasses> +Optional. Do you use mock classes in your tests? If you want Psalm to ignore them when checking files, include a fully-qualified path to the class with `` + +#### <universalObjectCrates> +Optional. Do you have objects with properties that cannot be determined statically? If you want Psalm to treat all properties on a given classlike as mixed, include a fully-qualified path to the class with ``. By default, `stdClass` and `SimpleXMLElement` are configured to be universal object crates. + +#### <stubs> +Optional. If your codebase uses classes and functions that are not visible to Psalm via reflection (e.g. if there are internal packages that your codebase relies on that are not available on the machine running Psalm), you can use stub files. Used by PhpStorm (a popular IDE) and others, stubs provide a description of classes and functions without the implementations. You can find a list of stubs for common classes [here](https://github.com/JetBrains/phpstorm-stubs). List out each file with ``. + +#### <ignoreExceptions> +Optional. A list of exceptions to not report for `checkForThrowsDocblock` or `checkForThrowsInGlobalScope`. If an exception has `onlyGlobalScope` set to `true`, only `checkForThrowsInGlobalScope` is ignored for that exception, e.g. +```xml + + + +``` + +#### <globals> +Optional. If your codebase uses global variables that are accessed with the `global` keyword, you can declare their type. e.g. +```xml + + + +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/dealing_with_code_issues.md b/lib/composer/vimeo/psalm/docs/running_psalm/dealing_with_code_issues.md new file mode 100644 index 0000000000000000000000000000000000000000..a18b3179aa35fd0d2a6e0c80d8f084fd2a804676 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/dealing_with_code_issues.md @@ -0,0 +1,106 @@ +# Dealing with code issues + +Psalm has a large number of [code issues](issues.md). Each project can specify its own reporting level for a given issue. + +Code issue levels in Psalm fall into three categories: +
+
error
+
This will cause Psalm to print a message, and to ultimately terminate with a non-zero exit status
+
info
+
This will cause Psalm to print a message
+
suppress
+
This will cause Psalm to ignore the code issue entirely
+
+ +The third category, `suppress`, is the one you will probably be most interested in, especially when introducing Psalm to a large codebase. + +## Suppressing issues + +There are two ways to suppress an issue – via the Psalm config or via a function docblock. + +### Config suppression + +You can use the `` tag in the config file to influence how issues are treated. + +Some issue types allow the use of `referencedMethod`, `referencedClass` or `referencedVariable` to isolate known trouble spots. + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### Docblock suppression + +You can also use `@psalm-suppress IssueName` on a function's docblock to suppress Psalm issues e.g. + +```php += 7.3 and [Composer](https://getcomposer.org/). + +```bash +composer require --dev vimeo/psalm +``` + +Generate a config file: + +```bash +./vendor/bin/psalm --init +``` + +Psalm will scan your project and figure out an appropriate [error level](error_levels.md) for your codebase. + +Then run Psalm: + +```bash +./vendor/bin/psalm +``` + +Psalm will probably find a number of issues - find out how to deal with them in [Dealing with code issues](dealing_with_code_issues.md). + +## Installing plugins + +While Psalm can figure out the types used by various libraries based on the +their source code and docblocks, it works even better with custom-tailored types +provided by Psalm plugins. + +Check out the list of existing plugins on Packagist: https://packagist.org/?type=psalm-plugin +Install them with `composer require --dev && vendor/bin/psalm-plugin enable ` + +Read more about plugins in [Using Plugins chapter](plugins/using_plugins.md). + +## Using the Phar + +Sometimes your project can conflict with one or more of Psalm’s dependencies. + +In that case you may find the Phar (a self-contained PHP executable) useful. + +Run `composer require --dev psalm/phar` to install it. diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues.md new file mode 100644 index 0000000000000000000000000000000000000000..8d4c80b9bbe98f18a0832e32dccb9cb0e748b88c --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues.md @@ -0,0 +1,225 @@ +# Issue types + + - [AbstractInstantiation](issues/AbstractInstantiation.md) + - [AbstractMethodCall](issues/AbstractMethodCall.md) + - [ArgumentTypeCoercion](issues/ArgumentTypeCoercion.md) + - [AssignmentToVoid](issues/AssignmentToVoid.md) + - [CircularReference](issues/CircularReference.md) + - [ConflictingReferenceConstraint](issues/ConflictingReferenceConstraint.md) + - [ContinueOutsideLoop](issues/ContinueOutsideLoop.md) + - [DeprecatedClass](issues/DeprecatedClass.md) + - [DeprecatedConstant](issues/DeprecatedConstant.md) + - [DeprecatedFunction](issues/DeprecatedFunction.md) + - [DeprecatedInterface](issues/DeprecatedInterface.md) + - [DeprecatedMethod](issues/DeprecatedMethod.md) + - [DeprecatedProperty](issues/DeprecatedProperty.md) + - [DeprecatedTrait](issues/DeprecatedTrait.md) + - [DocblockTypeContradiction](issues/DocblockTypeContradiction.md) + - [DuplicateArrayKey](issues/DuplicateArrayKey.md) + - [DuplicateClass](issues/DuplicateClass.md) + - [DuplicateFunction](issues/DuplicateFunction.md) + - [DuplicateMethod](issues/DuplicateMethod.md) + - [DuplicateParam](issues/DuplicateParam.md) + - [EmptyArrayAccess](issues/EmptyArrayAccess.md) + - [ExtensionRequirementViolation](issues/ExtensionRequirementViolation.md) + - [FalsableReturnStatement](issues/FalsableReturnStatement.md) + - [FalseOperand](issues/FalseOperand.md) + - [ForbiddenCode](issues/ForbiddenCode.md) + - [ForbiddenEcho](issues/ForbiddenEcho.md) + - [ImplementationRequirementViolation](issues/ImplementationRequirementViolation.md) + - [ImplementedParamTypeMismatch](issues/ImplementedParamTypeMismatch.md) + - [ImplementedReturnTypeMismatch](issues/ImplementedReturnTypeMismatch.md) + - [ImplicitToStringCast](issues/ImplicitToStringCast.md) + - [ImpureByReferenceAssignment](issues/ImpureByReferenceAssignment.md) + - [ImpureFunctionCall](issues/ImpureFunctionCall.md) + - [ImpureMethodCall](issues/ImpureMethodCall.md) + - [ImpurePropertyAssignment](issues/ImpurePropertyAssignment.md) + - [ImpureStaticProperty](issues/ImpureStaticProperty.md) + - [ImpureStaticVariable](issues/ImpureStaticVariable.md) + - [InaccessibleClassConstant](issues/InaccessibleClassConstant.md) + - [InaccessibleMethod](issues/InaccessibleMethod.md) + - [InaccessibleProperty](issues/InaccessibleProperty.md) + - [InterfaceInstantiation](issues/InterfaceInstantiation.md) + - [InternalClass](issues/InternalClass.md) + - [InternalMethod](issues/InternalMethod.md) + - [InternalProperty](issues/InternalProperty.md) + - [InvalidArgument](issues/InvalidArgument.md) + - [InvalidArrayAccess](issues/InvalidArrayAccess.md) + - [InvalidArrayAssignment](issues/InvalidArrayAssignment.md) + - [InvalidArrayOffset](issues/InvalidArrayOffset.md) + - [InvalidCast](issues/InvalidCast.md) + - [InvalidCatch](issues/InvalidCatch.md) + - [InvalidClass](issues/InvalidClass.md) + - [InvalidExtendClass](issues/InvalidExtendClass.md) + - [InvalidClone](issues/InvalidClone.md) + - [InvalidDocblock](issues/InvalidDocblock.md) + - [InvalidDocblockParamName](issues/InvalidDocblockParamName.md) + - [InvalidFalsableReturnType](issues/InvalidFalsableReturnType.md) + - [InvalidFunctionCall](issues/InvalidFunctionCall.md) + - [InvalidGlobal](issues/InvalidGlobal.md) + - [InvalidIterator](issues/InvalidIterator.md) + - [InvalidMethodCall](issues/InvalidMethodCall.md) + - [InvalidNullableReturnType](issues/InvalidNullableReturnType.md) + - [InvalidOperand](issues/InvalidOperand.md) + - [InvalidParamDefault](issues/InvalidParamDefault.md) + - [InvalidParent](issues/InvalidParent.md) + - [InvalidPassByReference](issues/InvalidPassByReference.md) + - [InvalidPropertyAssignment](issues/InvalidPropertyAssignment.md) + - [InvalidPropertyAssignmentValue](issues/InvalidPropertyAssignmentValue.md) + - [InvalidPropertyFetch](issues/InvalidPropertyFetch.md) + - [InvalidReturnStatement](issues/InvalidReturnStatement.md) + - [InvalidReturnType](issues/InvalidReturnType.md) + - [InvalidScalarArgument](issues/InvalidScalarArgument.md) + - [InvalidScope](issues/InvalidScope.md) + - [InvalidStaticInvocation](issues/InvalidStaticInvocation.md) + - [InvalidStringClass](issues/InvalidStringClass.md) + - [InvalidTemplateParam](issues/InvalidTemplateParam.md) + - [InvalidThrow](issues/InvalidThrow.md) + - [InvalidToString](issues/InvalidToString.md) + - [LessSpecificImplementedReturnType](issues/LessSpecificImplementedReturnType.md) + - [LessSpecificReturnStatement](issues/LessSpecificReturnStatement.md) + - [LessSpecificReturnType](issues/LessSpecificReturnType.md) + - [LoopInvalidation](issues/LoopInvalidation.md) + - [MethodSignatureMismatch](issues/MethodSignatureMismatch.md) + - [MethodSignatureMustOmitReturnType](issues/MethodSignatureMustOmitReturnType.md) + - [MismatchingDocblockParamType](issues/MismatchingDocblockParamType.md) + - [MismatchingDocblockReturnType](issues/MismatchingDocblockReturnType.md) + - [MissingClosureParamType](issues/MissingClosureParamType.md) + - [MissingClosureReturnType](issues/MissingClosureReturnType.md) + - [MissingConstructor](issues/MissingConstructor.md) + - [MissingDependency](issues/MissingDependency.md) + - [MissingDocblockType](issues/MissingDocblockType.md) + - [MissingFile](issues/MissingFile.md) + - [MissingImmutableAnnotation](issues/MissingImmutableAnnotation.md) + - [MissingParamType](issues/MissingParamType.md) + - [MissingPropertyType](issues/MissingPropertyType.md) + - [MissingReturnType](issues/MissingReturnType.md) + - [MissingTemplateParam](issues/MissingTemplateParam.md) + - [MissingThrowsDocblock](issues/MissingThrowsDocblock.md) + - [MixedArgument](issues/MixedArgument.md) + - [MixedArgumentTypeCoercion](issues/MixedArgumentTypeCoercion.md) + - [MixedArrayAccess](issues/MixedArrayAccess.md) + - [MixedArrayAssignment](issues/MixedArrayAssignment.md) + - [MixedArrayOffset](issues/MixedArrayOffset.md) + - [MixedArrayTypeCoercion](issues/MixedArrayTypeCoercion.md) + - [MixedAssignment](issues/MixedAssignment.md) + - [MixedClone](issues/MixedClone.md) + - [MixedFunctionCall](issues/MixedFunctionCall.md) + - [MixedInferredReturnType](issues/MixedInferredReturnType.md) + - [MixedMethodCall](issues/MixedMethodCall.md) + - [MixedOperand](issues/MixedOperand.md) + - [MixedPropertyAssignment](issues/MixedPropertyAssignment.md) + - [MixedPropertyFetch](issues/MixedPropertyFetch.md) + - [MixedPropertyTypeCoercion](issues/MixedPropertyTypeCoercion.md) + - [MixedReturnStatement](issues/MixedReturnStatement.md) + - [MixedReturnTypeCoercion](issues/MixedReturnTypeCoercion.md) + - [MixedStringOffsetAssignment](issues/MixedStringOffsetAssignment.md) + - [MoreSpecificImplementedParamType](issues/MoreSpecificImplementedParamType.md) + - [MoreSpecificReturnType](issues/MoreSpecificReturnType.md) + - [MutableDependency](issues/MutableDependency.md) + - [NoInterfaceProperties](issues/NoInterfaceProperties.md) + - [NoValue](issues/NoValue.md) + - [NonStaticSelfCall](issues/NonStaticSelfCall.md) + - [NullArgument](issues/NullArgument.md) + - [NullArrayAccess](issues/NullArrayAccess.md) + - [NullArrayOffset](issues/NullArrayOffset.md) + - [NullFunctionCall](issues/NullFunctionCall.md) + - [NullIterator](issues/NullIterator.md) + - [NullOperand](issues/NullOperand.md) + - [NullPropertyAssignment](issues/NullPropertyAssignment.md) + - [NullPropertyFetch](issues/NullPropertyFetch.md) + - [NullReference](issues/NullReference.md) + - [NullableReturnStatement](issues/NullableReturnStatement.md) + - [OverriddenMethodAccess](issues/OverriddenMethodAccess.md) + - [OverriddenPropertyAccess](issues/OverriddenPropertyAccess.md) + - [ParadoxicalCondition](issues/ParadoxicalCondition.md) + - [ParentNotFound](issues/ParentNotFound.md) + - [PossibleRawObjectIteration](issues/PossibleRawObjectIteration.md) + - [PossiblyFalseArgument](issues/PossiblyFalseArgument.md) + - [PossiblyFalseIterator](issues/PossiblyFalseIterator.md) + - [PossiblyFalseOperand](issues/PossiblyFalseOperand.md) + - [PossiblyFalsePropertyAssignmentValue](issues/PossiblyFalsePropertyAssignmentValue.md) + - [PossiblyFalseReference](issues/PossiblyFalseReference.md) + - [PossiblyInvalidArgument](issues/PossiblyInvalidArgument.md) + - [PossiblyInvalidArrayAccess](issues/PossiblyInvalidArrayAccess.md) + - [PossiblyInvalidArrayAssignment](issues/PossiblyInvalidArrayAssignment.md) + - [PossiblyInvalidArrayOffset](issues/PossiblyInvalidArrayOffset.md) + - [PossiblyInvalidCast](issues/PossiblyInvalidCast.md) + - [PossiblyInvalidFunctionCall](issues/PossiblyInvalidFunctionCall.md) + - [PossiblyInvalidIterator](issues/PossiblyInvalidIterator.md) + - [PossiblyInvalidMethodCall](issues/PossiblyInvalidMethodCall.md) + - [PossiblyInvalidOperand](issues/PossiblyInvalidOperand.md) + - [PossiblyInvalidPropertyAssignment](issues/PossiblyInvalidPropertyAssignment.md) + - [PossiblyInvalidPropertyAssignmentValue](issues/PossiblyInvalidPropertyAssignmentValue.md) + - [PossiblyInvalidPropertyFetch](issues/PossiblyInvalidPropertyFetch.md) + - [PossiblyNullArgument](issues/PossiblyNullArgument.md) + - [PossiblyNullArrayAccess](issues/PossiblyNullArrayAccess.md) + - [PossiblyNullArrayAssignment](issues/PossiblyNullArrayAssignment.md) + - [PossiblyNullArrayOffset](issues/PossiblyNullArrayOffset.md) + - [PossiblyNullFunctionCall](issues/PossiblyNullFunctionCall.md) + - [PossiblyNullIterator](issues/PossiblyNullIterator.md) + - [PossiblyNullOperand](issues/PossiblyNullOperand.md) + - [PossiblyNullPropertyAssignment](issues/PossiblyNullPropertyAssignment.md) + - [PossiblyNullPropertyAssignmentValue](issues/PossiblyNullPropertyAssignmentValue.md) + - [PossiblyNullPropertyFetch](issues/PossiblyNullPropertyFetch.md) + - [PossiblyNullReference](issues/PossiblyNullReference.md) + - [PossiblyUndefinedArrayOffset](issues/PossiblyUndefinedArrayOffset.md) + - [PossiblyUndefinedGlobalVariable](issues/PossiblyUndefinedGlobalVariable.md) + - [PossiblyUndefinedIntArrayOffset](issues/PossiblyUndefinedIntArrayOffset.md) + - [PossiblyUndefinedMethod](issues/PossiblyUndefinedMethod.md) + - [PossiblyUndefinedStringArrayOffset](issues/PossiblyUndefinedStringArrayOffset.md) + - [PossiblyUndefinedVariable](issues/PossiblyUndefinedVariable.md) + - [PossiblyUnusedMethod](issues/PossiblyUnusedMethod.md) + - [PossiblyUnusedParam](issues/PossiblyUnusedParam.md) + - [PossiblyUnusedProperty](issues/PossiblyUnusedProperty.md) + - [PropertyNotSetInConstructor](issues/PropertyNotSetInConstructor.md) + - [PropertyTypeCoercion](issues/PropertyTypeCoercion.md) + - [RawObjectIteration](issues/RawObjectIteration.md) + - [RedundantCondition](issues/RedundantCondition.md) + - [RedundantConditionGivenDocblockType](issues/RedundantConditionGivenDocblockType.md) + - [RedundantIdentityWithTrue](issues/RedundantIdentityWithTrue.md) + - [ReferenceConstraintViolation](issues/ReferenceConstraintViolation.md) + - [ReservedWord](issues/ReservedWord.md) + - [StringIncrement](issues/StringIncrement.md) + - [TaintedInput](issues/TaintedInput.md) + - [TooFewArguments](issues/TooFewArguments.md) + - [TooManyArguments](issues/TooManyArguments.md) + - [TooManyTemplateParams](issues/TooManyTemplateParams.md) + - [TraitMethodSignatureMismatch](issues/TraitMethodSignatureMismatch.md) + - [TypeDoesNotContainNull](issues/TypeDoesNotContainNull.md) + - [TypeDoesNotContainType](issues/TypeDoesNotContainType.md) + - [UncaughtThrowInGlobalScope](issues/UncaughtThrowInGlobalScope.md) + - [UndefinedClass](issues/UndefinedClass.md) + - [UndefinedConstant](issues/UndefinedConstant.md) + - [UndefinedDocblockClass](issues/UndefinedDocblockClass.md) + - [UndefinedFunction](issues/UndefinedFunction.md) + - [UndefinedGlobalVariable](issues/UndefinedGlobalVariable.md) + - [UndefinedInterface](issues/UndefinedInterface.md) + - [UndefinedInterfaceMethod](issues/UndefinedInterfaceMethod.md) + - [UndefinedMagicMethod](issues/UndefinedMagicMethod.md) + - [UndefinedMagicPropertyAssignment](issues/UndefinedMagicPropertyAssignment.md) + - [UndefinedMagicPropertyFetch](issues/UndefinedMagicPropertyFetch.md) + - [UndefinedMethod](issues/UndefinedMethod.md) + - [UndefinedPropertyAssignment](issues/UndefinedPropertyAssignment.md) + - [UndefinedPropertyFetch](issues/UndefinedPropertyFetch.md) + - [UndefinedThisPropertyAssignment](issues/UndefinedThisPropertyAssignment.md) + - [UndefinedThisPropertyFetch](issues/UndefinedThisPropertyFetch.md) + - [UndefinedTrait](issues/UndefinedTrait.md) + - [UndefinedVariable](issues/UndefinedVariable.md) + - [UnevaluatedCode](issues/UnevaluatedCode.md) + - [UnimplementedAbstractMethod](issues/UnimplementedAbstractMethod.md) + - [UnimplementedInterfaceMethod](issues/UnimplementedInterfaceMethod.md) + - [UninitializedProperty](issues/UninitializedProperty.md) + - [UnnecessaryVarAnnotation](issues/UnnecessaryVarAnnotation.md) + - [UnrecognizedExpression](issues/UnrecognizedExpression.md) + - [UnrecognizedStatement](issues/UnrecognizedStatement.md) + - [UnresolvableInclude](issues/UnresolvableInclude.md) + - [UnusedClass](issues/UnusedClass.md) + - [UnusedClosureParam](issues/UnusedClosureParam.md) + - [UnusedFunctionCall](issues/UnusedFunctionCall.md) + - [UnusedMethod](issues/UnusedMethod.md) + - [UnusedMethodCall](issues/UnusedMethodCall.md) + - [UnusedParam](issues/UnusedParam.md) + - [UnusedProperty](issues/UnusedProperty.md) + - [UnusedPsalmSuppress](issues/UnusedPsalmSuppress.md) + - [UnusedVariable](issues/UnusedVariable.md) diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/AbstractInstantiation.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/AbstractInstantiation.md new file mode 100644 index 0000000000000000000000000000000000000000..b07311b1ec018da93fab59c2eb91d251e54f143c --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/AbstractInstantiation.md @@ -0,0 +1,10 @@ +# AbstractInstantiation + +Emitted when an attempt is made to instantiate an abstract class: + +```php +foo = &$foo; + } +} + +class B { + /** @var string */ + private $bar; + + public function __construct(string &$bar) { + $this->bar = &$bar; + } +} + +if (rand(0, 1)) { + $v = 5; + $c = (new A($v)); // $v is constrained to an int +} else { + $v = "hello"; + $c = (new B($v)); // $v is constrained to a string +} + +$v = 8; +``` + +## Why this is bad + +Psalm doesn't understand what the type of `$c` should be diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/ConstructorSignatureMismatch.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/ConstructorSignatureMismatch.md new file mode 100644 index 0000000000000000000000000000000000000000..767375e14fa82dccd052e9d9ef0cd320e8720b19 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/ConstructorSignatureMismatch.md @@ -0,0 +1,17 @@ +# ConstructorSignatureMismatch + +Emitted when a constructor parameter differs from a parent constructor parameter, or if there are fewer parameters than the parent constructor AND where the parent class has a `@psalm-consistent-constructor` annotation. + +```php +foo(); +``` + +## Why this is bad + +The `@deprecated` tag is normally indicative of code that will stop working in the near future. + +## How to fix + +Don’t use the deprecated method. diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/DeprecatedProperty.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/DeprecatedProperty.md new file mode 100644 index 0000000000000000000000000000000000000000..c7a112032dd2bf14191707f23da8ecb5fa156032 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/DeprecatedProperty.md @@ -0,0 +1,24 @@ +# DeprecatedProperty + +Emitted when getting/setting a deprecated property of a given class + +```php +foo = 5; +``` + +## Why this is bad + +The `@deprecated` tag is normally indicative of code that will stop working in the near future. + +## How to fix + +Don’t use the deprecated property. diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/DeprecatedTrait.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/DeprecatedTrait.md new file mode 100644 index 0000000000000000000000000000000000000000..251b91ceac59ad1bb39743ae1175bf0fc54fce9d --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/DeprecatedTrait.md @@ -0,0 +1,21 @@ +# DeprecatedTrait + +Emitted when referring to a deprecated trait: + +```php + 'one', + 'b' => 'two', + 'c' => 'this text will be overwritten by the next line', + 'c' => 'three', +]; +``` + +## How to fix + +Remove the offending duplicates: + +```php + 'one', + 'b' => 'two', + 'c' => 'three', +]; +``` + +The first matching `'c'` key was removed to prevent a change in behaviour (any new duplicate keys overwrite the values of previous ones). diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/DuplicateClass.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/DuplicateClass.md new file mode 100644 index 0000000000000000000000000000000000000000..2ef6a880282a94e4bde7fd4fdd809e465919204b --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/DuplicateClass.md @@ -0,0 +1,32 @@ +# DuplicateClass + +Emitted when a class is defined twice + +```php + + + + + + + + + +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/ForbiddenEcho.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/ForbiddenEcho.md new file mode 100644 index 0000000000000000000000000000000000000000..2a56883874d9509e9e57611878fe11dac2a97be2 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/ForbiddenEcho.md @@ -0,0 +1,9 @@ +# ForbiddenEcho + +Emitted when Psalm encounters an echo statement and the `forbidEcho` flag in your config is set to `true` + +```php +a++; + } +} + +/** @psalm-pure */ +function filterOdd(int $i, A $a) : ?int { + $a->foo(); + + if ($i % 2 === 0 || $a->a === 2) { + return $i; + } + + return null; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/ImpurePropertyAssignment.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/ImpurePropertyAssignment.md new file mode 100644 index 0000000000000000000000000000000000000000..f36a27bd558a02f81af7af31bec42404c78365a8 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/ImpurePropertyAssignment.md @@ -0,0 +1,18 @@ +# ImpurePropertyAssignment + +Emitted when updating a property value from a function or method marked as pure. + +```php +a = $i; + + return $i; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/ImpurePropertyFetch.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/ImpurePropertyFetch.md new file mode 100644 index 0000000000000000000000000000000000000000..7aca5b595e29a8433760a6da6d7fa93ad448d8fb --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/ImpurePropertyFetch.md @@ -0,0 +1,16 @@ +# ImpurePropertyFetch + +Emitted when fetching a property value inside a function or method marked as pure. + +```php +a; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/ImpureStaticProperty.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/ImpureStaticProperty.md new file mode 100644 index 0000000000000000000000000000000000000000..26385d11283001694611d68255fbb6633d83e913 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/ImpureStaticProperty.md @@ -0,0 +1,18 @@ +# ImpureStaticProperty + +Emitted when attempting to use a static property from a function or method marked as pure + +```php +foo(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/InaccessibleProperty.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InaccessibleProperty.md new file mode 100644 index 0000000000000000000000000000000000000000..1f9985b74cbe51e593800eb818c3f28d9b4bc194 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InaccessibleProperty.md @@ -0,0 +1,13 @@ +# InaccessibleProperty + +Emitted when attempting to access a protected/private property from outside its available scope + +```php +foo; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/InterfaceInstantiation.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InterfaceInstantiation.md new file mode 100644 index 0000000000000000000000000000000000000000..09822883f0676c64b6feb98b53c7bbd19e73e80b --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InterfaceInstantiation.md @@ -0,0 +1,10 @@ +# InterfaceInstantiation + +Emitted when an attempt is made to instantiate an interface: + +```php +foo; + } + } +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidArgument.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidArgument.md new file mode 100644 index 0000000000000000000000000000000000000000..00370cf7e871ed7b40dd1c0073ede04e5ccffb77 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidArgument.md @@ -0,0 +1,40 @@ +# InvalidArgument + +Emitted when a supplied function/method argument is incompatible with the method signature or docblock one. + +```php +foo(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidNamedArgument.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidNamedArgument.md new file mode 100644 index 0000000000000000000000000000000000000000..cad53b494811d3460ba84fc9ceac19168982b355 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidNamedArgument.md @@ -0,0 +1,12 @@ +# InvalidNamedArgument + +Emitted when a supplied function/method argument name is incompatible with the function/method signature. + +```php +bar = "bar"; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidPropertyAssignmentValue.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidPropertyAssignmentValue.md new file mode 100644 index 0000000000000000000000000000000000000000..bba60d7a2188960f81c36b22a56329625d4e95dc --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidPropertyAssignmentValue.md @@ -0,0 +1,14 @@ +# InvalidPropertyAssignmentValue + +Emitted when attempting to assign a value to a property that cannot contain that type. + +```php +foo = new stdClass(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidPropertyFetch.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidPropertyFetch.md new file mode 100644 index 0000000000000000000000000000000000000000..7fbc79135ecdcf10552feae840f2e0fd67fda4da --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidPropertyFetch.md @@ -0,0 +1,10 @@ +# InvalidPropertyFetch + +Emitted when attempting to get a property from a non-object + +```php +bar; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidReturnStatement.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidReturnStatement.md new file mode 100644 index 0000000000000000000000000000000000000000..19e55a7a2d7658e10af209764c884760f622adc6 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidReturnStatement.md @@ -0,0 +1,11 @@ +# InvalidReturnStatement + +Emitted when a function return statement is incorrect + +```php +foo; + } +} + +A::bar(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidStringClass.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidStringClass.md new file mode 100644 index 0000000000000000000000000000000000000000..1f59f9a952fdd37939efb45d5c97c7ac23efccd2 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidStringClass.md @@ -0,0 +1,11 @@ +# InvalidStringClass + +Emitted when you have `allowStringToStandInForClass="false"` in your config and you’re passing a string instead of calling a class directly + +```php + */ +class SpecializedByInheritance extends Base {} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidThrow.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidThrow.md new file mode 100644 index 0000000000000000000000000000000000000000..60214eec37cb4b70d6f1089b49558d1ae7c3a2f2 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/InvalidThrow.md @@ -0,0 +1,10 @@ +# InvalidThrow + +Emitted when trying to throw a class that doesn't extend `Exception` or implement `Throwable` + +```php +counter; + } +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/MissingParamType.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MissingParamType.md new file mode 100644 index 0000000000000000000000000000000000000000..33eae8a184e6dedd6cd97f22aa8dc0d9779f8953 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MissingParamType.md @@ -0,0 +1,9 @@ +# MissingParamType + +Emitted when a function parameter has no type information associated with it + +```php + + */ +class SomeIterator implements ArrayAccess +{ + public function offsetSet($offset, $value) { + } + + public function offsetExists($offset) { + return false; + } + + public function offsetUnset($offset) { + } + + public function offsetGet($offset) { + return null; + } +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/MissingThrowsDocblock.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MissingThrowsDocblock.md new file mode 100644 index 0000000000000000000000000000000000000000..b601f2c77196ee5adee8add981440ecd3dc28137 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MissingThrowsDocblock.md @@ -0,0 +1,17 @@ +# MissingThrowsDocblock + +Enabled when the `checkForThrowsDocblock` configuration option is enabled. + +Emitted when a function throws (or fails to handle) an exception and does not have a `@throws` annotation. + +```php + $a + * @param array $b + */ +function foo(array $a, array $b) : void { + foreach ($a as $j => $k) { + echo $b[$j]; + } +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedAssignment.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedAssignment.md new file mode 100644 index 0000000000000000000000000000000000000000..116d58c87f6b291211b00afcc45e346e84c266a8 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedAssignment.md @@ -0,0 +1,38 @@ +# MixedAssignment + +Emitted when assigning an unannotated variable to a value for which Psalm +cannot infer a type more specific than `mixed`. + +```php +foo(); // MixedMethodCall emitted here +} + +callFoo( + [new A()] +); +``` + +## Why this is bad + +If Psalm does not know what `array_pop($arr)` is, it can't verify whether `array_pop($arr)->foo()` will work or not. + +## How to fix + +Make sure that to provide as much type information as possible to Psalm so that it can perform inference. For example, you could add a docblock to the `callFoo` function: + +```php + $arr + */ +function callFoo(array $arr) : void { + array_pop($arr)->foo(); // MixedMethodCall emitted here +} + +callFoo( + [new A()] +); +``` + +Alternatively you could add a runtime check: + +```php +foo(); // MixedMethodCall emitted here +} + +callFoo( + [new A()] +); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedOperand.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedOperand.md new file mode 100644 index 0000000000000000000000000000000000000000..12833b903248431b5fcf2a02cc672d72e1815e49 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedOperand.md @@ -0,0 +1,9 @@ +# MixedOperand + +Emitted when Psalm cannot infer a type for an operand in any calculated expression + +```php +foo = "bar"; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedPropertyFetch.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedPropertyFetch.md new file mode 100644 index 0000000000000000000000000000000000000000..51e4ee2a307dcb91b3ba8df75efc0c20cf77ff70 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedPropertyFetch.md @@ -0,0 +1,12 @@ +# MixedPropertyFetch + +Emitted when retrieving a property on a value for which Psalm cannot infer a type + +```php +foo; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedPropertyTypeCoercion.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedPropertyTypeCoercion.md new file mode 100644 index 0000000000000000000000000000000000000000..c0d5325871e22cf34eeb87c9963a7c6d592306db --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedPropertyTypeCoercion.md @@ -0,0 +1,16 @@ +# MixedPropertyTypeCoercion + +Emitted when Psalm cannot be sure that part of an array/iterable argument's type constraints can be fulfilled + +```php +takesStringArray = $arr; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedReturnStatement.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedReturnStatement.md new file mode 100644 index 0000000000000000000000000000000000000000..95c0bd937170ad83b92af9d955d72c17ca388c66 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MixedReturnStatement.md @@ -0,0 +1,11 @@ +# MixedReturnStatement + +Emitted when Psalm cannot determine the type of a given return statement + +```php +bar(); + } +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/MoreSpecificReturnType.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MoreSpecificReturnType.md new file mode 100644 index 0000000000000000000000000000000000000000..6e53458df713c64b86a81e37c15b506b4307febf --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/MoreSpecificReturnType.md @@ -0,0 +1,14 @@ +# MoreSpecificReturnType + +Emitted when the declared return type for a method is more specific than the inferred one (emitted in the same methods that `LessSpecificReturnStatement` is) + +```php +i++; + } +} + +/** + * @psalm-immutable + */ +final class NotReallyImmutableClass extends MutableParent {} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/NoInterfaceProperties.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NoInterfaceProperties.md new file mode 100644 index 0000000000000000000000000000000000000000..3b1dd8d99be6867a691a96505ed64ca74b819b35 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NoInterfaceProperties.md @@ -0,0 +1,16 @@ +# NoInterfaceProperties + +Emitted when trying to fetch a property on an interface as interfaces, by definition, do not have definitions for properties. + +```php +foo) {} +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/NoValue.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NoValue.md new file mode 100644 index 0000000000000000000000000000000000000000..4d4de48b30d11843f688cfdf54e0789fabffc48f --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NoValue.md @@ -0,0 +1,16 @@ +# NoValue + +Emitted when using the result of a function that never returns. + +```php + 5, 'foo' => 1]; +echo $arr[null]; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullFunctionCall.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullFunctionCall.md new file mode 100644 index 0000000000000000000000000000000000000000..18fc64a745a97ad515e18fe4a7b4979b4be0647d --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullFunctionCall.md @@ -0,0 +1,10 @@ +# NullFunctionCall + +Emitted when trying to use `null` as a `callable` + +```php +foo = "bar"; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullPropertyFetch.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullPropertyFetch.md new file mode 100644 index 0000000000000000000000000000000000000000..7615354798b07da80553ad40b2d68583843cba42 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullPropertyFetch.md @@ -0,0 +1,10 @@ +# NullPropertyFetch + +Emitted when trying to fetch a property on a `null` value + +```php +foo; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullReference.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullReference.md new file mode 100644 index 0000000000000000000000000000000000000000..0e2b1dcd760db0e1661c0d32aa55848aa858bf56 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullReference.md @@ -0,0 +1,10 @@ +# NullReference + +Emitted when attempting to call a method on `null` + +```php +foo(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullableReturnStatement.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullableReturnStatement.md new file mode 100644 index 0000000000000000000000000000000000000000..5bdcfd2d24a9b2e0fd0d4b14384f579595fa9584 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/NullableReturnStatement.md @@ -0,0 +1,15 @@ +# NullableReturnStatement + +Emitted if a return statement contains a null value, but the function return type is not nullable + +```php +foo(str: "hello"); +} +``` + +In the first example passing `new AChild()` to `callFoo()` results in a fatal error, as AChild's definition of the method `foo()` doesn't have a parameter named `$str`. + +## How to fix + +You can change the child method param name to match: + +```php +foo = strpos($s, "haystack"); +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyFalseReference.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyFalseReference.md new file mode 100644 index 0000000000000000000000000000000000000000..5e300dec1452923e38ed9f368c84940cea55e8f4 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyFalseReference.md @@ -0,0 +1,18 @@ +# PossiblyFalseReference + +Emitted when making a method call on a value than might be `false` + +```php +bar(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidArgument.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidArgument.md new file mode 100644 index 0000000000000000000000000000000000000000..611130dd04b45fb99877c9c823c374759f72883c --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidArgument.md @@ -0,0 +1,14 @@ +# PossiblyInvalidArgument + +Emitted when + +```php + 5] : "hello"; +echo $arr[0]; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidCast.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidCast.md new file mode 100644 index 0000000000000000000000000000000000000000..cffa5f5e6f0fc552510f41c2aa1199cbda8f9aa5 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidCast.md @@ -0,0 +1,15 @@ +# PossiblyInvalidCast + +Emitted when attempting to cast a value that may not be castable + +```php +bar(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidOperand.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidOperand.md new file mode 100644 index 0000000000000000000000000000000000000000..d3ca4068c726b09b4a1590135c18fbfa8151b991 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidOperand.md @@ -0,0 +1,12 @@ +# PossiblyInvalidOperand + +Emitted when using a possibly invalid value as part of an operation (e.g. `+`, `.`, `^` etc. + +```php +bar = "5"; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidPropertyAssignmentValue.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidPropertyAssignmentValue.md new file mode 100644 index 0000000000000000000000000000000000000000..945c475faecaf3b9b5c260fb408cd8b09df1dfec --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidPropertyAssignmentValue.md @@ -0,0 +1,20 @@ +# PossiblyInvalidPropertyAssignmentValue + +Emitted when trying to assign a possibly invalid value to a typed property. + +```php +bb = ["hello", "world"]; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidPropertyFetch.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidPropertyFetch.md new file mode 100644 index 0000000000000000000000000000000000000000..3a309a04d25bbc019efa91286594b9fc385afe0f --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyInvalidPropertyFetch.md @@ -0,0 +1,20 @@ +# PossiblyInvalidPropertyFetch + +Emitted when trying to fetch a property on a value that may not be an object or may be an object that does not have the desired property. + +```php +bar; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullArgument.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullArgument.md new file mode 100644 index 0000000000000000000000000000000000000000..97e1c229fe0c1ca196f246e32b4a112d4885411d --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullArgument.md @@ -0,0 +1,10 @@ +# PossiblyNullArgument + +Emitted when calling a function with a value that’s possibly null when the function does not expect it + +```php +foo = "bar"; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullPropertyAssignmentValue.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullPropertyAssignmentValue.md new file mode 100644 index 0000000000000000000000000000000000000000..663b32b4ee7a924a292e5761e7f66af5bc254b34 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullPropertyAssignmentValue.md @@ -0,0 +1,17 @@ +# PossiblyNullPropertyAssignmentValue + +Emitted when trying to assign a value that may be null to a property that only takes non-null values. + +```php +foo = $s; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullPropertyFetch.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullPropertyFetch.md new file mode 100644 index 0000000000000000000000000000000000000000..770d1aa36643932400b7a394b527117547b3f3bc --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullPropertyFetch.md @@ -0,0 +1,15 @@ +# PossiblyNullPropertyFetch + +Emitted when trying to fetch a property on a possibly null object + +```php +foo; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullReference.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullReference.md new file mode 100644 index 0000000000000000000000000000000000000000..ead8b837a838c119c62ffd35d033defa23010989 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyNullReference.md @@ -0,0 +1,14 @@ +# PossiblyNullReference + +Emitted when trying to call a method on a possibly null value + +```php +bar(); +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedArrayOffset.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedArrayOffset.md new file mode 100644 index 0000000000000000000000000000000000000000..17e1bbff7ce5ee0be04da688645ead97ab06ed1b --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedArrayOffset.md @@ -0,0 +1,41 @@ +# PossiblyUndefinedArrayOffset + +Emitted when trying to access a possibly undefined array offset + +```php + 1, "b" => 2]; +} else { + $arr = ["a" => 3]; +} + +echo $arr["b"]; +``` + +## How to fix + +You can use the null coalesce operator to provide a default value in the event the array offset does not exist: + +```php + 1, "b" => 2]; +} else { + $arr = ["a" => 3, "b" => 0]; +} + +echo $arr["b"]; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedGlobalVariable.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedGlobalVariable.md new file mode 100644 index 0000000000000000000000000000000000000000..a545a1d74df66d80ef712463d6417a0c8c368a25 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedGlobalVariable.md @@ -0,0 +1,12 @@ +# PossiblyUndefinedGlobalVariable + +Emitted when trying to access a variable in the global scope that may not be defined + +```php + $arr + */ +function foo(array $arr) : void { + echo $arr[0]; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedMethod.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedMethod.md new file mode 100644 index 0000000000000000000000000000000000000000..f74b6a79eecbfbc5191b77794397d3d098c120db --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedMethod.md @@ -0,0 +1,15 @@ +# PossiblyUndefinedMethod + +Emitted when trying to access a method that may not be defined on the object + +```php +bar(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedStringArrayOffset.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedStringArrayOffset.md new file mode 100644 index 0000000000000000000000000000000000000000..2f2968de0f694c8a8d244e27fb135f38eaaace5e --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedStringArrayOffset.md @@ -0,0 +1,14 @@ +# PossiblyUndefinedStringArrayOffset + +Emitted when the config flag `ensureArrayStringOffsetsExist` is set to `true` and an integer-keyed offset is not checked for existence + +```php + $arr + */ +function foo(array $arr) : void { + echo $arr["hello"]; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedVariable.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedVariable.md new file mode 100644 index 0000000000000000000000000000000000000000..e309364bdde14a76b8818e2f2431eb3f7894e0c7 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUndefinedVariable.md @@ -0,0 +1,14 @@ +# PossiblyUndefinedVariable + +Emitted when trying to access a variable in function scope that may not be defined + +```php +foo(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUnusedParam.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUnusedParam.md new file mode 100644 index 0000000000000000000000000000000000000000..57a3e048980820365ade2654f9289a96dddbc671 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PossiblyUnusedParam.md @@ -0,0 +1,27 @@ +# PossiblyUnusedParam + +Emitted when `--find-dead-code` is turned on and Psalm cannot find any uses of a particular parameter in a public/protected method + +```php +foo(1, 2); +``` + +Can be suppressed by prefixing the parameter name with an underscore: + +```php +foo; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/PropertyNotSetInConstructor.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PropertyNotSetInConstructor.md new file mode 100644 index 0000000000000000000000000000000000000000..57b5f58cd02adb8035e1c321b026b99a0d5885a4 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/PropertyNotSetInConstructor.md @@ -0,0 +1,14 @@ +# PropertyNotSetInConstructor + +Emitted when a non-null property without a default value is declared but not set in the class’s constructor + +```php +b = $a; +} + +class C { + /** @var ?B */ + public $b; +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/RawObjectIteration.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/RawObjectIteration.md new file mode 100644 index 0000000000000000000000000000000000000000..1cc2008ffca9fe5136ee468bdcd65b3350e94fe3 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/RawObjectIteration.md @@ -0,0 +1,19 @@ +# RawObjectIteration + +Emitted when iterating over an object’s properties. This issue exists because it may be undesired behaviour (e.g. you may have meant to iterate over an array) + +```php + + */ +class SomeIterator implements IteratorAggregate +{ + public function getIterator() { + yield 5; + } +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/Trace.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/Trace.md new file mode 100644 index 0000000000000000000000000000000000000000..688dc32c6cbeaaa5dffa5552f2f46a497950f622 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/Trace.md @@ -0,0 +1,14 @@ +# Trace + +Not really an issue. Just reports type of the variable. + +```php +bar(); +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMagicMethod.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMagicMethod.md new file mode 100644 index 0000000000000000000000000000000000000000..efdcab684d039c447f94558dec0150ffd5b76a25 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMagicMethod.md @@ -0,0 +1,17 @@ +# UndefinedMagicMethod + +Emitted when calling a magic method that does not exist + +```php +foo(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMagicPropertyAssignment.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMagicPropertyAssignment.md new file mode 100644 index 0000000000000000000000000000000000000000..8eba8d77a06939c842c24fff58a2bacc74f21682 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMagicPropertyAssignment.md @@ -0,0 +1,17 @@ +# UndefinedMagicPropertyAssignment + +Emitted when assigning a property on an object that does not have that magic property defined + +```php +foo = "bar"; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMagicPropertyFetch.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMagicPropertyFetch.md new file mode 100644 index 0000000000000000000000000000000000000000..ee56d9d378f2597fcf4d18fcb7d1896f013c0156 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMagicPropertyFetch.md @@ -0,0 +1,18 @@ +# UndefinedMagicPropertyFetch + +Emitted when getting a property on an object that does not have that magic property defined + +```php +foo; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMethod.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMethod.md new file mode 100644 index 0000000000000000000000000000000000000000..8555b117e22149c81dea24e579f51155b6e87f51 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedMethod.md @@ -0,0 +1,10 @@ +# UndefinedMethod + +Emitted when calling a method that does not exist + +```php +foo = "bar"; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedPropertyFetch.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedPropertyFetch.md new file mode 100644 index 0000000000000000000000000000000000000000..284c68e56ae665fb963b9f49a54838bc29827244 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedPropertyFetch.md @@ -0,0 +1,11 @@ +# UndefinedPropertyFetch + +Emitted when getting a property on an object that does not have that property defined + +```php +foo; +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedThisPropertyAssignment.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedThisPropertyAssignment.md new file mode 100644 index 0000000000000000000000000000000000000000..33f1a16acb5c12aff821ecd8515049a54acf53c9 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedThisPropertyAssignment.md @@ -0,0 +1,13 @@ +# UndefinedThisPropertyAssignment + +Emitted when assigning a property on an object in one of that object’s methods when no such property exists + +```php +foo = "bar"; + } +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedThisPropertyFetch.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedThisPropertyFetch.md new file mode 100644 index 0000000000000000000000000000000000000000..2856e78771f67461f5154c55a2bddd3647baa33e --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedThisPropertyFetch.md @@ -0,0 +1,13 @@ +# UndefinedThisPropertyFetch + +Emitted when getting a property for an object in one of that object’s methods when no such property exists + +```php +foo; + } +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedTrace.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedTrace.md new file mode 100644 index 0000000000000000000000000000000000000000..6d204f999cc37326c6233f5ec94c100106d14b19 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UndefinedTrace.md @@ -0,0 +1,14 @@ +# UndefinedTrace + +Attempt to trace an undefined variable + +```php + 'foo', + }; +} +``` + +## Why this is bad + +The above code will fail 50% of the time with an `UnhandledMatchError` error. diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnimplementedAbstractMethod.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnimplementedAbstractMethod.md new file mode 100644 index 0000000000000000000000000000000000000000..16199129e2ac2fbe2dbbe067a2913a9da30ab495 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnimplementedAbstractMethod.md @@ -0,0 +1,12 @@ +# UnimplementedAbstractMethod + +Emitted when a class extends another, but does not implement all of its abstract methods + +```php +foo); + $this->foo = "foo"; + } +} +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnnecessaryVarAnnotation.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnnecessaryVarAnnotation.md new file mode 100644 index 0000000000000000000000000000000000000000..bf4a174949d83fcba2661be324b3275f1a454cf6 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnnecessaryVarAnnotation.md @@ -0,0 +1,14 @@ +# UnnecessaryVarAnnotation + +Emitted when `--find-dead-code` is turned on and you're using a `@var` annotation on an assignment that Psalm has already identified a type for. + +```php +foo(); + } + private function foo() : void {} + private function bar() : void {} +} +$a = new A(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnusedMethodCall.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnusedMethodCall.md new file mode 100644 index 0000000000000000000000000000000000000000..f7a4a862f58e9ec98f63064b0870edd213f63db5 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnusedMethodCall.md @@ -0,0 +1,22 @@ +# UnusedMethodCall + +Emitted when `--find-dead-code` is turned on and Psalm finds a method call whose return value is not used anywhere + +```php +foo = $foo; + } + + public function getFoo() : string { + return $this->foo; + } +} + +$a = new A("hello"); +$a->getFoo(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnusedParam.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnusedParam.md new file mode 100644 index 0000000000000000000000000000000000000000..5f3d0722c58ee6b8520d8a605f13c3e7cb75ced0 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnusedParam.md @@ -0,0 +1,19 @@ +# UnusedParam + +Emitted when `--find-dead-code` is turned on and Psalm cannot find any uses of a particular parameter in a private method or function + +```php +foo; + } +} + +$a = new A(); +echo $a->getFoo(); +``` diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnusedPsalmSuppress.md b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnusedPsalmSuppress.md new file mode 100644 index 0000000000000000000000000000000000000000..6cd499748165712e2b9c56a0e4bf7a815c4f5dd2 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/issues/UnusedPsalmSuppress.md @@ -0,0 +1,10 @@ +# UnusedPsalmSuppress + +Emitted when `--find-unused-psalm-suppress` is turned on and Psalm cannot find any uses of a given `@psalm-suppress` annotation + +```php +` e.g. `/usr/local/bin/php` or `C:\php\php.exe` + - this should be an absolute path, not just `php` + - Args: `vendor/bin/psalm-language-server` (on Windows use `vendor/vimeo/psalm/psalm-language-server`, or for a 'global' install '%APPDATA%' + `\Composer\vendor\vimeo\psalm\psalm-language-server`, where the '%APPDATA%' environment variable is probably something like `C:\Users\\AppData\Roaming\`) + +In the "Timeouts" tab you can adjust the initialization timeout. This is important if you have a large project. You should set the "Init" value to the number of milliseconds you allow Psalm to scan your entire project and your project's dependencies. For opening a couple of projects that use large PHP frameworks, on a high end business laptop, try `240000` milliseconds for Init. + +## Sublime Text + +I use the excellent Sublime [LSP plugin](https://github.com/tomv564/LSP) with the following config: + +```json + "psalm": + { + "command": ["php", "vendor/bin/psalm-language-server"], + "scopes": ["source.php", "embedding.php"], + "syntaxes": ["Packages/PHP/PHP.sublime-syntax"], + "languageId": "php" + } +``` + +## Vim & Neovim + +**ALE** + +[ALE](https://github.com/w0rp/ale) has support for Psalm (since v2.3.0). + +```vim +let g:ale_linters = { 'php': ['php', 'psalm'] } +``` + +**vim-lsp** + +I also got it working with [vim-lsp](https://github.com/prabirshrestha/vim-lsp) + +This is the config I used (for Vim): + +```vim +au User lsp_setup call lsp#register_server({ + \ 'name': 'psalm-language-server', + \ 'cmd': {server_info->[expand('vendor/bin/psalm-language-server')]}, + \ 'whitelist': ['php'], + \ }) +``` + +**coc.nvim** + +It also works with [coc.nvim](https://github.com/neoclide/coc.nvim). + +Add settings to `coc-settings.json`: + +```jsonc + "languageserver": { + "psalmls": { + "command": "vendor/bin/psalm-language-server", + "filetypes": ["php"], + "rootPatterns": ["psalm.xml", "psalm.xml.dist"], + "requireRootPattern": true + } + } +``` + +## VS Code + +[Get the Psalm plugin here](https://marketplace.visualstudio.com/items?itemName=getpsalm.psalm-vscode-plugin) (Requires VS Code 1.26+): diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/plugins/authoring_plugins.md b/lib/composer/vimeo/psalm/docs/running_psalm/plugins/authoring_plugins.md new file mode 100644 index 0000000000000000000000000000000000000000..e826c152ab45041743e5a1b5c7c07ea8cdf87675 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/plugins/authoring_plugins.md @@ -0,0 +1,141 @@ +# Authoring Plugins + +## Quick start + +### Using a template repository + +Head over to [plugin template repository](https://github.com/weirdan/psalm-plugin-skeleton) on Github and click `Use this template` button. + +### Using skeleton project + +Run `composer create-project weirdan/psalm-plugin-skeleton:dev-master your-plugin-name` to quickly bootstrap a new plugin project in `your-plugin-name` folder. Make sure you adjust namespaces in `composer.json`, `Plugin.php` and `tests` folder. + + +## Stub files + +Stub files provide a way to override third-party type information when you cannot add Psalm's extended docblocks to the upstream source files directly. +By convention, stub files have `.phpstub` extension to avoid IDEs treating them as actual php code. + +## Generating stubs + +Dev-require the library you want to tweak types for, e.g. +``` +composer require --dev cakephp/chronos +``` +Then generate the stubs +``` +vendor/bin/psalm --generate-stubs=stubs/chronos.phpstub +``` +Open the generated file and remove everything not related to the library you're stubbing. Tweak the docblocks to provide more accurate types. + +## Registering stub files + +Skeleton/template project includes the code to register all `.phpstub` files from the `stubs` directory. + +To register a stub file manually use `Psalm\Plugin\RegistrationInterface::addStubFile()`. + +## Publishing your plugin on Packagist + +Follow instructions on packagist.org under 'Publishing Packages' section. + +## Advanced topics + +### Starting from scratch + +Composer-based plugin is a composer package which conforms to these requirements: + +1. Its `type` field is set to `psalm-plugin` +2. It has `extra.psalm.pluginClass` subkey in its `composer.json` that reference an entry-point class that will be invoked to register the plugin into Psalm runtime. +3. Entry-point class implements `Psalm\Plugin\PluginEntryPointInterface` + +### Psalm API + +Plugins may implement one of (or more than one of) `Psalm\Plugin\Hook\*` interface(s). + +```php + + + +``` + +You can also specify an absolute path to your plugin: +```xml + + + +``` + +### Using Xdebug + +As Psalm disables _Xdebug_ at runtime, if you need to debug your code step-by-step when authoring a plugin, you can allow the extension by running Psalm as following: + +```console +$ PSALM_ALLOW_XDEBUG=1 path/to/psalm +``` + +## Type system + +Understand how Psalm handles types by [reading this guide](plugins_type_system.md). + +## Handling custom plugin issues + +Plugins may sometimes need to emit their own issues (i.e. not emit one of the [existing issues](../issues.md)). If this is the case, they can emit an issue that extends `Psalm\Issue\PluginIssue`. + +To suppress a custom plugin issue in docblocks you can just use its issue name (e.g. `/** @psalm-suppress NoFloatAssignment */`, but to [suppress it in Psalm’s config](../dealing_with_code_issues.md#config-suppression) you must use the pattern: + +```xml + +``` + +You can also use more complex rules in the `` element, as you can with any other issue type e.g. + +```xml + + + + + +``` + +## Upgrading file-based plugin to composer-based version + +Create new plugin project using skeleton, then pass the class name of you file-based plugin to `registerHooksFromClass()` method of the `Psalm\Plugin\RegistrationInterface` instance that was passed into your plugin entry point's `__invoke()` method. See the [conversion example](https://github.com/vimeo/psalm/tree/master/examples/plugins/composer-based/echo-checker/). diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/plugins/plugins_type_system.md b/lib/composer/vimeo/psalm/docs/running_psalm/plugins/plugins_type_system.md new file mode 100644 index 0000000000000000000000000000000000000000..58162451e1a8cbe4af54f2e9929c5d0f1b9962d3 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/plugins/plugins_type_system.md @@ -0,0 +1,233 @@ +# Plugins: type system internals + +Psalm's type system represents the types of variables within a program using different classes. Plugins both receive, and can update this type information. + +## Union types + +All type information you are likely to use will be wrapped in a [Union Type](../../annotating_code/type_syntax/union_types.md). + +The `Union` class constructor takes an array of `Atomic` types, and can represent one or more of these types at a time. They correspond to a vertical bar in a doc comment. + +``` php +new Union([new TNamedObject('Foo\\Bar\\SomeClass')]); // equivalent to Foo\Bar\SomeClass in docblock +new Union([new TString(), new TInt()]); // equivalent to string|int in docblock +``` + +## Atomic types + +Primitive types like floats, integers and strings, plus arrays, and classes. You can find all of these in [`src/Psalm/Types/Atomic`](https://github.com/vimeo/psalm/tree/master/src/Psalm/Type/Atomic). + +Note that all non-abstract classes in this folder are valid types. Most (with the exception of `Fn`, `TKeyedArray`, `GetClassT` and `GetTypeT`) are prefixed 'T'. + +The classes are as follows: + +### Misc + +`TVoid` - denotes the `void` type, normally just used to annotate a function/method that returns nothing + +`TNull` - denotes the `null` type + +`TNever` - denotes the `no-return`/`never-return` type for functions that never return, either throwing an exception or terminating (like the builtin `exit()`). + +`TMixed` - denotes the `mixed` type, used when you don’t know the type of an expression. + +`TNonEmptyMixed `- as above, but not empty. Generated for `$x` inside the `if` statement `if ($x) {...}` when `$x` is `mixed` outside. + +`TEmptyMixed` - as above, but empty. Generated for `$x` inside the `if` statement `if (!$x) {...}` when `$x` is `mixed` outside. + +`TEmpty` - denotes the `empty` type, used to describe a type corresponding to no value whatsoever. Empty arrays `[]` have the type `array`. + +`TIterable` - denotes the [`iterable` type](https://www.php.net/manual/en/language.types.iterable.php) (which can also result from an `is_iterable` check). + +`TResource` - denotes the `resource` type (e.g. a file handle). + +### Scalar supertype + +`TScalar` - denotes the `scalar` super type (which can also result from an `is_scalar` check). This type encompasses `float`, `int`, `bool` and `string`. + +`TEmptyScalar` - denotes a `scalar` type that is also empty. + +### Numeric supertype + +`TNumeric` - denotes the `numeric` type (which can also result from an `is_numeric` check). + +### Scalar types + +All scalar types have literal versions e.g. `int` vs `int(5)`. + +#### Ints + +`TInt` - denotes the `int` type, where the exact value is unknown. + +`TLiteralInt` is used to represent an integer value where the exact numeric value is known. + +#### Floats + +`TFloat` - denotes the `float` type, where the exact value is unknown. + +`TLiteralFloat` is used to represent a floating point value where the exact numeric value is known. + +#### Bools + +`TBool`, `TFalse`, `TTrue` + +`TBool` - denotes the `bool` type where the exact value is unknown. + +`TFalse` - denotes the `false` value type + +`TTrue` - denotes the `true` value type + +``` php +/** @return string|false false when string is empty, first char of the parameter otherwise */ +function firstChar(string $s) { return empty($s) ? false : $s[0]; } +``` + +Here, the function may never return true, but if you had to replace false with bool, Psalm would have to consider true as a possible return value. With narrower type it's able to report meaningless code like this (https://psalm.dev/r/037291351d): + +``` php +$first = firstChar("sdf"); +if (true === $first) { + echo "This is actually dead code"; +} +``` + +#### Strings + +`TString` - denotes the `string` type, where the exact value is unknown. + +`TNumericString` - denotes a string that's also a numeric value e.g. `"5"`. It can result from `is_string($s) && is_numeric($s)`. + +`TLiteralString` is used to represent a string whose value is known. + +`TClassString` - denotes the `class-string` type, used to describe a string representing a valid PHP class. The parent type from which the classes descend may or may not be specified in the constructor. + +`TLiteralClassString` - denotes a specific class string, generated by expressions like `A::class`. + +`TCallableString` - denotes the `callable-string` type, used to represent an unknown string that is also `callable`. + +`THtmlEscapedString`, `TSqlSelectString` - these are special types, specifically for consumption by plugins. + +#### Scalar class constants + +`TScalarClassConstant` - denotes a class constant whose value might not yet be known. + +#### Array key supertype + +`TArrayKey` - denotes the `array-key` type, used for something that could be the offset of an `array`. + +### Arrays + +`TArray` - denotes a simple array of the form `array`. It expects an array with two elements, both union types. + +`TNonEmptyArray` - as above, but denotes an array known to be non-empty. + +`TKeyedArray` represents an 'object-like array' - an array with known keys. + +``` php +$x = ["a" => 1, "b" => 2]; // is TKeyedArray, array{a: int, b: int} +$y = rand(0, 1) ? ["a" => null] : ["a" => 1, "b" => "b"]; // is TKeyedArray with optional keys/values, array{a: ?int, b?: string} +``` + +Note that not all associative arrays are considered object-like. If the keys are not known, the array is treated as a mapping between two types. + +``` php +$a = []; +foreach (range(1,1) as $_) $a[(string)rand(0,1)] = rand(0,1); // array +``` + +`TCallableArray` - denotes an array that is _also_ `callable`. + +`TCallableKeyedArray` - denotes an object-like array that is _also_ `callable`. + +### Callables & closures + +`TCallable` - denotes the `callable` type. Can result from an `is_callable` check. +`Fn` - denotes a `Closure` type. + +`TCallable` and `Fn` can optionally be defined with parameters and return types, too + +### Object supertypes + +`TObject` - denotes the `object` type + +`TObjectWithProperties` - an object with specified member variables e.g. `object{foo:int, bar:string}`. + +### Object types + +`TNamedObject` - denotes an object type where the type of the object is known e.g. `Exception`, `Throwable`, `Foo\Bar` + +`TGenericObject` - denotes an object type that has generic parameters e.g. `ArrayObject` + +`TCallableObject` - denotes an object that is also `callable` (i.e. it has `__invoke` defined). + +### Template + +`TTemplateParam` - denotes a template parameter that has been previously specified in a `@template` tag. + +`TTemplateParamClass` - denotes a `class-string` corresponding to a template parameter previously specified in a `@template` tag. + +## Creating type object instances + +There are two ways of creating the object instances which describe a given type. They can be created directly using new, or created declaratively from a doc string. Normally, you'd want to use the second option. However, understanding the structure of this data will help you understand types passed into a plugin. + +Note that these classes do sometimes change, so `Type::parseString` is always going to be the more robust option. + +### Creating type object instances directly + +The following example constructs types representing a string, a floating-point number, and a class called 'Foo\Bar\SomeClass'. + +``` php +new TLiteralString('A text string') +new TLiteralFloat(3.142) +new TNamedObject('Foo\Bar\SomeClass') +``` + +Types within Psalm are always wrapped in a union as a convenience feature. Almost anywhere you may expect a type, you can get a union as well (property types, return types, argument types, etc). So wrapping a single atomic type (like TInt) in a union container allows to uniformly handle that type elsewhere, without repetitive checks like this: + +``` php +if ($type instanceof Union) + foreach ($types->getTypes() as $atomic) + handleAtomic($atomic); +else handleAtomic($type); + +// with union container it becomes +foreach ($types->getTypes() as $atomic) + handleAtomic($atomic); +``` + +Also, union trees are always shallow, because Psalm will flatten union of unions into a single-level union `((A|B)|(C|D) => A|B|C|D)`. + +More complex types can be constructed as follows. The following represents an associative array with 3 keys. Psalm calls these 'object-like arrays', and represents them with the 'TKeyedArray' class. + + +``` php + new Union([ + new TKeyedArray([ + 'key_1' => new Union([new TString()]), + 'key_2' => new Union([new TInt()]), + 'key_3' => new Union([new TBool()])])]); +``` + +The Type object includes some static helper methods, which automatically wrap the type in a Union. Thus this can be written more tersely: + +``` php +new Union([ + new Type\Atomic\TKeyedArray([ + 'first' => Type::getInt(), + 'second' => Type::getString()])]); +``` + +You can also use `Type::getInt(5)` to generate a union type corresponding to the literal int value 5. + + +### Creating type object instances from doc string types + +Another way of creating these instances is to use the class `Psalm\Type` which includes a static method `parseString`. You may pass any doc string type description to this, and it will return the corresponding object representation. + +``` php +\Psalm\Type::parseString('int|null'); +``` + +You can find how Psalm would represent a given type as objects, by specifying the type as an input to this function, and calling `var_dump` on the result. + + diff --git a/lib/composer/vimeo/psalm/docs/running_psalm/plugins/using_plugins.md b/lib/composer/vimeo/psalm/docs/running_psalm/plugins/using_plugins.md new file mode 100644 index 0000000000000000000000000000000000000000..96a7cf00e077d40a6975d2331f3688e82c2106c5 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/running_psalm/plugins/using_plugins.md @@ -0,0 +1,31 @@ +# Using Plugins + +Psalm can be extended through plugins to find and fix domain-specific issues. + +## Using Composer-based plugins + +Psalm plugins are distributed as composer packages. + +### Discovering plugins + +You can find a list of plugins on [Psalm’s own website](https://psalm.dev/plugins), and [also on Packagist](https://packagist.org/?type=psalm-plugin). Alternatively you can get a list via the CLI by typing `composer search -t psalm-plugin '.'` + +### Installing plugins + +`composer require --dev ` + +### Managing known plugins + +Once installed, use the `psalm-plugin` tool to enable, disable and show available and enabled plugins. + +To enable a plugin, run `vendor/bin/psalm-plugin enable plugin-vendor/plugin-package`. + +To disable a plugin, run `vendor/bin/psalm-plugin disable plugin-vendor/plugin-package`. + +`vendor/bin/psalm-plugin show` will show you a list of all local plugins (enabled and disabled). + +## Using your own plugins + +Is there no plugin for your favourite framework / library yet? Create it! It's as easy as forking a repository, tweaking some docblocks and publishing the package to Packagist. + +Consult [Authoring Plugins](authoring_plugins.md) chapter to get started. diff --git a/lib/composer/vimeo/psalm/docs/security_analysis/annotations.md b/lib/composer/vimeo/psalm/docs/security_analysis/annotations.md new file mode 100644 index 0000000000000000000000000000000000000000..560e7fcc547d1eddd2dd3be2db4e17c63c8cb976 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/security_analysis/annotations.md @@ -0,0 +1,17 @@ +# Security analysis annotations + +## `@psalm-taint-source` + +See [Custom taint sources](custom_taint_sources.md#taint-source-annotation). + +## `@psalm-taint-sink` + +See [Custom taint sinks](custom_taint_sinks.md). + +## `@psalm-taint-escape` + +See [Escaping tainted output](avoiding_false_positives.md#escaping-tainted-output). + +## `@psalm-taint-specialize` + +See [Specializing taints in functions](avoiding_false_positives.md#specializing-taints-in-functions) and [Specializing taints in classes](avoiding_false_positives.md#specializing-taints-in-classes). diff --git a/lib/composer/vimeo/psalm/docs/security_analysis/avoiding_false_positives.md b/lib/composer/vimeo/psalm/docs/security_analysis/avoiding_false_positives.md new file mode 100644 index 0000000000000000000000000000000000000000..b570e47a15346d575c1abb88e91b16f58b391b8a --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/security_analysis/avoiding_false_positives.md @@ -0,0 +1,190 @@ +# Avoiding false-positives + +When you run Psalm’s taint analysis for the first time you may see a bunch of false-positives. + +Nobody likes false-positives! + +There are a number of ways you can prevent them: + +## Escaping tainted input + +Some operations remove taints from data – for example, wrapping `$_GET['name']` in an `htmlentities` call prevents cross-site-scripting attacks in that `$_GET` call. + +Psalm allows you to remove taints via a `@psalm-taint-escape ` annotation: + +```php +'], '', $str); + echo $str; +} + +echoVar($_GET["text"]); +``` + +## Specializing taints in functions + +For functions, methods and classes you can use the `@psalm-taint-specialize` annotation. + +```php +name = $name; + } +} + +/** + * @psalm-taint-specialize + */ +function echoUserName(User $user) { + echo $user->name; // Error, detected tainted input +} + +$user1 = new User("Keith"); +$user2 = new User($_GET["name"]); + +echoUserName($user1); +``` + +Adding `@psalm-taint-specialize` to the class fixes the issue. + +```php +name = $name; + } +} + +/** + * @psalm-taint-specialize + */ +function echoUserName(User $user) { + echo $user->name; // No error +} + +$user1 = new User("Keith"); +$user2 = new User($_GET["name"]); + +echoUserName($user1); +``` + +And, because it’s form of purity enforcement, `@psalm-immutable` can also be used: + +```php +name = $name; + } +} + +/** + * @psalm-taint-specialize + */ +function echoUserName(User $user) { + echo $user->name; // No error +} + +$user1 = new User("Keith"); +$user2 = new User($_GET["name"]); + +echoUserName($user1); +``` + +## Avoiding files in taint paths + +You can also tell Psalm that you’re not interested in any taint paths that flow through certain files or directories by specifying them in your Psalm config: + +```xml + + + + + +``` diff --git a/lib/composer/vimeo/psalm/docs/security_analysis/custom_taint_sinks.md b/lib/composer/vimeo/psalm/docs/security_analysis/custom_taint_sinks.md new file mode 100644 index 0000000000000000000000000000000000000000..39dec048666399fc1f1f6b3b9c8fd2bfa0f02e4d --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/security_analysis/custom_taint_sinks.md @@ -0,0 +1,20 @@ +# Custom Taint Sinks + +The `@psalm-taint-sink ` annotation allows you to define a taint sink. + +Any tainted value matching the given [taint type](index.md#taint-types) will be reported as an error by Psalm. + +### Example + +Here the `PDOWrapper` class has an `exec` method that should not receive tainted SQL, so we can prevent its insertion: + +```php +` to indicate a function or method that provides user input. + +In the below example the `input` taint type is specified as a standin for the four input taints `text`, `html`, `sql` and `shell`. + +```php +/** + * @psalm-taint-source input + */ +function getQueryParam(string $name) : string {} +``` + +## Custom taint plugin + +For example this plugin treats all variables named `$bad_data` as taint sources. + +```php +name === '$bad_data' + ) { + $expr_type = $statements_source->getNodeTypeProvider()->getType($expr); + + // should be a globally unique id + // you can use its line number/start offset + $expr_identifier = '$bad_data' + . '-' . $statements_source->getFileName() + . ':' . $expr->getAttribute('startFilePos'); + + if ($expr_type) { + $codebase->addTaintSource( + $expr_type, + $expr_identifier, + TaintKindGroup::ALL_INPUT, + new CodeLocation($statements_source, $expr) + ); + } + } + } +} +``` diff --git a/lib/composer/vimeo/psalm/docs/security_analysis/index.md b/lib/composer/vimeo/psalm/docs/security_analysis/index.md new file mode 100644 index 0000000000000000000000000000000000000000..423032002feaf9fd8a1a623dccb408f21f66d5a1 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/security_analysis/index.md @@ -0,0 +1,55 @@ +# Security Analysis in Psalm + +Psalm can attempt to find connections between user-controlled input (like `$_GET['name']`) and places that we don’t want unescaped user-controlled input to end up (like `echo "

$name

"` by looking at the ways that data flows through your application (via assignments, function/method calls and array/property access). + +You can enable this mode with the `--taint-analysis` command line flag. When taint analysis is enabled, no other analysis is performed. + +Tainted input is anything that can be controlled, wholly or in part, by a user of your application. In taint analysis, tainted input is called a _taint source_. + +Example sources: + + - `$_GET[‘id’]` + - `$_POST['email']` + - `$_COOKIE['token']` + + Taint analysis tracks how data flows from taint sources into _taint sinks_. Taint sinks are places you really don’t want untrusted data to end up. + +Example sinks: + + - `
` + - `$pdo->exec("select * from users where name='" . $name . "'")` + +## Taint Types + +Psalm recognises a number of taint types by default, defined in the [Psalm\Type\TaintKind](https://github.com/vimeo/psalm/blob/master/src/Psalm/Type/TaintKind.php) class: + +- `text` - used for strings that could be user-controlled +- `sql` - used for strings that could contain SQL +- `html` - used for strings that could contain angle brackets or unquoted strings +- `shell` - used for strings that could contain shell commands +- `user_secret` - used for strings that could contain user-supplied secrets +- `system_secret` - used for strings that could contain system secrets + +You're also free to define your own taint types when defining custom taint sources – they're just strings. + +## Taint Sources + +Psalm currently defines three default taint sources: the `$_GET`, `$_POST` and `$_COOKIE` server variables. + +You can also [define your own taint sources](custom_taint_sources.md). + +## Taint Sinks + +Psalm currently defines a number of different for builtin functions and methods, including `echo`, `include`, `header`. + +You can also [define your own taint sinks](custom_taint_sinks.md). + +## Avoiding False-Positives + +Nobody likes to wade through a ton of false-positives – [here’s a guide to avoiding them](avoiding_false_positives.md). + +## Using Baseline With Taint Analysis + +Since taint analysis is performed separately from other static code analysis, it makes sense to use a separate baseline for it. + +You can use --use-baseline=PATH option to set a different baseline for taint analysis. diff --git a/lib/composer/vimeo/psalm/docs/what_makes_psalm_complicated.md b/lib/composer/vimeo/psalm/docs/what_makes_psalm_complicated.md new file mode 100644 index 0000000000000000000000000000000000000000..550fb3fd154336789ce214282797da83acab52c7 --- /dev/null +++ b/lib/composer/vimeo/psalm/docs/what_makes_psalm_complicated.md @@ -0,0 +1,50 @@ +# Things that make developing Psalm complicated + +This is a somewhat informal list that might aid others. + +## Statement analysis +- **Type inference** + what effect do different PHP elements (function calls, if/for/foreach statements etc.) have on the types of things +- **Especially loops** + loops are hard to reason about - break and continue are a pain +- **Also dealing with literal strings/ints/floats** +- **Code liveness detection** + what effect do different PHP elements have on whether code is in scope, whether code is redundant +- **Logical assertions** + what effect do different PHP elements have on user-asserted logic in if conditionals, ternarys etc. +- **Generics & Templated code** + Figuring out how templated code should work (`@template` tags), how much it should work like it does in other languages (Hack, TypeScript etc.) + +## Supporting the community +- **Supporting formal PHPDoc annotations** +- **Supporting informal PHPDoc annotations** + e.g. `ArrayIterator|string[]` to denote an `ArrayIterator` over strings +- **non-Composer projects** + e.g. WordPress + +## Making Psalm fast +- **Parser-based reflection** + requires scanning everything necessary for analysis +- **Forking processes** (non-windows) + mostly handled by code borrowed from Phan, but can introduce subtle issues, also requires to think about how to make work happen in processes +- **Caching things** + see below + +## Cache invalidation +- **Invalidating analysis results** + requires tracking what methods/properties are used in what other files, and invalidating those results when linked methods change +- **Partial parsing** + Reparsing bits of files that have changed, which is hard + +## Language Server Support +- **Making Psalm fast** + see above +- **Handling temporary file changes** +- **Dealing with malformed PHP code** + When people write code, it's not always pretty as they write it. A language server needs to deal with that bad code somehow + +## Fixing code with Psalter +- **Adding/replacing code** + Figuring out what changed, making edits that could have been made by a human +- **Minimal diffs** + hard to change more than you need diff --git a/lib/composer/vimeo/psalm/examples/TemplateChecker.php b/lib/composer/vimeo/psalm/examples/TemplateChecker.php new file mode 100644 index 0000000000000000000000000000000000000000..79f5b71005f8e4b761cb4820613247b0221f6bf0 --- /dev/null +++ b/lib/composer/vimeo/psalm/examples/TemplateChecker.php @@ -0,0 +1,169 @@ +project_analyzer->getCodebase(); + $stmts = $codebase->getStatementsForFile($this->file_path); + + if ($stmts === []) { + return; + } + + $first_stmt = $stmts[0]; + + $this_params = null; + + if (($first_stmt instanceof PhpParser\Node\Stmt\Nop) && ($doc_comment = $first_stmt->getDocComment())) { + $comment_block = DocComment::parsePreservingLength($doc_comment); + + if (isset($comment_block->tags['variablesfrom'])) { + $variables_from = trim($comment_block->tags['variablesfrom'][0]); + + $first_line_regex = '/([A-Za-z\\\0-9]+::[a-z_A-Z]+)(\s+weak)?/'; + + $matches = []; + + if (!preg_match($first_line_regex, $variables_from, $matches)) { + throw new \InvalidArgumentException('Could not interpret doc comment correctly'); + } + + /** @psalm-suppress ArgumentTypeCoercion */ + $method_id = new \Psalm\Internal\MethodIdentifier(...explode('::', $matches[1])); + + $this_params = $this->checkMethod($method_id, $first_stmt, $codebase); + + if ($this_params === false) { + return; + } + + $this_params->vars_in_scope['$this'] = new Type\Union([ + new Type\Atomic\TNamedObject(self::VIEW_CLASS), + ]); + } + } + + if (!$this_params) { + $this_params = new Context(); + $this_params->check_variables = false; + $this_params->self = self::VIEW_CLASS; + $this_params->vars_in_scope['$this'] = new Type\Union([ + new Type\Atomic\TNamedObject(self::VIEW_CLASS), + ]); + } + + $this->checkWithViewClass($this_params, $stmts); + } + + /** + * @return Context|false + */ + private function checkMethod(\Psalm\Internal\MethodIdentifier $method_id, PhpParser\Node $stmt, Codebase $codebase) + { + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $this, + $method_id->fq_class_name, + new CodeLocation($this, $stmt), + null, + null, + [], + true + ) === false + ) { + return false; + } + + $this_context = new Context(); + $this_context->self = $method_id->fq_class_name; + + $class_storage = $codebase->classlike_storage_provider->get($method_id->fq_class_name); + + $this_context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic\TNamedObject($class_storage->name)]); + + $this->project_analyzer->getMethodMutations( + new \Psalm\Internal\MethodIdentifier($method_id->fq_class_name, '__construct'), + $this_context, + $this->getRootFilePath(), + $this->getRootFileName() + ); + + $this_context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic\TNamedObject($class_storage->name)]); + + // check the actual method + $this->project_analyzer->getMethodMutations( + $method_id, + $this_context, + $this->getRootFilePath(), + $this->getRootFileName() + ); + + $view_context = new Context(); + $view_context->self = strtolower(self::VIEW_CLASS); + + // add all $this-> vars to scope + foreach ($this_context->vars_possibly_in_scope as $var => $_) { + $view_context->vars_in_scope[str_replace('$this->', '$', $var)] = Type::getMixed(); + } + + foreach ($this_context->vars_in_scope as $var => $type) { + $view_context->vars_in_scope[str_replace('$this->', '$', $var)] = $type; + } + + return $view_context; + } + + /** + * @param array $stmts + * + */ + protected function checkWithViewClass(Context $context, array $stmts): void + { + $pseudo_method_stmts = []; + + foreach ($stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\Use_) { + $this->visitUse($stmt); + } else { + $pseudo_method_stmts[] = $stmt; + } + } + + $pseudo_method_name = preg_replace('/[^a-zA-Z0-9_]+/', '_', $this->file_name); + + $class_method = new PhpParser\Node\Stmt\ClassMethod($pseudo_method_name, ['stmts' => []]); + + $class = new PhpParser\Node\Stmt\Class_(self::VIEW_CLASS); + + $class_analyzer = new ClassAnalyzer($class, $this, self::VIEW_CLASS); + + $view_method_analyzer = new MethodAnalyzer($class_method, $class_analyzer, new MethodStorage()); + + if (!$context->check_variables) { + $view_method_analyzer->addSuppressedIssue('UndefinedVariable'); + } + + $statements_source = new StatementsAnalyzer( + $view_method_analyzer, + new \Psalm\Internal\Provider\NodeDataProvider() + ); + + $statements_source->analyze($pseudo_method_stmts, $context); + } +} diff --git a/lib/composer/vimeo/psalm/examples/TemplateScanner.php b/lib/composer/vimeo/psalm/examples/TemplateScanner.php new file mode 100644 index 0000000000000000000000000000000000000000..86afa8766a4742de3df7d72e3e5f01deba9517ea --- /dev/null +++ b/lib/composer/vimeo/psalm/examples/TemplateScanner.php @@ -0,0 +1,64 @@ +statements_provider->getStatementsForFile( + $file_storage->file_path, + '7.4', + $progress + ); + + if ($stmts === []) { + return; + } + + $first_stmt = $stmts[0]; + + if (($first_stmt instanceof PhpParser\Node\Stmt\Nop) && ($doc_comment = $first_stmt->getDocComment())) { + $comment_block = DocComment::parsePreservingLength($doc_comment); + + if (isset($comment_block->tags['variablesfrom'])) { + $variables_from = trim($comment_block->tags['variablesfrom'][0]); + + $first_line_regex = '/([A-Za-z\\\0-9]+::[a-z_A-Z]+)(\s+weak)?/'; + + $matches = []; + + if (!preg_match($first_line_regex, $variables_from, $matches)) { + throw new \InvalidArgumentException('Could not interpret doc comment correctly'); + } + + [$fq_class_name] = explode('::', $matches[1]); + + $codebase->scanner->queueClassLikeForScanning( + $fq_class_name, + true + ); + } + } + + $codebase->scanner->queueClassLikeForScanning(self::VIEW_CLASS); + + parent::scan($codebase, $file_storage, $storage_from_cache, $progress); + } +} diff --git a/lib/composer/vimeo/psalm/examples/plugins/ClassUnqualifier.php b/lib/composer/vimeo/psalm/examples/plugins/ClassUnqualifier.php new file mode 100644 index 0000000000000000000000000000000000000000..47421da37931291e22012ca7b57cff5e11e13f57 --- /dev/null +++ b/lib/composer/vimeo/psalm/examples/plugins/ClassUnqualifier.php @@ -0,0 +1,56 @@ +getSelectedText(); + $aliases = $statements_source->getAliasedClassesFlipped(); + + if ($statements_source->getFilePath() !== $code_location->file_path) { + return; + } + + if (strpos($candidate_type, '\\' . $fq_class_name) !== false) { + $type_tokens = \Psalm\Internal\Type\TypeTokenizer::tokenize($candidate_type, false); + + foreach ($type_tokens as &$type_token) { + if ($type_token[0] === ('\\' . $fq_class_name) + && isset($aliases[strtolower($fq_class_name)]) + ) { + $type_token[0] = $aliases[strtolower($fq_class_name)]; + } + } + + $new_candidate_type = implode( + '', + array_map( + function ($f) { + return $f[0]; + }, + $type_tokens + ) + ); + + if ($new_candidate_type !== $candidate_type) { + $bounds = $code_location->getSelectionBounds(); + $file_replacements[] = new FileManipulation($bounds[0], $bounds[1], $new_candidate_type); + } + } + } +} diff --git a/lib/composer/vimeo/psalm/examples/plugins/FunctionCasingChecker.php b/lib/composer/vimeo/psalm/examples/plugins/FunctionCasingChecker.php new file mode 100644 index 0000000000000000000000000000000000000000..5ea44578d1e797b568e3b44f61a1f8cd1cae8817 --- /dev/null +++ b/lib/composer/vimeo/psalm/examples/plugins/FunctionCasingChecker.php @@ -0,0 +1,122 @@ +name instanceof PhpParser\Node\Identifier) { + return; + } + + try { + /** @psalm-suppress ArgumentTypeCoercion */ + $method_id = new \Psalm\Internal\MethodIdentifier(...explode('::', $declaring_method_id)); + $function_storage = $codebase->methods->getStorage($method_id); + + if ($function_storage->cased_name === '__call') { + return; + } + + if ($function_storage->cased_name === '__callStatic') { + return; + } + + if ($function_storage->cased_name !== (string)$expr->name) { + if (\Psalm\IssueBuffer::accepts( + new IncorrectFunctionCasing( + 'Function is incorrectly cased, expecting ' . $function_storage->cased_name, + new CodeLocation($statements_source, $expr->name) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + } catch (\Exception $e) { + // can throw if storage is missing + } + } + + /** + * @param non-empty-string $function_id + * @param FileManipulation[] $file_replacements + */ + public static function afterFunctionCallAnalysis( + FuncCall $expr, + string $function_id, + Context $context, + StatementsSource $statements_source, + Codebase $codebase, + Union $return_type_candidate, + array &$file_replacements + ): void { + if ($expr->name instanceof PhpParser\Node\Expr) { + return; + } + + try { + $function_storage = $codebase->functions->getStorage( + $statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer + ? $statements_source + : null, + strtolower($function_id) + ); + + if (!$function_storage->cased_name) { + return; + } + + $function_name_parts = explode('\\', $function_storage->cased_name); + + if (end($function_name_parts) !== end($expr->name->parts)) { + if (\Psalm\IssueBuffer::accepts( + new IncorrectFunctionCasing( + 'Function is incorrectly cased, expecting ' . $function_storage->cased_name, + new CodeLocation($statements_source, $expr->name) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + } catch (\Exception $e) { + // can throw if storage is missing + } + } +} + +class IncorrectFunctionCasing extends \Psalm\Issue\PluginIssue { +} diff --git a/lib/composer/vimeo/psalm/examples/plugins/PreventFloatAssignmentChecker.php b/lib/composer/vimeo/psalm/examples/plugins/PreventFloatAssignmentChecker.php new file mode 100644 index 0000000000000000000000000000000000000000..b9713a43efc10db35ad6ddea4eaae8c08322b068 --- /dev/null +++ b/lib/composer/vimeo/psalm/examples/plugins/PreventFloatAssignmentChecker.php @@ -0,0 +1,52 @@ +getNodeTypeProvider()->getType($expr->expr)) + && $expr_type->hasFloat() + ) { + if (\Psalm\IssueBuffer::accepts( + new NoFloatAssignment( + 'Don’t assign to floats', + new CodeLocation($statements_source, $expr) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + + return null; + } +} + +class NoFloatAssignment extends \Psalm\Issue\PluginIssue { +} diff --git a/lib/composer/vimeo/psalm/examples/plugins/StringChecker.php b/lib/composer/vimeo/psalm/examples/plugins/StringChecker.php new file mode 100644 index 0000000000000000000000000000000000000000..d8b5ca8f53b7f45e34408c99ad5ee42d3e4f9591 --- /dev/null +++ b/lib/composer/vimeo/psalm/examples/plugins/StringChecker.php @@ -0,0 +1,81 @@ +getFileName(), 'base/DefinitionManager.php') === false + && strpos($expr->value, 'TestController') === false + && preg_match($class_or_class_method, $expr->value) + ) { + $absolute_class = preg_split('/[:]/', $expr->value)[0]; + + if (\Psalm\IssueBuffer::accepts( + new \Psalm\Issue\InvalidClass( + 'Use ::class constants when representing class names', + new CodeLocation($statements_source, $expr), + $absolute_class + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + } elseif ($expr instanceof PhpParser\Node\Expr\BinaryOp\Concat + && $expr->left instanceof PhpParser\Node\Expr\ClassConstFetch + && $expr->left->class instanceof PhpParser\Node\Name + && $expr->left->name instanceof PhpParser\Node\Identifier + && strtolower($expr->left->name->name) === 'class' + && !in_array(strtolower($expr->left->class->parts[0]), ['self', 'static', 'parent']) + && $expr->right instanceof PhpParser\Node\Scalar\String_ + && preg_match('/^::[A-Za-z0-9]+$/', $expr->right->value) + ) { + $method_id = ((string) $expr->left->class->getAttribute('resolvedName')) . $expr->right->value; + + $appearing_method_id = $codebase->getAppearingMethodId($method_id); + + if (!$appearing_method_id) { + if (\Psalm\IssueBuffer::accepts( + new \Psalm\Issue\UndefinedMethod( + 'Method ' . $method_id . ' does not exist', + new CodeLocation($statements_source, $expr), + $method_id + ), + $statements_source->getSuppressedIssues() + )) { + return false; + } + + return null; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/examples/plugins/composer-based/echo-checker/EchoChecker.php b/lib/composer/vimeo/psalm/examples/plugins/composer-based/echo-checker/EchoChecker.php new file mode 100644 index 0000000000000000000000000000000000000000..e8192b14dff1fe015d245b0e9b8d307e80af5148 --- /dev/null +++ b/lib/composer/vimeo/psalm/examples/plugins/composer-based/echo-checker/EchoChecker.php @@ -0,0 +1,73 @@ +exprs as $expr) { + $expr_type = $statements_source->getNodeTypeProvider()->getType($expr); + + if (!$expr_type || $expr_type->hasMixed()) { + if (IssueBuffer::accepts( + new ArgumentTypeCoercion( + 'Echo requires an unescaped string, ' . $expr_type . ' provided', + new CodeLocation($statements_source, $expr), + 'echo' + ), + $statements_source->getSuppressedIssues() + )) { + // keep soldiering on + } + + continue; + } + + $types = $expr_type->getAtomicTypes(); + + foreach ($types as $type) { + if ($type instanceof \Psalm\Type\Atomic\TString + && !$type instanceof \Psalm\Type\Atomic\TLiteralString + && !$type instanceof \Psalm\Type\Atomic\THtmlEscapedString + ) { + if (IssueBuffer::accepts( + new ArgumentTypeCoercion( + 'Echo requires an unescaped string, ' . $expr_type . ' provided', + new CodeLocation($statements_source, $expr), + 'echo' + ), + $statements_source->getSuppressedIssues() + )) { + // keep soldiering on + } + } + } + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/examples/plugins/composer-based/echo-checker/PluginEntryPoint.php b/lib/composer/vimeo/psalm/examples/plugins/composer-based/echo-checker/PluginEntryPoint.php new file mode 100644 index 0000000000000000000000000000000000000000..1ed87f5db0e61fef67f3c636ee22594c32829d5a --- /dev/null +++ b/lib/composer/vimeo/psalm/examples/plugins/composer-based/echo-checker/PluginEntryPoint.php @@ -0,0 +1,14 @@ +registerHooksFromClass(EchoChecker::class); + } +} diff --git a/lib/composer/vimeo/psalm/examples/plugins/composer-based/echo-checker/composer.json b/lib/composer/vimeo/psalm/examples/plugins/composer-based/echo-checker/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..d8b4858c1ea0fdc0ad3e52f819288c851923d771 --- /dev/null +++ b/lib/composer/vimeo/psalm/examples/plugins/composer-based/echo-checker/composer.json @@ -0,0 +1,21 @@ +{ + "name": "psalm/echo-checker-plugin", + "description": "Checks echo statements", + "type": "psalm-plugin", + "license": "MIT", + "authors": [ + { + "name": "Matthew Brown" + } + ], + "extra": { + "psalm": { + "pluginClass": "Vimeo\\CodeAnalysis\\EchoChecker\\PluginEntryPoint" + } + }, + "autoload": { + "psr-4": { + "Vimeo\\CodeAnalysis\\EchoChecker\\": ["."] + } + } +} diff --git a/lib/composer/vimeo/psalm/infection.json.dist b/lib/composer/vimeo/psalm/infection.json.dist new file mode 100644 index 0000000000000000000000000000000000000000..9d6bfffb78f09a67c8ed2a758d76e0c126ab1975 --- /dev/null +++ b/lib/composer/vimeo/psalm/infection.json.dist @@ -0,0 +1,14 @@ +{ + "timeout": 10, + "source": { + "directories": [ + "src" + ] + }, + "logs": { + "text": "build\/infection.log" + }, + "mutators": { + "@default": true + } +} \ No newline at end of file diff --git a/lib/composer/vimeo/psalm/keys.asc.gpg b/lib/composer/vimeo/psalm/keys.asc.gpg new file mode 100644 index 0000000000000000000000000000000000000000..02dad89d82c40c5c768eb5d961638776e94f772f Binary files /dev/null and b/lib/composer/vimeo/psalm/keys.asc.gpg differ diff --git a/lib/composer/vimeo/psalm/phpcs.xml b/lib/composer/vimeo/psalm/phpcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..c34c9b83a3c58b2621c18f770ece9bcb833cd006 --- /dev/null +++ b/lib/composer/vimeo/psalm/phpcs.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + bin/ + src/ + tests/ + + src/Psalm/Internal/Visitor/SimpleNameResolver.php + + + src/Psalm/Internal/Stubs/ + tests/fixtures/ + + tests + src/Psalm/Internal/Type/UnionTemplateHandler.php + + + + * + + diff --git a/lib/composer/vimeo/psalm/phpunit.xml.dist b/lib/composer/vimeo/psalm/phpunit.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..df47af08e02f3a9803bfdff70f8d1034a1d8c942 --- /dev/null +++ b/lib/composer/vimeo/psalm/phpunit.xml.dist @@ -0,0 +1,53 @@ + + + + + tests + + + + + + src + + src/Psalm/Issue/ + src/Psalm/Internal/Stubs/ + src/Psalm/Internal/LanguageServer/ + src/Psalm/Internal/ExecutionEnvironment/ + src/Psalm/SourceControl/ + src/command_functions.php + src/psalm.php + src/psalm-language-server.php + src/psalter.php + src/psalm_plugin.php + src/psalm-refactor.php + src/Psalm/Plugin/Shepherd.php + src/Psalm/Internal/CallMap.php + src/Psalm/Internal/Fork/Pool.php + src/Psalm/Internal/Fork/Restarter.php + src/Psalm/Internal/PropertyMap.php + src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php + src/Psalm/Internal/Provider/FileReferenceCacheProvider.php + src/Psalm/Internal/Provider/FileStorageCacheProvider.php + src/Psalm/Internal/Provider/ParserCacheProvider.php + + + + + + + + + + diff --git a/lib/composer/vimeo/psalm/psalm b/lib/composer/vimeo/psalm/psalm new file mode 100755 index 0000000000000000000000000000000000000000..b6546edd87a2b41d24a2c740f5c9d8f39303f6da --- /dev/null +++ b/lib/composer/vimeo/psalm/psalm @@ -0,0 +1,2 @@ +#!/usr/bin/env php + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/composer/vimeo/psalm/psalter b/lib/composer/vimeo/psalm/psalter new file mode 100755 index 0000000000000000000000000000000000000000..da332d144b78ed937221697636fe1cf69128f6ca --- /dev/null +++ b/lib/composer/vimeo/psalm/psalter @@ -0,0 +1,2 @@ +#!/usr/bin/env php + [ + function ($filePath, $prefix, $contents) { + // + // PHP-Parser patch + // + if ($filePath === 'vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php') { + $length = 15 + strlen($prefix) + 1; + + return preg_replace( + '%strpos\((.+?)\) \+ 15%', + sprintf('strpos($1) + %d', $length), + $contents + ); + } + + return $contents; + }, + function ($filePath, $prefix, $contents) { + return str_replace( + '\\'.$prefix.'\Composer\Autoload\ClassLoader', + '\Composer\Autoload\ClassLoader', + $contents + ); + }, + function ($filePath, $prefix, $contents) { + if (strpos($filePath, 'src/Psalm') === 0) { + return str_replace( + [' \\PhpParser\\'], + [' \\' . $prefix . '\\PhpParser\\'], + $contents + ); + } + + return $contents; + }, + function ($filePath, $prefix, $contents) { + if (strpos($filePath, 'vendor/phpmyadmin/sql-parser/src/Context.php') === 0) { + return str_replace( + '\'' . $prefix, + '\'\\\\' . $prefix, + $contents + ); + } + + return $contents; + }, + function ($filePath, $prefix, $contents) { + if (strpos($filePath, 'vendor/openlss') === 0) { + return str_replace( + $prefix . '\\DomDocument', + 'DomDocument', + $contents + ); + } + + return $contents; + }, + function ($filePath, $prefix, $contents) { + if ($filePath === 'src/psalm.php') { + return str_replace( + '\\' . $prefix . '\\PSALM_VERSION', + 'PSALM_VERSION', + $contents + ); + } + + return $contents; + }, + function ($filePath, $prefix, $contents) { + $ret = str_replace( + $prefix . '\\Psalm\\', + 'Psalm\\', + $contents + ); + return $ret; + }, + ], + 'whitelist' => [ + \Composer\Autoload\ClassLoader::class, + 'Psalm\*', + ], + 'files-whitelist' => [ + 'src/spl_object_id.php', + ], +]; diff --git a/lib/composer/vimeo/psalm/src/Psalm/Aliases.php b/lib/composer/vimeo/psalm/src/Psalm/Aliases.php new file mode 100644 index 0000000000000000000000000000000000000000..3abaf060f39278b63de72b5725cef46f65aac832 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Aliases.php @@ -0,0 +1,73 @@ + + */ + public $uses; + + /** + * @var array + */ + public $uses_flipped; + + /** + * @var array + */ + public $functions; + + /** + * @var array + */ + public $functions_flipped; + + /** + * @var array + */ + public $constants; + + /** + * @var array + */ + public $constants_flipped; + + /** @var string|null */ + public $namespace; + + /** @var ?int */ + public $namespace_first_stmt_start; + + /** @var ?int */ + public $uses_start; + + /** @var ?int */ + public $uses_end; + + /** + * @param array $uses + * @param array $functions + * @param array $constants + * @param array $uses_flipped + * @param array $functions_flipped + * @param array $constants_flipped + */ + public function __construct( + ?string $namespace = null, + array $uses = [], + array $functions = [], + array $constants = [], + array $uses_flipped = [], + array $functions_flipped = [], + array $constants_flipped = [] + ) { + $this->namespace = $namespace; + $this->uses = $uses; + $this->functions = $functions; + $this->constants = $constants; + $this->uses_flipped = $uses_flipped; + $this->functions_flipped = $functions_flipped; + $this->constants_flipped = $constants_flipped; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/CodeLocation.php b/lib/composer/vimeo/psalm/src/Psalm/CodeLocation.php new file mode 100644 index 0000000000000000000000000000000000000000..bf7e6263569601290576f75401725690829e3794 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/CodeLocation.php @@ -0,0 +1,389 @@ +file_start = (int)$stmt->getAttribute('startFilePos'); + $this->file_end = (int)$stmt->getAttribute('endFilePos'); + $this->raw_file_start = $this->file_start; + $this->raw_file_end = $this->file_end; + $this->file_path = $file_source->getFilePath(); + $this->file_name = $file_source->getFileName(); + $this->single_line = $single_line; + $this->regex_type = $regex_type; + $this->previous_location = $previous_location; + $this->text = $selected_text; + + $doc_comment = $stmt->getDocComment(); + + $this->docblock_start = $doc_comment ? $doc_comment->getStartFilePos() : null; + $this->docblock_end = $doc_comment ? $this->file_start : null; + $this->docblock_start_line_number = $doc_comment ? $doc_comment->getStartLine() : null; + + $this->preview_start = $this->docblock_start ?: $this->file_start; + + $this->raw_line_number = $stmt->getLine(); + } + + public function setCommentLine(int $line): void + { + $this->docblock_line_number = $line; + } + + /** + * @psalm-suppress MixedArrayAccess + */ + private function calculateRealLocation(): void + { + if ($this->have_recalculated) { + return; + } + + $this->have_recalculated = true; + + $this->selection_start = $this->file_start; + $this->selection_end = $this->file_end + 1; + + $project_analyzer = Internal\Analyzer\ProjectAnalyzer::getInstance(); + + $codebase = $project_analyzer->getCodebase(); + + $file_contents = $codebase->getFileContents($this->file_path); + + $file_length = strlen($file_contents); + + $search_limit = $this->single_line ? $this->selection_start : $this->selection_end; + + if ($search_limit <= $file_length) { + $preview_end = strpos( + $file_contents, + "\n", + $search_limit + ); + } else { + $preview_end = false; + } + + // if the string didn't contain a newline + if ($preview_end === false) { + $preview_end = $this->selection_end; + } + + $this->preview_end = $preview_end; + + if ($this->docblock_line_number && + $this->docblock_start_line_number && + $this->preview_start < $this->selection_start + ) { + $preview_lines = explode( + "\n", + substr( + $file_contents, + $this->preview_start, + $this->selection_start - $this->preview_start - 1 + ) + ); + + $preview_offset = 0; + + $comment_line_offset = $this->docblock_line_number - $this->docblock_start_line_number; + + for ($i = 0; $i < $comment_line_offset; ++$i) { + $preview_offset += strlen($preview_lines[$i]) + 1; + } + + if (!isset($preview_lines[$i])) { + throw new \Exception('Should have offset'); + } + + $key_line = $preview_lines[$i]; + + $indentation = (int)strpos($key_line, '@'); + + $key_line = trim(preg_replace('@\**/\s*@', '', substr($key_line, $indentation))); + + $this->selection_start = $preview_offset + $indentation + $this->preview_start; + $this->selection_end = $this->selection_start + strlen($key_line); + } + + if ($this->regex_type !== null) { + switch ($this->regex_type) { + case self::VAR_TYPE: + $regex = '/@(psalm-)?var[ \t]+' . CommentAnalyzer::TYPE_REGEX . '/'; + $match_offset = 2; + break; + + case self::FUNCTION_RETURN_TYPE: + $regex = '/\\:\s+(\\??\s*[A-Za-z0-9_\\\\\[\]]+)/'; + $match_offset = 1; + break; + + case self::FUNCTION_PARAM_TYPE: + $regex = '/^(\\??\s*[A-Za-z0-9_\\\\\[\]]+)\s/'; + $match_offset = 1; + break; + + case self::FUNCTION_PHPDOC_RETURN_TYPE: + $regex = '/@(psalm-)?return[ \t]+' . CommentAnalyzer::TYPE_REGEX . '/'; + $match_offset = 2; + break; + + case self::FUNCTION_PHPDOC_METHOD: + $regex = '/@(psalm-)method[ \t]+.*/'; + $match_offset = 2; + break; + + case self::FUNCTION_PHPDOC_PARAM_TYPE: + $regex = '/@(psalm-)?param[ \t]+' . CommentAnalyzer::TYPE_REGEX . '/'; + $match_offset = 2; + break; + + case self::FUNCTION_PARAM_VAR: + $regex = '/(\$[^ ]*)/'; + $match_offset = 1; + break; + + case self::CATCH_VAR: + $regex = '/(\$[^ ^\)]*)/'; + $match_offset = 1; + break; + + default: + throw new \UnexpectedValueException('Unrecognised regex type ' . $this->regex_type); + } + + $preview_snippet = substr( + $file_contents, + $this->selection_start, + $this->selection_end - $this->selection_start + ); + + if ($this->text) { + $regex = '/(' . str_replace(',', ',[ ]*', preg_quote($this->text, '/')) . ')/'; + $match_offset = 1; + } + + if (preg_match($regex, $preview_snippet, $matches, PREG_OFFSET_CAPTURE)) { + $this->selection_start = $this->selection_start + (int)$matches[$match_offset][1]; + $this->selection_end = $this->selection_start + strlen((string)$matches[$match_offset][0]); + } + } + + // reset preview start to beginning of line + $this->preview_start = (int)strrpos( + $file_contents, + "\n", + min($this->preview_start, $this->selection_start) - strlen($file_contents) + ) + 1; + + $this->selection_start = max($this->preview_start, $this->selection_start); + $this->selection_end = min($this->preview_end, $this->selection_end); + + if ($this->preview_end - $this->selection_end > 200) { + $this->preview_end = (int)strrpos( + $file_contents, + "\n", + $this->selection_end + 200 - strlen($file_contents) + ); + + // if the line is over 200 characters long + if ($this->preview_end < $this->selection_end) { + $this->preview_end = $this->selection_end + 50; + } + } + + $this->snippet = substr($file_contents, $this->preview_start, $this->preview_end - $this->preview_start); + $this->text = substr($file_contents, $this->selection_start, $this->selection_end - $this->selection_start); + + // reset preview start to beginning of line + $this->column_from = $this->selection_start - + (int)strrpos($file_contents, "\n", $this->selection_start - strlen($file_contents)); + + $newlines = substr_count($this->text, "\n"); + + if ($newlines) { + $this->column_to = $this->selection_end - + (int)strrpos($file_contents, "\n", $this->selection_end - strlen($file_contents)); + } else { + $this->column_to = $this->column_from + strlen($this->text); + } + + $this->end_line_number = $this->getLineNumber() + $newlines; + } + + public function getLineNumber(): int + { + return $this->docblock_line_number ?: $this->raw_line_number; + } + + public function getEndLineNumber(): int + { + $this->calculateRealLocation(); + + return $this->end_line_number; + } + + public function getSnippet(): string + { + $this->calculateRealLocation(); + + return $this->snippet; + } + + public function getSelectedText(): string + { + $this->calculateRealLocation(); + + return (string)$this->text; + } + + public function getColumn(): int + { + $this->calculateRealLocation(); + + return $this->column_from; + } + + public function getEndColumn(): int + { + $this->calculateRealLocation(); + + return $this->column_to; + } + + /** + * @return array{0: int, 1: int} + */ + public function getSelectionBounds(): array + { + $this->calculateRealLocation(); + + return [$this->selection_start, $this->selection_end]; + } + + /** + * @return array{0: int, 1: int} + */ + public function getSnippetBounds(): array + { + $this->calculateRealLocation(); + + return [$this->preview_start, $this->preview_end]; + } + + public function getHash(): string + { + return (string) $this->file_start; + } + + public function getShortSummary() : string + { + return $this->file_name . ':' . $this->getLineNumber() . ':' . $this->getColumn(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/CodeLocation/DocblockTypeLocation.php b/lib/composer/vimeo/psalm/src/Psalm/CodeLocation/DocblockTypeLocation.php new file mode 100644 index 0000000000000000000000000000000000000000..b49f2cd04a656da65f529c10ad97b7cdf7b8837d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/CodeLocation/DocblockTypeLocation.php @@ -0,0 +1,26 @@ +file_start = $file_start; + // matches how CodeLocation works + $this->file_end = $file_end - 1; + + $this->raw_file_start = $file_start; + $this->raw_file_end = $file_end; + $this->raw_line_number = $line_number; + + $this->file_path = $file_source->getFilePath(); + $this->file_name = $file_source->getFileName(); + $this->single_line = false; + + $this->preview_start = $this->file_start; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/CodeLocation/ParseErrorLocation.php b/lib/composer/vimeo/psalm/src/Psalm/CodeLocation/ParseErrorLocation.php new file mode 100644 index 0000000000000000000000000000000000000000..9c146b0c5a0fa633b0f7f5d4654c2c71908c3237 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/CodeLocation/ParseErrorLocation.php @@ -0,0 +1,32 @@ +file_start = (int)$error->getAttributes()['startFilePos']; + /** @psalm-suppress PossiblyUndefinedStringArrayOffset */ + $this->file_end = (int)$error->getAttributes()['endFilePos']; + $this->raw_file_start = $this->file_start; + $this->raw_file_end = $this->file_end; + $this->file_path = $file_path; + $this->file_name = $file_name; + $this->single_line = false; + + $this->preview_start = $this->file_start; + $this->raw_line_number = substr_count( + substr($file_contents, 0, $this->file_start), + "\n" + ) + 1; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/CodeLocation/Raw.php b/lib/composer/vimeo/psalm/src/Psalm/CodeLocation/Raw.php new file mode 100644 index 0000000000000000000000000000000000000000..eeb19c1641880bbcb65dd093a455789138bb9211 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/CodeLocation/Raw.php @@ -0,0 +1,30 @@ +file_start = $file_start; + $this->file_end = $file_end; + $this->raw_file_start = $this->file_start; + $this->raw_file_end = $this->file_end; + $this->file_path = $file_path; + $this->file_name = $file_name; + $this->single_line = false; + + $this->preview_start = $this->file_start; + $this->raw_line_number = substr_count( + substr($file_contents, 0, $this->file_start), + "\n" + ) + 1; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Codebase.php b/lib/composer/vimeo/psalm/src/Psalm/Codebase.php new file mode 100644 index 0000000000000000000000000000000000000000..0c07f34ec9931ba445a4cf91784793b3e7c1fb84 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Codebase.php @@ -0,0 +1,1643 @@ +> + */ + public $use_referencing_locations = []; + + /** + * A map of file names to the classes that they contain explicit references to + * used in collaboration with use_referencing_locations + * + * @var array> + */ + public $use_referencing_files = []; + + /** + * @var FileStorageProvider + */ + public $file_storage_provider; + + /** + * @var ClassLikeStorageProvider + */ + public $classlike_storage_provider; + + /** + * @var bool + */ + public $collect_references = false; + + /** + * @var bool + */ + public $collect_locations = false; + + /** + * @var null|'always'|'auto' + */ + public $find_unused_code = null; + + /** + * @var FileProvider + */ + public $file_provider; + + /** + * @var FileReferenceProvider + */ + public $file_reference_provider; + + /** + * @var StatementsProvider + */ + public $statements_provider; + + /** + * @var Progress + */ + private $progress; + + /** + * @var array + */ + private static $stubbed_constants = []; + + /** + * Whether to register autoloaded information + * + * @var bool + */ + public $register_autoload_files = false; + + /** + * Whether to log functions just at the file level or globally (for stubs) + * + * @var bool + */ + public $register_stub_files = false; + + /** + * @var bool + */ + public $find_unused_variables = false; + + /** + * @var Internal\Codebase\Scanner + */ + public $scanner; + + /** + * @var Internal\Codebase\Analyzer + */ + public $analyzer; + + /** + * @var Internal\Codebase\Functions + */ + public $functions; + + /** + * @var Internal\Codebase\ClassLikes + */ + public $classlikes; + + /** + * @var Internal\Codebase\Methods + */ + public $methods; + + /** + * @var Internal\Codebase\Properties + */ + public $properties; + + /** + * @var Internal\Codebase\Populator + */ + public $populator; + + /** + * @var ?Internal\Codebase\TaintFlowGraph + */ + public $taint_flow_graph = null; + + /** + * @var bool + */ + public $server_mode = false; + + /** + * @var bool + */ + public $store_node_types = false; + + /** + * Whether or not to infer types from usage. Computationally expensive, so turned off by default + * + * @var bool + */ + public $infer_types_from_usage = false; + + /** + * @var bool + */ + public $alter_code = false; + + /** + * @var bool + */ + public $diff_methods = false; + + /** + * @var array + */ + public $methods_to_move = []; + + /** + * @var array + */ + public $methods_to_rename = []; + + /** + * @var array + */ + public $properties_to_move = []; + + /** + * @var array + */ + public $properties_to_rename = []; + + /** + * @var array + */ + public $class_constants_to_move = []; + + /** + * @var array + */ + public $class_constants_to_rename = []; + + /** + * @var array + */ + public $classes_to_move = []; + + /** + * @var array + */ + public $call_transforms = []; + + /** + * @var array + */ + public $property_transforms = []; + + /** + * @var array + */ + public $class_constant_transforms = []; + + /** + * @var array + */ + public $class_transforms = []; + + /** + * @var bool + */ + public $allow_backwards_incompatible_changes = true; + + /** + * @var int + */ + public $php_major_version = PHP_MAJOR_VERSION; + + /** + * @var int + */ + public $php_minor_version = PHP_MINOR_VERSION; + + /** + * @var bool + */ + public $track_unused_suppressions = false; + + public function __construct( + Config $config, + Providers $providers, + ?Progress $progress = null + ) { + if ($progress === null) { + $progress = new VoidProgress(); + } + + $this->config = $config; + $this->file_storage_provider = $providers->file_storage_provider; + $this->classlike_storage_provider = $providers->classlike_storage_provider; + $this->progress = $progress; + $this->file_provider = $providers->file_provider; + $this->file_reference_provider = $providers->file_reference_provider; + $this->statements_provider = $providers->statements_provider; + + self::$stubbed_constants = []; + + $reflection = new Internal\Codebase\Reflection($providers->classlike_storage_provider, $this); + + $this->scanner = new Internal\Codebase\Scanner( + $this, + $config, + $providers->file_storage_provider, + $providers->file_provider, + $reflection, + $providers->file_reference_provider, + $progress + ); + + $this->loadAnalyzer(); + + $this->functions = new Internal\Codebase\Functions($providers->file_storage_provider, $reflection); + + $this->properties = new Internal\Codebase\Properties( + $providers->classlike_storage_provider, + $providers->file_reference_provider + ); + + $this->classlikes = new Internal\Codebase\ClassLikes( + $this->config, + $providers->classlike_storage_provider, + $providers->file_reference_provider, + $providers->statements_provider, + $this->scanner + ); + + $this->methods = new Internal\Codebase\Methods( + $providers->classlike_storage_provider, + $providers->file_reference_provider, + $this->classlikes + ); + + $this->populator = new Internal\Codebase\Populator( + $config, + $providers->classlike_storage_provider, + $providers->file_storage_provider, + $this->classlikes, + $providers->file_reference_provider, + $progress + ); + + $this->loadAnalyzer(); + } + + private function loadAnalyzer(): void + { + $this->analyzer = new Internal\Codebase\Analyzer( + $this->config, + $this->file_provider, + $this->file_storage_provider, + $this->progress + ); + } + + /** + * @param array $candidate_files + * + */ + public function reloadFiles(ProjectAnalyzer $project_analyzer, array $candidate_files): void + { + $this->loadAnalyzer(); + + $this->file_reference_provider->loadReferenceCache(false); + + Internal\Analyzer\FunctionLikeAnalyzer::clearCache(); + + if (!$this->statements_provider->parser_cache_provider) { + $diff_files = $candidate_files; + } else { + $diff_files = []; + + $parser_cache_provider = $this->statements_provider->parser_cache_provider; + + foreach ($candidate_files as $candidate_file_path) { + if ($parser_cache_provider->loadExistingFileContentsFromCache($candidate_file_path) + !== $this->file_provider->getContents($candidate_file_path) + ) { + $diff_files[] = $candidate_file_path; + } + } + } + + $referenced_files = $project_analyzer->getReferencedFilesFromDiff($diff_files, false); + + foreach ($diff_files as $diff_file_path) { + $this->invalidateInformationForFile($diff_file_path); + } + + foreach ($referenced_files as $referenced_file_path) { + if (in_array($referenced_file_path, $diff_files, true)) { + continue; + } + + $file_storage = $this->file_storage_provider->get($referenced_file_path); + + foreach ($file_storage->classlikes_in_file as $fq_classlike_name) { + $this->classlike_storage_provider->remove($fq_classlike_name); + $this->classlikes->removeClassLike($fq_classlike_name); + } + + $this->file_storage_provider->remove($referenced_file_path); + $this->scanner->removeFile($referenced_file_path); + } + + $referenced_files = array_combine($referenced_files, $referenced_files); + + $this->scanner->addFilesToDeepScan($referenced_files); + $this->addFilesToAnalyze(array_combine($candidate_files, $candidate_files)); + + $this->scanner->scanFiles($this->classlikes); + + $this->file_reference_provider->updateReferenceCache($this, $referenced_files); + + $this->populator->populateCodebase(); + } + + public function enterServerMode(): void + { + $this->server_mode = true; + $this->store_node_types = true; + } + + public function collectLocations(): void + { + $this->collect_locations = true; + $this->classlikes->collect_locations = true; + $this->methods->collect_locations = true; + $this->properties->collect_locations = true; + } + + /** + * @param 'always'|'auto' $find_unused_code + * + */ + public function reportUnusedCode(string $find_unused_code = 'auto'): void + { + $this->collect_references = true; + $this->classlikes->collect_references = true; + $this->find_unused_code = $find_unused_code; + $this->find_unused_variables = true; + } + + public function reportUnusedVariables(): void + { + $this->collect_references = true; + $this->find_unused_variables = true; + } + + /** + * @param array $files_to_analyze + * + */ + public function addFilesToAnalyze(array $files_to_analyze): void + { + $this->scanner->addFilesToDeepScan($files_to_analyze); + $this->analyzer->addFilesToAnalyze($files_to_analyze); + } + + /** + * Scans all files their related files + * + */ + public function scanFiles(int $threads = 1): void + { + $has_changes = $this->scanner->scanFiles($this->classlikes, $threads); + + if ($has_changes) { + $this->populator->populateCodebase(); + } + } + + public function getFileContents(string $file_path): string + { + return $this->file_provider->getContents($file_path); + } + + /** + * @return list + */ + public function getStatementsForFile(string $file_path): array + { + return $this->statements_provider->getStatementsForFile( + $file_path, + $this->php_major_version . '.' . $this->php_minor_version, + $this->progress + ); + } + + public function createClassLikeStorage(string $fq_classlike_name): ClassLikeStorage + { + return $this->classlike_storage_provider->create($fq_classlike_name); + } + + public function cacheClassLikeStorage(ClassLikeStorage $classlike_storage, string $file_path): void + { + $file_contents = $this->file_provider->getContents($file_path); + + if ($this->classlike_storage_provider->cache) { + $this->classlike_storage_provider->cache->writeToCache($classlike_storage, $file_path, $file_contents); + } + } + + public function exhumeClassLikeStorage(string $fq_classlike_name, string $file_path): void + { + $file_contents = $this->file_provider->getContents($file_path); + $storage = $this->classlike_storage_provider->exhume( + $fq_classlike_name, + $file_path, + $file_contents + ); + + if ($storage->is_trait) { + $this->classlikes->addFullyQualifiedTraitName($storage->name, $file_path); + } elseif ($storage->is_interface) { + $this->classlikes->addFullyQualifiedInterfaceName($storage->name, $file_path); + } else { + $this->classlikes->addFullyQualifiedClassName($storage->name, $file_path); + } + } + + public static function getPsalmTypeFromReflection(?\ReflectionType $type) : Type\Union + { + return \Psalm\Internal\Codebase\Reflection::getPsalmTypeFromReflectionType($type); + } + + public function createFileStorageForPath(string $file_path): FileStorage + { + return $this->file_storage_provider->create($file_path); + } + + /** + * @return array + */ + public function findReferencesToSymbol(string $symbol): array + { + if (!$this->collect_locations) { + throw new \UnexpectedValueException('Should not be checking references'); + } + + if (strpos($symbol, '::$') !== false) { + return $this->findReferencesToProperty($symbol); + } + + if (strpos($symbol, '::') !== false) { + return $this->findReferencesToMethod($symbol); + } + + return $this->findReferencesToClassLike($symbol); + } + + /** + * @return array + */ + public function findReferencesToMethod(string $method_id): array + { + return $this->file_reference_provider->getClassMethodLocations(strtolower($method_id)); + } + + /** + * @return array + */ + public function findReferencesToProperty(string $property_id): array + { + [$fq_class_name, $property_name] = explode('::', $property_id); + + return $this->file_reference_provider->getClassPropertyLocations( + strtolower($fq_class_name) . '::' . $property_name + ); + } + + /** + * @return CodeLocation[] + * + * @psalm-return array + */ + public function findReferencesToClassLike(string $fq_class_name): array + { + $fq_class_name_lc = strtolower($fq_class_name); + $locations = $this->file_reference_provider->getClassLocations($fq_class_name_lc); + + if (isset($this->use_referencing_locations[$fq_class_name_lc])) { + $locations = array_merge($locations, $this->use_referencing_locations[$fq_class_name_lc]); + } + + return $locations; + } + + public function getClosureStorage(string $file_path, string $closure_id): Storage\FunctionStorage + { + $file_storage = $this->file_storage_provider->get($file_path); + + // closures can be returned here + if (isset($file_storage->functions[$closure_id])) { + return $file_storage->functions[$closure_id]; + } + + throw new \UnexpectedValueException( + 'Expecting ' . $closure_id . ' to have storage in ' . $file_path + ); + } + + public function addGlobalConstantType(string $const_id, Type\Union $type): void + { + self::$stubbed_constants[$const_id] = $type; + } + + public function getStubbedConstantType(string $const_id): ?Type\Union + { + return isset(self::$stubbed_constants[$const_id]) ? self::$stubbed_constants[$const_id] : null; + } + + /** + * @return array + */ + public function getAllStubbedConstants(): array + { + return self::$stubbed_constants; + } + + public function fileExists(string $file_path): bool + { + return $this->file_provider->fileExists($file_path); + } + + /** + * Check whether a class/interface exists + */ + public function classOrInterfaceExists( + string $fq_class_name, + ?CodeLocation $code_location = null, + ?string $calling_fq_class_name = null, + ?string $calling_method_id = null + ): bool { + return $this->classlikes->classOrInterfaceExists( + $fq_class_name, + $code_location, + $calling_fq_class_name, + $calling_method_id + ); + } + + public function classExtendsOrImplements(string $fq_class_name, string $possible_parent): bool + { + return $this->classlikes->classExtends($fq_class_name, $possible_parent) + || $this->classlikes->classImplements($fq_class_name, $possible_parent); + } + + /** + * Determine whether or not a given class exists + */ + public function classExists( + string $fq_class_name, + ?CodeLocation $code_location = null, + ?string $calling_fq_class_name = null, + ?string $calling_method_id = null + ): bool { + return $this->classlikes->classExists( + $fq_class_name, + $code_location, + $calling_fq_class_name, + $calling_method_id + ); + } + + /** + * Determine whether or not a class extends a parent + * + * @throws \Psalm\Exception\UnpopulatedClasslikeException when called on unpopulated class + * @throws \InvalidArgumentException when class does not exist + */ + public function classExtends(string $fq_class_name, string $possible_parent): bool + { + return $this->classlikes->classExtends($fq_class_name, $possible_parent, true); + } + + /** + * Check whether a class implements an interface + */ + public function classImplements(string $fq_class_name, string $interface): bool + { + return $this->classlikes->classImplements($fq_class_name, $interface); + } + + public function interfaceExists( + string $fq_interface_name, + ?CodeLocation $code_location = null, + ?string $calling_fq_class_name = null, + ?string $calling_method_id = null + ): bool { + return $this->classlikes->interfaceExists( + $fq_interface_name, + $code_location, + $calling_fq_class_name, + $calling_method_id + ); + } + + public function interfaceExtends(string $interface_name, string $possible_parent): bool + { + return $this->classlikes->interfaceExtends($interface_name, $possible_parent); + } + + /** + * @return array all interfaces extended by $interface_name + */ + public function getParentInterfaces(string $fq_interface_name): array + { + return $this->classlikes->getParentInterfaces( + $this->classlikes->getUnAliasedName($fq_interface_name) + ); + } + + /** + * Determine whether or not a class has the correct casing + */ + public function classHasCorrectCasing(string $fq_class_name): bool + { + return $this->classlikes->classHasCorrectCasing($fq_class_name); + } + + public function interfaceHasCorrectCasing(string $fq_interface_name): bool + { + return $this->classlikes->interfaceHasCorrectCasing($fq_interface_name); + } + + public function traitHasCorrectCase(string $fq_trait_name): bool + { + return $this->classlikes->traitHasCorrectCase($fq_trait_name); + } + + /** + * Given a function id, return the function like storage for + * a method, closure, or function. + * + * @param non-empty-string $function_id + * + * @return Storage\FunctionStorage|Storage\MethodStorage + */ + public function getFunctionLikeStorage( + StatementsAnalyzer $statements_analyzer, + string $function_id + ): FunctionLikeStorage { + $doesMethodExist = + \Psalm\Internal\MethodIdentifier::isValidMethodIdReference($function_id) + && $this->methodExists($function_id); + + if ($doesMethodExist) { + $method_id = \Psalm\Internal\MethodIdentifier::wrap($function_id); + + $declaring_method_id = $this->methods->getDeclaringMethodId($method_id); + + if (!$declaring_method_id) { + throw new \UnexpectedValueException('Declaring method for ' . $method_id . ' cannot be found'); + } + + return $this->methods->getStorage($declaring_method_id); + } + + return $this->functions->getStorage($statements_analyzer, strtolower($function_id)); + } + + /** + * Whether or not a given method exists + * + * @param string|\Psalm\Internal\MethodIdentifier $method_id + * @param string|\Psalm\Internal\MethodIdentifier|null $calling_method_id + * + @return bool + */ + public function methodExists( + $method_id, + ?CodeLocation $code_location = null, + $calling_method_id = null, + ?string $file_path = null + ): bool { + return $this->methods->methodExists( + Internal\MethodIdentifier::wrap($method_id), + is_string($calling_method_id) ? strtolower($calling_method_id) : strtolower((string) $calling_method_id), + $code_location, + null, + $file_path + ); + } + + /** + * @param string|\Psalm\Internal\MethodIdentifier $method_id + * + * @return array + */ + public function getMethodParams($method_id): array + { + return $this->methods->getMethodParams(Internal\MethodIdentifier::wrap($method_id)); + } + + /** + * @param string|\Psalm\Internal\MethodIdentifier $method_id + * + */ + public function isVariadic($method_id): bool + { + return $this->methods->isVariadic(Internal\MethodIdentifier::wrap($method_id)); + } + + /** + * @param string|\Psalm\Internal\MethodIdentifier $method_id + * @param array $call_args + * + */ + public function getMethodReturnType($method_id, ?string &$self_class, array $call_args = []): ?Type\Union + { + return $this->methods->getMethodReturnType( + Internal\MethodIdentifier::wrap($method_id), + $self_class, + null, + $call_args + ); + } + + /** + * @param string|\Psalm\Internal\MethodIdentifier $method_id + * + */ + public function getMethodReturnsByRef($method_id): bool + { + return $this->methods->getMethodReturnsByRef(Internal\MethodIdentifier::wrap($method_id)); + } + + /** + * @param string|\Psalm\Internal\MethodIdentifier $method_id + * @param CodeLocation|null $defined_location + * + */ + public function getMethodReturnTypeLocation( + $method_id, + CodeLocation &$defined_location = null + ): ?CodeLocation { + return $this->methods->getMethodReturnTypeLocation( + Internal\MethodIdentifier::wrap($method_id), + $defined_location + ); + } + + /** + * @param string|\Psalm\Internal\MethodIdentifier $method_id + * + */ + public function getDeclaringMethodId($method_id): ?string + { + return $this->methods->getDeclaringMethodId(Internal\MethodIdentifier::wrap($method_id)); + } + + /** + * Get the class this method appears in (vs is declared in, which could give a trait) + * + * @param string|\Psalm\Internal\MethodIdentifier $method_id + * + */ + public function getAppearingMethodId($method_id): ?string + { + return $this->methods->getAppearingMethodId(Internal\MethodIdentifier::wrap($method_id)); + } + + /** + * @param string|\Psalm\Internal\MethodIdentifier $method_id + * + * @return array + */ + public function getOverriddenMethodIds($method_id): array + { + return $this->methods->getOverriddenMethodIds(Internal\MethodIdentifier::wrap($method_id)); + } + + /** + * @param string|\Psalm\Internal\MethodIdentifier $method_id + * + */ + public function getCasedMethodId($method_id): string + { + return $this->methods->getCasedMethodId(Internal\MethodIdentifier::wrap($method_id)); + } + + public function invalidateInformationForFile(string $file_path): void + { + $this->scanner->removeFile($file_path); + + try { + $file_storage = $this->file_storage_provider->get($file_path); + } catch (\InvalidArgumentException $e) { + return; + } + + foreach ($file_storage->classlikes_in_file as $fq_classlike_name) { + $this->classlike_storage_provider->remove($fq_classlike_name); + $this->classlikes->removeClassLike($fq_classlike_name); + } + + $this->file_storage_provider->remove($file_path); + } + + public function getSymbolInformation(string $file_path, string $symbol): ?string + { + if (\is_numeric($symbol[0])) { + return \preg_replace('/^[^:]*:/', '', $symbol); + } + + try { + if (strpos($symbol, '::')) { + if (strpos($symbol, '()')) { + $symbol = substr($symbol, 0, -2); + + /** @psalm-suppress ArgumentTypeCoercion */ + $method_id = new \Psalm\Internal\MethodIdentifier(...explode('::', $symbol)); + + $declaring_method_id = $this->methods->getDeclaringMethodId($method_id); + + if (!$declaring_method_id) { + return null; + } + + $storage = $this->methods->getStorage($declaring_method_id); + + return 'getSignature(true); + } + + [, $symbol_name] = explode('::', $symbol); + + if (strpos($symbol, '$') !== false) { + $storage = $this->properties->getStorage($symbol); + + return 'getInfo() . ' ' . $symbol_name; + } + + [$fq_classlike_name, $const_name] = explode('::', $symbol); + + $class_constants = $this->classlikes->getConstantsForClass( + $fq_classlike_name, + \ReflectionProperty::IS_PRIVATE + ); + + if (!isset($class_constants[$const_name])) { + return null; + } + + return 'file_storage_provider->get($file_path); + + if (isset($file_storage->functions[$function_id])) { + $function_storage = $file_storage->functions[$function_id]; + + return 'getSignature(true); + } + + if (!$function_id) { + return null; + } + + $function = $this->functions->getStorage(null, $function_id); + return 'getSignature(true); + } + + $storage = $this->classlike_storage_provider->get($symbol); + + return 'abstract ? 'abstract ' : '') . 'class ' . $storage->name; + } catch (\Exception $e) { + error_log($e->getMessage()); + + return null; + } + } + + public function getSymbolLocation(string $file_path, string $symbol): ?CodeLocation + { + if (\is_numeric($symbol[0])) { + $symbol = \preg_replace('/:.*/', '', $symbol); + $symbol_parts = explode('-', $symbol); + + $file_contents = $this->getFileContents($file_path); + + return new CodeLocation\Raw( + $file_contents, + $file_path, + $this->config->shortenFileName($file_path), + (int) $symbol_parts[0], + (int) $symbol_parts[1] + ); + } + + try { + if (strpos($symbol, '::')) { + if (strpos($symbol, '()')) { + $symbol = substr($symbol, 0, -2); + + /** @psalm-suppress ArgumentTypeCoercion */ + $method_id = new \Psalm\Internal\MethodIdentifier(...explode('::', $symbol)); + + $declaring_method_id = $this->methods->getDeclaringMethodId($method_id); + + if (!$declaring_method_id) { + return null; + } + + $storage = $this->methods->getStorage($declaring_method_id); + + return $storage->location; + } + + if (strpos($symbol, '$') !== false) { + $storage = $this->properties->getStorage($symbol); + + return $storage->location; + } + + [$fq_classlike_name, $const_name] = explode('::', $symbol); + + $class_constants = $this->classlikes->getConstantsForClass( + $fq_classlike_name, + \ReflectionProperty::IS_PRIVATE + ); + + if (!isset($class_constants[$const_name])) { + return null; + } + + return $class_constants[$const_name]->location; + } + + if (strpos($symbol, '()')) { + $file_storage = $this->file_storage_provider->get($file_path); + + $function_id = strtolower(substr($symbol, 0, -2)); + + if (isset($file_storage->functions[$function_id])) { + return $file_storage->functions[$function_id]->location; + } + + if (!$function_id) { + return null; + } + + $function = $this->functions->getStorage(null, $function_id); + return $function->location; + } + + $storage = $this->classlike_storage_provider->get($symbol); + + return $storage->location; + } catch (\UnexpectedValueException $e) { + error_log($e->getMessage()); + + return null; + } catch (\InvalidArgumentException $e) { + return null; + } + } + + /** + * @return array{0: string, 1: Range}|null + */ + public function getReferenceAtPosition(string $file_path, Position $position): ?array + { + $is_open = $this->file_provider->isOpen($file_path); + + if (!$is_open) { + throw new \Psalm\Exception\UnanalyzedFileException($file_path . ' is not open'); + } + + $file_contents = $this->getFileContents($file_path); + + $offset = $position->toOffset($file_contents); + + [$reference_map, $type_map] = $this->analyzer->getMapsForFile($file_path); + + $reference = null; + + if (!$reference_map && !$type_map) { + return null; + } + + $reference_start_pos = null; + $reference_end_pos = null; + + ksort($reference_map); + + foreach ($reference_map as $start_pos => [$end_pos, $possible_reference]) { + if ($offset < $start_pos) { + break; + } + + if ($offset > $end_pos) { + continue; + } + $reference_start_pos = $start_pos; + $reference_end_pos = $end_pos; + $reference = $possible_reference; + } + + if ($reference === null || $reference_start_pos === null || $reference_end_pos === null) { + return null; + } + + $range = new Range( + self::getPositionFromOffset($reference_start_pos, $file_contents), + self::getPositionFromOffset($reference_end_pos, $file_contents) + ); + + return [$reference, $range]; + } + + /** + * @return array{0: non-empty-string, 1: int, 2: Range}|null + */ + public function getFunctionArgumentAtPosition(string $file_path, Position $position): ?array + { + $is_open = $this->file_provider->isOpen($file_path); + + if (!$is_open) { + throw new \Psalm\Exception\UnanalyzedFileException($file_path . ' is not open'); + } + + $file_contents = $this->getFileContents($file_path); + + $offset = $position->toOffset($file_contents); + + [, , $argument_map] = $this->analyzer->getMapsForFile($file_path); + + $reference = null; + $argument_number = null; + + if (!$argument_map) { + return null; + } + + $start_pos = null; + $end_pos = null; + + ksort($argument_map); + + foreach ($argument_map as $start_pos => [$end_pos, $possible_reference, $possible_argument_number]) { + if ($offset < $start_pos) { + break; + } + + if ($offset > $end_pos) { + continue; + } + + $reference = $possible_reference; + $argument_number = $possible_argument_number; + } + + if ($reference === null || $start_pos === null || $end_pos === null || $argument_number === null) { + return null; + } + + $range = new Range( + self::getPositionFromOffset($start_pos, $file_contents), + self::getPositionFromOffset($end_pos, $file_contents) + ); + + return [$reference, $argument_number, $range]; + } + + /** + * @param non-empty-string $function_symbol + */ + public function getSignatureInformation(string $function_symbol) : ?\LanguageServerProtocol\SignatureInformation + { + if (strpos($function_symbol, '::') !== false) { + /** @psalm-suppress ArgumentTypeCoercion */ + $method_id = new \Psalm\Internal\MethodIdentifier(...explode('::', $function_symbol)); + + $declaring_method_id = $this->methods->getDeclaringMethodId($method_id); + + if ($declaring_method_id === null) { + return null; + } + + $method_storage = $this->methods->getStorage($declaring_method_id); + $params = $method_storage->params; + } else { + try { + $function_storage = $this->functions->getStorage(null, strtolower($function_symbol)); + + $params = $function_storage->params; + } catch (\Exception $exception) { + if (InternalCallMapHandler::inCallMap($function_symbol)) { + $callables = InternalCallMapHandler::getCallablesFromCallMap($function_symbol); + + if (!$callables || !$callables[0]->params) { + return null; + } + + $params = $callables[0]->params; + } else { + return null; + } + } + } + + $signature_label = '('; + $parameters = []; + + foreach ($params as $i => $param) { + $parameter_label = ($param->type ?: 'mixed') . ' $' . $param->name; + $parameters[] = new \LanguageServerProtocol\ParameterInformation([ + strlen($signature_label), + strlen($signature_label) + strlen($parameter_label), + ]); + + $signature_label .= $parameter_label; + + if ($i < (count($params) - 1)) { + $signature_label .= ', '; + } + } + + $signature_label .= ')'; + + return new \LanguageServerProtocol\SignatureInformation( + $signature_label, + $parameters + ); + } + + /** + * @return array{0: string, 1: '->'|'::'|'symbol', 2: int}|null + */ + public function getCompletionDataAtPosition(string $file_path, Position $position): ?array + { + $is_open = $this->file_provider->isOpen($file_path); + + if (!$is_open) { + throw new \Psalm\Exception\UnanalyzedFileException($file_path . ' is not open'); + } + + $file_contents = $this->getFileContents($file_path); + + $offset = $position->toOffset($file_contents); + + [$reference_map, $type_map] = $this->analyzer->getMapsForFile($file_path); + + if (!$reference_map && !$type_map) { + return null; + } + + krsort($type_map); + + foreach ($type_map as $start_pos => [$end_pos_excluding_whitespace, $possible_type]) { + if ($offset < $start_pos) { + continue; + } + + $num_whitespace_bytes = preg_match('/\G\s+/', $file_contents, $matches, 0, $end_pos_excluding_whitespace) + ? strlen($matches[0]) + : 0; + $end_pos = $end_pos_excluding_whitespace + $num_whitespace_bytes; + + if ($offset - $end_pos === 2 || $offset - $end_pos === 3) { + $candidate_gap = substr($file_contents, $end_pos, 2); + + if ($candidate_gap === '->' || $candidate_gap === '::') { + $gap = $candidate_gap; + $recent_type = $possible_type; + + if ($recent_type === 'mixed') { + return null; + } + + return [$recent_type, $gap, $offset]; + } + } + } + + foreach ($reference_map as $start_pos => [$end_pos, $possible_reference]) { + if ($offset < $start_pos || $possible_reference[0] !== '*') { + continue; + } + + if ($offset - $end_pos === 0) { + $recent_type = $possible_reference; + + return [$recent_type, 'symbol', $offset]; + } + } + + return null; + } + + /** + * @return list<\LanguageServerProtocol\CompletionItem> + */ + public function getCompletionItemsForClassishThing(string $type_string, string $gap) : array + { + $instance_completion_items = []; + $static_completion_items = []; + + $type = Type::parseString($type_string); + + foreach ($type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TNamedObject) { + try { + $class_storage = $this->classlike_storage_provider->get($atomic_type->value); + + foreach ($class_storage->appearing_method_ids as $declaring_method_id) { + $method_storage = $this->methods->getStorage($declaring_method_id); + + $completion_item = new \LanguageServerProtocol\CompletionItem( + $method_storage->cased_name, + \LanguageServerProtocol\CompletionItemKind::METHOD, + (string)$method_storage, + null, + (string)$method_storage->visibility, + $method_storage->cased_name, + $method_storage->cased_name . (count($method_storage->params) !== 0 ? '($0)' : '()'), + null, + null, + new Command('Trigger parameter hints', 'editor.action.triggerParameterHints') + ); + $completion_item->insertTextFormat = \LanguageServerProtocol\InsertTextFormat::SNIPPET; + + if ($method_storage->is_static) { + $static_completion_items[] = $completion_item; + } else { + $instance_completion_items[] = $completion_item; + } + } + + foreach ($class_storage->declaring_property_ids as $property_name => $declaring_class) { + $property_storage = $this->properties->getStorage( + $declaring_class . '::$' . $property_name + ); + + $completion_item = new \LanguageServerProtocol\CompletionItem( + '$' . $property_name, + \LanguageServerProtocol\CompletionItemKind::PROPERTY, + $property_storage->getInfo(), + null, + (string)$property_storage->visibility, + $property_name, + ($gap === '::' ? '$' : '') . $property_name + ); + + if ($property_storage->is_static) { + $static_completion_items[] = $completion_item; + } else { + $instance_completion_items[] = $completion_item; + } + } + + foreach ($class_storage->constants as $const_name => $_) { + $static_completion_items[] = new \LanguageServerProtocol\CompletionItem( + $const_name, + \LanguageServerProtocol\CompletionItemKind::VARIABLE, + 'const ' . $const_name, + null, + null, + $const_name, + $const_name + ); + } + } catch (\Exception $e) { + error_log($e->getMessage()); + continue; + } + } + } + + if ($gap === '->') { + $completion_items = $instance_completion_items; + } else { + $completion_items = array_merge( + $instance_completion_items, + $static_completion_items + ); + } + + return $completion_items; + } + + /** + * @return list<\LanguageServerProtocol\CompletionItem> + */ + public function getCompletionItemsForPartialSymbol( + string $type_string, + int $offset, + string $file_path + ) : array { + $matching_classlike_names = $this->classlikes->getMatchingClassLikeNames($type_string); + + $completion_items = []; + + $file_storage = $this->file_storage_provider->get($file_path); + + $aliases = null; + + foreach ($file_storage->classlikes_in_file as $fq_class_name => $_) { + try { + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + } catch (\Exception $e) { + continue; + } + + if (!$class_storage->stmt_location) { + continue; + } + + if ($offset > $class_storage->stmt_location->raw_file_start + && $offset < $class_storage->stmt_location->raw_file_end + ) { + $aliases = $class_storage->aliases; + break; + } + } + + if (!$aliases) { + foreach ($file_storage->namespace_aliases as $namespace_start => $namespace_aliases) { + if ($namespace_start < $offset) { + $aliases = $namespace_aliases; + break; + } + } + + if (!$aliases) { + $aliases = $file_storage->aliases; + } + } + + foreach ($matching_classlike_names as $fq_class_name) { + $extra_edits = []; + + $insertion_text = Type::getStringFromFQCLN( + $fq_class_name, + $aliases && $aliases->namespace ? $aliases->namespace : null, + $aliases ? $aliases->uses_flipped : [], + null + ); + + if ($aliases + && $aliases->namespace + && $insertion_text === '\\' . $fq_class_name + && $aliases->namespace_first_stmt_start + ) { + $file_contents = $this->getFileContents($file_path); + + $class_name = \preg_replace('/^.*\\\/', '', $fq_class_name); + + if ($aliases->uses_end) { + $position = self::getPositionFromOffset($aliases->uses_end, $file_contents); + $extra_edits[] = new \LanguageServerProtocol\TextEdit( + new Range( + $position, + $position + ), + "\n" . 'use ' . $fq_class_name . ';' + ); + } else { + $position = self::getPositionFromOffset($aliases->namespace_first_stmt_start, $file_contents); + $extra_edits[] = new \LanguageServerProtocol\TextEdit( + new Range( + $position, + $position + ), + 'use ' . $fq_class_name . ';' . "\n" . "\n" + ); + } + + $insertion_text = $class_name; + } + + $completion_items[] = new \LanguageServerProtocol\CompletionItem( + $fq_class_name, + \LanguageServerProtocol\CompletionItemKind::CLASS_, + null, + null, + null, + $fq_class_name, + $insertion_text, + null, + $extra_edits + ); + } + + return $completion_items; + } + + private static function getPositionFromOffset(int $offset, string $file_contents) : Position + { + $file_contents = substr($file_contents, 0, $offset); + + $before_newline_count = strrpos($file_contents, "\n", $offset - strlen($file_contents)); + + return new Position( + substr_count($file_contents, "\n"), + $offset - (int)$before_newline_count - 1 + ); + } + + public function addTemporaryFileChanges(string $file_path, string $new_content): void + { + $this->file_provider->addTemporaryFileChanges($file_path, $new_content); + } + + public function removeTemporaryFileChanges(string $file_path): void + { + $this->file_provider->removeTemporaryFileChanges($file_path); + } + + /** + * Checks if type is a subtype of other + * + * Given two types, checks if `$input_type` is a subtype of `$container_type`. + * If you consider `Type\Union` as a set of types, this will tell you + * if `$input_type` is fully contained in `$container_type`, + * + * $input_type ⊆ $container_type + * + * Useful for emitting issues like InvalidArgument, where argument at the call site + * should be a subset of the function parameter type. + */ + public function isTypeContainedByType( + Type\Union $input_type, + Type\Union $container_type + ): bool { + return UnionTypeComparator::isContainedBy($this, $input_type, $container_type); + } + + /** + * Checks if type has any part that is a subtype of other + * + * Given two types, checks if *any part* of `$input_type` is a subtype of `$container_type`. + * If you consider `Type\Union` as a set of types, this will tell you if intersection + * of `$input_type` with `$container_type` is not empty. + * + * $input_type ∩ $container_type ≠ ∅ , e.g. they are not disjoint. + * + * Useful for emitting issues like PossiblyInvalidArgument, where argument at the call + * site should be a subtype of the function parameter type, but it's has some types that are + * not a subtype of the required type. + */ + public function canTypeBeContainedByType( + Type\Union $input_type, + Type\Union $container_type + ): bool { + return UnionTypeComparator::canBeContainedBy($this, $input_type, $container_type); + } + + /** + * Extracts key and value types from a traversable object (or iterable) + * + * Given an iterable type (*but not TArray*) returns a tuple of it's key/value types. + * First element of the tuple holds key type, second has the value type. + * + * Example: + * ```php + * $codebase->getKeyValueParamsForTraversableObject(Type::parseString('iterable')) + * // returns [Union(TInt), Union(TString)] + * ``` + * + * @return array{Type\Union,Type\Union} + */ + public function getKeyValueParamsForTraversableObject(Type\Atomic $type): array + { + $key_type = null; + $value_type = null; + + ForeachAnalyzer::getKeyValueParamsForTraversableObject($type, $this, $key_type, $value_type); + + return [ + $key_type ?? Type::getMixed(), + $value_type ?? Type::getMixed(), + ]; + } + + /** + * @param array $phantom_classes + * @psalm-suppress PossiblyUnusedMethod part of the public API + */ + public function queueClassLikeForScanning( + string $fq_classlike_name, + bool $analyze_too = false, + bool $store_failure = true, + array $phantom_classes = [] + ): void { + $this->scanner->queueClassLikeForScanning($fq_classlike_name, $analyze_too, $store_failure, $phantom_classes); + } + + /** + * @param array $taints + * + * @psalm-suppress PossiblyUnusedMethod + */ + public function addTaintSource( + Type\Union $expr_type, + string $taint_id, + array $taints = \Psalm\Type\TaintKindGroup::ALL_INPUT, + ?CodeLocation $code_location = null + ) : void { + if (!$this->taint_flow_graph) { + return; + } + + $source = new \Psalm\Internal\DataFlow\TaintSource( + $taint_id, + $taint_id, + $code_location, + null, + $taints + ); + + $this->taint_flow_graph->addSource($source); + + $expr_type->parent_nodes = [ + $source->id => $source, + ]; + } + + /** + * @param array $taints + * + * @psalm-suppress PossiblyUnusedMethod + */ + public function addTaintSink( + string $taint_id, + array $taints = \Psalm\Type\TaintKindGroup::ALL_INPUT, + ?CodeLocation $code_location = null + ) : void { + if (!$this->taint_flow_graph) { + return; + } + + $sink = new \Psalm\Internal\DataFlow\TaintSink( + $taint_id, + $taint_id, + $code_location, + null, + $taints + ); + + $this->taint_flow_graph->addSink($sink); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Config.php b/lib/composer/vimeo/psalm/src/Psalm/Config.php new file mode 100644 index 0000000000000000000000000000000000000000..491cea59e35376cba54bbded576bb23782cacc54 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Config.php @@ -0,0 +1,2025 @@ + + */ + public static $ERROR_LEVELS = [ + self::REPORT_INFO, + self::REPORT_ERROR, + self::REPORT_SUPPRESS, + ]; + + /** + * @var array + */ + private const MIXED_ISSUES = [ + 'MixedArgument', + 'MixedArrayAccess', + 'MixedArrayAssignment', + 'MixedArrayOffset', + 'MixedArrayTypeCoercion', + 'MixedAssignment', + 'MixedFunctionCall', + 'MixedInferredReturnType', + 'MixedMethodCall', + 'MixedOperand', + 'MixedPropertyFetch', + 'MixedPropertyAssignment', + 'MixedReturnStatement', + 'MixedStringOffsetAssignment', + 'MixedArgumentTypeCoercion', + 'MixedPropertyTypeCoercion', + 'MixedReturnTypeCoercion', + ]; + + /** + * These are special object classes that allow any and all properties to be get/set on them + * @var array + */ + protected $universal_object_crates = [ + \stdClass::class, + SimpleXMLElement::class, + ]; + + /** + * @var static|null + */ + private static $instance; + + /** + * Whether or not to use types as defined in docblocks + * + * @var bool + */ + public $use_docblock_types = true; + + /** + * Whether or not to use types as defined in property docblocks. + * This is distinct from the above because you may want to use + * property docblocks, but not function docblocks. + * + * @var bool + */ + public $use_docblock_property_types = false; + + /** + * Whether or not to throw an exception on first error + * + * @var bool + */ + public $throw_exception = false; + + /** + * Whether or not to load Xdebug stub + * + * @var bool|null + */ + public $load_xdebug_stub = null; + + /** + * The directory to store PHP Parser (and other) caches + * + * @var string|null + */ + public $cache_directory; + + /** + * The directory to store all Psalm project caches + * + * @var string|null + */ + public $global_cache_directory; + + /** + * Path to the autoader + * + * @var string|null + */ + public $autoloader; + + /** + * @var ProjectFileFilter|null + */ + protected $project_files; + + /** + * @var ProjectFileFilter|null + */ + protected $extra_files; + + /** + * The base directory of this config file + * + * @var string + */ + public $base_dir; + + /** + * The PHP version to assume as declared in the config file + * + * @var string|null + */ + private $configured_php_version; + + /** + * @var array + */ + private $file_extensions = ['php']; + + /** + * @var array> + */ + private $filetype_scanners = []; + + /** + * @var array> + */ + private $filetype_analyzers = []; + + /** + * @var array + */ + private $filetype_scanner_paths = []; + + /** + * @var array + */ + private $filetype_analyzer_paths = []; + + /** + * @var array + */ + private $issue_handlers = []; + + /** + * @var array + */ + private $mock_classes = []; + + /** + * @var array + */ + private $stub_files = []; + + /** + * @var bool + */ + public $hide_external_errors = false; + + /** @var bool */ + public $allow_includes = true; + + /** @var 1|2|3|4|5|6|7|8 */ + public $level = 1; + + /** + * @var ?bool + */ + public $show_mixed_issues = null; + + /** @var bool */ + public $strict_binary_operands = false; + + /** + * @var bool + */ + public $remember_property_assignments_after_call = true; + + /** @var bool */ + public $use_igbinary = false; + + /** + * @var bool + */ + public $allow_phpstorm_generics = false; + + /** + * @var bool + */ + public $allow_string_standin_for_class = false; + + /** + * @var bool + */ + public $use_phpdoc_method_without_magic_or_parent = false; + + /** + * @var bool + */ + public $use_phpdoc_property_without_magic_or_parent = false; + + /** + * @var bool + */ + public $skip_checks_on_unresolvable_includes = false; + + /** + * @var bool + */ + public $seal_all_methods = false; + + /** + * @var bool + */ + public $memoize_method_calls = false; + + /** + * @var bool + */ + public $hoist_constants = false; + + /** + * @var bool + */ + public $add_param_default_to_docblock_type = false; + + /** + * @var bool + */ + public $check_for_throws_docblock = false; + + /** + * @var bool + */ + public $check_for_throws_in_global_scope = false; + + /** + * @var bool + */ + public $ignore_internal_falsable_issues = true; + + /** + * @var bool + */ + public $ignore_internal_nullable_issues = true; + + /** + * @var array + */ + public $ignored_exceptions = []; + + /** + * @var array + */ + public $ignored_exceptions_in_global_scope = []; + + /** + * @var array + */ + public $ignored_exceptions_and_descendants = []; + + /** + * @var array + */ + public $ignored_exceptions_and_descendants_in_global_scope = []; + + /** + * @var bool + */ + public $infer_property_types_from_constructor = true; + + /** + * @var bool + */ + public $ensure_array_string_offsets_exist = false; + + /** + * @var bool + */ + public $ensure_array_int_offsets_exist = false; + + /** + * @var array + */ + public $forbidden_functions = []; + + /** + * @var bool + */ + public $forbid_echo = false; + + /** + * @var bool + */ + public $find_unused_code = false; + + /** + * @var bool + */ + public $find_unused_variables = false; + + /** + * @var bool + */ + public $find_unused_psalm_suppress = false; + + /** + * @var bool + */ + public $run_taint_analysis = false; + + /** @var bool */ + public $use_phpstorm_meta_path = true; + + /** + * @var bool + */ + public $resolve_from_config_file = true; + + /** + * @var string[] + */ + public $plugin_paths = []; + + /** + * @var array + */ + private $plugin_classes = []; + + /** + * Static methods to be called after method checks have completed + * + * @var class-string[] + */ + public $after_method_checks = []; + + /** + * Static methods to be called after project function checks have completed + * + * Called after function calls to functions defined in the project. + * + * Allows influencing the return type and adding of modifications. + * + * @var class-string[] + */ + public $after_function_checks = []; + + /** + * Static methods to be called after every function call + * + * Called after each function call, including php internal functions. + * + * Cannot change the call or influence its return type + * + * @var class-string[] + */ + public $after_every_function_checks = []; + + + /** + * Static methods to be called after expression checks have completed + * + * @var class-string[] + */ + public $after_expression_checks = []; + + /** + * Static methods to be called after statement checks have completed + * + * @var class-string[] + */ + public $after_statement_checks = []; + + /** + * Static methods to be called after method checks have completed + * + * @var class-string[] + */ + public $string_interpreters = []; + + /** + * Static methods to be called after classlike exists checks have completed + * + * @var class-string[] + */ + public $after_classlike_exists_checks = []; + + /** + * Static methods to be called after classlike checks have completed + * + * @var class-string[] + */ + public $after_classlike_checks = []; + + /** + * Static methods to be called after classlikes have been scanned + * + * @var class-string[] + */ + public $after_visit_classlikes = []; + + /** + * Static methods to be called after codebase has been populated + * + * @var class-string[] + */ + public $after_codebase_populated = []; + + /** + * Static methods to be called after codebase has been populated + * + * @var class-string[] + */ + public $after_analysis = []; + + /** + * Static methods to be called after a file has been analyzed + * @var class-string[] + */ + public $after_file_checks = []; + + /** + * Static methods to be called before a file is analyzed + * @var class-string[] + */ + public $before_file_checks = []; + + /** + * @var bool + */ + public $allow_internal_named_arg_calls = true; + + /** + * @var bool + */ + public $allow_named_arg_calls = true; + + /** + * Static methods to be called after functionlike checks have completed + * + * @var class-string[] + */ + public $after_functionlike_checks = []; + + /** @var array */ + private $predefined_constants = []; + + /** @var array */ + private $predefined_functions = []; + + /** @var ClassLoader|null */ + private $composer_class_loader; + + /** + * Custom functions that always exit + * + * @var array + */ + public $exit_functions = []; + + /** + * @var string + */ + public $hash = ''; + + /** @var string|null */ + public $error_baseline = null; + + /** + * @var bool + */ + public $include_php_versions_in_error_baseline = false; + + /** @var string */ + public $shepherd_host = 'shepherd.dev'; + + /** + * @var array + */ + public $globals = []; + + /** + * @var int + */ + public $max_string_length = 1000; + + /** @var ?IncludeCollector */ + private $include_collector; + + /** + * @var TaintAnalysisFileFilter|null + */ + protected $taint_analysis_ignored_files; + + /** + * @var bool whether to emit a backtrace of emitted issues to stderr + */ + public $debug_emitted_issues = false; + + /** + * @var bool + */ + private $report_info = true; + + protected function __construct() + { + self::$instance = $this; + } + + /** + * Gets a Config object from an XML file. + * + * Searches up a folder hierarchy for the most immediate config. + * + * @throws ConfigException if a config path is not found + * + */ + public static function getConfigForPath(string $path, string $current_dir, string $output_format): Config + { + $config_path = self::locateConfigFile($path); + + if (!$config_path) { + if (in_array($output_format, [\Psalm\Report::TYPE_CONSOLE, \Psalm\Report::TYPE_PHP_STORM])) { + echo 'Could not locate a config XML file in path ' . $path + . '. Have you run \'psalm --init\' ?' . PHP_EOL; + exit(1); + } + throw new ConfigException('Config not found for path ' . $path); + } + + return self::loadFromXMLFile($config_path, $current_dir); + } + + /** + * Searches up a folder hierarchy for the most immediate config. + * + * @throws ConfigException + * + */ + public static function locateConfigFile(string $path): ?string + { + $dir_path = realpath($path); + + if ($dir_path === false) { + throw new ConfigException('Config not found for path ' . $path); + } + + if (!is_dir($dir_path)) { + $dir_path = dirname($dir_path); + } + + do { + $maybe_path = $dir_path . DIRECTORY_SEPARATOR . Config::DEFAULT_FILE_NAME; + + if (file_exists($maybe_path) || file_exists($maybe_path .= '.dist')) { + return $maybe_path; + } + + $dir_path = dirname($dir_path); + } while (dirname($dir_path) !== $dir_path); + + return null; + } + + /** + * Creates a new config object from the file + */ + public static function loadFromXMLFile(string $file_path, string $current_dir): Config + { + $file_contents = file_get_contents($file_path); + + $base_dir = dirname($file_path) . DIRECTORY_SEPARATOR; + + if ($file_contents === false) { + throw new \InvalidArgumentException('Cannot open ' . $file_path); + } + + try { + $config = self::loadFromXML($base_dir, $file_contents, $current_dir); + $config->hash = sha1($file_contents . \PSALM_VERSION); + } catch (ConfigException $e) { + throw new ConfigException( + 'Problem parsing ' . $file_path . ":\n" . ' ' . $e->getMessage() + ); + } + + return $config; + } + + /** + * Creates a new config object from an XML string + * @param string|null $current_dir Current working directory, if different to $base_dir + * + * @throws ConfigException + */ + public static function loadFromXML(string $base_dir, string $file_contents, ?string $current_dir = null): Config + { + if ($current_dir === null) { + $current_dir = $base_dir; + } + + self::validateXmlConfig($base_dir, $file_contents); + + return self::fromXmlAndPaths($base_dir, $file_contents, $current_dir); + } + + private static function loadDomDocument(string $base_dir, string $file_contents): DOMDocument + { + $dom_document = new DOMDocument(); + + // there's no obvious way to set xml:base for a document when loading it from string + // so instead we're changing the current directory instead to be able to process XIncludes + $oldpwd = getcwd(); + chdir($base_dir); + + $dom_document->loadXML($file_contents, LIBXML_NONET); + $dom_document->xinclude(LIBXML_NONET); + + chdir($oldpwd); + return $dom_document; + } + + /** + * @throws ConfigException + */ + private static function validateXmlConfig(string $base_dir, string $file_contents): void + { + $schema_path = dirname(__DIR__, 2). '/config.xsd'; + + if (!file_exists($schema_path)) { + throw new ConfigException('Cannot locate config schema'); + } + + $dom_document = self::loadDomDocument($base_dir, $file_contents); + + $psalm_nodes = $dom_document->getElementsByTagName('psalm'); + + /** @var \DomElement|null */ + $psalm_node = $psalm_nodes->item(0); + + if (!$psalm_node) { + throw new ConfigException( + 'Missing psalm node' + ); + } + + if (!$psalm_node->hasAttribute('xmlns')) { + $psalm_node->setAttribute('xmlns', 'https://getpsalm.org/schema/config'); + + $old_dom_document = $dom_document; + $dom_document = self::loadDomDocument($base_dir, $old_dom_document->saveXML()); + } + + // Enable user error handling + libxml_use_internal_errors(true); + + if (!$dom_document->schemaValidate($schema_path)) { + $errors = libxml_get_errors(); + foreach ($errors as $error) { + if ($error->level === LIBXML_ERR_FATAL || $error->level === LIBXML_ERR_ERROR) { + throw new ConfigException( + 'Error on line ' . $error->line . ":\n" . ' ' . $error->message + ); + } + } + libxml_clear_errors(); + } + } + + + /** + * @psalm-suppress MixedMethodCall + * @psalm-suppress MixedAssignment + * @psalm-suppress MixedOperand + * @psalm-suppress MixedArgument + * @psalm-suppress MixedPropertyFetch + * + * @throws ConfigException + */ + private static function fromXmlAndPaths(string $base_dir, string $file_contents, string $current_dir): self + { + $config = new static(); + + $dom_document = self::loadDomDocument($base_dir, $file_contents); + + $config_xml = simplexml_import_dom($dom_document); + + $booleanAttributes = [ + 'useDocblockTypes' => 'use_docblock_types', + 'useDocblockPropertyTypes' => 'use_docblock_property_types', + 'throwExceptionOnError' => 'throw_exception', + 'hideExternalErrors' => 'hide_external_errors', + 'resolveFromConfigFile' => 'resolve_from_config_file', + 'allowFileIncludes' => 'allow_includes', + 'strictBinaryOperands' => 'strict_binary_operands', + 'rememberPropertyAssignmentsAfterCall' => 'remember_property_assignments_after_call', + 'allowPhpStormGenerics' => 'allow_phpstorm_generics', + 'allowStringToStandInForClass' => 'allow_string_standin_for_class', + 'usePhpDocMethodsWithoutMagicCall' => 'use_phpdoc_method_without_magic_or_parent', + 'usePhpDocPropertiesWithoutMagicCall' => 'use_phpdoc_property_without_magic_or_parent', + 'memoizeMethodCallResults' => 'memoize_method_calls', + 'hoistConstants' => 'hoist_constants', + 'addParamDefaultToDocblockType' => 'add_param_default_to_docblock_type', + 'checkForThrowsDocblock' => 'check_for_throws_docblock', + 'checkForThrowsInGlobalScope' => 'check_for_throws_in_global_scope', + 'forbidEcho' => 'forbid_echo', + 'ignoreInternalFunctionFalseReturn' => 'ignore_internal_falsable_issues', + 'ignoreInternalFunctionNullReturn' => 'ignore_internal_nullable_issues', + 'includePhpVersionsInErrorBaseline' => 'include_php_versions_in_error_baseline', + 'loadXdebugStub' => 'load_xdebug_stub', + 'ensureArrayStringOffsetsExist' => 'ensure_array_string_offsets_exist', + 'ensureArrayIntOffsetsExist' => 'ensure_array_int_offsets_exist', + 'reportMixedIssues' => 'show_mixed_issues', + 'skipChecksOnUnresolvableIncludes' => 'skip_checks_on_unresolvable_includes', + 'sealAllMethods' => 'seal_all_methods', + 'runTaintAnalysis' => 'run_taint_analysis', + 'usePhpStormMetaPath' => 'use_phpstorm_meta_path', + 'allowInternalNamedArgumentsCalls' => 'allow_internal_named_arg_calls', + 'allowNamedArgumentCalls' => 'allow_named_arg_calls', + 'findUnusedPsalmSuppress' => 'find_unused_psalm_suppress', + 'reportInfo' => 'report_info', + ]; + + foreach ($booleanAttributes as $xmlName => $internalName) { + if (isset($config_xml[$xmlName])) { + $attribute_text = (string) $config_xml[$xmlName]; + $config->setBooleanAttribute( + $internalName, + $attribute_text === 'true' || $attribute_text === '1' + ); + } + } + + if ($config->resolve_from_config_file) { + $config->base_dir = $base_dir; + } else { + $config->base_dir = $current_dir; + $base_dir = $current_dir; + } + + if (isset($config_xml['phpVersion'])) { + $config->configured_php_version = (string) $config_xml['phpVersion']; + } + + if (isset($config_xml['autoloader'])) { + $autoloader_path = $config->base_dir . DIRECTORY_SEPARATOR . $config_xml['autoloader']; + + if (!file_exists($autoloader_path)) { + throw new ConfigException('Cannot locate autoloader'); + } + + $config->autoloader = realpath($autoloader_path); + } + + if (isset($config_xml['cacheDirectory'])) { + $config->cache_directory = (string)$config_xml['cacheDirectory']; + } elseif ($user_cache_dir = (new Xdg())->getHomeCacheDir()) { + $config->cache_directory = $user_cache_dir . '/psalm'; + } else { + $config->cache_directory = sys_get_temp_dir() . '/psalm'; + } + + $config->global_cache_directory = $config->cache_directory; + + $config->cache_directory .= DIRECTORY_SEPARATOR . sha1($base_dir); + + if (is_dir($config->cache_directory) === false && @mkdir($config->cache_directory, 0777, true) === false) { + trigger_error('Could not create cache directory: ' . $config->cache_directory, E_USER_ERROR); + } + + if (isset($config_xml['serializer'])) { + $attribute_text = (string) $config_xml['serializer']; + $config->use_igbinary = $attribute_text === 'igbinary'; + } elseif ($igbinary_version = phpversion('igbinary')) { + $config->use_igbinary = version_compare($igbinary_version, '2.0.5') >= 0; + } + + + if (isset($config_xml['findUnusedCode'])) { + $attribute_text = (string) $config_xml['findUnusedCode']; + $config->find_unused_code = $attribute_text === 'true' || $attribute_text === '1'; + $config->find_unused_variables = $config->find_unused_code; + } + + if (isset($config_xml['findUnusedVariablesAndParams'])) { + $attribute_text = (string) $config_xml['findUnusedVariablesAndParams']; + $config->find_unused_variables = $attribute_text === 'true' || $attribute_text === '1'; + } + + if (isset($config_xml['errorLevel'])) { + $attribute_text = (int) $config_xml['errorLevel']; + + if (!in_array($attribute_text, [1, 2, 3, 4, 5, 6, 7, 8], true)) { + throw new Exception\ConfigException( + 'Invalid error level ' . $config_xml['errorLevel'] + ); + } + + $config->level = $attribute_text; + } elseif (isset($config_xml['totallyTyped'])) { + $totally_typed = (string) $config_xml['totallyTyped']; + + if ($totally_typed === 'true' || $totally_typed === '1') { + $config->level = 1; + } else { + $config->level = 2; + + if ($config->show_mixed_issues === null) { + $config->show_mixed_issues = false; + } + } + } else { + $config->level = 2; + } + + if (isset($config_xml['errorBaseline'])) { + $attribute_text = (string) $config_xml['errorBaseline']; + $config->error_baseline = $attribute_text; + } + + if (isset($config_xml['maxStringLength'])) { + $attribute_text = intval($config_xml['maxStringLength']); + $config->max_string_length = $attribute_text; + } + + if (isset($config_xml['inferPropertyTypesFromConstructor'])) { + $attribute_text = (string) $config_xml['inferPropertyTypesFromConstructor']; + $config->infer_property_types_from_constructor = $attribute_text === 'true' || $attribute_text === '1'; + } + + if (isset($config_xml->projectFiles)) { + $config->project_files = ProjectFileFilter::loadFromXMLElement($config_xml->projectFiles, $base_dir, true); + } + + if (isset($config_xml->extraFiles)) { + $config->extra_files = ProjectFileFilter::loadFromXMLElement($config_xml->extraFiles, $base_dir, true); + } + + if (isset($config_xml->taintAnalysis->ignoreFiles)) { + $config->taint_analysis_ignored_files = TaintAnalysisFileFilter::loadFromXMLElement( + $config_xml->taintAnalysis->ignoreFiles, + $base_dir, + false + ); + } + + if (isset($config_xml->fileExtensions)) { + $config->file_extensions = []; + + $config->loadFileExtensions($config_xml->fileExtensions->extension); + } + + if (isset($config_xml->mockClasses) && isset($config_xml->mockClasses->class)) { + /** @var \SimpleXMLElement $mock_class */ + foreach ($config_xml->mockClasses->class as $mock_class) { + $config->mock_classes[] = strtolower((string)$mock_class['name']); + } + } + + if (isset($config_xml->universalObjectCrates) && isset($config_xml->universalObjectCrates->class)) { + /** @var \SimpleXMLElement $universal_object_crate */ + foreach ($config_xml->universalObjectCrates->class as $universal_object_crate) { + /** @var class-string $classString */ + $classString = $universal_object_crate['name']; + $config->addUniversalObjectCrate($classString); + } + } + + if (isset($config_xml->ignoreExceptions)) { + if (isset($config_xml->ignoreExceptions->class)) { + /** @var \SimpleXMLElement $exception_class */ + foreach ($config_xml->ignoreExceptions->class as $exception_class) { + $exception_name = (string) $exception_class['name']; + $global_attribute_text = (string) $exception_class['onlyGlobalScope']; + if ($global_attribute_text !== 'true' && $global_attribute_text !== '1') { + $config->ignored_exceptions[$exception_name] = true; + } + $config->ignored_exceptions_in_global_scope[$exception_name] = true; + } + } + if (isset($config_xml->ignoreExceptions->classAndDescendants)) { + /** @var \SimpleXMLElement $exception_class */ + foreach ($config_xml->ignoreExceptions->classAndDescendants as $exception_class) { + $exception_name = (string) $exception_class['name']; + $global_attribute_text = (string) $exception_class['onlyGlobalScope']; + if ($global_attribute_text !== 'true' && $global_attribute_text !== '1') { + $config->ignored_exceptions_and_descendants[$exception_name] = true; + } + $config->ignored_exceptions_and_descendants_in_global_scope[$exception_name] = true; + } + } + } + + if (isset($config_xml->forbiddenFunctions) && isset($config_xml->forbiddenFunctions->function)) { + /** @var \SimpleXMLElement $forbidden_function */ + foreach ($config_xml->forbiddenFunctions->function as $forbidden_function) { + $config->forbidden_functions[strtolower((string) $forbidden_function['name'])] = true; + } + } + + if (isset($config_xml->exitFunctions) && isset($config_xml->exitFunctions->function)) { + /** @var \SimpleXMLElement $exit_function */ + foreach ($config_xml->exitFunctions->function as $exit_function) { + $config->exit_functions[strtolower((string) $exit_function['name'])] = true; + } + } + + if (isset($config_xml->stubs) && isset($config_xml->stubs->file)) { + /** @var \SimpleXMLElement $stub_file */ + foreach ($config_xml->stubs->file as $stub_file) { + $stub_file_name = (string)$stub_file['name']; + if (!Path::isAbsolute($stub_file_name)) { + $stub_file_name = $config->base_dir . DIRECTORY_SEPARATOR . $stub_file_name; + } + $file_path = realpath($stub_file_name); + + if (!$file_path) { + throw new Exception\ConfigException( + 'Cannot resolve stubfile path ' . $config->base_dir . DIRECTORY_SEPARATOR . $stub_file['name'] + ); + } + + $config->addStubFile($file_path); + } + } + + // this plugin loading system borrows heavily from etsy/phan + if (isset($config_xml->plugins)) { + if (isset($config_xml->plugins->plugin)) { + /** @var \SimpleXMLElement $plugin */ + foreach ($config_xml->plugins->plugin as $plugin) { + $plugin_file_name = (string) $plugin['filename']; + + $path = Path::isAbsolute($plugin_file_name) + ? $plugin_file_name + : $config->base_dir . $plugin_file_name; + + $config->addPluginPath($path); + } + } + if (isset($config_xml->plugins->pluginClass)) { + /** @var \SimpleXMLElement $plugin */ + foreach ($config_xml->plugins->pluginClass as $plugin) { + $plugin_class_name = $plugin['class']; + // any child elements are used as plugin configuration + $plugin_config = null; + if ($plugin->count()) { + $plugin_config = $plugin->children(); + } + + $config->addPluginClass((string) $plugin_class_name, $plugin_config); + } + } + } + + if (isset($config_xml->issueHandlers)) { + /** @var \SimpleXMLElement $issue_handler */ + foreach ($config_xml->issueHandlers->children() as $key => $issue_handler) { + if ($key === 'PluginIssue') { + $custom_class_name = (string) $issue_handler['name']; + /** @var string $key */ + $config->issue_handlers[$custom_class_name] = IssueHandler::loadFromXMLElement( + $issue_handler, + $base_dir + ); + } else { + /** @var string $key */ + $config->issue_handlers[$key] = IssueHandler::loadFromXMLElement( + $issue_handler, + $base_dir + ); + } + } + } + + if (isset($config_xml->globals) && isset($config_xml->globals->var)) { + /** @var \SimpleXMLElement $var */ + foreach ($config_xml->globals->var as $var) { + $config->globals['$' . (string) $var['name']] = (string) $var['type']; + } + } + + return $config; + } + + public static function getInstance(): Config + { + if (self::$instance) { + return self::$instance; + } + + throw new \UnexpectedValueException('No config initialized'); + } + + public function setComposerClassLoader(?ClassLoader $loader = null): void + { + $this->composer_class_loader = $loader; + } + + public function setCustomErrorLevel(string $issue_key, string $error_level): void + { + $this->issue_handlers[$issue_key] = new IssueHandler(); + $this->issue_handlers[$issue_key]->setErrorLevel($error_level); + } + + /** + * @throws ConfigException if a Config file could not be found + * + */ + private function loadFileExtensions(SimpleXMLElement $extensions): void + { + foreach ($extensions as $extension) { + $extension_name = preg_replace('/^\.?/', '', (string)$extension['name']); + $this->file_extensions[] = $extension_name; + + if (isset($extension['scanner'])) { + $path = $this->base_dir . (string)$extension['scanner']; + + if (!file_exists($path)) { + throw new Exception\ConfigException('Error parsing config: cannot find file ' . $path); + } + + $this->filetype_scanner_paths[$extension_name] = $path; + } + + if (isset($extension['checker'])) { + $path = $this->base_dir . (string)$extension['checker']; + + if (!file_exists($path)) { + throw new Exception\ConfigException('Error parsing config: cannot find file ' . $path); + } + + $this->filetype_analyzer_paths[$extension_name] = $path; + } + } + } + + public function addPluginPath(string $path): void + { + if (!file_exists($path)) { + throw new \InvalidArgumentException('Cannot find plugin file ' . $path); + } + + $this->plugin_paths[] = $path; + } + + public function addPluginClass(string $class_name, ?SimpleXMLElement $plugin_config = null): void + { + $this->plugin_classes[] = ['class' => $class_name, 'config' => $plugin_config]; + } + + /** @return array */ + public function getPluginClasses(): array + { + return $this->plugin_classes; + } + + /** + * Initialises all the plugins (done once the config is fully loaded) + * + * @psalm-suppress MixedAssignment + */ + public function initializePlugins(ProjectAnalyzer $project_analyzer): void + { + $codebase = $project_analyzer->getCodebase(); + + $project_analyzer->progress->debug('Initializing plugins...' . PHP_EOL); + + $socket = new PluginRegistrationSocket($this, $codebase); + // initialize plugin classes earlier to let them hook into subsequent load process + foreach ($this->plugin_classes as $plugin_class_entry) { + $plugin_class_name = $plugin_class_entry['class']; + $plugin_config = $plugin_class_entry['config']; + + try { + // Below will attempt to load plugins from the project directory first. + // Failing that, it will use registered autoload chain, which will load + // plugins from Psalm directory or phar file. If that fails as well, it + // will fall back to project autoloader. It may seem that the last step + // will always fail, but it's only true if project uses Composer autoloader + if ($this->composer_class_loader + && ($plugin_class_path = $this->composer_class_loader->findFile($plugin_class_name)) + ) { + $project_analyzer->progress->debug( + 'Loading plugin ' . $plugin_class_name . ' via require' . PHP_EOL + ); + + self::requirePath($plugin_class_path); + } else { + if (!class_exists($plugin_class_name, true)) { + throw new \UnexpectedValueException($plugin_class_name . ' is not a known class'); + } + } + + /** + * @psalm-suppress InvalidStringClass + * + * @var Plugin\PluginEntryPointInterface + */ + $plugin_object = new $plugin_class_name; + $plugin_object($socket, $plugin_config); + } catch (\Throwable $e) { + throw new ConfigException('Failed to load plugin ' . $plugin_class_name, 0, $e); + } + + $project_analyzer->progress->debug('Loaded plugin ' . $plugin_class_name . ' successfully' . PHP_EOL); + } + + foreach ($this->filetype_scanner_paths as $extension => $path) { + $fq_class_name = $this->getPluginClassForPath( + $codebase, + $path, + FileScanner::class + ); + + self::requirePath($path); + + $this->filetype_scanners[$extension] = $fq_class_name; + } + + foreach ($this->filetype_analyzer_paths as $extension => $path) { + $fq_class_name = $this->getPluginClassForPath( + $codebase, + $path, + FileAnalyzer::class + ); + + self::requirePath($path); + + $this->filetype_analyzers[$extension] = $fq_class_name; + } + + foreach ($this->plugin_paths as $path) { + try { + $plugin_object = new FileBasedPluginAdapter($path, $this, $codebase); + $plugin_object($socket); + } catch (\Throwable $e) { + throw new ConfigException('Failed to load plugin ' . $path, 0, $e); + } + } + } + + private static function requirePath(string $path): void + { + /** @psalm-suppress UnresolvableInclude */ + require_once($path); + } + + /** + * @template T + * + * @param T::class $must_extend + * + * @return class-string + */ + private function getPluginClassForPath(Codebase $codebase, string $path, string $must_extend): string + { + $file_storage = $codebase->createFileStorageForPath($path); + $file_to_scan = new FileScanner($path, $this->shortenFileName($path), true); + $file_to_scan->scan( + $codebase, + $file_storage + ); + + $declared_classes = ClassLikeAnalyzer::getClassesForFile($codebase, $path); + + if (!count($declared_classes)) { + throw new \InvalidArgumentException( + 'Plugins must have at least one class in the file - ' . $path . ' has ' . + count($declared_classes) + ); + } + + $fq_class_name = reset($declared_classes); + + if (!$codebase->classlikes->classExtends( + $fq_class_name, + $must_extend + ) + ) { + throw new \InvalidArgumentException( + 'This plugin must extend ' . $must_extend . ' - ' . $path . ' does not' + ); + } + + /** + * @var class-string + */ + return $fq_class_name; + } + + public function shortenFileName(string $file_name): string + { + return preg_replace('/^' . preg_quote($this->base_dir, '/') . '/', '', $file_name); + } + + public function reportIssueInFile(string $issue_type, string $file_path): bool + { + if (($this->show_mixed_issues === false || $this->level > 2) + && in_array($issue_type, self::MIXED_ISSUES, true) + ) { + return false; + } + + if ($this->mustBeIgnored($file_path)) { + return false; + } + + $dependent_files = [strtolower($file_path) => $file_path]; + + $project_analyzer = ProjectAnalyzer::getInstance(); + + $codebase = $project_analyzer->getCodebase(); + + if (!$this->hide_external_errors) { + try { + $file_storage = $codebase->file_storage_provider->get($file_path); + $dependent_files += $file_storage->required_by_file_paths; + } catch (\InvalidArgumentException $e) { + // do nothing + } + } + + $any_file_path_matched = false; + + foreach ($dependent_files as $dependent_file_path) { + if (((!$project_analyzer->full_run && $codebase->analyzer->canReportIssues($dependent_file_path)) + || $project_analyzer->canReportIssues($dependent_file_path)) + && ($file_path === $dependent_file_path || !$this->mustBeIgnored($dependent_file_path)) + ) { + $any_file_path_matched = true; + break; + } + } + + if (!$any_file_path_matched) { + return false; + } + + if ($this->getReportingLevelForFile($issue_type, $file_path) === self::REPORT_SUPPRESS) { + return false; + } + + return true; + } + + public function isInProjectDirs(string $file_path): bool + { + return $this->project_files && $this->project_files->allows($file_path); + } + + public function isInExtraDirs(string $file_path): bool + { + return $this->extra_files && $this->extra_files->allows($file_path); + } + + public function mustBeIgnored(string $file_path): bool + { + return $this->project_files && $this->project_files->forbids($file_path); + } + + public function trackTaintsInPath(string $file_path): bool + { + return !$this->taint_analysis_ignored_files + || $this->taint_analysis_ignored_files->allows($file_path); + } + + public function getReportingLevelForIssue(CodeIssue $e): string + { + $fqcn_parts = explode('\\', get_class($e)); + $issue_type = array_pop($fqcn_parts); + + $reporting_level = null; + + if ($e instanceof ClassIssue) { + $reporting_level = $this->getReportingLevelForClass($issue_type, $e->fq_classlike_name); + } elseif ($e instanceof MethodIssue) { + $reporting_level = $this->getReportingLevelForMethod($issue_type, $e->method_id); + } elseif ($e instanceof FunctionIssue) { + $reporting_level = $this->getReportingLevelForFunction($issue_type, $e->function_id); + } elseif ($e instanceof PropertyIssue) { + $reporting_level = $this->getReportingLevelForProperty($issue_type, $e->property_id); + } elseif ($e instanceof ArgumentIssue && $e->function_id) { + $reporting_level = $this->getReportingLevelForArgument($issue_type, $e->function_id); + } elseif ($e instanceof VariableIssue) { + $reporting_level = $this->getReportingLevelForVariable($issue_type, $e->var_name); + } + + if ($reporting_level === null) { + $reporting_level = $this->getReportingLevelForFile($issue_type, $e->getFilePath()); + } + + if (!$this->report_info && $reporting_level === self::REPORT_INFO) { + $reporting_level = self::REPORT_SUPPRESS; + } + + $parent_issue_type = self::getParentIssueType($issue_type); + + if ($parent_issue_type && $reporting_level === Config::REPORT_ERROR) { + $parent_reporting_level = $this->getReportingLevelForFile($parent_issue_type, $e->getFilePath()); + + if ($parent_reporting_level !== $reporting_level) { + return $parent_reporting_level; + } + } + + return $reporting_level; + } + + /** + * @psalm-pure + */ + public static function getParentIssueType(string $issue_type): ?string + { + if ($issue_type === 'PossiblyUndefinedIntArrayOffset' + || $issue_type === 'PossiblyUndefinedStringArrayOffset' + ) { + return 'PossiblyUndefinedArrayOffset'; + } + + if ($issue_type === 'PossiblyNullReference') { + return 'NullReference'; + } + + if ($issue_type === 'PossiblyFalseReference') { + return null; + } + + if ($issue_type === 'PossiblyUndefinedArrayOffset') { + return null; + } + + if (strpos($issue_type, 'Possibly') === 0) { + $stripped_issue_type = preg_replace('/^Possibly(False|Null)?/', '', $issue_type); + + if (strpos($stripped_issue_type, 'Invalid') === false && strpos($stripped_issue_type, 'Un') !== 0) { + $stripped_issue_type = 'Invalid' . $stripped_issue_type; + } + + return $stripped_issue_type; + } + + if (preg_match('/^(False|Null)[A-Z]/', $issue_type) && !strpos($issue_type, 'Reference')) { + return preg_replace('/^(False|Null)/', 'Invalid', $issue_type); + } + + if ($issue_type === 'UndefinedInterfaceMethod') { + return 'UndefinedMethod'; + } + + if ($issue_type === 'UndefinedMagicPropertyFetch') { + return 'UndefinedPropertyFetch'; + } + + if ($issue_type === 'UndefinedMagicPropertyAssignment') { + return 'UndefinedPropertyAssignment'; + } + + if ($issue_type === 'UndefinedMagicMethod') { + return 'UndefinedMethod'; + } + + if ($issue_type === 'PossibleRawObjectIteration') { + return 'RawObjectIteration'; + } + + if ($issue_type === 'UninitializedProperty') { + return 'PropertyNotSetInConstructor'; + } + + if ($issue_type === 'InvalidDocblockParamName') { + return 'InvalidDocblock'; + } + + if ($issue_type === 'UnusedClosureParam') { + return 'UnusedParam'; + } + + if ($issue_type === 'StringIncrement') { + return 'InvalidOperand'; + } + + if ($issue_type === 'InvalidLiteralArgument') { + return 'InvalidArgument'; + } + + if ($issue_type === 'TraitMethodSignatureMismatch') { + return 'MethodSignatureMismatch'; + } + + if ($issue_type === 'ImplementedParamTypeMismatch') { + return 'MoreSpecificImplementedParamType'; + } + + if ($issue_type === 'UndefinedDocblockClass') { + return 'UndefinedClass'; + } + + return null; + } + + public function getReportingLevelForFile(string $issue_type, string $file_path): string + { + if (isset($this->issue_handlers[$issue_type])) { + return $this->issue_handlers[$issue_type]->getReportingLevelForFile($file_path); + } + + // this string is replaced by scoper for Phars, so be careful + $issue_class = 'Psalm\\Issue\\' . $issue_type; + + if (!class_exists($issue_class) || !is_a($issue_class, \Psalm\Issue\CodeIssue::class, true)) { + return self::REPORT_ERROR; + } + + /** @var int */ + $issue_level = $issue_class::ERROR_LEVEL; + + if ($issue_level > 0 && $issue_level < $this->level) { + return self::REPORT_INFO; + } + + return self::REPORT_ERROR; + } + + public function getReportingLevelForClass(string $issue_type, string $fq_classlike_name): ?string + { + if (isset($this->issue_handlers[$issue_type])) { + return $this->issue_handlers[$issue_type]->getReportingLevelForClass($fq_classlike_name); + } + + return null; + } + + public function getReportingLevelForMethod(string $issue_type, string $method_id): ?string + { + if (isset($this->issue_handlers[$issue_type])) { + return $this->issue_handlers[$issue_type]->getReportingLevelForMethod($method_id); + } + + return null; + } + + public function getReportingLevelForFunction(string $issue_type, string $function_id): ?string + { + if (isset($this->issue_handlers[$issue_type])) { + return $this->issue_handlers[$issue_type]->getReportingLevelForFunction($function_id); + } + + return null; + } + + public function getReportingLevelForArgument(string $issue_type, string $function_id): ?string + { + if (isset($this->issue_handlers[$issue_type])) { + return $this->issue_handlers[$issue_type]->getReportingLevelForArgument($function_id); + } + + return null; + } + + public function getReportingLevelForProperty(string $issue_type, string $property_id): ?string + { + if (isset($this->issue_handlers[$issue_type])) { + return $this->issue_handlers[$issue_type]->getReportingLevelForProperty($property_id); + } + + return null; + } + + public function getReportingLevelForVariable(string $issue_type, string $var_name): ?string + { + if (isset($this->issue_handlers[$issue_type])) { + return $this->issue_handlers[$issue_type]->getReportingLevelForVariable($var_name); + } + + return null; + } + + /** + * @return array + */ + public function getProjectDirectories(): array + { + if (!$this->project_files) { + return []; + } + + return $this->project_files->getDirectories(); + } + + /** + * @return array + */ + public function getProjectFiles(): array + { + if (!$this->project_files) { + return []; + } + + return $this->project_files->getFiles(); + } + + /** + * @return array + */ + public function getExtraDirectories(): array + { + if (!$this->extra_files) { + return []; + } + + return $this->extra_files->getDirectories(); + } + + public function reportTypeStatsForFile(string $file_path): bool + { + return $this->project_files + && $this->project_files->allows($file_path) + && $this->project_files->reportTypeStats($file_path); + } + + public function useStrictTypesForFile(string $file_path): bool + { + return $this->project_files && $this->project_files->useStrictTypes($file_path); + } + + /** + * @return array + */ + public function getFileExtensions(): array + { + return $this->file_extensions; + } + + /** + * @return array> + */ + public function getFiletypeScanners(): array + { + return $this->filetype_scanners; + } + + /** + * @return array> + */ + public function getFiletypeAnalyzers(): array + { + return $this->filetype_analyzers; + } + + /** + * @return array + */ + public function getMockClasses(): array + { + return $this->mock_classes; + } + + public function visitStubFiles(Codebase $codebase, ?Progress $progress = null): void + { + if ($progress === null) { + $progress = new VoidProgress(); + } + + $codebase->register_stub_files = true; + + // note: don't realpath $generic_stubs_path, or phar version will fail + $generic_stubs_path = dirname(__DIR__, 2) . '/stubs/CoreGenericFunctions.phpstub'; + + if (!file_exists($generic_stubs_path)) { + throw new \UnexpectedValueException('Cannot locate core generic stubs'); + } + + // note: don't realpath $generic_classes_path, or phar version will fail + $generic_classes_path = dirname(__DIR__, 2) . '/stubs/CoreGenericClasses.phpstub'; + + if (!file_exists($generic_classes_path)) { + throw new \UnexpectedValueException('Cannot locate core generic classes'); + } + + // note: don't realpath $generic_classes_path, or phar version will fail + $immutable_classes_path = dirname(__DIR__, 2) . '/stubs/CoreImmutableClasses.phpstub'; + + if (!file_exists($immutable_classes_path)) { + throw new \UnexpectedValueException('Cannot locate core immutable classes'); + } + + $core_generic_files = [$generic_stubs_path, $generic_classes_path, $immutable_classes_path]; + + if (\extension_loaded('ds')) { + $ext_ds_path = dirname(__DIR__, 2) . '/stubs/ext-ds.php'; + + if (!file_exists($ext_ds_path)) { + throw new \UnexpectedValueException('Cannot locate core generic classes'); + } + + $core_generic_files[] = $ext_ds_path; + } + + $stub_files = array_merge($core_generic_files, $this->stub_files); + + $phpstorm_meta_path = $this->base_dir . DIRECTORY_SEPARATOR . '.phpstorm.meta.php'; + + if ($this->use_phpstorm_meta_path) { + if (is_file($phpstorm_meta_path)) { + $stub_files[] = $phpstorm_meta_path; + } elseif (is_dir($phpstorm_meta_path)) { + $phpstorm_meta_path = realpath($phpstorm_meta_path); + + foreach (glob($phpstorm_meta_path . '/*.meta.php', GLOB_NOSORT) as $glob) { + if (is_file($glob) && realpath(dirname($glob)) === $phpstorm_meta_path) { + $stub_files[] = $glob; + } + } + } + } + + if ($this->load_xdebug_stub) { + $xdebug_stub_path = dirname(__DIR__, 2) . '/stubs/Xdebug.php'; + + if (!file_exists($xdebug_stub_path)) { + throw new \UnexpectedValueException('Cannot locate XDebug stub'); + } + + $stub_files[] = $xdebug_stub_path; + } + + foreach ($stub_files as $file_path) { + $file_path = \str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $file_path); + $codebase->scanner->addFileToDeepScan($file_path); + } + + $progress->debug('Registering stub files' . "\n"); + + $codebase->scanFiles(); + + $progress->debug('Finished registering stub files' . "\n"); + + $codebase->register_stub_files = false; + } + + public function getCacheDirectory(): ?string + { + return $this->cache_directory; + } + + public function getGlobalCacheDirectory(): ?string + { + return $this->global_cache_directory; + } + + /** + * @return array + */ + public function getPredefinedConstants(): array + { + return $this->predefined_constants; + } + + public function collectPredefinedConstants(): void + { + $this->predefined_constants = get_defined_constants(); + } + + /** + * @return array + */ + public function getPredefinedFunctions(): array + { + return $this->predefined_functions; + } + + public function collectPredefinedFunctions(): void + { + $defined_functions = get_defined_functions(); + + if (isset($defined_functions['user'])) { + foreach ($defined_functions['user'] as $function_name) { + $this->predefined_functions[$function_name] = true; + } + } + + if (isset($defined_functions['internal'])) { + foreach ($defined_functions['internal'] as $function_name) { + $this->predefined_functions[$function_name] = true; + } + } + } + + public function setIncludeCollector(IncludeCollector $include_collector): void + { + $this->include_collector = $include_collector; + } + + /** + * @psalm-suppress MixedAssignment + * @psalm-suppress MixedArrayAccess + */ + public function visitComposerAutoloadFiles(ProjectAnalyzer $project_analyzer, ?Progress $progress = null): void + { + if ($progress === null) { + $progress = new VoidProgress(); + } + + if (!$this->include_collector) { + throw new LogicException("IncludeCollector should be set at this point"); + } + + $vendor_autoload_files_path + = $this->base_dir . DIRECTORY_SEPARATOR . 'vendor' + . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR . 'autoload_files.php'; + + if (file_exists($vendor_autoload_files_path)) { + $this->include_collector->runAndCollect( + function () use ($vendor_autoload_files_path) { + /** + * @psalm-suppress UnresolvableInclude + * @var string[] + */ + return require $vendor_autoload_files_path; + } + ); + } + + $codebase = $project_analyzer->getCodebase(); + + $this->collectPredefinedFunctions(); + + if ($this->autoloader) { + // somee classes that we think are missing may not actually be missing + // as they might be autoloadable once we require the autoloader below + $codebase->classlikes->forgetMissingClassLikes(); + + $this->include_collector->runAndCollect( + function () { + // do this in a separate method so scope does not leak + /** @psalm-suppress UnresolvableInclude */ + require $this->autoloader; + } + ); + } + + $this->collectPredefinedConstants(); + + $autoload_included_files = $this->include_collector->getFilteredIncludedFiles(); + + if ($autoload_included_files) { + $codebase->register_autoload_files = true; + + $progress->debug('Registering autoloaded files' . "\n"); + foreach ($autoload_included_files as $file_path) { + $file_path = \str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $file_path); + $progress->debug(' ' . $file_path . "\n"); + $codebase->scanner->addFileToDeepScan($file_path); + } + + $codebase->scanner->scanFiles($codebase->classlikes); + + $progress->debug('Finished registering autoloaded files' . "\n"); + + $codebase->register_autoload_files = false; + } + } + + /** + * @return string|false + */ + public function getComposerFilePathForClassLike(string $fq_classlike_name) + { + if (!$this->composer_class_loader) { + return false; + } + + return $this->composer_class_loader->findFile($fq_classlike_name); + } + + public function getPotentialComposerFilePathForClassLike(string $class): ?string + { + if (!$this->composer_class_loader) { + return null; + } + + /** @var array> */ + $psr4_prefixes = $this->composer_class_loader->getPrefixesPsr4(); + + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php'; + + $candidate_path = null; + + $maxDepth = 0; + + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($psr4_prefixes[$search])) { + $depth = substr_count($search, '\\'); + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + + foreach ($psr4_prefixes[$search] as $dir) { + $dir = realpath($dir); + + if ($dir + && $depth > $maxDepth + && $this->isInProjectDirs($dir . DIRECTORY_SEPARATOR . 'testdummy.php') + ) { + $maxDepth = $depth; + $candidate_path = realpath($dir) . $pathEnd; + } + } + } + } + + return $candidate_path; + } + + public static function removeCacheDirectory(string $dir): void + { + if (is_dir($dir)) { + $objects = scandir($dir, SCANDIR_SORT_NONE); + + if ($objects === false) { + throw new \UnexpectedValueException('Not expecting false here'); + } + + foreach ($objects as $object) { + if ($object !== '.' && $object !== '..') { + if (filetype($dir . '/' . $object) === 'dir') { + self::removeCacheDirectory($dir . '/' . $object); + } else { + unlink($dir . '/' . $object); + } + } + } + + reset($objects); + rmdir($dir); + } + } + + public function setServerMode(): void + { + $this->cache_directory .= '-s'; + } + + public function addStubFile(string $stub_file): void + { + $this->stub_files[$stub_file] = $stub_file; + } + + public function hasStubFile(string $stub_file): bool + { + return isset($this->stub_files[$stub_file]); + } + + /** + * @return array + */ + public function getStubFiles(): array + { + return $this->stub_files; + } + + public function getPhpVersion(): ?string + { + if (isset($this->configured_php_version)) { + return $this->configured_php_version; + } + + return $this->getPHPVersionFromComposerJson(); + } + + private function setBooleanAttribute(string $name, bool $value): void + { + $this->$name = $value; + } + + /** + * @psalm-suppress MixedAssignment + * @psalm-suppress MixedArrayAccess + */ + private function getPHPVersionFromComposerJson(): ?string + { + $composer_json_path = Composer::getJsonFilePath($this->base_dir); + + if (file_exists($composer_json_path)) { + if (!$composer_json = json_decode(file_get_contents($composer_json_path), true)) { + throw new \UnexpectedValueException('Invalid composer.json at ' . $composer_json_path); + } + $php_version = $composer_json['require']['php'] ?? null; + + if (\is_string($php_version)) { + foreach (['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] as $candidate) { + if (Semver::satisfies($candidate, $php_version)) { + return $candidate; + } + } + } + } + return null; + } + + /** + * @param class-string $class + */ + public function addUniversalObjectCrate(string $class): void + { + $this->universal_object_crates[] = $class; + } + + /** + * @return array + */ + public function getUniversalObjectCrates(): array + { + return array_map('strtolower', $this->universal_object_crates); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Config/Creator.php b/lib/composer/vimeo/psalm/src/Psalm/Config/Creator.php new file mode 100644 index 0000000000000000000000000000000000000000..39e1d782c50309aeb0456c9bb8a374b412c1b1bd --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Config/Creator.php @@ -0,0 +1,281 @@ + + + + + + + + + +'; + + public static function getContents( + string $current_dir, + ?string $suggested_dir, + int $level, + string $vendor_dir + ) : string { + $paths = self::getPaths($current_dir, $suggested_dir); + + $template = str_replace( + '', + implode("\n ", $paths), + self::TEMPLATE + ); + + $template = str_replace( + '', + '', + $template + ); + + $template = str_replace( + 'errorLevel="1"', + 'errorLevel="' . $level . '"', + $template + ); + + return $template; + } + + public static function createBareConfig( + string $current_dir, + ?string $suggested_dir, + string $vendor_dir + ) : void { + $config_contents = self::getContents($current_dir, $suggested_dir, 1, $vendor_dir); + + \Psalm\Config::loadFromXML($current_dir, $config_contents); + } + + /** + * @param array<\Psalm\Internal\Analyzer\IssueData> $issues + */ + public static function getLevel(array $issues, int $counted_types) : int + { + if ($counted_types === 0) { + $counted_types = 1; + } + + $issues_at_level = []; + + foreach ($issues as $issue) { + $issue_type = $issue->type; + $issue_level = $issue->error_level; + + if ($issue_level < 1) { + continue; + } + + // exclude some directories that are probably ignorable + if (strpos($issue->file_path, 'vendor') || strpos($issue->file_path, 'stub')) { + continue; + } + + if (!isset($issues_at_level[$issue_level][$issue_type])) { + $issues_at_level[$issue_level][$issue_type] = 0; + } + + $issues_at_level[$issue_level][$issue_type] += 100/$counted_types; + } + + foreach ($issues_at_level as $level => $issues) { + ksort($issues); + + // remove any issues where < 0.1% of expressions are affected + $filtered_issues = array_filter( + $issues, + function ($amount): bool { + return $amount > 0.1; + } + ); + + if (array_sum($filtered_issues) > 0.5) { + $issues_at_level[$level] = $filtered_issues; + } else { + unset($issues_at_level[$level]); + } + } + + if (!$issues_at_level) { + return 1; + } + + if (count($issues_at_level) === 1) { + return array_keys($issues_at_level)[0] + 1; + } + + return max(...array_keys($issues_at_level)) + 1; + } + + /** + * @return non-empty-list + */ + public static function getPaths(string $current_dir, ?string $suggested_dir): array + { + $replacements = []; + + if ($suggested_dir) { + if (is_dir($current_dir . DIRECTORY_SEPARATOR . $suggested_dir)) { + $replacements[] = ''; + } else { + $bad_dir_path = $current_dir . DIRECTORY_SEPARATOR . $suggested_dir; + + throw new ConfigCreationException( + 'The given path "' . $bad_dir_path . '" does not appear to be a directory' + ); + } + } elseif (is_dir($current_dir . DIRECTORY_SEPARATOR . 'src')) { + $replacements[] = ''; + } else { + $composer_json_location = Composer::getJsonFilePath($current_dir); + + if (!file_exists($composer_json_location)) { + throw new ConfigCreationException( + 'Problem during config autodiscovery - could not find composer.json during initialization.' + ); + } + + /** @psalm-suppress MixedAssignment */ + if (!$composer_json = json_decode(file_get_contents($composer_json_location), true)) { + throw new ConfigCreationException('Invalid composer.json at ' . $composer_json_location); + } + + if (!is_array($composer_json)) { + throw new ConfigCreationException('Invalid composer.json at ' . $composer_json_location); + } + + $replacements = self::getPsr4Or0Paths($current_dir, $composer_json); + + if (!$replacements) { + throw new ConfigCreationException( + 'Could not located any PSR-0 or PSR-4-compatible paths in ' . $composer_json_location + ); + } + } + + return $replacements; + } + + /** + * @return list + * @psalm-suppress MixedAssignment + * @psalm-suppress MixedArgument + */ + private static function getPsr4Or0Paths(string $current_dir, array $composer_json) : array + { + $psr_paths = array_merge( + $composer_json['autoload']['psr-4'] ?? [], + $composer_json['autoload']['psr-0'] ?? [] + ); + + if (!$psr_paths) { + return self::guessPhpFileDirs($current_dir); + } + + $nodes = []; + + foreach ($psr_paths as $paths) { + if (!is_array($paths)) { + $paths = [$paths]; + } + + foreach ($paths as $path) { + if ($path === '') { + $nodes = array_merge( + $nodes, + self::guessPhpFileDirs($current_dir) + ); + + continue; + } + + $path = preg_replace('@[\\\\/]$@', '', $path); + + if ($path !== 'tests') { + $nodes[] = ''; + } + } + } + + $nodes = array_unique($nodes); + + sort($nodes); + + return $nodes; + } + + /** + * @return list + */ + private static function guessPhpFileDirs(string $current_dir) : array + { + $nodes = []; + + /** @var string[] */ + $php_files = array_merge( + glob($current_dir . DIRECTORY_SEPARATOR . '*.php', GLOB_NOSORT), + glob($current_dir . DIRECTORY_SEPARATOR . '**/*.php', GLOB_NOSORT), + glob($current_dir . DIRECTORY_SEPARATOR . '**/**/*.php', GLOB_NOSORT) + ); + + foreach ($php_files as $php_file) { + $php_file = str_replace($current_dir . DIRECTORY_SEPARATOR, '', $php_file); + + $parts = explode(DIRECTORY_SEPARATOR, $php_file); + + if (!$parts[0]) { + array_shift($parts); + } + + if ($parts[0] === 'vendor' || $parts[0] === 'tests') { + continue; + } + + if (count($parts) === 1) { + $nodes[] = ''; + } else { + $nodes[] = ''; + } + } + + return \array_values(\array_unique($nodes)); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Config/ErrorLevelFileFilter.php b/lib/composer/vimeo/psalm/src/Psalm/Config/ErrorLevelFileFilter.php new file mode 100644 index 0000000000000000000000000000000000000000..f0bd476daad08db4d497091d384956279da64b28 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Config/ErrorLevelFileFilter.php @@ -0,0 +1,41 @@ +error_level = (string) $e['type']; + + if (!in_array($filter->error_level, \Psalm\Config::$ERROR_LEVELS, true)) { + throw new \Psalm\Exception\ConfigException('Unexpected error level ' . $filter->error_level); + } + } else { + throw new \Psalm\Exception\ConfigException(' element expects a level'); + } + + return $filter; + } + + public function getErrorLevel(): string + { + return $this->error_level; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Config/FileFilter.php b/lib/composer/vimeo/psalm/src/Psalm/Config/FileFilter.php new file mode 100644 index 0000000000000000000000000000000000000000..7c7ed8b92ecf94bf631a887a86312acd57a5ba24 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Config/FileFilter.php @@ -0,0 +1,480 @@ + + */ + protected $directories = []; + + /** + * @var array + */ + protected $files = []; + + /** + * @var array + */ + protected $fq_classlike_names = []; + + /** + * @var array + */ + protected $fq_classlike_patterns = []; + + /** + * @var array + */ + protected $method_ids = []; + + /** + * @var array + */ + protected $property_ids = []; + + /** + * @var array + */ + protected $var_names = []; + + /** + * @var array + */ + protected $files_lowercase = []; + + /** + * @var bool + */ + protected $inclusive; + + /** + * @var array + */ + protected $ignore_type_stats = []; + + /** + * @var array + */ + protected $declare_strict_types = []; + + public function __construct(bool $inclusive) + { + $this->inclusive = $inclusive; + } + + /** + * @return static + */ + public static function loadFromXMLElement( + SimpleXMLElement $e, + string $base_dir, + bool $inclusive + ) { + $allow_missing_files = ((string) $e['allowMissingFiles']) === 'true'; + + $filter = new static($inclusive); + + if ($e->directory) { + /** @var \SimpleXMLElement $directory */ + foreach ($e->directory as $directory) { + $directory_path = (string) $directory['name']; + $ignore_type_stats = strtolower( + isset($directory['ignoreTypeStats']) ? (string) $directory['ignoreTypeStats'] : '' + ) === 'true'; + $declare_strict_types = strtolower( + isset($directory['useStrictTypes']) ? (string) $directory['useStrictTypes'] : '' + ) === 'true'; + + if ($directory_path[0] === '/' && DIRECTORY_SEPARATOR === '/') { + $prospective_directory_path = $directory_path; + } else { + $prospective_directory_path = $base_dir . DIRECTORY_SEPARATOR . $directory_path; + } + + if (strpos($prospective_directory_path, '*') !== false) { + $globs = array_map( + 'realpath', + glob($prospective_directory_path, GLOB_ONLYDIR) + ); + + if (empty($globs)) { + if ($allow_missing_files) { + continue; + } + + throw new ConfigException( + 'Could not resolve config path to ' . $base_dir + . DIRECTORY_SEPARATOR . (string)$directory['name'] + ); + } + + foreach ($globs as $glob_index => $directory_path) { + if (!$directory_path) { + if ($allow_missing_files) { + continue; + } + + throw new ConfigException( + 'Could not resolve config path to ' . $base_dir + . DIRECTORY_SEPARATOR . (string)$directory['name'] . ':' . $glob_index + ); + } + + if ($ignore_type_stats && $filter instanceof ProjectFileFilter) { + $filter->ignore_type_stats[$directory_path] = true; + } + + if ($declare_strict_types && $filter instanceof ProjectFileFilter) { + $filter->declare_strict_types[$directory_path] = true; + } + + $filter->addDirectory($directory_path); + } + continue; + } + + $directory_path = realpath($prospective_directory_path); + + if (!$directory_path) { + if ($allow_missing_files) { + continue; + } + + throw new ConfigException( + 'Could not resolve config path to ' . $base_dir + . DIRECTORY_SEPARATOR . (string)$directory['name'] + ); + } + + if (!is_dir($directory_path)) { + throw new ConfigException( + $base_dir . DIRECTORY_SEPARATOR . (string)$directory['name'] + . ' is not a directory' + ); + } + + /** @var \RecursiveDirectoryIterator */ + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory_path)); + $iterator->rewind(); + + while ($iterator->valid()) { + if (!$iterator->isDot() && $iterator->isLink()) { + $linked_path = readlink($iterator->getPathname()); + + if (stripos($linked_path, $directory_path) !== 0) { + if ($ignore_type_stats && $filter instanceof ProjectFileFilter) { + $filter->ignore_type_stats[$directory_path] = true; + } + + if ($declare_strict_types && $filter instanceof ProjectFileFilter) { + $filter->declare_strict_types[$directory_path] = true; + } + + if (is_dir($linked_path)) { + $filter->addDirectory($linked_path); + } + } + } + + $iterator->next(); + } + + if ($ignore_type_stats && $filter instanceof ProjectFileFilter) { + $filter->ignore_type_stats[$directory_path] = true; + } + + if ($declare_strict_types && $filter instanceof ProjectFileFilter) { + $filter->declare_strict_types[$directory_path] = true; + } + + $filter->addDirectory($directory_path); + } + } + + if ($e->file) { + /** @var \SimpleXMLElement $file */ + foreach ($e->file as $file) { + $file_path = (string) $file['name']; + + if ($file_path[0] === '/' && DIRECTORY_SEPARATOR === '/') { + $prospective_file_path = $file_path; + } else { + $prospective_file_path = $base_dir . DIRECTORY_SEPARATOR . $file_path; + } + + if (strpos($prospective_file_path, '*') !== false) { + $globs = array_map( + 'realpath', + array_filter( + glob($prospective_file_path, GLOB_NOSORT), + 'file_exists' + ) + ); + + if (empty($globs)) { + throw new ConfigException( + 'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR . + (string)$file['name'] + ); + } + + foreach ($globs as $glob_index => $file_path) { + if (!$file_path) { + throw new ConfigException( + 'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR . + (string)$file['name'] . ':' . $glob_index + ); + } + $filter->addFile($file_path); + } + continue; + } + + $file_path = realpath($prospective_file_path); + + if (!$file_path && !$allow_missing_files) { + throw new ConfigException( + 'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR . + (string)$file['name'] + ); + } + + $filter->addFile($file_path); + } + } + + if ($e->referencedClass) { + /** @var \SimpleXMLElement $referenced_class */ + foreach ($e->referencedClass as $referenced_class) { + $class_name = strtolower((string)$referenced_class['name']); + + if (strpos($class_name, '*') !== false) { + $regex = '/' . \str_replace('*', '.*', str_replace('\\', '\\\\', $class_name)) . '/i'; + $filter->fq_classlike_patterns[] = $regex; + } else { + $filter->fq_classlike_names[] = $class_name; + } + } + } + + if ($e->referencedMethod) { + /** @var \SimpleXMLElement $referenced_method */ + foreach ($e->referencedMethod as $referenced_method) { + $method_id = (string)$referenced_method['name']; + + if (!preg_match('/^[^:]+::[^:]+$/', $method_id) && !static::isRegularExpression($method_id)) { + throw new ConfigException( + 'Invalid referencedMethod ' . $method_id + ); + } + + $filter->method_ids[] = strtolower($method_id); + } + } + + if ($e->referencedFunction) { + /** @var \SimpleXMLElement $referenced_function */ + foreach ($e->referencedFunction as $referenced_function) { + $filter->method_ids[] = strtolower((string)$referenced_function['name']); + } + } + + if ($e->referencedProperty) { + /** @var \SimpleXMLElement $referenced_property */ + foreach ($e->referencedProperty as $referenced_property) { + $filter->property_ids[] = strtolower((string)$referenced_property['name']); + } + } + + if ($e->referencedVariable) { + /** @var \SimpleXMLElement $referenced_variable */ + foreach ($e->referencedVariable as $referenced_variable) { + $filter->var_names[] = strtolower((string)$referenced_variable['name']); + } + } + + return $filter; + } + + private static function isRegularExpression(string $string) : bool + { + set_error_handler( + function () : bool { + return false; + }, + E_WARNING + ); + $is_regexp = preg_match($string, '') !== false; + restore_error_handler(); + + return $is_regexp; + } + + /** + * @psalm-pure + */ + protected static function slashify(string $str): string + { + return preg_replace('/\/?$/', DIRECTORY_SEPARATOR, $str); + } + + public function allows(string $file_name, bool $case_sensitive = false): bool + { + if ($this->inclusive) { + foreach ($this->directories as $include_dir) { + if ($case_sensitive) { + if (strpos($file_name, $include_dir) === 0) { + return true; + } + } else { + if (stripos($file_name, $include_dir) === 0) { + return true; + } + } + } + + if ($case_sensitive) { + if (in_array($file_name, $this->files, true)) { + return true; + } + } else { + if (in_array(strtolower($file_name), $this->files_lowercase, true)) { + return true; + } + } + + return false; + } + + // exclusive + foreach ($this->directories as $exclude_dir) { + if ($case_sensitive) { + if (strpos($file_name, $exclude_dir) === 0) { + return false; + } + } else { + if (stripos($file_name, $exclude_dir) === 0) { + return false; + } + } + } + + if ($case_sensitive) { + if (in_array($file_name, $this->files, true)) { + return false; + } + } else { + if (in_array(strtolower($file_name), $this->files_lowercase, true)) { + return false; + } + } + + return true; + } + + public function allowsClass(string $fq_classlike_name): bool + { + if ($this->fq_classlike_patterns) { + foreach ($this->fq_classlike_patterns as $pattern) { + if (preg_match($pattern, $fq_classlike_name)) { + return true; + } + } + } + + return in_array(strtolower($fq_classlike_name), $this->fq_classlike_names, true); + } + + public function allowsMethod(string $method_id): bool + { + if (!$this->method_ids) { + return false; + } + + if (preg_match('/^[^:]+::[^:]+$/', $method_id)) { + $method_stub = '*::' . explode('::', $method_id)[1]; + + foreach ($this->method_ids as $config_method_id) { + if ($config_method_id === $method_id) { + return true; + } + + if ($config_method_id === $method_stub) { + return true; + } + + if ($config_method_id[0] === '/' && preg_match($config_method_id, $method_id)) { + return true; + } + } + + return false; + } + + return in_array($method_id, $this->method_ids, true); + } + + public function allowsProperty(string $property_id): bool + { + return in_array(strtolower($property_id), $this->property_ids, true); + } + + public function allowsVariable(string $var_name): bool + { + return in_array(strtolower($var_name), $this->var_names, true); + } + + /** + * @return array + */ + public function getDirectories(): array + { + return $this->directories; + } + + /** + * @return array + */ + public function getFiles(): array + { + return $this->files; + } + + public function addFile(string $file_name): void + { + $this->files[] = $file_name; + $this->files_lowercase[] = strtolower($file_name); + } + + public function addDirectory(string $dir_name): void + { + $this->directories[] = self::slashify($dir_name); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Config/IssueHandler.php b/lib/composer/vimeo/psalm/src/Psalm/Config/IssueHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..72704b451b758f3d6e7026d7fd2db4233af30162 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Config/IssueHandler.php @@ -0,0 +1,164 @@ + + */ + private $custom_levels = []; + + public static function loadFromXMLElement(SimpleXMLElement $e, string $base_dir): IssueHandler + { + $handler = new self(); + + if (isset($e['errorLevel'])) { + $handler->error_level = (string) $e['errorLevel']; + + if (!in_array($handler->error_level, \Psalm\Config::$ERROR_LEVELS, true)) { + throw new \Psalm\Exception\ConfigException('Unexpected error level ' . $handler->error_level); + } + } + + /** @var \SimpleXMLElement $error_level */ + foreach ($e->errorLevel as $error_level) { + $handler->custom_levels[] = ErrorLevelFileFilter::loadFromXMLElement($error_level, $base_dir, true); + } + + return $handler; + } + + public function setErrorLevel(string $error_level): void + { + if (!in_array($error_level, \Psalm\Config::$ERROR_LEVELS, true)) { + throw new \Psalm\Exception\ConfigException('Unexpected error level ' . $error_level); + } + + $this->error_level = $error_level; + } + + public function getReportingLevelForFile(string $file_path): string + { + foreach ($this->custom_levels as $custom_level) { + if ($custom_level->allows($file_path)) { + return $custom_level->getErrorLevel(); + } + } + + return $this->error_level; + } + + public function getReportingLevelForClass(string $fq_classlike_name): ?string + { + foreach ($this->custom_levels as $custom_level) { + if ($custom_level->allowsClass($fq_classlike_name)) { + return $custom_level->getErrorLevel(); + } + } + + return null; + } + + public function getReportingLevelForMethod(string $method_id): ?string + { + foreach ($this->custom_levels as $custom_level) { + if ($custom_level->allowsMethod(strtolower($method_id))) { + return $custom_level->getErrorLevel(); + } + } + + return null; + } + + public function getReportingLevelForFunction(string $function_id): ?string + { + foreach ($this->custom_levels as $custom_level) { + if ($custom_level->allowsMethod(strtolower($function_id))) { + return $custom_level->getErrorLevel(); + } + } + + return null; + } + + public function getReportingLevelForArgument(string $function_id): ?string + { + foreach ($this->custom_levels as $custom_level) { + if ($custom_level->allowsMethod(strtolower($function_id))) { + return $custom_level->getErrorLevel(); + } + } + + return null; + } + + public function getReportingLevelForProperty(string $property_id): ?string + { + foreach ($this->custom_levels as $custom_level) { + if ($custom_level->allowsProperty($property_id)) { + return $custom_level->getErrorLevel(); + } + } + + return null; + } + + public function getReportingLevelForVariable(string $var_name): ?string + { + foreach ($this->custom_levels as $custom_level) { + if ($custom_level->allowsVariable($var_name)) { + return $custom_level->getErrorLevel(); + } + } + + return null; + } + + /** + * @return array + */ + public static function getAllIssueTypes(): array + { + return array_filter( + array_map( + /** + * @param string $file_name + * + * @return string + */ + function ($file_name) { + return substr($file_name, 0, -4); + }, + scandir(dirname(__DIR__) . '/Issue', SCANDIR_SORT_NONE) + ), + function (string $issue_name): bool { + return $issue_name !== '' + && $issue_name !== 'MethodIssue' + && $issue_name !== 'PropertyIssue' + && $issue_name !== 'FunctionIssue' + && $issue_name !== 'ArgumentIssue' + && $issue_name !== 'VariableIssue' + && $issue_name !== 'ClassIssue' + && $issue_name !== 'CodeIssue' + && $issue_name !== 'PsalmInternalError' + && $issue_name !== 'ParseError' + && $issue_name !== 'PluginIssue'; + } + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Config/ProjectFileFilter.php b/lib/composer/vimeo/psalm/src/Psalm/Config/ProjectFileFilter.php new file mode 100644 index 0000000000000000000000000000000000000000..d8e036fdf27932e20cbd0fe25a96bd21ee5e6c01 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Config/ProjectFileFilter.php @@ -0,0 +1,92 @@ +ignoreFiles)) { + if (!$inclusive) { + throw new \Psalm\Exception\ConfigException('Cannot nest ignoreFiles inside itself'); + } + + /** @var \SimpleXMLElement $e->ignoreFiles */ + $filter->file_filter = static::loadFromXMLElement($e->ignoreFiles, $base_dir, false); + } + + return $filter; + } + + public function allows(string $file_name, bool $case_sensitive = false): bool + { + if ($this->inclusive && $this->file_filter) { + if (!$this->file_filter->allows($file_name, $case_sensitive)) { + return false; + } + } + + return parent::allows($file_name, $case_sensitive); + } + + public function forbids(string $file_name, bool $case_sensitive = false): bool + { + if ($this->inclusive && $this->file_filter) { + if (!$this->file_filter->allows($file_name, $case_sensitive)) { + return true; + } + } + + return false; + } + + public function reportTypeStats(string $file_name, bool $case_sensitive = false): bool + { + foreach ($this->ignore_type_stats as $exclude_dir => $_) { + if ($case_sensitive) { + if (strpos($file_name, $exclude_dir) === 0) { + return false; + } + } else { + if (stripos($file_name, $exclude_dir) === 0) { + return false; + } + } + } + + return true; + } + + public function useStrictTypes(string $file_name, bool $case_sensitive = false): bool + { + foreach ($this->declare_strict_types as $exclude_dir => $_) { + if ($case_sensitive) { + if (strpos($file_name, $exclude_dir) === 0) { + return true; + } + } else { + if (stripos($file_name, $exclude_dir) === 0) { + return true; + } + } + } + + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Config/TaintAnalysisFileFilter.php b/lib/composer/vimeo/psalm/src/Psalm/Config/TaintAnalysisFileFilter.php new file mode 100644 index 0000000000000000000000000000000000000000..cfa3c8893d2a57c3e8f52972647c164016ad42bc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Config/TaintAnalysisFileFilter.php @@ -0,0 +1,6 @@ + + */ + public $vars_in_scope = []; + + /** + * @var array + */ + public $vars_possibly_in_scope = []; + + /** + * Whether or not we're inside the conditional of an if/where etc. + * + * This changes whether or not the context is cloned + * + * @var bool + */ + public $inside_conditional = false; + + /** + * Whether or not we're inside a __construct function + * + * @var bool + */ + public $inside_constructor = false; + + /** + * Whether or not we're inside an isset call + * + * Inside issets Psalm is more lenient about certain things + * + * @var bool + */ + public $inside_isset = false; + + /** + * Whether or not we're inside an unset call, where + * we don't care about possibly undefined variables + * + * @var bool + */ + public $inside_unset = false; + + /** + * Whether or not we're inside an class_exists call, where + * we don't care about possibly undefined classes + * + * @var bool + */ + public $inside_class_exists = false; + + /** + * Whether or not we're inside a function/method call + * + * @var bool + */ + public $inside_call = false; + + /** + * Whether or not we're inside any other situation that treats a variable as used + * + * @var bool + */ + public $inside_use = false; + + /** + * Whether or not we're inside a throw + * + * @var bool + */ + public $inside_throw = false; + + /** + * Whether or not we're inside an assignment + * + * @var bool + */ + public $inside_assignment = false; + + /** + * @var null|CodeLocation + */ + public $include_location = null; + + /** + * @var string|null + */ + public $self; + + /** + * @var string|null + */ + public $parent; + + /** + * @var bool + */ + public $check_classes = true; + + /** + * @var bool + */ + public $check_variables = true; + + /** + * @var bool + */ + public $check_methods = true; + + /** + * @var bool + */ + public $check_consts = true; + + /** + * @var bool + */ + public $check_functions = true; + + /** + * A list of classes checked with class_exists + * + * @var array + */ + public $phantom_classes = []; + + /** + * A list of files checked with file_exists + * + * @var array + */ + public $phantom_files = []; + + /** + * A list of clauses in Conjunctive Normal Form + * + * @var list + */ + public $clauses = []; + + /** + * A list of hashed clauses that have already been factored in + * + * @var list + */ + public $reconciled_expression_clauses = []; + + /** + * Whether or not to do a deep analysis and collect mutations to this context + * + * @var bool + */ + public $collect_mutations = false; + + /** + * Whether or not to do a deep analysis and collect initializations from private or final methods + * + * @var bool + */ + public $collect_initializations = false; + + /** + * Whether or not to do a deep analysis and collect initializations from public non-final methods + * + * @var bool + */ + public $collect_nonprivate_initializations = false; + + /** + * Stored to prevent re-analysing methods when checking for initialised properties + * + * @var array|null + */ + public $initialized_methods = null; + + /** + * @var array + */ + public $constants = []; + + /** + * Whether or not to track exceptions + * + * @var bool + */ + public $collect_exceptions = false; + + /** + * A list of variables that have been referenced + * + * @var array + */ + public $referenced_var_ids = []; + + /** + * A list of variables that have been passed by reference (where we know their type) + * + * @var array + */ + public $byref_constraints = []; + + /** + * If this context inherits from a context, it is here + * + * @var Context|null + */ + public $parent_context; + + /** + * @var array + */ + public $possible_param_types = []; + + /** + * A list of vars that have been assigned to + * + * @var array + */ + public $assigned_var_ids = []; + + /** + * A list of vars that have been may have been assigned to + * + * @var array + */ + public $possibly_assigned_var_ids = []; + + /** + * A list of classes or interfaces that may have been thrown + * + * @var array> + */ + public $possibly_thrown_exceptions = []; + + /** + * @var bool + */ + public $is_global = false; + + /** + * @var array + */ + public $protected_var_ids = []; + + /** + * If we've branched from the main scope, a byte offset for where that branch happened + * + * @var int|null + */ + public $branch_point; + + /** + * What does break mean in this context? + * + * 'loop' means we're breaking out of a loop, + * 'switch' means we're breaking out of a switch + * + * @var list<'loop'|'switch'> + */ + public $break_types = []; + + /** + * @var bool + */ + public $inside_loop = false; + + /** + * @var Internal\Scope\LoopScope|null + */ + public $loop_scope = null; + + /** + * @var Internal\Scope\CaseScope|null + */ + public $case_scope = null; + + /** + * @var Internal\Scope\FinallyScope|null + */ + public $finally_scope = null; + + /** + * @var Context|null + */ + public $if_context = null; + + /** + * @var \Psalm\Internal\Scope\IfScope|null + */ + public $if_scope = null; + + /** + * @var bool + */ + public $strict_types = false; + + /** + * @var string|null + */ + public $calling_function_id; + + /** + * @var lowercase-string|null + */ + public $calling_method_id; + + /** + * @var bool + */ + public $inside_negation = false; + + /** + * @var bool + */ + public $ignore_variable_property = false; + + /** + * @var bool + */ + public $ignore_variable_method = false; + + /** + * @var bool + */ + public $pure = false; + + /** + * @var bool + */ + public $mutation_free = false; + + /** + * @var bool + */ + public $external_mutation_free = false; + + /** + * @var bool + */ + public $error_suppressing = false; + + /** + * @var bool + */ + public $has_returned = false; + + public function __construct(?string $self = null) + { + $this->self = $self; + } + + public function __destruct() + { + $this->case_scope = null; + $this->parent_context = null; + } + + public function __clone() + { + foreach ($this->clauses as &$clause) { + $clause = clone $clause; + } + + foreach ($this->constants as &$constant) { + $constant = clone $constant; + } + } + + /** + * Updates the parent context, looking at the changes within a block and then applying those changes, where + * necessary, to the parent context + * + * @param bool $has_leaving_statements whether or not the parent scope is abandoned between + * $start_context and $end_context + * @param array $updated_vars + * + */ + public function update( + Context $start_context, + Context $end_context, + bool $has_leaving_statements, + array $vars_to_update, + array &$updated_vars + ): void { + foreach ($start_context->vars_in_scope as $var_id => $old_type) { + // this is only true if there was some sort of type negation + if (in_array($var_id, $vars_to_update, true)) { + // if we're leaving, we're effectively deleting the possibility of the if types + $new_type = !$has_leaving_statements && $end_context->hasVariable($var_id) + ? $end_context->vars_in_scope[$var_id] + : null; + + $existing_type = isset($this->vars_in_scope[$var_id]) ? $this->vars_in_scope[$var_id] : null; + + if (!$existing_type) { + if ($new_type) { + $this->vars_in_scope[$var_id] = clone $new_type; + $updated_vars[$var_id] = true; + } + + continue; + } + + $existing_type = clone $existing_type; + + // if the type changed within the block of statements, process the replacement + // also never allow ourselves to remove all types from a union + if ((!$new_type || !$old_type->equals($new_type)) + && ($new_type || count($existing_type->getAtomicTypes()) > 1) + ) { + $existing_type->substitute($old_type, $new_type); + + if ($new_type && $new_type->from_docblock) { + $existing_type->setFromDocblock(); + } + + $updated_vars[$var_id] = true; + } + + $this->vars_in_scope[$var_id] = $existing_type; + } + } + } + + /** + * @param array $new_vars_in_scope + * + * @return array + */ + public function getRedefinedVars(array $new_vars_in_scope, bool $include_new_vars = false): array + { + $redefined_vars = []; + + foreach ($this->vars_in_scope as $var_id => $this_type) { + if (!isset($new_vars_in_scope[$var_id])) { + if ($include_new_vars) { + $redefined_vars[$var_id] = $this_type; + } + continue; + } + + $new_type = $new_vars_in_scope[$var_id]; + + if (!$this_type->isEmpty() + && !$new_type->isEmpty() + && !$this_type->equals($new_type) + ) { + $redefined_vars[$var_id] = $this_type; + } + } + + return $redefined_vars; + } + + /** + * @return list + */ + public static function getNewOrUpdatedVarIds(Context $original_context, Context $new_context): array + { + $redefined_var_ids = []; + + foreach ($new_context->vars_in_scope as $var_id => $context_type) { + if (!isset($original_context->vars_in_scope[$var_id]) + || !$original_context->vars_in_scope[$var_id]->equals($context_type) + ) { + $redefined_var_ids[] = $var_id; + } + } + + return $redefined_var_ids; + } + + public function remove(string $remove_var_id): void + { + unset( + $this->referenced_var_ids[$remove_var_id], + $this->vars_possibly_in_scope[$remove_var_id] + ); + + if (isset($this->vars_in_scope[$remove_var_id])) { + $existing_type = $this->vars_in_scope[$remove_var_id]; + unset($this->vars_in_scope[$remove_var_id]); + + $this->removeDescendents($remove_var_id, $existing_type); + } + } + + /** + * @param Clause[] $clauses + * @param array $changed_var_ids + * + * @return array{0: list, list} + * + * @psalm-pure + */ + public static function removeReconciledClauses(array $clauses, array $changed_var_ids): array + { + $included_clauses = []; + $rejected_clauses = []; + + foreach ($clauses as $c) { + if ($c->wedge) { + $included_clauses[] = $c; + continue; + } + + foreach ($c->possibilities as $key => $_) { + if (isset($changed_var_ids[$key])) { + $rejected_clauses[] = $c; + continue 2; + } + } + + $included_clauses[] = $c; + } + + return [$included_clauses, $rejected_clauses]; + } + + /** + * @param Clause[] $clauses + * + * @return list + */ + public static function filterClauses( + string $remove_var_id, + array $clauses, + ?Union $new_type = null, + ?StatementsAnalyzer $statements_analyzer = null + ): array { + $new_type_string = $new_type ? $new_type->getId() : ''; + + $clauses_to_keep = []; + + foreach ($clauses as $clause) { + $clause = $clause->calculateNegation(); + + $quoted_remove_var_id = preg_quote($remove_var_id, '/'); + + foreach ($clause->possibilities as $var_id => $_) { + if (preg_match('/' . $quoted_remove_var_id . '[\]\[\-]/', $var_id)) { + break 2; + } + } + + if (!isset($clause->possibilities[$remove_var_id]) || + $clause->possibilities[$remove_var_id] === [$new_type_string] + ) { + $clauses_to_keep[] = $clause; + } elseif ($statements_analyzer && + $new_type && + !$new_type->hasMixed() + ) { + $type_changed = false; + + // if the clause contains any possibilities that would be altered + // by the new type + foreach ($clause->possibilities[$remove_var_id] as $type) { + // if we're negating a type, we generally don't need the clause anymore + if ($type[0] === '!' && $type !== '!falsy' && $type !== '!empty') { + $type_changed = true; + break; + } + + // empty and !empty are not definitive for arrays and scalar types + if (($type === '!falsy' || $type === 'falsy') && + ($new_type->hasArray() || $new_type->hasPossiblyNumericType()) + ) { + $type_changed = true; + break; + } + + $result_type = AssertionReconciler::reconcile( + $type, + clone $new_type, + null, + $statements_analyzer, + false, + [], + null, + [], + $failed_reconciliation + ); + + if ($result_type->getId() !== $new_type_string) { + $type_changed = true; + break; + } + } + + if (!$type_changed) { + $clauses_to_keep[] = $clause; + } + } + } + + return $clauses_to_keep; + } + + public function removeVarFromConflictingClauses( + string $remove_var_id, + ?Union $new_type = null, + ?StatementsAnalyzer $statements_analyzer = null + ): void { + $this->clauses = self::filterClauses($remove_var_id, $this->clauses, $new_type, $statements_analyzer); + + if ($this->parent_context) { + $this->parent_context->removeVarFromConflictingClauses($remove_var_id); + } + } + + public function removeDescendents( + string $remove_var_id, + ?Union $existing_type = null, + ?Union $new_type = null, + ?StatementsAnalyzer $statements_analyzer = null + ): void { + if (!$existing_type && isset($this->vars_in_scope[$remove_var_id])) { + $existing_type = $this->vars_in_scope[$remove_var_id]; + } + + if (!$existing_type) { + return; + } + + $this->removeVarFromConflictingClauses( + $remove_var_id, + $existing_type->hasMixed() + || ($new_type && $existing_type->from_docblock !== $new_type->from_docblock) + ? null + : $new_type, + $statements_analyzer + ); + + foreach ($this->vars_in_scope as $var_id => $_) { + if (preg_match('/' . preg_quote($remove_var_id, '/') . '[\]\[\-]/', $var_id)) { + unset($this->vars_in_scope[$var_id]); + } + } + } + + public function removeAllObjectVars(): void + { + $vars_to_remove = []; + + foreach ($this->vars_in_scope as $var_id => $_) { + if (strpos($var_id, '->') !== false || strpos($var_id, '::') !== false) { + $vars_to_remove[] = $var_id; + } + } + + if (!$vars_to_remove) { + return; + } + + foreach ($vars_to_remove as $var_id) { + unset($this->vars_in_scope[$var_id], $this->vars_possibly_in_scope[$var_id]); + } + + $clauses_to_keep = []; + + foreach ($this->clauses as $clause) { + $abandon_clause = false; + + foreach (array_keys($clause->possibilities) as $key) { + if (strpos($key, '->') !== false || strpos($key, '::') !== false) { + $abandon_clause = true; + break; + } + } + + if (!$abandon_clause) { + $clauses_to_keep[] = $clause; + } + } + + $this->clauses = $clauses_to_keep; + } + + public function updateChecks(Context $op_context): void + { + $this->check_classes = $this->check_classes && $op_context->check_classes; + $this->check_variables = $this->check_variables && $op_context->check_variables; + $this->check_methods = $this->check_methods && $op_context->check_methods; + $this->check_functions = $this->check_functions && $op_context->check_functions; + $this->check_consts = $this->check_consts && $op_context->check_consts; + } + + public function isPhantomClass(string $class_name): bool + { + return isset($this->phantom_classes[strtolower($class_name)]); + } + + public function hasVariable(?string $var_name): bool + { + if (!$var_name) { + return false; + } + + $stripped_var = preg_replace('/(->|\[).*$/', '', $var_name); + + if ($stripped_var !== '$this' || $var_name !== $stripped_var) { + $this->referenced_var_ids[$var_name] = true; + + if (!isset($this->vars_possibly_in_scope[$var_name]) + && !isset($this->vars_in_scope[$var_name]) + ) { + return false; + } + } + + return isset($this->vars_in_scope[$var_name]); + } + + public function getScopeSummary() : string + { + $summary = []; + foreach ($this->vars_possibly_in_scope as $k => $_) { + $summary[$k] = true; + } + foreach ($this->vars_in_scope as $k => $v) { + $summary[$k] = $v->getId(); + } + + return json_encode($summary); + } + + public function defineGlobals(): void + { + $globals = [ + '$argv' => new Type\Union([ + new Type\Atomic\TArray([Type::getInt(), Type::getString()]), + ]), + '$argc' => Type::getInt(), + ]; + + $config = Config::getInstance(); + + foreach ($config->globals as $global_id => $type_string) { + $globals[$global_id] = Type::parseString($type_string); + } + + foreach ($globals as $global_id => $type) { + $this->vars_in_scope[$global_id] = $type; + $this->vars_possibly_in_scope[$global_id] = true; + } + } + + public function mergeExceptions(Context $other_context): void + { + foreach ($other_context->possibly_thrown_exceptions as $possibly_thrown_exception => $codelocations) { + foreach ($codelocations as $hash => $codelocation) { + $this->possibly_thrown_exceptions[$possibly_thrown_exception][$hash] = $codelocation; + } + } + } + + public function isSuppressingExceptions(StatementsAnalyzer $statements_analyzer): bool + { + if (!$this->collect_exceptions) { + return true; + } + + $issue_type = $this->is_global ? 'UncaughtThrowInGlobalScope' : 'MissingThrowsDocblock'; + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + $suppressed_issue_position = array_search($issue_type, $suppressed_issues, true); + if ($suppressed_issue_position !== false) { + if (is_int($suppressed_issue_position)) { + $file = $statements_analyzer->getFileAnalyzer()->getFilePath(); + IssueBuffer::addUsedSuppressions([ + $file => [$suppressed_issue_position => true], + ]); + } + return true; + } + + return false; + } + + public function mergeFunctionExceptions( + FunctionLikeStorage $function_storage, + CodeLocation $codelocation + ): void { + $hash = $codelocation->getHash(); + foreach ($function_storage->throws as $possibly_thrown_exception => $_) { + $this->possibly_thrown_exceptions[$possibly_thrown_exception][$hash] = $codelocation; + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/DocComment.php b/lib/composer/vimeo/psalm/src/Psalm/DocComment.php new file mode 100644 index 0000000000000000000000000000000000000000..f6c0d5efc7a66488e6ab117e9310a1512301a1c2 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/DocComment.php @@ -0,0 +1,237 @@ +>} + * @psalm-suppress PossiblyUnusedMethod + * + * @deprecated use parsePreservingLength instead + * + * @psalm-pure + */ + public static function parse(string $docblock, ?int $line_number = null, bool $preserve_format = false): array + { + // Strip off comments. + $docblock = trim($docblock); + $docblock = preg_replace('@^/\*\*@', '', $docblock); + $docblock = preg_replace('@\*/$@', '', $docblock); + $docblock = preg_replace('@^[ \t]*\*@m', '', $docblock); + + // Normalize multi-line @specials. + $lines = explode("\n", $docblock); + + $line_map = []; + + $last = false; + foreach ($lines as $k => $line) { + if (preg_match('/^\s?@\w/i', $line)) { + $last = $k; + } elseif (preg_match('/^\s*$/', $line)) { + $last = false; + } elseif ($last !== false) { + $old_last_line = $lines[$last]; + $lines[$last] = rtrim($old_last_line) + . ($preserve_format || trim($old_last_line) === '@return' ? "\n" . $line : ' ' . trim($line)); + + if ($line_number) { + $old_line_number = $line_map[$old_last_line]; + unset($line_map[$old_last_line]); + $line_map[$lines[$last]] = $old_line_number; + } + + unset($lines[$k]); + } + + if ($line_number) { + $line_map[$line] = $line_number++; + } + } + + $special = []; + + if ($preserve_format) { + foreach ($lines as $m => $line) { + if (preg_match('/^\s?@([\w\-:]+)[\t ]*(.*)$/sm', $line, $matches)) { + [$full_match, $type, $data] = $matches; + + $docblock = str_replace($full_match, '', $docblock); + + if (empty($special[$type])) { + $special[$type] = []; + } + + $line_number = $line_map && isset($line_map[$full_match]) ? $line_map[$full_match] : (int)$m; + + $special[$type][$line_number] = rtrim($data); + } + } + } else { + $docblock = implode("\n", $lines); + + // Parse @specials. + if (preg_match_all('/^\s?@([\w\-:]+)[\t ]*([^\n]*)/m', $docblock, $matches, PREG_SET_ORDER)) { + $docblock = preg_replace('/^\s?@([\w\-:]+)\s*([^\n]*)/m', '', $docblock); + foreach ($matches as $m => $match) { + [$_, $type, $data] = $match; + + if (empty($special[$type])) { + $special[$type] = []; + } + + $line_number = $line_map && isset($line_map[$_]) ? $line_map[$_] : (int)$m; + + $special[$type][$line_number] = $data; + } + } + } + + $docblock = str_replace("\t", ' ', $docblock); + + // Smush the whole docblock to the left edge. + $min_indent = 80; + $indent = 0; + foreach (array_filter(explode("\n", $docblock)) as $line) { + for ($ii = 0, $iiMax = strlen($line); $ii < $iiMax; ++$ii) { + if ($line[$ii] !== ' ') { + break; + } + ++$indent; + } + + $min_indent = min($indent, $min_indent); + } + + $docblock = preg_replace('/^' . str_repeat(' ', $min_indent) . '/m', '', $docblock); + $docblock = rtrim($docblock); + + // Trim any empty lines off the front, but leave the indent level if there + // is one. + $docblock = preg_replace('/^\s*\n/', '', $docblock); + + foreach ($special as $special_key => $_) { + if (substr($special_key, 0, 6) === 'psalm-') { + $special_key = substr($special_key, 6); + + if (!in_array( + $special_key, + self::PSALM_ANNOTATIONS, + true + )) { + throw new DocblockParseException('Unrecognised annotation @psalm-' . $special_key); + } + } + } + + return [ + 'description' => $docblock, + 'specials' => $special, + ]; + } + + /** + * Parse a docblock comment into its parts. + * + * @param bool $preserve_format + */ + public static function parsePreservingLength(\PhpParser\Comment\Doc $docblock) : ParsedDocblock + { + $parsed_docblock = \Psalm\Internal\Scanner\DocblockParser::parse($docblock->getText()); + + foreach ($parsed_docblock->tags as $special_key => $_) { + if (substr($special_key, 0, 6) === 'psalm-') { + $special_key = substr($special_key, 6); + + if (!in_array( + $special_key, + self::PSALM_ANNOTATIONS, + true + )) { + throw new DocblockParseException('Unrecognised annotation @psalm-' . $special_key); + } + } + } + + return $parsed_docblock; + } + + /** + * @psalm-pure + * @return array + */ + public static function parseSuppressList(string $suppress_entry): array + { + preg_match( + '/ + (?(DEFINE) + # either a single issue or comma separated list of issues + (? (?&issue) \s* , \s* (?&issue_list) | (?&issue) ) + + # definition of a single issue + (? [A-Za-z0-9_-]+ ) + ) + ^ (?P (?&issue_list) ) (?P .* ) $ + /xm', + $suppress_entry, + $matches + ); + + if (!isset($matches['issues'])) { + return []; + } + + $issue_offset = 0; + $ret = []; + + foreach (explode(',', $matches['issues']) as $suppressed_issue) { + $issue_offset += strspn($suppressed_issue, "\t\n\f\r "); + $ret[$issue_offset] = trim($suppressed_issue); + $issue_offset += strlen($suppressed_issue) + 1; + } + + return $ret; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/ErrorBaseline.php b/lib/composer/vimeo/psalm/src/Psalm/ErrorBaseline.php new file mode 100644 index 0000000000000000000000000000000000000000..b128c1667d65c9bd4fb7b8eaf98a1fd8d76cab5e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/ErrorBaseline.php @@ -0,0 +1,315 @@ +}>> $existingIssues + * + * + * @psalm-pure + */ + public static function countTotalIssues(array $existingIssues): int + { + $totalIssues = 0; + + foreach ($existingIssues as $existingIssue) { + $totalIssues += array_reduce( + $existingIssue, + /** + * @param array{o:int, s:array} $existingIssue + */ + function (int $carry, array $existingIssue): int { + return $carry + $existingIssue['o']; + }, + 0 + ); + } + + return $totalIssues; + } + + /** + * @param array> $issues + * + */ + public static function create( + FileProvider $fileProvider, + string $baselineFile, + array $issues, + bool $include_php_versions + ): void { + $groupedIssues = self::countIssueTypesByFile($issues); + + self::writeToFile($fileProvider, $baselineFile, $groupedIssues, $include_php_versions); + } + + /** + * @return array}>> + * + * @throws Exception\ConfigException + */ + public static function read(FileProvider $fileProvider, string $baselineFile): array + { + if (!$fileProvider->fileExists($baselineFile)) { + throw new Exception\ConfigException("{$baselineFile} does not exist or is not readable"); + } + + $xmlSource = $fileProvider->getContents($baselineFile); + + $baselineDoc = new \DOMDocument(); + $baselineDoc->loadXML($xmlSource, LIBXML_NOBLANKS); + + $filesElement = $baselineDoc->getElementsByTagName('files'); + + if ($filesElement->length === 0) { + throw new Exception\ConfigException('Baseline file does not contain '); + } + + $files = []; + + /** @var \DOMElement $filesElement */ + $filesElement = $filesElement[0]; + + foreach ($filesElement->getElementsByTagName('file') as $file) { + $fileName = $file->getAttribute('src'); + + $fileName = str_replace('\\', '/', $fileName); + + $files[$fileName] = []; + + foreach ($file->childNodes as $issue) { + if (!$issue instanceof \DOMElement) { + continue; + } + + $issueType = $issue->tagName; + + $files[$fileName][$issueType] = [ + 'o' => (int)$issue->getAttribute('occurrences'), + 's' => [], + ]; + $codeSamples = $issue->getElementsByTagName('code'); + + foreach ($codeSamples as $codeSample) { + $files[$fileName][$issueType]['s'][] = $codeSample->textContent; + } + } + } + + return $files; + } + + /** + * @param array> $issues + * + * @return array}>> + * + * @throws Exception\ConfigException + */ + public static function update( + FileProvider $fileProvider, + string $baselineFile, + array $issues, + bool $include_php_versions + ): array { + $existingIssues = self::read($fileProvider, $baselineFile); + $newIssues = self::countIssueTypesByFile($issues); + + foreach ($existingIssues as $file => &$existingIssuesCount) { + if (!isset($newIssues[$file])) { + unset($existingIssues[$file]); + + continue; + } + + foreach ($existingIssuesCount as $issueType => $existingIssueType) { + if (!isset($newIssues[$file][$issueType])) { + unset($existingIssuesCount[$issueType]); + + continue; + } + + $existingIssuesCount[$issueType]['o'] = min( + $existingIssueType['o'], + $newIssues[$file][$issueType]['o'] + ); + $existingIssuesCount[$issueType]['s'] = array_intersect( + $existingIssueType['s'], + $newIssues[$file][$issueType]['s'] + ); + } + } + + $groupedIssues = array_filter($existingIssues); + + self::writeToFile($fileProvider, $baselineFile, $groupedIssues, $include_php_versions); + + return $groupedIssues; + } + + /** + * @param array> $issues + * + * @return array}>> + */ + private static function countIssueTypesByFile(array $issues): array + { + if ($issues === []) { + return []; + } + $groupedIssues = array_reduce( + array_merge(...array_values($issues)), + /** + * @param array}>> $carry + * + * @return array}>> + */ + function (array $carry, IssueData $issue): array { + if ($issue->severity !== Config::REPORT_ERROR) { + return $carry; + } + + $fileName = $issue->file_name; + $fileName = str_replace('\\', '/', $fileName); + $issueType = $issue->type; + + if (!isset($carry[$fileName])) { + $carry[$fileName] = []; + } + + if (!isset($carry[$fileName][$issueType])) { + $carry[$fileName][$issueType] = ['o' => 0, 's' => []]; + } + + ++$carry[$fileName][$issueType]['o']; + + if (!strpos($issue->selected_text, "\n")) { + $carry[$fileName][$issueType]['s'][] = $issue->selected_text; + } + + return $carry; + }, + [] + ); + + // Sort files first + ksort($groupedIssues); + + foreach ($groupedIssues as &$issues) { + ksort($issues); + } + + return $groupedIssues; + } + + /** + * @param array}>> $groupedIssues + * + */ + private static function writeToFile( + FileProvider $fileProvider, + string $baselineFile, + array $groupedIssues, + bool $include_php_versions + ): void { + $baselineDoc = new \DOMDocument('1.0', 'UTF-8'); + $filesNode = $baselineDoc->createElement('files'); + $filesNode->setAttribute('psalm-version', PSALM_VERSION); + + if ($include_php_versions) { + $extensions = array_merge(get_loaded_extensions(), get_loaded_extensions(true)); + + usort($extensions, 'strnatcasecmp'); + + $filesNode->setAttribute('php-version', implode(';' . "\n\t", array_merge( + [ + ('php:' . PHP_VERSION), + ], + array_map( + function (string $extension) : string { + return $extension . ':' . phpversion($extension); + }, + $extensions + ) + ))); + } + + foreach ($groupedIssues as $file => $issueTypes) { + $fileNode = $baselineDoc->createElement('file'); + + $fileNode->setAttribute('src', $file); + + foreach ($issueTypes as $issueType => $existingIssueType) { + $issueNode = $baselineDoc->createElement($issueType); + + $issueNode->setAttribute('occurrences', (string)$existingIssueType['o']); + + \sort($existingIssueType['s']); + + foreach ($existingIssueType['s'] as $selection) { + $codeNode = $baselineDoc->createElement('code'); + + $codeNode->textContent = $selection; + $issueNode->appendChild($codeNode); + } + $fileNode->appendChild($issueNode); + } + + $filesNode->appendChild($fileNode); + } + + $baselineDoc->appendChild($filesNode); + $baselineDoc->formatOutput = true; + + $xml = preg_replace_callback( + '/)\n)/', + /** + * @param array $matches + */ + function (array $matches) : string { + return + 'saveXML() + ); + + if ($xml === null) { + throw new RuntimeException('Failed to reformat opening attributes!'); + } + + $fileProvider->setContents($baselineFile, $xml); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Exception/CircularReferenceException.php b/lib/composer/vimeo/psalm/src/Psalm/Exception/CircularReferenceException.php new file mode 100644 index 0000000000000000000000000000000000000000..991557ee846db6ac8a81f165dcd11e63dd08005d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Exception/CircularReferenceException.php @@ -0,0 +1,6 @@ +path = $path; + $this->config = $config; + $this->codebase = $codebase; + } + + /** + * @psalm-suppress PossiblyUnusedParam + */ + public function __invoke(Plugin\RegistrationInterface $registration, ?SimpleXMLElement $config = null): void + { + $fq_class_name = $this->getPluginClassForPath($this->path); + + /** @psalm-suppress UnresolvableInclude */ + require_once($this->path); + + $registration->registerHooksFromClass($fq_class_name); + } + + private function getPluginClassForPath(string $path): string + { + $codebase = $this->codebase; + + $path = \str_replace(['/', '\\'], \DIRECTORY_SEPARATOR, $path); + + $file_storage = $codebase->createFileStorageForPath($path); + $file_to_scan = new FileScanner($path, $this->config->shortenFileName($path), true); + $file_to_scan->scan( + $codebase, + $file_storage + ); + + $declared_classes = ClassLikeAnalyzer::getClassesForFile($codebase, $path); + + $fq_class_name = reset($declared_classes); + + return $fq_class_name; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/FileManipulation.php b/lib/composer/vimeo/psalm/src/Psalm/FileManipulation.php new file mode 100644 index 0000000000000000000000000000000000000000..891f02dacc0125fafac2e88c082db82141b08fc3 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/FileManipulation.php @@ -0,0 +1,82 @@ +start = $start; + $this->end = $end; + $this->insertion_text = $insertion_text; + $this->preserve_indentation = $preserve_indentation; + $this->remove_trailing_newline = $remove_trailing_newline; + } + + public function getKey() : string + { + return $this->start === $this->end + ? ($this->start . ':' . sha1($this->insertion_text)) + : ($this->start . ':' . $this->end); + } + + public function transform(string $existing_contents) : string + { + if ($this->preserve_indentation) { + $newline_pos = strrpos($existing_contents, "\n", $this->start - strlen($existing_contents)); + + $newline_pos = $newline_pos !== false ? $newline_pos + 1 : 0; + + $indentation = substr($existing_contents, $newline_pos, $this->start - $newline_pos); + + if (trim($indentation) === '') { + $this->insertion_text = $this->insertion_text . $indentation; + } + } + + if ($this->remove_trailing_newline + && strlen($existing_contents) > $this->end + && $existing_contents[$this->end] === "\n" + ) { + $newline_pos = strrpos($existing_contents, "\n", $this->start - strlen($existing_contents)); + + $newline_pos = $newline_pos !== false ? $newline_pos + 1 : 0; + + $indentation = substr($existing_contents, $newline_pos, $this->start - $newline_pos); + + if (trim($indentation) === '') { + $this->start -= strlen($indentation); + $this->end++; + } + } + + return substr($existing_contents, 0, $this->start) + . $this->insertion_text + . substr($existing_contents, $this->end); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/FileSource.php b/lib/composer/vimeo/psalm/src/Psalm/FileSource.php new file mode 100644 index 0000000000000000000000000000000000000000..a29638fc9ce194ca95ef340d883eb62b4125248f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/FileSource.php @@ -0,0 +1,15 @@ + $formula_1 + * @param list $formula_2 + * @param array $new_assigned_var_ids + */ + public static function checkForParadox( + array $formula_1, + array $formula_2, + StatementsAnalyzer $statements_analyzer, + PhpParser\Node $stmt, + array $new_assigned_var_ids + ): void { + try { + $negated_formula2 = Algebra::negateFormula($formula_2); + } catch (\Psalm\Exception\ComplicatedExpressionException $e) { + return; + } + + $formula_1_hashes = []; + + foreach ($formula_1 as $formula_1_clause) { + $formula_1_hashes[$formula_1_clause->hash] = true; + } + + $formula_2_hashes = []; + + foreach ($formula_2 as $formula_2_clause) { + $hash = $formula_2_clause->hash; + + if (!$formula_2_clause->generated + && !$formula_2_clause->wedge + && $formula_2_clause->reconcilable + && (isset($formula_1_hashes[$hash]) || isset($formula_2_hashes[$hash])) + && !array_intersect_key($new_assigned_var_ids, $formula_2_clause->possibilities) + ) { + if (IssueBuffer::accepts( + new RedundantCondition( + $formula_2_clause . ' has already been asserted', + new CodeLocation($statements_analyzer, $stmt), + null + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + foreach ($formula_2_clause->possibilities as $key => $values) { + if (!$formula_2_clause->generated + && count($values) > 1 + && !isset($new_assigned_var_ids[$key]) + && count(array_unique($values)) < count($values) + ) { + if (IssueBuffer::accepts( + new ParadoxicalCondition( + 'Found a redundant condition when evaluating assertion (' . $formula_2_clause . ')', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + $formula_2_hashes[$hash] = true; + } + + // remove impossible types + foreach ($negated_formula2 as $negated_clause_2) { + if (count($negated_formula2) === 1) { + foreach ($negated_clause_2->possibilities as $key => $values) { + if (count($values) > 1 + && !isset($new_assigned_var_ids[$key]) + && count(array_unique($values)) < count($values) + ) { + if (IssueBuffer::accepts( + new RedundantCondition( + 'Found a redundant condition when evaluating ' . $key, + new CodeLocation($statements_analyzer, $stmt), + null + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + if (!$negated_clause_2->reconcilable || $negated_clause_2->wedge) { + continue; + } + + foreach ($formula_1 as $clause_1) { + if ($negated_clause_2 === $clause_1 || !$clause_1->reconcilable || $clause_1->wedge) { + continue; + } + + $negated_clause_2_contains_1_possibilities = true; + + foreach ($clause_1->possibilities as $key => $keyed_possibilities) { + if (!isset($negated_clause_2->possibilities[$key])) { + $negated_clause_2_contains_1_possibilities = false; + break; + } + + if ($negated_clause_2->possibilities[$key] != $keyed_possibilities) { + $negated_clause_2_contains_1_possibilities = false; + break; + } + } + + if ($negated_clause_2_contains_1_possibilities) { + $mini_formula_2 = Algebra::negateFormula([$negated_clause_2]); + + if (!$mini_formula_2[0]->wedge) { + if (count($mini_formula_2) > 1) { + $paradox_message = 'Condition ((' . \implode(') && (', $mini_formula_2) . '))' + . ' contradicts a previously-established condition (' . $clause_1 . ')'; + } else { + $paradox_message = 'Condition (' . $mini_formula_2[0] . ')' + . ' contradicts a previously-established condition (' . $clause_1 . ')'; + } + } else { + $paradox_message = 'Condition not(' . $negated_clause_2 . ')' + . ' contradicts a previously-established condition (' . $clause_1 . ')'; + } + + if (IssueBuffer::accepts( + new ParadoxicalCondition( + $paradox_message, + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return; + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/CanAlias.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/CanAlias.php new file mode 100644 index 0000000000000000000000000000000000000000..530227487afc28c651627521dfc0d409aa604263 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/CanAlias.php @@ -0,0 +1,159 @@ + + */ + private $aliased_classes = []; + + /** + * @var array + */ + private $aliased_class_locations = []; + + /** + * @var array + */ + private $aliased_classes_flipped = []; + + /** + * @var array + */ + private $aliased_classes_flipped_replaceable = []; + + /** + * @var array + */ + private $aliased_functions = []; + + /** + * @var array + */ + private $aliased_constants = []; + + public function visitUse(PhpParser\Node\Stmt\Use_ $stmt): void + { + $codebase = $this->getCodebase(); + + foreach ($stmt->uses as $use) { + $use_path = implode('\\', $use->name->parts); + $use_alias = $use->alias ? $use->alias->name : $use->name->getLast(); + + switch ($use->type !== PhpParser\Node\Stmt\Use_::TYPE_UNKNOWN ? $use->type : $stmt->type) { + case PhpParser\Node\Stmt\Use_::TYPE_FUNCTION: + $this->aliased_functions[strtolower($use_alias)] = $use_path; + break; + + case PhpParser\Node\Stmt\Use_::TYPE_CONSTANT: + $this->aliased_constants[$use_alias] = $use_path; + break; + + case PhpParser\Node\Stmt\Use_::TYPE_NORMAL: + $codebase->analyzer->addOffsetReference( + $this->getFilePath(), + (int) $use->getAttribute('startFilePos'), + (int) $use->getAttribute('endFilePos'), + $use_path + ); + if ($codebase->collect_locations) { + // register the path + $codebase->use_referencing_locations[strtolower($use_path)][] = + new \Psalm\CodeLocation($this, $use); + + $codebase->use_referencing_files[$this->getFilePath()][strtolower($use_path)] = true; + } + + if ($codebase->alter_code) { + if (isset($codebase->class_transforms[strtolower($use_path)])) { + $new_fq_class_name = $codebase->class_transforms[strtolower($use_path)]; + + $file_manipulations = []; + + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $use->getAttribute('startFilePos'), + (int) $use->getAttribute('endFilePos') + 1, + $new_fq_class_name . ($use->alias ? ' as ' . $use_alias : '') + ); + + FileManipulationBuffer::add($this->getFilePath(), $file_manipulations); + } + + $this->aliased_classes_flipped_replaceable[strtolower($use_path)] = $use_alias; + } + + $this->aliased_classes[strtolower($use_alias)] = $use_path; + $this->aliased_class_locations[strtolower($use_alias)] = new CodeLocation($this, $stmt); + $this->aliased_classes_flipped[strtolower($use_path)] = $use_alias; + break; + } + } + } + + public function visitGroupUse(PhpParser\Node\Stmt\GroupUse $stmt): void + { + $use_prefix = implode('\\', $stmt->prefix->parts); + + $codebase = $this->getCodebase(); + + foreach ($stmt->uses as $use) { + $use_path = $use_prefix . '\\' . implode('\\', $use->name->parts); + $use_alias = $use->alias ? $use->alias->name : $use->name->getLast(); + + switch ($use->type !== PhpParser\Node\Stmt\Use_::TYPE_UNKNOWN ? $use->type : $stmt->type) { + case PhpParser\Node\Stmt\Use_::TYPE_FUNCTION: + $this->aliased_functions[strtolower($use_alias)] = $use_path; + break; + + case PhpParser\Node\Stmt\Use_::TYPE_CONSTANT: + $this->aliased_constants[$use_alias] = $use_path; + break; + + case PhpParser\Node\Stmt\Use_::TYPE_NORMAL: + if ($codebase->collect_locations) { + // register the path + $codebase->use_referencing_locations[strtolower($use_path)][] = + new \Psalm\CodeLocation($this, $use); + } + + $this->aliased_classes[strtolower($use_alias)] = $use_path; + $this->aliased_classes_flipped[strtolower($use_path)] = $use_alias; + break; + } + } + } + + /** + * @return array + */ + public function getAliasedClassesFlipped(): array + { + return $this->aliased_classes_flipped; + } + + /** + * @return array + */ + public function getAliasedClassesFlippedReplaceable(): array + { + return $this->aliased_classes_flipped_replaceable; + } + + public function getAliases(): Aliases + { + return new Aliases( + $this->getNamespace(), + $this->aliased_classes, + $this->aliased_functions, + $this->aliased_constants + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..fdbc8a79eed222cd8ea1c70898fbb4cda8d04048 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php @@ -0,0 +1,2222 @@ + + */ + public $inferred_property_types = []; + + public function __construct(PhpParser\Node\Stmt\Class_ $class, SourceAnalyzer $source, ?string $fq_class_name) + { + if (!$fq_class_name) { + $fq_class_name = self::getAnonymousClassName($class, $source->getFilePath()); + } + + parent::__construct($class, $source, $fq_class_name); + + if (!$this->class instanceof PhpParser\Node\Stmt\Class_) { + throw new \InvalidArgumentException('Bad'); + } + + if ($this->class->extends) { + $this->parent_fq_class_name = self::getFQCLNFromNameObject( + $this->class->extends, + $this->source->getAliases() + ); + } + } + + public static function getAnonymousClassName(PhpParser\Node\Stmt\Class_ $class, string $file_path): string + { + return preg_replace('/[^A-Za-z0-9]/', '_', $file_path) + . '_' . $class->getLine() . '_' . (int)$class->getAttribute('startFilePos'); + } + + /** + * @return null|false + */ + public function analyze( + ?Context $class_context = null, + ?Context $global_context = null + ): ?bool { + $class = $this->class; + + if (!$class instanceof PhpParser\Node\Stmt\Class_) { + throw new \LogicException('Something went badly wrong'); + } + + $fq_class_name = $class_context && $class_context->self ? $class_context->self : $this->fq_class_name; + + $storage = $this->storage; + + if ($storage->has_visitor_issues) { + return null; + } + + if ($class->name + && (preg_match( + '/(^|\\\)(int|float|bool|string|void|null|false|true|object|mixed)$/i', + $fq_class_name + ) || strtolower($fq_class_name) === 'resource') + ) { + $class_name_parts = explode('\\', $fq_class_name); + $class_name = array_pop($class_name_parts); + + if (IssueBuffer::accepts( + new ReservedWord( + $class_name . ' is a reserved word', + new CodeLocation( + $this, + $class->name, + null, + true + ), + $class_name + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + + return null; + } + + $project_analyzer = $this->file_analyzer->project_analyzer; + $codebase = $this->getCodebase(); + + if ($codebase->alter_code && $class->name && $codebase->classes_to_move) { + if (isset($codebase->classes_to_move[strtolower($this->fq_class_name)])) { + $destination_class = $codebase->classes_to_move[strtolower($this->fq_class_name)]; + + $source_class_parts = explode('\\', $this->fq_class_name); + $destination_class_parts = explode('\\', $destination_class); + + array_pop($source_class_parts); + array_pop($destination_class_parts); + + $source_ns = implode('\\', $source_class_parts); + $destination_ns = implode('\\', $destination_class_parts); + + if (strtolower($source_ns) !== strtolower($destination_ns)) { + if ($storage->namespace_name_location) { + $bounds = $storage->namespace_name_location->getSelectionBounds(); + + $file_manipulations = [ + new \Psalm\FileManipulation( + $bounds[0], + $bounds[1], + $destination_ns + ) + ]; + + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $this->getFilePath(), + $file_manipulations + ); + } elseif (!$source_ns) { + $first_statement_pos = $this->getFileAnalyzer()->getFirstStatementOffset(); + + if ($first_statement_pos === -1) { + $first_statement_pos = (int) $class->getAttribute('startFilePos'); + } + + $file_manipulations = [ + new \Psalm\FileManipulation( + $first_statement_pos, + $first_statement_pos, + 'namespace ' . $destination_ns . ';' . "\n\n", + true + ) + ]; + + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $this->getFilePath(), + $file_manipulations + ); + } + } + } + + $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $this, + $class->name, + $this->fq_class_name, + null + ); + } + + foreach ($storage->docblock_issues as $docblock_issue) { + IssueBuffer::add($docblock_issue); + } + + $classlike_storage_provider = $codebase->classlike_storage_provider; + + $parent_fq_class_name = $this->parent_fq_class_name; + + if ($class->extends) { + if (!$parent_fq_class_name) { + throw new \UnexpectedValueException('Parent class should be filled in for ' . $fq_class_name); + } + + $parent_reference_location = new CodeLocation($this, $class->extends); + + if (self::checkFullyQualifiedClassLikeName( + $this->getSource(), + $parent_fq_class_name, + $parent_reference_location, + null, + null, + $storage->suppressed_issues + $this->getSuppressedIssues(), + false + ) === false) { + return false; + } + + if ($codebase->alter_code && $codebase->classes_to_move) { + $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $this, + $class->extends, + $parent_fq_class_name, + null + ); + } + + try { + $parent_class_storage = $classlike_storage_provider->get($parent_fq_class_name); + + $code_location = new CodeLocation( + $this, + $class->extends, + $class_context ? $class_context->include_location : null, + true + ); + + if ($parent_class_storage->is_trait || $parent_class_storage->is_interface) { + if (IssueBuffer::accepts( + new UndefinedClass( + $parent_fq_class_name . ' is not a class', + $code_location, + $parent_fq_class_name . ' as class' + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + if ($parent_class_storage->final) { + if (IssueBuffer::accepts( + new InvalidExtendClass( + 'Class ' . $fq_class_name . ' may not inherit from final class ' . $parent_fq_class_name, + $code_location, + $fq_class_name + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + if ($parent_class_storage->deprecated) { + if (IssueBuffer::accepts( + new DeprecatedClass( + $parent_fq_class_name . ' is marked deprecated', + $code_location, + $parent_fq_class_name + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + if (! NamespaceAnalyzer::isWithin($fq_class_name, $parent_class_storage->internal)) { + if (IssueBuffer::accepts( + new InternalClass( + $parent_fq_class_name . ' is internal to ' . $parent_class_storage->internal + . ' but called from ' . $fq_class_name, + $code_location, + $parent_fq_class_name + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + if ($parent_class_storage->external_mutation_free + && !$storage->external_mutation_free + ) { + if (IssueBuffer::accepts( + new MissingImmutableAnnotation( + $parent_fq_class_name . ' is marked immutable, but ' + . $fq_class_name . ' is not marked immutable', + $code_location + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + if ($storage->mutation_free + && !$parent_class_storage->mutation_free + ) { + if (IssueBuffer::accepts( + new MutableDependency( + $fq_class_name . ' is marked immutable but ' . $parent_fq_class_name . ' is not', + $code_location + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + if ($codebase->store_node_types) { + $codebase->analyzer->addNodeReference( + $this->getFilePath(), + $class->extends, + $codebase->classlikes->classExists($parent_fq_class_name) + ? $parent_fq_class_name + : '*' . implode('\\', $class->extends->parts) + ); + } + + $code_location = new CodeLocation( + $this, + $class->name ?: $class, + $class_context ? $class_context->include_location : null, + true + ); + + if ($storage->template_type_extends_count !== null) { + $this->checkTemplateParams( + $codebase, + $storage, + $parent_class_storage, + $code_location, + $storage->template_type_extends_count + ); + } + } catch (\InvalidArgumentException $e) { + // do nothing + } + } + + foreach ($class->implements as $interface_name) { + $fq_interface_name = self::getFQCLNFromNameObject( + $interface_name, + $this->source->getAliases() + ); + + $codebase->analyzer->addNodeReference( + $this->getFilePath(), + $interface_name, + $codebase->classlikes->interfaceExists($fq_interface_name) + ? $fq_interface_name + : '*' . implode('\\', $interface_name->parts) + ); + + $interface_location = new CodeLocation($this, $interface_name); + + if (self::checkFullyQualifiedClassLikeName( + $this, + $fq_interface_name, + $interface_location, + null, + null, + $this->getSuppressedIssues(), + false + ) === false) { + continue; + } + + if ($codebase->store_node_types && $fq_class_name) { + $bounds = $interface_location->getSelectionBounds(); + + $codebase->analyzer->addOffsetReference( + $this->getFilePath(), + $bounds[0], + $bounds[1], + $fq_interface_name + ); + } + + $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $this, + $interface_name, + $fq_interface_name, + null + ); + + $fq_interface_name_lc = strtolower($fq_interface_name); + + try { + $interface_storage = $classlike_storage_provider->get($fq_interface_name_lc); + } catch (\InvalidArgumentException $e) { + continue; + } + + $code_location = new CodeLocation( + $this, + $interface_name, + $class_context ? $class_context->include_location : null, + true + ); + + if (!$interface_storage->is_interface) { + if (IssueBuffer::accepts( + new UndefinedInterface( + $fq_interface_name . ' is not an interface', + $code_location, + $fq_interface_name + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + if (isset($storage->template_type_implements_count[$fq_interface_name_lc])) { + $expected_param_count = $storage->template_type_implements_count[$fq_interface_name_lc]; + + $this->checkTemplateParams( + $codebase, + $storage, + $interface_storage, + $code_location, + $expected_param_count + ); + } + } + + if ($storage->template_types) { + foreach ($storage->template_types as $param_name => $_) { + $fq_classlike_name = Type::getFQCLNFromString( + $param_name, + $this->getAliases() + ); + + if ($codebase->classOrInterfaceExists($fq_classlike_name)) { + if (IssueBuffer::accepts( + new ReservedWord( + 'Cannot use ' . $param_name . ' as template name since the class already exists', + new CodeLocation($this, $this->class), + 'resource' + ), + $this->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + if (($storage->templatedMixins || $storage->namedMixins) + && $storage->mixin_declaring_fqcln === $storage->name) { + /** @var non-empty-array $mixins */ + $mixins = array_merge($storage->templatedMixins, $storage->namedMixins); + $union = new Type\Union($mixins); + $union->check( + $this, + new CodeLocation( + $this, + $class->name ?: $class, + null, + true + ), + $this->getSuppressedIssues() + ); + } + + if ($storage->template_type_extends) { + foreach ($storage->template_type_extends as $type_map) { + foreach ($type_map as $atomic_type) { + $atomic_type->check( + $this, + new CodeLocation( + $this, + $class->name ?: $class, + null, + true + ), + $this->getSuppressedIssues() + ); + } + } + } + + if ($storage->invalid_dependencies) { + return null; + } + + $class_interfaces = $storage->class_implements; + + foreach ($class_interfaces as $interface_name) { + try { + $interface_storage = $classlike_storage_provider->get($interface_name); + } catch (\InvalidArgumentException $e) { + continue; + } + + $code_location = new CodeLocation( + $this, + $class->name ? $class->name : $class, + $class_context ? $class_context->include_location : null, + true + ); + + if ($interface_storage->deprecated) { + if (IssueBuffer::accepts( + new DeprecatedInterface( + $interface_name . ' is marked deprecated', + $code_location, + $interface_name + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + if ($interface_storage->external_mutation_free + && !$storage->external_mutation_free + ) { + if (IssueBuffer::accepts( + new MissingImmutableAnnotation( + $interface_name . ' is marked immutable, but ' + . $fq_class_name . ' is not marked immutable', + $code_location + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + foreach ($interface_storage->methods as $interface_method_name_lc => $interface_method_storage) { + if ($interface_method_storage->visibility === self::VISIBILITY_PUBLIC) { + $implementer_declaring_method_id = $codebase->methods->getDeclaringMethodId( + new \Psalm\Internal\MethodIdentifier( + $this->fq_class_name, + $interface_method_name_lc + ) + ); + + $implementer_method_storage = null; + $implementer_classlike_storage = null; + + if ($implementer_declaring_method_id) { + $implementer_fq_class_name = $implementer_declaring_method_id->fq_class_name; + $implementer_method_storage = $codebase->methods->getStorage( + $implementer_declaring_method_id + ); + $implementer_classlike_storage = $classlike_storage_provider->get( + $implementer_fq_class_name + ); + } + + if (!$implementer_method_storage) { + if (IssueBuffer::accepts( + new UnimplementedInterfaceMethod( + 'Method ' . $interface_method_name_lc . ' is not defined on class ' . + $storage->name, + $code_location + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + return false; + } + + return null; + } + + $implementer_appearing_method_id = $codebase->methods->getAppearingMethodId( + new \Psalm\Internal\MethodIdentifier( + $this->fq_class_name, + $interface_method_name_lc + ) + ); + + $implementer_visibility = $implementer_method_storage->visibility; + + if ($implementer_appearing_method_id + && $implementer_appearing_method_id !== $implementer_declaring_method_id + ) { + $appearing_fq_class_name = $implementer_appearing_method_id->fq_class_name; + $appearing_method_name = $implementer_appearing_method_id->method_name; + + $appearing_class_storage = $classlike_storage_provider->get( + $appearing_fq_class_name + ); + + if (isset($appearing_class_storage->trait_visibility_map[$appearing_method_name])) { + $implementer_visibility + = $appearing_class_storage->trait_visibility_map[$appearing_method_name]; + } + } + + if ($implementer_visibility !== self::VISIBILITY_PUBLIC) { + if (IssueBuffer::accepts( + new InaccessibleMethod( + 'Interface-defined method ' . $implementer_method_storage->cased_name + . ' must be public in ' . $storage->name, + $code_location + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + return false; + } + + return null; + } + + if ($interface_method_storage->is_static && !$implementer_method_storage->is_static) { + if (IssueBuffer::accepts( + new MethodSignatureMismatch( + 'Method ' . $implementer_method_storage->cased_name + . ' should be static like ' + . $storage->name . '::' . $interface_method_storage->cased_name, + $code_location + ), + $implementer_method_storage->suppressed_issues + )) { + return false; + } + } + + if ($storage->abstract && $implementer_method_storage === $interface_method_storage) { + continue; + } + + MethodComparator::compare( + $codebase, + null, + $implementer_classlike_storage ?: $storage, + $interface_storage, + $implementer_method_storage, + $interface_method_storage, + $this->fq_class_name, + $implementer_visibility, + $code_location, + $implementer_method_storage->suppressed_issues, + false + ); + } + } + } + + if (!$class_context) { + $class_context = new Context($this->fq_class_name); + $class_context->parent = $parent_fq_class_name; + } + + if ($global_context) { + $class_context->strict_types = $global_context->strict_types; + } + + if ($this->leftover_stmts) { + (new StatementsAnalyzer( + $this, + new \Psalm\Internal\Provider\NodeDataProvider() + ))->analyze( + $this->leftover_stmts, + $class_context + ); + } + + if (!$storage->abstract) { + foreach ($storage->declaring_method_ids as $declaring_method_id) { + $method_storage = $codebase->methods->getStorage($declaring_method_id); + + $declaring_class_name = $declaring_method_id->fq_class_name; + $method_name_lc = $declaring_method_id->method_name; + + if ($method_storage->abstract) { + if (IssueBuffer::accepts( + new UnimplementedAbstractMethod( + 'Method ' . $method_name_lc . ' is not defined on class ' . + $this->fq_class_name . ', defined abstract in ' . $declaring_class_name, + new CodeLocation( + $this, + $class->name ? $class->name : $class, + $class_context->include_location, + true + ) + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + return false; + } + } + } + } + + self::addContextProperties( + $this, + $storage, + $class_context, + $this->fq_class_name, + $this->parent_fq_class_name, + $class->stmts + ); + + $constructor_analyzer = null; + $member_stmts = []; + + foreach ($class->stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod) { + $method_analyzer = $this->analyzeClassMethod( + $stmt, + $storage, + $this, + $class_context, + $global_context + ); + + if ($stmt->name->name === '__construct') { + $constructor_analyzer = $method_analyzer; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\TraitUse) { + if ($this->analyzeTraitUse( + $this->source->getAliases(), + $stmt, + $project_analyzer, + $storage, + $class_context, + $global_context, + $constructor_analyzer + ) === false) { + return false; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\Property) { + foreach ($stmt->props as $prop) { + if ($prop->default) { + $member_stmts[] = $stmt; + } + + if ($codebase->alter_code) { + $property_id = strtolower($this->fq_class_name) . '::$' . $prop->name; + + $property_storage = $codebase->properties->getStorage($property_id); + + if ($property_storage->type + && $property_storage->type_location + && $property_storage->type_location !== $property_storage->signature_type_location + ) { + $replace_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $property_storage->type, + $this->getFQCLN(), + $this->getFQCLN(), + $this->getParentFQCLN() + ); + + $codebase->classlikes->handleDocblockTypeInMigration( + $codebase, + $this, + $replace_type, + $property_storage->type_location, + null + ); + } + + foreach ($codebase->properties_to_rename as $original_property_id => $new_property_name) { + if ($property_id === $original_property_id) { + $file_manipulations = [ + new \Psalm\FileManipulation( + (int) $prop->name->getAttribute('startFilePos'), + (int) $prop->name->getAttribute('endFilePos') + 1, + '$' . $new_property_name + ) + ]; + + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $this->getFilePath(), + $file_manipulations + ); + } + } + } + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\ClassConst) { + $member_stmts[] = $stmt; + + foreach ($stmt->consts as $const) { + $const_id = strtolower($this->fq_class_name) . '::' . $const->name; + + foreach ($codebase->class_constants_to_rename as $original_const_id => $new_const_name) { + if ($const_id === $original_const_id) { + $file_manipulations = [ + new \Psalm\FileManipulation( + (int) $const->name->getAttribute('startFilePos'), + (int) $const->name->getAttribute('endFilePos') + 1, + $new_const_name + ) + ]; + + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $this->getFilePath(), + $file_manipulations + ); + } + } + } + } + } + + $statements_analyzer = new StatementsAnalyzer($this, new \Psalm\Internal\Provider\NodeDataProvider()); + $statements_analyzer->analyze($member_stmts, $class_context, $global_context, true); + + $config = Config::getInstance(); + + $this->checkPropertyInitialization( + $codebase, + $config, + $storage, + $class_context, + $global_context, + $constructor_analyzer + ); + + foreach ($class->stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\Property && !isset($stmt->type)) { + $this->checkForMissingPropertyType($this, $stmt, $class_context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\TraitUse) { + foreach ($stmt->traits as $trait) { + $fq_trait_name = self::getFQCLNFromNameObject( + $trait, + $this->source->getAliases() + ); + + try { + $trait_file_analyzer = $project_analyzer->getFileAnalyzerForClassLike($fq_trait_name); + } catch (\Exception $e) { + continue; + } + + $trait_storage = $codebase->classlike_storage_provider->get($fq_trait_name); + $trait_node = $codebase->classlikes->getTraitNode($fq_trait_name); + $trait_aliases = $trait_storage->aliases; + + if ($trait_aliases === null) { + continue; + } + + $trait_analyzer = new TraitAnalyzer( + $trait_node, + $trait_file_analyzer, + $fq_trait_name, + $trait_aliases + ); + + $fq_trait_name_lc = strtolower($fq_trait_name); + + if (isset($storage->template_type_uses_count[$fq_trait_name_lc])) { + $expected_param_count = $storage->template_type_uses_count[$fq_trait_name_lc]; + + $this->checkTemplateParams( + $codebase, + $storage, + $trait_storage, + new CodeLocation( + $this, + $trait + ), + $expected_param_count + ); + } + + foreach ($trait_node->stmts as $trait_stmt) { + if ($trait_stmt instanceof PhpParser\Node\Stmt\Property) { + $this->checkForMissingPropertyType($trait_analyzer, $trait_stmt, $class_context); + } + } + + $trait_file_analyzer->clearSourceBeforeDestruction(); + } + } + } + + $pseudo_methods = $storage->pseudo_methods + $storage->pseudo_static_methods; + + foreach ($pseudo_methods as $pseudo_method_name => $pseudo_method_storage) { + $pseudo_method_id = new \Psalm\Internal\MethodIdentifier( + $this->fq_class_name, + $pseudo_method_name + ); + + $overridden_method_ids = $codebase->methods->getOverriddenMethodIds($pseudo_method_id); + + if ($overridden_method_ids + && $pseudo_method_name !== '__construct' + && $pseudo_method_storage->location + ) { + foreach ($overridden_method_ids as $overridden_method_id) { + $parent_method_storage = $codebase->methods->getStorage($overridden_method_id); + + $overridden_fq_class_name = $overridden_method_id->fq_class_name; + + $parent_storage = $classlike_storage_provider->get($overridden_fq_class_name); + + MethodComparator::compare( + $codebase, + null, + $storage, + $parent_storage, + $pseudo_method_storage, + $parent_method_storage, + $this->fq_class_name, + $pseudo_method_storage->visibility ?: 0, + $storage->location ?: $pseudo_method_storage->location, + $storage->suppressed_issues, + true, + false + ); + } + } + } + + $plugin_classes = $codebase->config->after_classlike_checks; + + if ($plugin_classes) { + $file_manipulations = []; + + foreach ($plugin_classes as $plugin_fq_class_name) { + if ($plugin_fq_class_name::afterStatementAnalysis( + $class, + $storage, + $this, + $codebase, + $file_manipulations + ) === false) { + return false; + } + } + + if ($file_manipulations) { + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $this->getFilePath(), + $file_manipulations + ); + } + } + + return null; + } + + public static function addContextProperties( + StatementsSource $statements_source, + ClassLikeStorage $storage, + Context $class_context, + string $fq_class_name, + ?string $parent_fq_class_name, + array $stmts = [] + ) : void { + $codebase = $statements_source->getCodebase(); + + foreach ($storage->appearing_property_ids as $property_name => $appearing_property_id) { + $property_class_name = $codebase->properties->getDeclaringClassForProperty( + $appearing_property_id, + true + ); + + if ($property_class_name === null) { + continue; + } + + $property_class_storage = $codebase->classlike_storage_provider->get($property_class_name); + + $property_storage = $property_class_storage->properties[$property_name]; + + if (isset($storage->overridden_property_ids[$property_name])) { + foreach ($storage->overridden_property_ids[$property_name] as $overridden_property_id) { + [$guide_class_name] = explode('::$', $overridden_property_id); + $guide_class_storage = $codebase->classlike_storage_provider->get($guide_class_name); + $guide_property_storage = $guide_class_storage->properties[$property_name]; + + if ($property_storage->visibility > $guide_property_storage->visibility + && $property_storage->location + ) { + if (IssueBuffer::accepts( + new OverriddenPropertyAccess( + 'Property ' . $guide_class_storage->name . '::$' . $property_name + . ' has different access level than ' + . $storage->name . '::$' . $property_name, + $property_storage->location + ) + )) { + // fall through + } + + continue; + } + } + } + + if ($property_storage->type) { + $property_type = clone $property_storage->type; + + if (!$property_type->isMixed() + && !$property_storage->has_default + && !($property_type->isNullable() && $property_type->from_docblock) + ) { + $property_type->initialized = false; + } + } else { + $property_type = Type::getMixed(); + + if (!$property_storage->has_default) { + $property_type->initialized = false; + } + } + + $property_type_location = $property_storage->type_location; + + $fleshed_out_type = !$property_type->isMixed() + ? \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $property_type, + $fq_class_name, + $fq_class_name, + $parent_fq_class_name, + true, + false, + $storage->final + ) + : $property_type; + + $class_template_params = ClassTemplateParamCollector::collect( + $codebase, + $property_class_storage, + $storage, + null, + new Type\Atomic\TNamedObject($fq_class_name), + '$this' + ); + + $template_result = new \Psalm\Internal\Type\TemplateResult( + $class_template_params ?: [], + [] + ); + + if ($class_template_params) { + $fleshed_out_type = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $fleshed_out_type, + $template_result, + $codebase, + null, + null, + null, + $class_context->self + ); + } + + if ($property_type_location && !$fleshed_out_type->isMixed()) { + $stmt = array_filter($stmts, function ($stmt) use ($property_name): bool { + return $stmt instanceof PhpParser\Node\Stmt\Property + && isset($stmt->props[0]->name->name) + && $stmt->props[0]->name->name === $property_name; + }); + + $suppressed = []; + if (count($stmt) > 0) { + /** @var PhpParser\Node\Stmt\Property $stmt */ + $stmt = array_pop($stmt); + + $docComment = $stmt->getDocComment(); + if ($docComment) { + try { + $docBlock = DocComment::parsePreservingLength($docComment); + $suppressed = $docBlock->tags['psalm-suppress'] ?? []; + } catch (DocblockParseException $e) { + // do nothing to keep original behavior + } + } + } + + $fleshed_out_type->check( + $statements_source, + $property_type_location, + $storage->suppressed_issues + $statements_source->getSuppressedIssues() + $suppressed, + [], + false + ); + } + + if ($property_storage->is_static) { + $property_id = $fq_class_name . '::$' . $property_name; + + $class_context->vars_in_scope[$property_id] = $fleshed_out_type; + } else { + $class_context->vars_in_scope['$this->' . $property_name] = $fleshed_out_type; + } + } + + foreach ($storage->pseudo_property_get_types as $property_name => $property_type) { + $property_name = substr($property_name, 1); + + if (isset($class_context->vars_in_scope['$this->' . $property_name])) { + $fleshed_out_type = !$property_type->isMixed() + ? \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $property_type, + $fq_class_name, + $fq_class_name, + $parent_fq_class_name + ) + : $property_type; + + $class_context->vars_in_scope['$this->' . $property_name] = $fleshed_out_type; + } + } + } + + private function checkPropertyInitialization( + Codebase $codebase, + Config $config, + ClassLikeStorage $storage, + Context $class_context, + ?Context $global_context = null, + ?MethodAnalyzer $constructor_analyzer = null + ): void { + if (!$config->reportIssueInFile('PropertyNotSetInConstructor', $this->getFilePath())) { + return; + } + + if (!isset($storage->declaring_method_ids['__construct']) + && !$config->reportIssueInFile('MissingConstructor', $this->getFilePath()) + ) { + return; + } + + $fq_class_name = $class_context->self ? $class_context->self : $this->fq_class_name; + $fq_class_name_lc = strtolower($fq_class_name); + + $included_file_path = $this->getFilePath(); + + $method_already_analyzed = $codebase->analyzer->isMethodAlreadyAnalyzed( + $included_file_path, + $fq_class_name_lc . '::__construct', + true + ); + + if ($method_already_analyzed && !$codebase->diff_methods) { + // this can happen when re-analysing a class that has been include()d inside another + return; + } + + /** @var PhpParser\Node\Stmt\Class_ */ + $class = $this->class; + $classlike_storage_provider = $codebase->classlike_storage_provider; + $class_storage = $classlike_storage_provider->get($fq_class_name_lc); + + $constructor_appearing_fqcln = $fq_class_name_lc; + + $uninitialized_variables = []; + $uninitialized_properties = []; + $uninitialized_typed_properties = []; + $uninitialized_private_properties = false; + + foreach ($storage->appearing_property_ids as $property_name => $appearing_property_id) { + $property_class_name = $codebase->properties->getDeclaringClassForProperty( + $appearing_property_id, + true + ); + + if ($property_class_name === null) { + continue; + } + + $property_class_storage = $classlike_storage_provider->get($property_class_name); + + $property = $property_class_storage->properties[$property_name]; + + $property_is_initialized = isset($property_class_storage->initialized_properties[$property_name]); + + if ($property->is_static) { + continue; + } + + if ($property->has_default || $property_is_initialized) { + continue; + } + + if ($property->type && $property->type->isNullable() && $property->type->from_docblock) { + continue; + } + + if ($codebase->diff_methods && $method_already_analyzed && $property->location) { + [$start, $end] = $property->location->getSelectionBounds(); + + $existing_issues = $codebase->analyzer->getExistingIssuesForFile( + $this->getFilePath(), + $start, + $end, + 'PropertyNotSetInConstructor' + ); + + if ($existing_issues) { + IssueBuffer::addIssues([$this->getFilePath() => $existing_issues]); + continue; + } + } + + if ($property->location) { + $codebase->analyzer->removeExistingDataForFile( + $this->getFilePath(), + $property->location->raw_file_start, + $property->location->raw_file_end, + 'PropertyNotSetInConstructor' + ); + } + + $codebase->file_reference_provider->addMethodReferenceToMissingClassMember( + $fq_class_name_lc . '::__construct', + strtolower($property_class_name) . '::$' . $property_name + ); + + if ($property->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE) { + $uninitialized_private_properties = true; + } + + $uninitialized_variables[] = '$this->' . $property_name; + $uninitialized_properties[$property_class_name . '::$' . $property_name] = $property; + + if ($property->type && !$property->type->isMixed()) { + $uninitialized_typed_properties[$property_class_name . '::$' . $property_name] = $property; + } + } + + if (!$uninitialized_properties) { + return; + } + + if (!$storage->abstract + && !$constructor_analyzer + && isset($storage->declaring_method_ids['__construct']) + && isset($storage->appearing_method_ids['__construct']) + && $class->extends + ) { + $constructor_declaring_fqcln = $storage->declaring_method_ids['__construct']->fq_class_name; + $constructor_appearing_fqcln = $storage->appearing_method_ids['__construct']->fq_class_name; + + $constructor_class_storage = $classlike_storage_provider->get($constructor_declaring_fqcln); + + // ignore oldstyle constructors and classes without any declared properties + if ($constructor_class_storage->user_defined + && !$constructor_class_storage->stubbed + && isset($constructor_class_storage->methods['__construct']) + ) { + $constructor_storage = $constructor_class_storage->methods['__construct']; + + $fake_constructor_params = array_map( + function (FunctionLikeParameter $param) : PhpParser\Node\Param { + $fake_param = (new PhpParser\Builder\Param($param->name)); + if ($param->signature_type) { + $fake_param->setType((string)$param->signature_type); + } + + return $fake_param->getNode(); + }, + $constructor_storage->params + ); + + $fake_constructor_stmt_args = array_map( + function (FunctionLikeParameter $param) : PhpParser\Node\Arg { + return new PhpParser\Node\Arg(new PhpParser\Node\Expr\Variable($param->name)); + }, + $constructor_storage->params + ); + + $fake_constructor_stmts = [ + new PhpParser\Node\Stmt\Expression( + new PhpParser\Node\Expr\StaticCall( + new PhpParser\Node\Name\FullyQualified($constructor_declaring_fqcln), + new PhpParser\Node\Identifier('__construct'), + $fake_constructor_stmt_args, + [ + 'startLine' => $class->extends->getLine(), + 'startFilePos' => $class->extends->getAttribute('startFilePos'), + 'endFilePos' => $class->extends->getAttribute('endFilePos'), + 'comments' => [new PhpParser\Comment\Doc( + '/** @psalm-suppress InaccessibleMethod */', + $class->extends->getLine(), + (int) $class->extends->getAttribute('startFilePos') + )], + ] + ), + [ + 'startLine' => $class->extends->getLine(), + 'startFilePos' => $class->extends->getAttribute('startFilePos'), + 'endFilePos' => $class->extends->getAttribute('endFilePos'), + 'comments' => [new PhpParser\Comment\Doc( + '/** @psalm-suppress InaccessibleMethod */', + $class->extends->getLine(), + (int) $class->extends->getAttribute('startFilePos') + )], + ] + ), + ]; + + $fake_stmt = new PhpParser\Node\Stmt\ClassMethod( + new PhpParser\Node\Identifier('__construct'), + [ + 'type' => PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC, + 'params' => $fake_constructor_params, + 'stmts' => $fake_constructor_stmts, + ], + [ + 'startLine' => $class->extends->getLine(), + 'startFilePos' => $class->extends->getAttribute('startFilePos'), + 'endFilePos' => $class->extends->getAttribute('endFilePos'), + ] + ); + + $codebase->analyzer->disableMixedCounts(); + + $was_collecting_initializations = $class_context->collect_initializations; + + $class_context->collect_initializations = true; + $class_context->collect_nonprivate_initializations = !$uninitialized_private_properties; + + $constructor_analyzer = $this->analyzeClassMethod( + $fake_stmt, + $storage, + $this, + $class_context, + $global_context, + true + ); + + $class_context->collect_initializations = $was_collecting_initializations; + + $codebase->analyzer->enableMixedCounts(); + } + } + + if ($constructor_analyzer) { + $method_context = clone $class_context; + $method_context->collect_initializations = true; + $method_context->collect_nonprivate_initializations = !$uninitialized_private_properties; + $method_context->self = $fq_class_name; + + $this_atomic_object_type = new Type\Atomic\TNamedObject($fq_class_name); + $this_atomic_object_type->was_static = !$storage->final; + + $method_context->vars_in_scope['$this'] = new Type\Union([$this_atomic_object_type]); + $method_context->vars_possibly_in_scope['$this'] = true; + $method_context->calling_method_id = strtolower($fq_class_name) . '::__construct'; + + $constructor_analyzer->analyze( + $method_context, + new \Psalm\Internal\Provider\NodeDataProvider(), + $global_context, + true + ); + + foreach ($uninitialized_properties as $property_id => $property_storage) { + [, $property_name] = explode('::$', $property_id); + + if (!isset($method_context->vars_in_scope['$this->' . $property_name])) { + $end_type = Type::getVoid(); + $end_type->initialized = false; + } else { + $end_type = $method_context->vars_in_scope['$this->' . $property_name]; + } + + $constructor_class_property_storage = $property_storage; + + $error_location = $property_storage->location; + + if ($storage->declaring_property_ids[$property_name] !== $fq_class_name) { + $error_location = $storage->location ?: $storage->stmt_location; + } + + if ($fq_class_name_lc !== $constructor_appearing_fqcln + && $property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE + ) { + $a_class_storage = $classlike_storage_provider->get( + $end_type->initialized_class ?: $constructor_appearing_fqcln + ); + + if (!isset($a_class_storage->declaring_property_ids[$property_name])) { + $constructor_class_property_storage = null; + } else { + $declaring_property_class = $a_class_storage->declaring_property_ids[$property_name]; + $constructor_class_property_storage = $classlike_storage_provider + ->get($declaring_property_class) + ->properties[$property_name]; + } + } + + if ($property_storage->location + && $error_location + && (!$end_type->initialized || $property_storage !== $constructor_class_property_storage) + ) { + if ($property_storage->type) { + $expected_visibility = $uninitialized_private_properties + ? 'private or final ' + : ''; + + if (IssueBuffer::accepts( + new PropertyNotSetInConstructor( + 'Property ' . $class_storage->name . '::$' . $property_name + . ' is not defined in constructor of ' + . $this->fq_class_name . ' and in any ' . $expected_visibility + . 'methods called in the constructor', + $error_location, + $property_id + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // do nothing + } + } elseif (!$property_storage->has_default) { + if (isset($this->inferred_property_types[$property_name])) { + $this->inferred_property_types[$property_name]->addType(new Type\Atomic\TNull()); + $this->inferred_property_types[$property_name]->setFromDocblock(); + } + } + } + } + + $codebase->analyzer->setAnalyzedMethod( + $included_file_path, + $fq_class_name_lc . '::__construct', + true + ); + + return; + } + + if (!$storage->abstract && $uninitialized_typed_properties) { + foreach ($uninitialized_typed_properties as $id => $uninitialized_property) { + if ($uninitialized_property->location) { + if (IssueBuffer::accepts( + new MissingConstructor( + $class_storage->name . ' has an uninitialized property ' . $id . + ', but no constructor', + $uninitialized_property->location, + $class_storage->name . '::' . $uninitialized_variables[0] + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + + /** + * @return false|null + */ + private function analyzeTraitUse( + Aliases $aliases, + PhpParser\Node\Stmt\TraitUse $stmt, + ProjectAnalyzer $project_analyzer, + ClassLikeStorage $storage, + Context $class_context, + ?Context $global_context = null, + ?MethodAnalyzer &$constructor_analyzer = null, + ?TraitAnalyzer $previous_trait_analyzer = null + ): ?bool { + $codebase = $this->getCodebase(); + + $previous_context_include_location = $class_context->include_location; + + foreach ($stmt->traits as $trait_name) { + $trait_location = new CodeLocation($this, $trait_name, null, true); + $class_context->include_location = new CodeLocation($this, $trait_name, null, true); + + $fq_trait_name = self::getFQCLNFromNameObject( + $trait_name, + $aliases + ); + + if (!$codebase->classlikes->hasFullyQualifiedTraitName($fq_trait_name, $trait_location)) { + if (IssueBuffer::accepts( + new UndefinedTrait( + 'Trait ' . $fq_trait_name . ' does not exist', + new CodeLocation($previous_trait_analyzer ?: $this, $trait_name) + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + + return false; + } else { + if (!$codebase->traitHasCorrectCase($fq_trait_name)) { + if (IssueBuffer::accepts( + new UndefinedTrait( + 'Trait ' . $fq_trait_name . ' has wrong casing', + new CodeLocation($previous_trait_analyzer ?: $this, $trait_name) + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + return false; + } + + continue; + } + + $fq_trait_name_resolved = $codebase->classlikes->getUnAliasedName($fq_trait_name); + $trait_storage = $codebase->classlike_storage_provider->get($fq_trait_name_resolved); + + if ($trait_storage->deprecated) { + if (IssueBuffer::accepts( + new DeprecatedTrait( + 'Trait ' . $fq_trait_name . ' is deprecated', + new CodeLocation($previous_trait_analyzer ?: $this, $trait_name) + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + if ($trait_storage->extension_requirement !== null) { + $extension_requirement = $codebase->classlikes->getUnAliasedName( + $trait_storage->extension_requirement + ); + $extensionRequirementMet = in_array($extension_requirement, $storage->parent_classes); + + if (!$extensionRequirementMet) { + if (IssueBuffer::accepts( + new ExtensionRequirementViolation( + $fq_trait_name . ' requires using class to extend ' . $extension_requirement + . ', but ' . $storage->name . ' does not', + new CodeLocation($previous_trait_analyzer ?: $this, $trait_name) + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + } + + foreach ($trait_storage->implementation_requirements as $implementation_requirement) { + $implementation_requirement = $codebase->classlikes->getUnAliasedName($implementation_requirement); + $implementationRequirementMet = in_array($implementation_requirement, $storage->class_implements); + + if (!$implementationRequirementMet) { + if (IssueBuffer::accepts( + new ImplementationRequirementViolation( + $fq_trait_name . ' requires using class to implement ' + . $implementation_requirement . ', but ' . $storage->name . ' does not', + new CodeLocation($previous_trait_analyzer ?: $this, $trait_name) + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($storage->mutation_free && !$trait_storage->mutation_free) { + if (IssueBuffer::accepts( + new MutableDependency( + $storage->name . ' is marked immutable but ' . $fq_trait_name . ' is not', + new CodeLocation($previous_trait_analyzer ?: $this, $trait_name) + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + $trait_file_analyzer = $project_analyzer->getFileAnalyzerForClassLike($fq_trait_name_resolved); + $trait_node = $codebase->classlikes->getTraitNode($fq_trait_name_resolved); + $trait_aliases = $trait_storage->aliases; + if ($trait_aliases === null) { + continue; + } + + $trait_analyzer = new TraitAnalyzer( + $trait_node, + $trait_file_analyzer, + $fq_trait_name_resolved, + $trait_aliases + ); + + foreach ($trait_node->stmts as $trait_stmt) { + if ($trait_stmt instanceof PhpParser\Node\Stmt\ClassMethod) { + $trait_method_analyzer = $this->analyzeClassMethod( + $trait_stmt, + $storage, + $trait_analyzer, + $class_context, + $global_context + ); + + if ($trait_stmt->name->name === '__construct') { + $constructor_analyzer = $trait_method_analyzer; + } + } elseif ($trait_stmt instanceof PhpParser\Node\Stmt\TraitUse) { + if ($this->analyzeTraitUse( + $trait_aliases, + $trait_stmt, + $project_analyzer, + $storage, + $class_context, + $global_context, + $constructor_analyzer, + $trait_analyzer + ) === false) { + return false; + } + } + } + + $trait_file_analyzer->clearSourceBeforeDestruction(); + } + } + + $class_context->include_location = $previous_context_include_location; + + return null; + } + + private function checkForMissingPropertyType( + StatementsSource $source, + PhpParser\Node\Stmt\Property $stmt, + Context $context + ): void { + $fq_class_name = $source->getFQCLN(); + $property_name = $stmt->props[0]->name->name; + + $codebase = $this->getCodebase(); + + $property_id = $fq_class_name . '::$' . $property_name; + + $declaring_property_class = $codebase->properties->getDeclaringClassForProperty( + $property_id, + true + ); + + if (!$declaring_property_class) { + return; + } + + $fq_class_name = $declaring_property_class; + + // gets inherited property type + $class_property_type = $codebase->properties->getPropertyType($property_id, false, $source, $context); + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + $property_storage = $class_storage->properties[$property_name]; + + if ($class_property_type && ($property_storage->type_location || !$codebase->alter_code)) { + return; + } + + $message = 'Property ' . $property_id . ' does not have a declared type'; + + $suggested_type = $property_storage->suggested_type; + + if (isset($this->inferred_property_types[$property_name])) { + $suggested_type = $suggested_type + ? Type::combineUnionTypes( + $suggested_type, + $this->inferred_property_types[$property_name], + $codebase + ) + : $this->inferred_property_types[$property_name]; + } + + if ($suggested_type && !$property_storage->has_default && $property_storage->is_static) { + $suggested_type->addType(new Type\Atomic\TNull()); + } + + if ($suggested_type && !$suggested_type->isNull()) { + $message .= ' - consider ' . str_replace( + ['', ''], + '', + (string)$suggested_type + ); + } + + $project_analyzer = ProjectAnalyzer::getInstance(); + + if ($codebase->alter_code + && $source === $this + && isset($project_analyzer->getIssuesToFix()['MissingPropertyType']) + && !\in_array('MissingPropertyType', $this->getSuppressedIssues()) + && $suggested_type + ) { + if ($suggested_type->hasMixed() || $suggested_type->isNull()) { + return; + } + + self::addOrUpdatePropertyType( + $project_analyzer, + $stmt, + $suggested_type, + $this, + $suggested_type->from_docblock + ); + + return; + } + + if (IssueBuffer::accepts( + new MissingPropertyType( + $message, + new CodeLocation($source, $stmt->props[0]->name), + $property_id + ), + $this->source->getSuppressedIssues() + )) { + // fall through + } + } + + private static function addOrUpdatePropertyType( + ProjectAnalyzer $project_analyzer, + PhpParser\Node\Stmt\Property $property, + Type\Union $inferred_type, + StatementsSource $source, + bool $docblock_only = false + ) : void { + $manipulator = PropertyDocblockManipulator::getForProperty( + $project_analyzer, + $source->getFilePath(), + $property + ); + + $codebase = $project_analyzer->getCodebase(); + + $allow_native_type = !$docblock_only + && $codebase->php_major_version >= 7 + && ($codebase->php_major_version > 7 || $codebase->php_minor_version >= 4) + && $codebase->allow_backwards_incompatible_changes; + + $manipulator->setType( + $allow_native_type + ? (string) $inferred_type->toPhpString( + $source->getNamespace(), + $source->getAliasedClassesFlipped(), + $source->getFQCLN(), + $codebase->php_major_version, + $codebase->php_minor_version + ) : null, + $inferred_type->toNamespacedString( + $source->getNamespace(), + $source->getAliasedClassesFlipped(), + $source->getFQCLN(), + false + ), + $inferred_type->toNamespacedString( + $source->getNamespace(), + $source->getAliasedClassesFlipped(), + $source->getFQCLN(), + true + ), + $inferred_type->canBeFullyExpressedInPhp() + ); + } + + private function analyzeClassMethod( + PhpParser\Node\Stmt\ClassMethod $stmt, + ClassLikeStorage $class_storage, + SourceAnalyzer $source, + Context $class_context, + ?Context $global_context = null, + bool $is_fake = false + ): ?MethodAnalyzer { + $config = Config::getInstance(); + + if ($stmt->stmts === null && !$stmt->isAbstract()) { + \Psalm\IssueBuffer::add( + new \Psalm\Issue\ParseError( + 'Non-abstract class method must have statements', + new CodeLocation($this, $stmt) + ) + ); + + return null; + } + + try { + $method_analyzer = new MethodAnalyzer($stmt, $source); + } catch (\UnexpectedValueException $e) { + \Psalm\IssueBuffer::add( + new \Psalm\Issue\ParseError( + 'Problem loading method: ' . $e->getMessage(), + new CodeLocation($this, $stmt) + ) + ); + + return null; + } + + $actual_method_id = $method_analyzer->getMethodId(); + + $project_analyzer = $source->getProjectAnalyzer(); + $codebase = $source->getCodebase(); + + $analyzed_method_id = $actual_method_id; + + $included_file_path = $source->getFilePath(); + + if ($class_context->self && strtolower($class_context->self) !== strtolower((string) $source->getFQCLN())) { + $analyzed_method_id = $method_analyzer->getMethodId($class_context->self); + + $declaring_method_id = $codebase->methods->getDeclaringMethodId($analyzed_method_id); + + if ((string) $actual_method_id !== (string) $declaring_method_id) { + // the method is an abstract trait method + + $declaring_method_storage = $method_analyzer->getFunctionLikeStorage(); + + if (!$declaring_method_storage instanceof \Psalm\Storage\MethodStorage) { + throw new \LogicException('This should never happen'); + } + + if ($declaring_method_id && $declaring_method_storage->abstract) { + $implementer_method_storage = $codebase->methods->getStorage($declaring_method_id); + $declaring_storage = $codebase->classlike_storage_provider->get( + $actual_method_id->fq_class_name + ); + + MethodComparator::compare( + $codebase, + null, + $class_storage, + $declaring_storage, + $implementer_method_storage, + $declaring_method_storage, + $this->fq_class_name, + $implementer_method_storage->visibility, + new CodeLocation($source, $stmt), + $implementer_method_storage->suppressed_issues, + false + ); + } + + return null; + } + } + + $trait_safe_method_id = strtolower((string) $analyzed_method_id); + + $actual_method_id_str = strtolower((string) $actual_method_id); + + if ($actual_method_id_str !== $trait_safe_method_id) { + $trait_safe_method_id .= '&' . $actual_method_id_str; + } + + $method_already_analyzed = $codebase->analyzer->isMethodAlreadyAnalyzed( + $included_file_path, + $trait_safe_method_id + ); + + $start = (int)$stmt->getAttribute('startFilePos'); + $end = (int)$stmt->getAttribute('endFilePos'); + + $comments = $stmt->getComments(); + + if ($comments) { + $start = $comments[0]->getStartFilePos(); + } + + if ($codebase->diff_methods + && $method_already_analyzed + && !$class_context->collect_initializations + && !$class_context->collect_mutations + && !$is_fake + ) { + $project_analyzer->progress->debug( + 'Skipping analysis of pre-analyzed method ' . $analyzed_method_id . "\n" + ); + + $existing_issues = $codebase->analyzer->getExistingIssuesForFile( + $source->getFilePath(), + $start, + $end + ); + + IssueBuffer::addIssues([$source->getFilePath() => $existing_issues]); + + return $method_analyzer; + } + + $codebase->analyzer->removeExistingDataForFile( + $source->getFilePath(), + $start, + $end + ); + + $method_context = clone $class_context; + foreach ($method_context->vars_in_scope as $context_var_id => $context_type) { + $method_context->vars_in_scope[$context_var_id] = clone $context_type; + } + $method_context->collect_exceptions = $config->check_for_throws_docblock; + + $type_provider = new \Psalm\Internal\Provider\NodeDataProvider(); + + $method_analyzer->analyze( + $method_context, + $type_provider, + $global_context ? clone $global_context : null + ); + + if ($stmt->name->name !== '__construct' + && $config->reportIssueInFile('InvalidReturnType', $source->getFilePath()) + && $class_context->self + ) { + self::analyzeClassMethodReturnType( + $stmt, + $method_analyzer, + $source, + $type_provider, + $codebase, + $class_storage, + $class_context->self, + $analyzed_method_id, + $actual_method_id, + $method_context->has_returned + ); + } + + if (!$method_already_analyzed + && !$class_context->collect_initializations + && !$class_context->collect_mutations + && !$is_fake + ) { + $codebase->analyzer->setAnalyzedMethod($included_file_path, $trait_safe_method_id); + } + + return $method_analyzer; + } + + public static function analyzeClassMethodReturnType( + PhpParser\Node\Stmt\ClassMethod $stmt, + MethodAnalyzer $method_analyzer, + SourceAnalyzer $source, + \Psalm\Internal\Provider\NodeDataProvider $type_provider, + Codebase $codebase, + ClassLikeStorage $class_storage, + string $fq_classlike_name, + \Psalm\Internal\MethodIdentifier $analyzed_method_id, + \Psalm\Internal\MethodIdentifier $actual_method_id, + bool $did_explicitly_return + ) : void { + $secondary_return_type_location = null; + + $actual_method_storage = $codebase->methods->getStorage($actual_method_id); + + $return_type_location = $codebase->methods->getMethodReturnTypeLocation( + $actual_method_id, + $secondary_return_type_location + ); + + $original_fq_classlike_name = $fq_classlike_name; + + $return_type = $codebase->methods->getMethodReturnType( + $analyzed_method_id, + $fq_classlike_name, + $method_analyzer + ); + + if ($return_type && $class_storage->template_type_extends) { + $declaring_method_id = $codebase->methods->getDeclaringMethodId($analyzed_method_id); + + if ($declaring_method_id) { + $declaring_class_name = $declaring_method_id->fq_class_name; + + $class_storage = $codebase->classlike_storage_provider->get($declaring_class_name); + } + + if ($class_storage->template_types) { + $template_params = []; + + foreach ($class_storage->template_types as $param_name => $template_map) { + $key = array_keys($template_map)[0]; + + $template_params[] = new Type\Union([ + new Type\Atomic\TTemplateParam( + $param_name, + \reset($template_map)[0], + $key + ) + ]); + } + + $this_object_type = new Type\Atomic\TGenericObject( + $original_fq_classlike_name, + $template_params + ); + } else { + $this_object_type = new Type\Atomic\TNamedObject($original_fq_classlike_name); + } + + $class_template_params = ClassTemplateParamCollector::collect( + $codebase, + $class_storage, + $codebase->classlike_storage_provider->get($original_fq_classlike_name), + strtolower($stmt->name->name), + $this_object_type + ) ?: []; + + $template_result = new \Psalm\Internal\Type\TemplateResult( + $class_template_params ?: [], + [] + ); + + $return_type = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $return_type, + $template_result, + $codebase, + null, + null, + null, + $original_fq_classlike_name + ); + } + + $overridden_method_ids = isset($class_storage->overridden_method_ids[strtolower($stmt->name->name)]) + ? $class_storage->overridden_method_ids[strtolower($stmt->name->name)] + : []; + + if (!$return_type + && !$class_storage->is_interface + && $overridden_method_ids + ) { + foreach ($overridden_method_ids as $interface_method_id) { + $interface_class = $interface_method_id->fq_class_name; + + if (!$codebase->classlikes->interfaceExists($interface_class)) { + continue; + } + + $interface_return_type = $codebase->methods->getMethodReturnType( + $interface_method_id, + $interface_class + ); + + $interface_return_type_location = $codebase->methods->getMethodReturnTypeLocation( + $interface_method_id + ); + + FunctionLike\ReturnTypeAnalyzer::verifyReturnType( + $stmt, + $stmt->getStmts() ?: [], + $source, + $type_provider, + $method_analyzer, + $interface_return_type, + $interface_class, + $interface_return_type_location, + [$analyzed_method_id], + $did_explicitly_return + ); + } + } + + if ($actual_method_storage->overridden_downstream) { + $overridden_method_ids['overridden::downstream'] = 'overridden::downstream'; + } + + FunctionLike\ReturnTypeAnalyzer::verifyReturnType( + $stmt, + $stmt->getStmts() ?: [], + $source, + $type_provider, + $method_analyzer, + $return_type, + $fq_classlike_name, + $return_type_location, + $overridden_method_ids, + $did_explicitly_return + ); + } + + private function checkTemplateParams( + Codebase $codebase, + ClassLikeStorage $storage, + ClassLikeStorage $parent_storage, + CodeLocation $code_location, + int $expected_param_count + ): void { + $template_type_count = $parent_storage->template_types === null + ? 0 + : count($parent_storage->template_types); + + if ($template_type_count > $expected_param_count) { + if (IssueBuffer::accepts( + new MissingTemplateParam( + $storage->name . ' has missing template params, expecting ' + . $template_type_count, + $code_location + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } elseif ($template_type_count < $expected_param_count) { + if (IssueBuffer::accepts( + new TooManyTemplateParams( + $storage->name . ' has too many template params, expecting ' + . $template_type_count, + $code_location + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + + if ($parent_storage->template_types && $storage->template_type_extends) { + $i = 0; + + $previous_extended = []; + + foreach ($parent_storage->template_types as $template_name => $type_map) { + foreach ($type_map as $declaring_class => $template_type) { + if (isset($storage->template_type_extends[$parent_storage->name][$template_name])) { + $extended_type = $storage->template_type_extends[$parent_storage->name][$template_name]; + + if (isset($parent_storage->template_covariants[$i]) + && !$parent_storage->template_covariants[$i] + ) { + foreach ($extended_type->getAtomicTypes() as $t) { + if ($t instanceof Type\Atomic\TTemplateParam + && $storage->template_types + && $storage->template_covariants + && ($local_offset + = array_search($t->param_name, array_keys($storage->template_types))) + !== false + && !empty($storage->template_covariants[$local_offset]) + ) { + if (IssueBuffer::accepts( + new InvalidTemplateParam( + 'Cannot extend an invariant template param ' . $template_name + . ' into a covariant context', + $code_location + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + if (!$template_type[0]->isMixed()) { + $template_type_copy = clone $template_type[0]; + + $template_result = new \Psalm\Internal\Type\TemplateResult( + $previous_extended ?: [], + [] + ); + + $template_type_copy = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $template_type_copy, + $template_result, + $codebase, + null, + $extended_type, + null, + null + ); + + if (!UnionTypeComparator::isContainedBy($codebase, $extended_type, $template_type_copy)) { + if (IssueBuffer::accepts( + new InvalidTemplateParam( + 'Extended template param ' . $template_name + . ' expects type ' . $template_type_copy->getId() + . ', type ' . $extended_type->getId() . ' given', + $code_location + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + )) { + // fall through + } + } else { + $previous_extended[$template_name] = [ + $declaring_class => [$extended_type] + ]; + } + } else { + $previous_extended[$template_name] = [ + $declaring_class => [$extended_type] + ]; + } + } + } + + $i++; + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..d901bc9ddb99c0428c4129ca86903b42d1ec9dc3 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php @@ -0,0 +1,651 @@ + 'int', + 'string' => 'string', + 'float' => 'float', + 'bool' => 'bool', + 'false' => 'false', + 'object' => 'object', + 'empty' => 'empty', + 'callable' => 'callable', + 'array' => 'array', + 'iterable' => 'iterable', + 'null' => 'null', + 'mixed' => 'mixed', + ]; + + public const GETTYPE_TYPES = [ + 'boolean' => true, + 'integer' => true, + 'double' => true, + 'string' => true, + 'array' => true, + 'object' => true, + 'resource' => true, + 'resource (closed)' => true, + 'NULL' => true, + 'unknown type' => true, + ]; + + /** + * @var PhpParser\Node\Stmt\ClassLike + */ + protected $class; + + /** + * @var StatementsSource + */ + protected $source; + + /** @var FileAnalyzer */ + public $file_analyzer; + + /** + * @var string + */ + protected $fq_class_name; + + /** + * The parent class + * + * @var string|null + */ + protected $parent_fq_class_name; + + /** + * @var PhpParser\Node\Stmt[] + */ + protected $leftover_stmts = []; + + /** @var ClassLikeStorage */ + protected $storage; + + public function __construct(PhpParser\Node\Stmt\ClassLike $class, SourceAnalyzer $source, string $fq_class_name) + { + $this->class = $class; + $this->source = $source; + $this->file_analyzer = $source->getFileAnalyzer(); + $this->fq_class_name = $fq_class_name; + $codebase = $source->getCodebase(); + $this->storage = $codebase->classlike_storage_provider->get($fq_class_name); + } + + public function __destruct() + { + $this->source = null; + $this->file_analyzer = null; + } + + public function getMethodMutations( + string $method_name, + Context $context + ): void { + $project_analyzer = $this->getFileAnalyzer()->project_analyzer; + $codebase = $project_analyzer->getCodebase(); + + foreach ($this->class->stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod && + strtolower($stmt->name->name) === strtolower($method_name) + ) { + $method_analyzer = new MethodAnalyzer($stmt, $this); + + $method_analyzer->analyze($context, new \Psalm\Internal\Provider\NodeDataProvider(), null, true); + + $context->clauses = []; + } elseif ($stmt instanceof PhpParser\Node\Stmt\TraitUse) { + foreach ($stmt->traits as $trait) { + $fq_trait_name = self::getFQCLNFromNameObject( + $trait, + $this->source->getAliases() + ); + + $trait_file_analyzer = $project_analyzer->getFileAnalyzerForClassLike($fq_trait_name); + $trait_node = $codebase->classlikes->getTraitNode($fq_trait_name); + $trait_storage = $codebase->classlike_storage_provider->get($fq_trait_name); + $trait_aliases = $trait_storage->aliases; + + if ($trait_aliases === null) { + continue; + } + + $trait_analyzer = new TraitAnalyzer( + $trait_node, + $trait_file_analyzer, + $fq_trait_name, + $trait_aliases + ); + + foreach ($trait_node->stmts as $trait_stmt) { + if ($trait_stmt instanceof PhpParser\Node\Stmt\ClassMethod && + strtolower($trait_stmt->name->name) === strtolower($method_name) + ) { + $method_analyzer = new MethodAnalyzer($trait_stmt, $trait_analyzer); + + $actual_method_id = $method_analyzer->getMethodId(); + + if ($context->self && $context->self !== $this->fq_class_name) { + $analyzed_method_id = $method_analyzer->getMethodId($context->self); + $declaring_method_id = $codebase->methods->getDeclaringMethodId($analyzed_method_id); + + if ((string) $actual_method_id !== (string) $declaring_method_id) { + break; + } + } + + $method_analyzer->analyze( + $context, + new \Psalm\Internal\Provider\NodeDataProvider(), + null, + true + ); + } + } + + $trait_file_analyzer->clearSourceBeforeDestruction(); + } + } + } + } + + public function getFunctionLikeAnalyzer(string $method_name) : ?MethodAnalyzer + { + foreach ($this->class->stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod && + strtolower($stmt->name->name) === strtolower($method_name) + ) { + return new MethodAnalyzer($stmt, $this); + } + } + + return null; + } + + /** + * @param array $suppressed_issues + * @param bool $inferred - whether or not the type was inferred + */ + public static function checkFullyQualifiedClassLikeName( + StatementsSource $statements_source, + string $fq_class_name, + CodeLocation $code_location, + ?string $calling_fq_class_name, + ?string $calling_method_id, + array $suppressed_issues, + bool $inferred = true, + bool $allow_trait = false, + bool $allow_interface = true, + bool $from_docblock = false + ): ?bool { + $codebase = $statements_source->getCodebase(); + if ($fq_class_name === '') { + if (IssueBuffer::accepts( + new UndefinedClass( + 'Class or interface does not exist', + $code_location, + 'empty string' + ), + $suppressed_issues + )) { + return false; + } + + return null; + } + + $fq_class_name = preg_replace('/^\\\/', '', $fq_class_name); + + if (in_array($fq_class_name, ['callable', 'iterable', 'self', 'static', 'parent'], true)) { + return true; + } + + if (preg_match( + '/(^|\\\)(int|float|bool|string|void|null|false|true|object|mixed)$/i', + $fq_class_name + ) || strtolower($fq_class_name) === 'resource' + ) { + $class_name_parts = explode('\\', $fq_class_name); + $class_name = array_pop($class_name_parts); + + if (IssueBuffer::accepts( + new ReservedWord( + $class_name . ' is a reserved word', + $code_location, + $class_name + ), + $suppressed_issues + )) { + // fall through + } + + return null; + } + + $class_exists = $codebase->classlikes->classExists( + $fq_class_name, + !$inferred ? $code_location : null, + $calling_fq_class_name, + $calling_method_id + ); + $interface_exists = $codebase->classlikes->interfaceExists( + $fq_class_name, + !$inferred ? $code_location : null, + $calling_fq_class_name, + $calling_method_id + ); + + if (!$class_exists && !$interface_exists) { + if (!$allow_trait || !$codebase->classlikes->traitExists($fq_class_name, $code_location)) { + if ($from_docblock) { + if (IssueBuffer::accepts( + new UndefinedDocblockClass( + 'Docblock-defined class or interface ' . $fq_class_name . ' does not exist', + $code_location, + $fq_class_name + ), + $suppressed_issues + )) { + return false; + } + } else { + if (IssueBuffer::accepts( + new UndefinedClass( + 'Class or interface ' . $fq_class_name . ' does not exist', + $code_location, + $fq_class_name + ), + $suppressed_issues + )) { + return false; + } + } + } + + return null; + } elseif ($interface_exists && !$allow_interface) { + if (IssueBuffer::accepts( + new UndefinedClass( + 'Class ' . $fq_class_name . ' does not exist', + $code_location, + $fq_class_name + ), + $suppressed_issues + )) { + return false; + } + } + + $aliased_name = $codebase->classlikes->getUnAliasedName( + $fq_class_name + ); + + try { + $class_storage = $codebase->classlike_storage_provider->get($aliased_name); + } catch (\InvalidArgumentException $e) { + if (!$inferred) { + throw $e; + } + + return null; + } + + foreach ($class_storage->invalid_dependencies as $dependency_class_name) { + // if the implemented/extended class is stubbed, it may not yet have + // been hydrated + if ($codebase->classlike_storage_provider->has($dependency_class_name)) { + continue; + } + + if (IssueBuffer::accepts( + new MissingDependency( + $fq_class_name . ' depends on class or interface ' + . $dependency_class_name . ' that does not exist', + $code_location, + $fq_class_name + ), + $suppressed_issues + )) { + return false; + } + } + + if (!$inferred) { + if (($class_exists && !$codebase->classHasCorrectCasing($fq_class_name)) || + ($interface_exists && !$codebase->interfaceHasCorrectCasing($fq_class_name)) + ) { + if ($codebase->classlikes->isUserDefined(strtolower($aliased_name))) { + if (IssueBuffer::accepts( + new InvalidClass( + 'Class or interface ' . $fq_class_name . ' has wrong casing', + $code_location, + $fq_class_name + ), + $suppressed_issues + )) { + // fall through here + } + } + } + } + + if (!$inferred) { + $plugin_classes = $codebase->config->after_classlike_exists_checks; + + if ($plugin_classes) { + $file_manipulations = []; + + foreach ($plugin_classes as $plugin_fq_class_name) { + $plugin_fq_class_name::afterClassLikeExistenceCheck( + $fq_class_name, + $code_location, + $statements_source, + $codebase, + $file_manipulations + ); + } + + if ($file_manipulations) { + FileManipulationBuffer::add($code_location->file_path, $file_manipulations); + } + } + } + + return true; + } + + /** + * Gets the fully-qualified class name from a Name object + * + * + */ + public static function getFQCLNFromNameObject( + PhpParser\Node\Name $class_name, + Aliases $aliases + ): string { + /** @var string|null */ + $resolved_name = $class_name->getAttribute('resolvedName'); + + if ($resolved_name) { + return $resolved_name; + } + + if ($class_name instanceof PhpParser\Node\Name\FullyQualified) { + return implode('\\', $class_name->parts); + } + + if (in_array($class_name->parts[0], ['self', 'static', 'parent'], true)) { + return $class_name->parts[0]; + } + + return Type::getFQCLNFromString( + implode('\\', $class_name->parts), + $aliases + ); + } + + /** + * @return array + */ + public function getAliasedClassesFlipped(): array + { + if ($this->source instanceof NamespaceAnalyzer || $this->source instanceof FileAnalyzer) { + return $this->source->getAliasedClassesFlipped(); + } + + return []; + } + + /** + * @return array + */ + public function getAliasedClassesFlippedReplaceable(): array + { + if ($this->source instanceof NamespaceAnalyzer || $this->source instanceof FileAnalyzer) { + return $this->source->getAliasedClassesFlippedReplaceable(); + } + + return []; + } + + public function getFQCLN(): string + { + return $this->fq_class_name; + } + + public function getClassName(): ?string + { + return $this->class->name ? $this->class->name->name : null; + } + + /** + * @return array>|null + */ + public function getTemplateTypeMap(): ?array + { + return $this->storage->template_types; + } + + public function getParentFQCLN(): ?string + { + return $this->parent_fq_class_name; + } + + public function isStatic(): bool + { + return false; + } + + /** + * Gets the Psalm type from a particular value + * + * @param mixed $value + * + */ + public static function getTypeFromValue($value): Type\Union + { + switch (gettype($value)) { + case 'boolean': + if ($value) { + return Type::getTrue(); + } + + return Type::getFalse(); + + case 'integer': + return Type::getInt(false, $value); + + case 'double': + return Type::getFloat($value); + + case 'string': + return Type::getString($value); + + case 'array': + return Type::getArray(); + + case 'NULL': + return Type::getNull(); + + default: + return Type::getMixed(); + } + } + + /** + * @param string[] $suppressed_issues + */ + public static function checkPropertyVisibility( + string $property_id, + Context $context, + SourceAnalyzer $source, + CodeLocation $code_location, + array $suppressed_issues, + bool $emit_issues = true + ): ?bool { + [$fq_class_name, $property_name] = explode('::$', $property_id); + + $codebase = $source->getCodebase(); + + if ($codebase->properties->property_visibility_provider->has($fq_class_name)) { + $property_visible = $codebase->properties->property_visibility_provider->isPropertyVisible( + $source, + $fq_class_name, + $property_name, + true, + $context, + $code_location + ); + + if ($property_visible !== null) { + return $property_visible; + } + } + + $declaring_property_class = $codebase->properties->getDeclaringClassForProperty( + $property_id, + true + ); + $appearing_property_class = $codebase->properties->getAppearingClassForProperty( + $property_id, + true + ); + + if (!$declaring_property_class || !$appearing_property_class) { + throw new \UnexpectedValueException( + 'Appearing/Declaring classes are not defined for ' . $property_id + ); + } + + // if the calling class is the same, we know the property exists, so it must be visible + if ($appearing_property_class === $context->self) { + return $emit_issues ? null : true; + } + + if ($source->getSource() instanceof TraitAnalyzer + && strtolower($declaring_property_class) === strtolower((string) $source->getFQCLN()) + ) { + return $emit_issues ? null : true; + } + + $class_storage = $codebase->classlike_storage_provider->get($declaring_property_class); + + if (!isset($class_storage->properties[$property_name])) { + throw new \UnexpectedValueException('$storage should not be null for ' . $property_id); + } + + $storage = $class_storage->properties[$property_name]; + + switch ($storage->visibility) { + case self::VISIBILITY_PUBLIC: + return $emit_issues ? null : true; + + case self::VISIBILITY_PRIVATE: + if (!$context->self || $appearing_property_class !== $context->self) { + if ($emit_issues && IssueBuffer::accepts( + new InaccessibleProperty( + 'Cannot access private property ' . $property_id . ' from context ' . $context->self, + $code_location + ), + $suppressed_issues + )) { + // fall through + } + + return null; + } + + return $emit_issues ? null : true; + + case self::VISIBILITY_PROTECTED: + if ($appearing_property_class === $context->self) { + return null; + } + + if (!$context->self) { + if ($emit_issues && IssueBuffer::accepts( + new InaccessibleProperty( + 'Cannot access protected property ' . $property_id, + $code_location + ), + $suppressed_issues + )) { + // fall through + } + + return null; + } + + if ($codebase->classExtends($appearing_property_class, $context->self)) { + return $emit_issues ? null : true; + } + + if (!$codebase->classExtends($context->self, $appearing_property_class)) { + if ($emit_issues && IssueBuffer::accepts( + new InaccessibleProperty( + 'Cannot access protected property ' . $property_id . ' from context ' . $context->self, + $code_location + ), + $suppressed_issues + )) { + // fall through + } + + return null; + } + } + + return $emit_issues ? null : true; + } + + /** + * @return array + */ + public static function getClassesForFile(Codebase $codebase, string $file_path): array + { + try { + return $codebase->file_storage_provider->get($file_path)->classlikes_in_file; + } catch (\InvalidArgumentException $e) { + return []; + } + } + + public function getFileAnalyzer() : FileAnalyzer + { + return $this->file_analyzer; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..87eab23ca304cccce8710b8417791e2520de966b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php @@ -0,0 +1,335 @@ +getCodebase(); + + $function_id = \strtolower($source->getFilePath()) + . ':' . $function->getLine() + . ':' . (int)$function->getAttribute('startFilePos') + . ':-:closure'; + + $storage = $codebase->getClosureStorage($source->getFilePath(), $function_id); + + parent::__construct($function, $source, $storage); + } + + public function getTemplateTypeMap(): ?array + { + return $this->source->getTemplateTypeMap(); + } + + /** + * @return non-empty-lowercase-string + */ + public function getClosureId(): string + { + return strtolower($this->getFilePath()) + . ':' . $this->function->getLine() + . ':' . (int)$this->function->getAttribute('startFilePos') + . ':-:closure'; + } + + /** + * @param PhpParser\Node\Expr\Closure|PhpParser\Node\Expr\ArrowFunction $stmt + */ + public static function analyzeExpression( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\FunctionLike $stmt, + Context $context + ) : bool { + $closure_analyzer = new ClosureAnalyzer($stmt, $statements_analyzer); + + if ($stmt instanceof PhpParser\Node\Expr\Closure + && self::analyzeClosureUses($statements_analyzer, $stmt, $context) === false + ) { + return false; + } + + $use_context = new Context($context->self); + + $codebase = $statements_analyzer->getCodebase(); + + if (!$statements_analyzer->isStatic()) { + if ($context->collect_mutations && + $context->self && + $codebase->classExtends( + $context->self, + (string)$statements_analyzer->getFQCLN() + ) + ) { + /** @psalm-suppress PossiblyUndefinedStringArrayOffset */ + $use_context->vars_in_scope['$this'] = clone $context->vars_in_scope['$this']; + } elseif ($context->self) { + $this_atomic = new TNamedObject($context->self); + $this_atomic->was_static = true; + + $use_context->vars_in_scope['$this'] = new Type\Union([$this_atomic]); + } + } + + foreach ($context->vars_in_scope as $var => $type) { + if (strpos($var, '$this->') === 0) { + $use_context->vars_in_scope[$var] = clone $type; + } + } + + if ($context->self) { + $self_class_storage = $codebase->classlike_storage_provider->get($context->self); + + ClassAnalyzer::addContextProperties( + $statements_analyzer, + $self_class_storage, + $use_context, + $context->self, + $statements_analyzer->getParentFQCLN() + ); + } + + foreach ($context->vars_possibly_in_scope as $var => $_) { + if (strpos($var, '$this->') === 0) { + $use_context->vars_possibly_in_scope[$var] = true; + } + } + + $byref_uses = []; + + if ($stmt instanceof PhpParser\Node\Expr\Closure) { + foreach ($stmt->uses as $use) { + if (!is_string($use->var->name)) { + continue; + } + + $use_var_id = '$' . $use->var->name; + + if ($use->byRef) { + $byref_uses[$use_var_id] = true; + } + + // insert the ref into the current context if passed by ref, as whatever we're passing + // the closure to could execute it straight away. + if (!$context->hasVariable($use_var_id) && $use->byRef) { + $context->vars_in_scope[$use_var_id] = Type::getMixed(); + } + + if ($statements_analyzer->data_flow_graph instanceof \Psalm\Internal\Codebase\VariableUseGraph) { + $parent_nodes = $context->vars_in_scope[$use_var_id]->parent_nodes; + + foreach ($parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + new DataFlowNode('closure-use', 'closure use', null), + 'closure-use' + ); + } + } + + $use_context->vars_in_scope[$use_var_id] = + $context->hasVariable($use_var_id) && !$use->byRef + ? clone $context->vars_in_scope[$use_var_id] + : Type::getMixed(); + + $use_context->vars_possibly_in_scope[$use_var_id] = true; + } + } else { + $traverser = new PhpParser\NodeTraverser; + + $short_closure_visitor = new \Psalm\Internal\PhpVisitor\ShortClosureVisitor(); + + $traverser->addVisitor($short_closure_visitor); + $traverser->traverse($stmt->getStmts()); + + foreach ($short_closure_visitor->getUsedVariables() as $use_var_id => $_) { + if ($context->hasVariable($use_var_id)) { + $use_context->vars_in_scope[$use_var_id] = clone $context->vars_in_scope[$use_var_id]; + + if ($statements_analyzer->data_flow_graph instanceof \Psalm\Internal\Codebase\VariableUseGraph) { + $parent_nodes = $context->vars_in_scope[$use_var_id]->parent_nodes; + + foreach ($parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + new DataFlowNode('closure-use', 'closure use', null), + 'closure-use' + ); + } + } + } else { + $use_context->vars_in_scope[$use_var_id] = Type::getMixed(); + } + + $use_context->vars_possibly_in_scope[$use_var_id] = true; + } + } + + $use_context->calling_method_id = $context->calling_method_id; + + $closure_analyzer->analyze($use_context, $statements_analyzer->node_data, $context, false, $byref_uses); + + if ($closure_analyzer->inferred_impure + && $statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + ) { + $statements_analyzer->getSource()->inferred_impure = true; + } + + if ($closure_analyzer->inferred_has_mutation + && $statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + } + + if (!$statements_analyzer->node_data->getType($stmt)) { + $statements_analyzer->node_data->setType($stmt, Type::getClosure()); + } + + return true; + } + + /** + * @return false|null + */ + public static function analyzeClosureUses( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\Closure $stmt, + Context $context + ): ?bool { + $param_names = array_map( + function (PhpParser\Node\Param $p) : string { + if (!$p->var instanceof PhpParser\Node\Expr\Variable + || !is_string($p->var->name) + ) { + return ''; + } + return $p->var->name; + }, + $stmt->params + ); + + foreach ($stmt->uses as $use) { + if (!is_string($use->var->name)) { + continue; + } + + $use_var_id = '$' . $use->var->name; + + if (in_array($use->var->name, $param_names)) { + if (IssueBuffer::accepts( + new DuplicateParam( + 'Closure use duplicates param name ' . $use_var_id, + new CodeLocation($statements_analyzer->getSource(), $use->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + + if (!$context->hasVariable($use_var_id)) { + if ($use_var_id === '$argv' || $use_var_id === '$argc') { + continue; + } + + if ($use->byRef) { + $context->vars_in_scope[$use_var_id] = Type::getMixed(); + $context->vars_possibly_in_scope[$use_var_id] = true; + + if (!$statements_analyzer->hasVariable($use_var_id)) { + $statements_analyzer->registerVariable( + $use_var_id, + new CodeLocation($statements_analyzer, $use->var), + null + ); + } + + return null; + } + + if (!isset($context->vars_possibly_in_scope[$use_var_id])) { + if ($context->check_variables) { + if (IssueBuffer::accepts( + new UndefinedVariable( + 'Cannot find referenced variable ' . $use_var_id, + new CodeLocation($statements_analyzer->getSource(), $use->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return null; + } + } + + $first_appearance = $statements_analyzer->getFirstAppearance($use_var_id); + + if ($first_appearance) { + if (IssueBuffer::accepts( + new PossiblyUndefinedVariable( + 'Possibly undefined variable ' . $use_var_id . ', first seen on line ' . + $first_appearance->getLineNumber(), + new CodeLocation($statements_analyzer->getSource(), $use->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + continue; + } + + if ($context->check_variables) { + if (IssueBuffer::accepts( + new UndefinedVariable( + 'Cannot find referenced variable ' . $use_var_id, + new CodeLocation($statements_analyzer->getSource(), $use->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + continue; + } + } elseif ($use->byRef) { + $new_type = Type::getMixed(); + $new_type->parent_nodes = $context->vars_in_scope[$use_var_id]->parent_nodes; + + $context->remove($use_var_id); + + $context->vars_in_scope[$use_var_id] = $new_type; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/CommentAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/CommentAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..4f974f974ae177e4a67c2c3b6634e8e9866f7fbf --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/CommentAnalyzer.php @@ -0,0 +1,1430 @@ +\[\]\-\{\}:|?\\\\]*|\$[a-zA-Z_0-9_]+)'; + + /** + * @param array>|null $template_type_map + * @param array $type_aliases + * + * @throws DocblockParseException if there was a problem parsing the docblock + * + * @return list + */ + public static function getTypeFromComment( + PhpParser\Comment\Doc $comment, + FileSource $source, + Aliases $aliases, + ?array $template_type_map = null, + ?array $type_aliases = null + ): array { + $parsed_docblock = DocComment::parsePreservingLength($comment); + + return self::arrayToDocblocks( + $comment, + $parsed_docblock, + $source, + $aliases, + $template_type_map, + $type_aliases + ); + } + + /** + * @param array>|null $template_type_map + * @param array $type_aliases + * + * @return list + * + * @throws DocblockParseException if there was a problem parsing the docblock + */ + public static function arrayToDocblocks( + PhpParser\Comment\Doc $comment, + ParsedDocblock $parsed_docblock, + FileSource $source, + Aliases $aliases, + ?array $template_type_map = null, + ?array $type_aliases = null + ) : array { + $var_id = null; + + $var_type_tokens = null; + $original_type = null; + + $var_comments = []; + + $comment_text = $comment->getText(); + + $var_line_number = $comment->getStartLine(); + + if (isset($parsed_docblock->combined_tags['var'])) { + foreach ($parsed_docblock->combined_tags['var'] as $offset => $var_line) { + $var_line = trim($var_line); + + if (!$var_line) { + continue; + } + + $type_start = null; + $type_end = null; + + $line_parts = self::splitDocLine($var_line); + + $line_number = $comment->getStartLine() + substr_count($comment_text, "\n", 0, $offset); + + if ($line_parts && $line_parts[0]) { + $type_start = $offset + $comment->getStartFilePos(); + $type_end = $type_start + strlen($line_parts[0]); + + $line_parts[0] = self::sanitizeDocblockType($line_parts[0]); + + if ($line_parts[0] === '' + || ($line_parts[0][0] === '$' + && !preg_match('/^\$this(\||$)/', $line_parts[0])) + ) { + throw new IncorrectDocblockException('Misplaced variable'); + } + + try { + $var_type_tokens = TypeTokenizer::getFullyQualifiedTokens( + $line_parts[0], + $aliases, + $template_type_map, + $type_aliases + ); + } catch (TypeParseTreeException $e) { + throw new DocblockParseException($line_parts[0] . ' is not a valid type'); + } + + $original_type = $line_parts[0]; + + $var_line_number = $line_number; + + if (count($line_parts) > 1 && $line_parts[1][0] === '$') { + $var_id = $line_parts[1]; + } + } + + if (!$var_type_tokens || !$original_type) { + continue; + } + + try { + $defined_type = TypeParser::parseTokens( + $var_type_tokens, + null, + $template_type_map ?: [], + $type_aliases ?: [] + ); + } catch (TypeParseTreeException $e) { + throw new DocblockParseException( + $line_parts[0] . + ' is not a valid type' . + ' (from ' . + $source->getFilePath() . + ':' . + $comment->getStartLine() . + ')' + ); + } + + $defined_type->setFromDocblock(); + + $var_comment = new VarDocblockComment(); + $var_comment->type = $defined_type; + $var_comment->original_type = $original_type; + $var_comment->var_id = $var_id; + $var_comment->line_number = $var_line_number; + $var_comment->type_start = $type_start; + $var_comment->type_end = $type_end; + + self::decorateVarDocblockComment($var_comment, $parsed_docblock); + + $var_comments[] = $var_comment; + } + } + + if (!$var_comments + && (isset($parsed_docblock->tags['deprecated']) + || isset($parsed_docblock->tags['internal']) + || isset($parsed_docblock->tags['readonly']) + || isset($parsed_docblock->tags['psalm-readonly']) + || isset($parsed_docblock->tags['psalm-readonly-allow-private-mutation']) + || isset($parsed_docblock->tags['psalm-taint-escape']) + || isset($parsed_docblock->tags['psalm-internal'])) + ) { + $var_comment = new VarDocblockComment(); + + self::decorateVarDocblockComment($var_comment, $parsed_docblock); + + $var_comments[] = $var_comment; + } + + return $var_comments; + } + + private static function decorateVarDocblockComment( + VarDocblockComment $var_comment, + ParsedDocblock $parsed_docblock + ) : void { + $var_comment->deprecated = isset($parsed_docblock->tags['deprecated']); + $var_comment->internal = isset($parsed_docblock->tags['internal']); + $var_comment->readonly = isset($parsed_docblock->tags['readonly']) + || isset($parsed_docblock->tags['psalm-readonly']) + || isset($parsed_docblock->tags['psalm-readonly-allow-private-mutation']); + + $var_comment->allow_private_mutation + = isset($parsed_docblock->tags['psalm-allow-private-mutation']) + || isset($parsed_docblock->tags['psalm-readonly-allow-private-mutation']); + + if (isset($parsed_docblock->tags['psalm-taint-escape'])) { + foreach ($parsed_docblock->tags['psalm-taint-escape'] as $param) { + $param = trim($param); + $var_comment->removed_taints[] = $param; + } + } + + if (isset($parsed_docblock->tags['psalm-internal'])) { + $psalm_internal = reset($parsed_docblock->tags['psalm-internal']); + + if (!$psalm_internal) { + throw new DocblockParseException('psalm-internal annotation used without specifying namespace'); + } + + $var_comment->psalm_internal = reset($parsed_docblock->tags['psalm-internal']); + $var_comment->internal = true; + } + } + + /** + * @psalm-pure + */ + private static function sanitizeDocblockType(string $docblock_type) : string + { + $docblock_type = preg_replace('@^[ \t]*\*@m', '', $docblock_type); + $docblock_type = preg_replace('/,\n\s+\}/', '}', $docblock_type); + return str_replace("\n", '', $docblock_type); + } + + /** + * @param array $type_aliases + * + * @return array + * + * @throws DocblockParseException if there was a problem parsing the docblock + */ + public static function getTypeAliasesFromComment( + PhpParser\Comment\Doc $comment, + Aliases $aliases, + ?array $type_aliases, + ?string $self_fqcln + ): array { + $parsed_docblock = DocComment::parsePreservingLength($comment); + + if (!isset($parsed_docblock->tags['psalm-type'])) { + return []; + } + + return self::getTypeAliasesFromCommentLines( + $parsed_docblock->tags['psalm-type'], + $aliases, + $type_aliases, + $self_fqcln + ); + } + + /** + * @param array $type_alias_comment_lines + * @param array $type_aliases + * + * @return array + * + * @throws DocblockParseException if there was a problem parsing the docblock + */ + private static function getTypeAliasesFromCommentLines( + array $type_alias_comment_lines, + Aliases $aliases, + ?array $type_aliases, + ?string $self_fqcln + ): array { + $type_alias_tokens = []; + + foreach ($type_alias_comment_lines as $var_line) { + $var_line = trim($var_line); + + if (!$var_line) { + continue; + } + + $var_line = preg_replace('/[ \t]+/', ' ', preg_replace('@^[ \t]*\*@m', '', $var_line)); + $var_line = preg_replace('/,\n\s+\}/', '}', $var_line); + $var_line = str_replace("\n", '', $var_line); + + $var_line_parts = preg_split('/( |=)/', $var_line, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + + if (!$var_line_parts) { + continue; + } + + $type_alias = array_shift($var_line_parts); + + if (!isset($var_line_parts[0])) { + continue; + } + + if ($var_line_parts[0] === ' ') { + array_shift($var_line_parts); + } + + if ($var_line_parts[0] === '=') { + array_shift($var_line_parts); + } + + if (!isset($var_line_parts[0])) { + continue; + } + + if ($var_line_parts[0] === ' ') { + array_shift($var_line_parts); + } + + $type_string = str_replace("\n", '', implode('', $var_line_parts)); + + $type_string = preg_replace('/>[^>^\}]*$/', '>', $type_string); + $type_string = preg_replace('/\}[^>^\}]*$/', '}', $type_string); + + try { + $type_tokens = TypeTokenizer::getFullyQualifiedTokens( + $type_string, + $aliases, + null, + $type_alias_tokens + $type_aliases, + $self_fqcln + ); + } catch (TypeParseTreeException $e) { + throw new DocblockParseException($type_string . ' is not a valid type'); + } + + $type_alias_tokens[$type_alias] = new TypeAlias\InlineTypeAlias($type_tokens); + } + + return $type_alias_tokens; + } + + /** + * @throws DocblockParseException if there was a problem parsing the docblock + */ + public static function extractFunctionDocblockInfo(PhpParser\Comment\Doc $comment): FunctionDocblockComment + { + $parsed_docblock = DocComment::parsePreservingLength($comment); + + $comment_text = $comment->getText(); + + $info = new FunctionDocblockComment(); + + self::checkDuplicatedTags($parsed_docblock); + + if (isset($parsed_docblock->combined_tags['return'])) { + self::extractReturnType( + $comment, + $parsed_docblock->combined_tags['return'], + $info + ); + } + + if (isset($parsed_docblock->combined_tags['param'])) { + foreach ($parsed_docblock->combined_tags['param'] as $offset => $param) { + $line_parts = self::splitDocLine($param); + + if (count($line_parts) === 1 && isset($line_parts[0][0]) && $line_parts[0][0] === '$') { + continue; + } + + if (count($line_parts) > 1) { + if (preg_match('/^&?(\.\.\.)?&?\$[A-Za-z0-9_]+,?$/', $line_parts[1]) + && $line_parts[0][0] !== '{' + ) { + $line_parts[1] = str_replace('&', '', $line_parts[1]); + + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + + $start = $offset + $comment->getStartFilePos(); + $end = $start + strlen($line_parts[0]); + + $line_parts[0] = self::sanitizeDocblockType($line_parts[0]); + + if ($line_parts[0] === '' + || ($line_parts[0][0] === '$' + && !preg_match('/^\$this(\||$)/', $line_parts[0])) + ) { + throw new IncorrectDocblockException('Misplaced variable'); + } + + $info->params[] = [ + 'name' => trim($line_parts[1]), + 'type' => $line_parts[0], + 'line_number' => $comment->getStartLine() + substr_count($comment_text, "\n", 0, $offset), + 'start' => $start, + 'end' => $end, + ]; + } + } else { + throw new DocblockParseException('Badly-formatted @param'); + } + } + } + + if (isset($parsed_docblock->tags['param-out'])) { + foreach ($parsed_docblock->tags['param-out'] as $offset => $param) { + $line_parts = self::splitDocLine($param); + + if (count($line_parts) === 1 && isset($line_parts[0][0]) && $line_parts[0][0] === '$') { + continue; + } + + if (count($line_parts) > 1) { + if (!preg_match('/\[[^\]]+\]/', $line_parts[0]) + && preg_match('/^(\.\.\.)?&?\$[A-Za-z0-9_]+,?$/', $line_parts[1]) + && $line_parts[0][0] !== '{' + ) { + if ($line_parts[1][0] === '&') { + $line_parts[1] = substr($line_parts[1], 1); + } + + $line_parts[0] = str_replace("\n", '', preg_replace('@^[ \t]*\*@m', '', $line_parts[0])); + + if ($line_parts[0] === '' + || ($line_parts[0][0] === '$' + && !preg_match('/^\$this(\||$)/', $line_parts[0])) + ) { + throw new IncorrectDocblockException('Misplaced variable'); + } + + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + + $info->params_out[] = [ + 'name' => trim($line_parts[1]), + 'type' => str_replace("\n", '', $line_parts[0]), + 'line_number' => $comment->getStartLine() + substr_count($comment_text, "\n", 0, $offset), + ]; + } + } else { + throw new DocblockParseException('Badly-formatted @param'); + } + } + } + + if (isset($parsed_docblock->tags['psalm-self-out'])) { + foreach ($parsed_docblock->tags['psalm-self-out'] as $offset => $param) { + $line_parts = self::splitDocLine($param); + + if (count($line_parts) > 0) { + $line_parts[0] = str_replace("\n", '', preg_replace('@^[ \t]*\*@m', '', $line_parts[0])); + + $info->self_out = [ + 'type' => str_replace("\n", '', $line_parts[0]), + 'line_number' => $comment->getStartLine() + substr_count($comment_text, "\n", 0, $offset), + ]; + } + } + } + + if (isset($parsed_docblock->tags['psalm-flow'])) { + foreach ($parsed_docblock->tags['psalm-flow'] as $param) { + $info->flows[] = trim($param); + } + } + + if (isset($parsed_docblock->tags['psalm-taint-sink'])) { + foreach ($parsed_docblock->tags['psalm-taint-sink'] as $param) { + $param_parts = preg_split('/\s+/', trim($param)); + + if (count($param_parts) === 2) { + $info->taint_sink_params[] = ['name' => $param_parts[1], 'taint' => $param_parts[0]]; + } + } + } + + // support for MediaWiki taint plugin + if (isset($parsed_docblock->tags['param-taint'])) { + foreach ($parsed_docblock->tags['param-taint'] as $param) { + $param_parts = preg_split('/\s+/', trim($param)); + + if (count($param_parts) === 2) { + $taint_type = $param_parts[1]; + + if (substr($taint_type, 0, 5) === 'exec_') { + $taint_type = substr($taint_type, 5); + + if ($taint_type === 'tainted') { + $taint_type = 'input'; + } + + if ($taint_type === 'misc') { + $taint_type = 'text'; + } + + $info->taint_sink_params[] = ['name' => $param_parts[0], 'taint' => $taint_type]; + } + } + } + } + + if (isset($parsed_docblock->tags['psalm-taint-source'])) { + foreach ($parsed_docblock->tags['psalm-taint-source'] as $param) { + $param_parts = preg_split('/\s+/', trim($param)); + + if ($param_parts[0]) { + $info->taint_source_types[] = $param_parts[0]; + } + } + } elseif (isset($parsed_docblock->tags['return-taint'])) { + // support for MediaWiki taint plugin + foreach ($parsed_docblock->tags['return-taint'] as $param) { + $param_parts = preg_split('/\s+/', trim($param)); + + if ($param_parts[0]) { + if ($param_parts[0] === 'tainted') { + $param_parts[0] = 'input'; + } + + if ($param_parts[0] === 'misc') { + $param_parts[0] = 'text'; + } + + if ($param_parts[0] !== 'none') { + $info->taint_source_types[] = $param_parts[0]; + } + } + } + } + + if (isset($parsed_docblock->tags['psalm-taint-unescape'])) { + foreach ($parsed_docblock->tags['psalm-taint-unescape'] as $param) { + $param = trim($param); + $info->added_taints[] = $param; + } + } + + if (isset($parsed_docblock->tags['psalm-taint-escape'])) { + foreach ($parsed_docblock->tags['psalm-taint-escape'] as $param) { + $param = trim($param); + $info->removed_taints[] = $param; + } + } + + if (isset($parsed_docblock->tags['psalm-assert-untainted'])) { + foreach ($parsed_docblock->tags['psalm-assert-untainted'] as $param) { + $param = trim($param); + + $info->assert_untainted_params[] = ['name' => $param]; + } + } + + if (isset($parsed_docblock->tags['psalm-taint-specialize'])) { + $info->specialize_call = true; + } + + if (isset($parsed_docblock->tags['global'])) { + foreach ($parsed_docblock->tags['global'] as $offset => $global) { + $line_parts = self::splitDocLine($global); + + if (count($line_parts) === 1 && isset($line_parts[0][0]) && $line_parts[0][0] === '$') { + continue; + } + + if (count($line_parts) > 1) { + if (!preg_match('/\[[^\]]+\]/', $line_parts[0]) + && preg_match('/^(\.\.\.)?&?\$[A-Za-z0-9_]+,?$/', $line_parts[1]) + && $line_parts[0][0] !== '{' + ) { + if ($line_parts[1][0] === '&') { + $line_parts[1] = substr($line_parts[1], 1); + } + + if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) { + throw new IncorrectDocblockException('Misplaced variable'); + } + + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + + $info->globals[] = [ + 'name' => $line_parts[1], + 'type' => $line_parts[0], + 'line_number' => $comment->getStartLine() + substr_count($comment_text, "\n", 0, $offset), + ]; + } + } else { + throw new DocblockParseException('Badly-formatted @param'); + } + } + } + + if (isset($parsed_docblock->tags['deprecated'])) { + $info->deprecated = true; + } + + if (isset($parsed_docblock->tags['internal'])) { + $info->internal = true; + } + + if (isset($parsed_docblock->tags['psalm-internal'])) { + $psalm_internal = reset($parsed_docblock->tags['psalm-internal']); + if ($psalm_internal) { + $info->psalm_internal = $psalm_internal; + } else { + throw new DocblockParseException('@psalm-internal annotation used without specifying namespace'); + } + $info->psalm_internal = reset($parsed_docblock->tags['psalm-internal']); + $info->internal = true; + } + + if (isset($parsed_docblock->tags['psalm-suppress'])) { + foreach ($parsed_docblock->tags['psalm-suppress'] as $offset => $suppress_entry) { + foreach (DocComment::parseSuppressList($suppress_entry) as $issue_offset => $suppressed_issue) { + $info->suppressed_issues[$issue_offset + $offset + $comment->getStartFilePos()] = $suppressed_issue; + } + } + } + + if (isset($parsed_docblock->tags['throws'])) { + foreach ($parsed_docblock->tags['throws'] as $offset => $throws_entry) { + $throws_class = preg_split('/[\s]+/', $throws_entry)[0]; + + if (!$throws_class) { + throw new IncorrectDocblockException('Unexpectedly empty @throws'); + } + + $info->throws[] = [ + $throws_class, + $offset + $comment->getStartFilePos(), + $comment->getStartLine() + substr_count($comment->getText(), "\n", 0, $offset) + ]; + } + } + + if (strpos(strtolower($parsed_docblock->description), '@inheritdoc') !== false + || isset($parsed_docblock->tags['inheritdoc']) + || isset($parsed_docblock->tags['inheritDoc']) + ) { + $info->inheritdoc = true; + } + + if (isset($parsed_docblock->combined_tags['template'])) { + foreach ($parsed_docblock->combined_tags['template'] as $template_line) { + $template_type = preg_split('/[\s]+/', preg_replace('@^[ \t]*\*@m', '', $template_line)); + + $template_name = array_shift($template_type); + + if (!$template_name) { + throw new IncorrectDocblockException('Empty @template tag'); + } + + if (count($template_type) > 1 + && in_array(strtolower($template_type[0]), ['as', 'super', 'of'], true) + ) { + $template_modifier = strtolower(array_shift($template_type)); + $info->templates[] = [ + $template_name, + $template_modifier, + implode(' ', $template_type), + false + ]; + } else { + $info->templates[] = [$template_name, null, null, false]; + } + } + } + + if (isset($parsed_docblock->tags['psalm-assert'])) { + foreach ($parsed_docblock->tags['psalm-assert'] as $assertion) { + $line_parts = self::splitDocLine($assertion); + + if (count($line_parts) < 2 || $line_parts[1][0] !== '$') { + throw new IncorrectDocblockException('Misplaced variable'); + } + + $line_parts[0] = self::sanitizeDocblockType($line_parts[0]); + + $info->assertions[] = [ + 'type' => $line_parts[0], + 'param_name' => substr($line_parts[1], 1), + ]; + } + } + + if (isset($parsed_docblock->tags['psalm-assert-if-true'])) { + foreach ($parsed_docblock->tags['psalm-assert-if-true'] as $assertion) { + $line_parts = self::splitDocLine($assertion); + + if (count($line_parts) < 2 || $line_parts[1][0] !== '$') { + throw new IncorrectDocblockException('Misplaced variable'); + } + + $info->if_true_assertions[] = [ + 'type' => $line_parts[0], + 'param_name' => substr($line_parts[1], 1), + ]; + } + } + + if (isset($parsed_docblock->tags['psalm-assert-if-false'])) { + foreach ($parsed_docblock->tags['psalm-assert-if-false'] as $assertion) { + $line_parts = self::splitDocLine($assertion); + + if (count($line_parts) < 2 || $line_parts[1][0] !== '$') { + throw new IncorrectDocblockException('Misplaced variable'); + } + + $info->if_false_assertions[] = [ + 'type' => $line_parts[0], + 'param_name' => substr($line_parts[1], 1), + ]; + } + } + + $info->variadic = isset($parsed_docblock->tags['psalm-variadic']); + $info->pure = isset($parsed_docblock->tags['psalm-pure']) + || isset($parsed_docblock->tags['pure']); + + if (isset($parsed_docblock->tags['psalm-mutation-free'])) { + $info->mutation_free = true; + } + + if (isset($parsed_docblock->tags['psalm-external-mutation-free'])) { + $info->external_mutation_free = true; + } + + if (isset($parsed_docblock->tags['no-named-arguments'])) { + $info->no_named_args = true; + } + + $info->ignore_nullable_return = isset($parsed_docblock->tags['psalm-ignore-nullable-return']); + $info->ignore_falsable_return = isset($parsed_docblock->tags['psalm-ignore-falsable-return']); + $info->stub_override = isset($parsed_docblock->tags['psalm-stub-override']); + + return $info; + } + + /** + * @param array $return_specials + */ + private static function extractReturnType( + PhpParser\Comment\Doc $comment, + array $return_specials, + FunctionDocblockComment $info + ): void { + foreach ($return_specials as $offset => $return_block) { + $return_lines = explode("\n", $return_block); + + if (!trim($return_lines[0])) { + return; + } + + $return_block = trim($return_block); + + if (!$return_block) { + return; + } + + $line_parts = self::splitDocLine($return_block); + + if ($line_parts[0][0] !== '{') { + if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) { + throw new IncorrectDocblockException('Misplaced variable'); + } + + $start = $offset + $comment->getStartFilePos(); + $end = $start + strlen($line_parts[0]); + + $line_parts[0] = self::sanitizeDocblockType($line_parts[0]); + + $info->return_type = array_shift($line_parts); + $info->return_type_description = $line_parts ? implode(' ', $line_parts) : null; + + $info->return_type_line_number + = $comment->getStartLine() + substr_count($comment->getText(), "\n", 0, $offset); + $info->return_type_start = $start; + $info->return_type_end = $end; + } else { + throw new DocblockParseException('Badly-formatted @return type'); + } + + break; + } + } + + /** + * @throws DocblockParseException if there was a problem parsing the docblock + * + * @psalm-suppress MixedArrayAccess + */ + public static function extractClassLikeDocblockInfo( + \PhpParser\Node $node, + PhpParser\Comment\Doc $comment, + Aliases $aliases + ): ClassLikeDocblockComment { + $parsed_docblock = DocComment::parsePreservingLength($comment); + $codebase = ProjectAnalyzer::getInstance()->getCodebase(); + + $info = new ClassLikeDocblockComment(); + + if (isset($parsed_docblock->combined_tags['template'])) { + foreach ($parsed_docblock->combined_tags['template'] as $offset => $template_line) { + $template_type = preg_split('/[\s]+/', preg_replace('@^[ \t]*\*@m', '', $template_line)); + + $template_name = array_shift($template_type); + + if (!$template_name) { + throw new IncorrectDocblockException('Empty @template tag'); + } + + if (count($template_type) > 1 + && in_array(strtolower($template_type[0]), ['as', 'super', 'of'], true) + ) { + $template_modifier = strtolower(array_shift($template_type)); + $info->templates[] = [ + $template_name, + $template_modifier, + implode(' ', $template_type), + false, + $offset + ]; + } else { + $info->templates[] = [$template_name, null, null, false, $offset]; + } + } + } + + if (isset($parsed_docblock->combined_tags['template-covariant'])) { + foreach ($parsed_docblock->combined_tags['template-covariant'] as $offset => $template_line) { + $template_type = preg_split('/[\s]+/', preg_replace('@^[ \t]*\*@m', '', $template_line)); + + $template_name = array_shift($template_type); + + if (!$template_name) { + throw new IncorrectDocblockException('Empty @template-covariant tag'); + } + + if (count($template_type) > 1 + && in_array(strtolower($template_type[0]), ['as', 'super', 'of'], true) + ) { + $template_modifier = strtolower(array_shift($template_type)); + $info->templates[] = [ + $template_name, + $template_modifier, + implode(' ', $template_type), + true, + $offset + ]; + } else { + $info->templates[] = [$template_name, null, null, true, $offset]; + } + } + } + + if (isset($parsed_docblock->combined_tags['extends'])) { + foreach ($parsed_docblock->combined_tags['extends'] as $template_line) { + $info->template_extends[] = trim(preg_replace('@^[ \t]*\*@m', '', $template_line)); + } + } + + if (isset($parsed_docblock->tags['psalm-require-extends']) + && count($extension_requirements = $parsed_docblock->tags['psalm-require-extends']) > 0) { + $info->extension_requirement = trim(preg_replace( + '@^[ \t]*\*@m', + '', + $extension_requirements[array_key_first($extension_requirements)] + )); + } + + if (isset($parsed_docblock->tags['psalm-require-implements'])) { + foreach ($parsed_docblock->tags['psalm-require-implements'] as $implementation_requirement) { + $info->implementation_requirements[] = trim(preg_replace( + '@^[ \t]*\*@m', + '', + $implementation_requirement + )); + } + } + + if (isset($parsed_docblock->combined_tags['implements'])) { + foreach ($parsed_docblock->combined_tags['implements'] as $template_line) { + $info->template_implements[] = trim(preg_replace('@^[ \t]*\*@m', '', $template_line)); + } + } + + if (isset($parsed_docblock->tags['psalm-yield']) + ) { + $yield = reset($parsed_docblock->tags['psalm-yield']); + + $info->yield = trim(preg_replace('@^[ \t]*\*@m', '', $yield)); + } + + if (isset($parsed_docblock->tags['deprecated'])) { + $info->deprecated = true; + } + + if (isset($parsed_docblock->tags['internal'])) { + $info->internal = true; + } + + if (isset($parsed_docblock->tags['final'])) { + $info->final = true; + } + + if (isset($parsed_docblock->tags['psalm-consistent-constructor'])) { + $info->consistent_constructor = true; + } + + if (isset($parsed_docblock->tags['psalm-internal'])) { + $psalm_internal = reset($parsed_docblock->tags['psalm-internal']); + if ($psalm_internal) { + $info->psalm_internal = $psalm_internal; + } else { + throw new DocblockParseException('psalm-internal annotation used without specifying namespace'); + } + + $info->internal = true; + } + + if (isset($parsed_docblock->tags['mixin'])) { + foreach ($parsed_docblock->tags['mixin'] as $rawMixin) { + $mixin = trim($rawMixin); + $doc_line_parts = self::splitDocLine($mixin); + $mixin = $doc_line_parts[0]; + + if ($mixin) { + $info->mixins[] = $mixin; + } else { + throw new DocblockParseException('@mixin annotation used without specifying class'); + } + } + + // backwards compatibility + if ($info->mixins) { + /** @psalm-suppress DeprecatedProperty */ + $info->mixin = reset($info->mixins); + } + } + + if (isset($parsed_docblock->tags['psalm-seal-properties'])) { + $info->sealed_properties = true; + } + + if (isset($parsed_docblock->tags['psalm-seal-methods'])) { + $info->sealed_methods = true; + } + + if (isset($parsed_docblock->tags['psalm-immutable']) + || isset($parsed_docblock->tags['psalm-mutation-free']) + ) { + $info->mutation_free = true; + $info->external_mutation_free = true; + $info->taint_specialize = true; + } + + if (isset($parsed_docblock->tags['psalm-external-mutation-free'])) { + $info->external_mutation_free = true; + } + + if (isset($parsed_docblock->tags['psalm-taint-specialize'])) { + $info->taint_specialize = true; + } + + if (isset($parsed_docblock->tags['psalm-override-property-visibility'])) { + $info->override_property_visibility = true; + } + + if (isset($parsed_docblock->tags['psalm-override-method-visibility'])) { + $info->override_method_visibility = true; + } + + if (isset($parsed_docblock->tags['psalm-suppress'])) { + foreach ($parsed_docblock->tags['psalm-suppress'] as $offset => $suppress_entry) { + foreach (DocComment::parseSuppressList($suppress_entry) as $issue_offset => $suppressed_issue) { + $info->suppressed_issues[$issue_offset + $offset + $comment->getStartFilePos()] = $suppressed_issue; + } + } + } + + if (isset($parsed_docblock->tags['psalm-import-type'])) { + foreach ($parsed_docblock->tags['psalm-import-type'] as $offset => $imported_type_entry) { + $info->imported_types[] = [ + 'line_number' => $comment->getStartLine() + substr_count($comment->getText(), "\n", 0, $offset), + 'start_offset' => $comment->getStartFilePos() + $offset, + 'end_offset' => $comment->getStartFilePos() + $offset + strlen($imported_type_entry), + 'parts' => self::splitDocLine($imported_type_entry) ?: [] + ]; + } + } + + if (isset($parsed_docblock->combined_tags['method'])) { + foreach ($parsed_docblock->combined_tags['method'] as $offset => $method_entry) { + $method_entry = preg_replace('/[ \t]+/', ' ', trim($method_entry)); + + $docblock_lines = []; + + $is_static = false; + + $has_return = false; + + if (!preg_match('/^([a-z_A-Z][a-z_0-9A-Z]+) *\(/', $method_entry, $matches)) { + $doc_line_parts = self::splitDocLine($method_entry); + + if ($doc_line_parts[0] === 'static' && !strpos($doc_line_parts[1], '(')) { + $is_static = true; + array_shift($doc_line_parts); + } + + if (count($doc_line_parts) > 1) { + $docblock_lines[] = '@return ' . array_shift($doc_line_parts); + $has_return = true; + + $method_entry = implode(' ', $doc_line_parts); + } + } + + $method_entry = trim(preg_replace('/\/\/.*/', '', $method_entry)); + + $method_entry = preg_replace( + '/array\(([0-9a-zA-Z_\'\" ]+,)*([0-9a-zA-Z_\'\" ]+)\)/', + '[]', + $method_entry + ); + + $end_of_method_regex = '/(?create(); + } catch (TypeParseTreeException $e) { + throw new DocblockParseException($method_entry . ' is not a valid method'); + } + + if (!$method_tree instanceof ParseTree\MethodWithReturnTypeTree + && !$method_tree instanceof ParseTree\MethodTree) { + throw new DocblockParseException($method_entry . ' is not a valid method'); + } + + if ($method_tree instanceof ParseTree\MethodWithReturnTypeTree) { + if (!$has_return) { + $docblock_lines[] = '@return ' . TypeParser::getTypeFromTree( + $method_tree->children[1], + $codebase + )->toNamespacedString($aliases->namespace, $aliases->uses, null, false); + } + + $method_tree = $method_tree->children[0]; + } + + if (!$method_tree instanceof ParseTree\MethodTree) { + throw new DocblockParseException($method_entry . ' is not a valid method'); + } + + $args = []; + + foreach ($method_tree->children as $method_tree_child) { + if (!$method_tree_child instanceof ParseTree\MethodParamTree) { + throw new DocblockParseException($method_entry . ' is not a valid method'); + } + + $args[] = ($method_tree_child->byref ? '&' : '') + . ($method_tree_child->variadic ? '...' : '') + . $method_tree_child->name + . ($method_tree_child->default != '' ? ' = ' . $method_tree_child->default : ''); + + + if ($method_tree_child->children) { + try { + $param_type = TypeParser::getTypeFromTree($method_tree_child->children[0], $codebase); + } catch (\Exception $e) { + throw new DocblockParseException( + 'Badly-formatted @method string ' . $method_entry . ' - ' . $e + ); + } + + $param_type_string = $param_type->toNamespacedString('\\', [], null, false); + $docblock_lines[] = '@param ' . $param_type_string . ' ' + . ($method_tree_child->variadic ? '...' : '') + . $method_tree_child->name; + } + } + + $function_string = 'function ' . $method_tree->value . '(' . implode(', ', $args) . ')'; + + if ($is_static) { + $function_string = 'static ' . $function_string; + } + + $function_docblock = $docblock_lines ? "/**\n * " . implode("\n * ", $docblock_lines) . "\n*/\n" : ""; + + $php_string = 'php_major_version . '.' . $codebase->php_minor_version + ); + } catch (\Exception $e) { + throw new DocblockParseException('Badly-formatted @method string ' . $method_entry); + } + + if (!$statements + || !$statements[0] instanceof \PhpParser\Node\Stmt\Class_ + || !isset($statements[0]->stmts[0]) + || !$statements[0]->stmts[0] instanceof \PhpParser\Node\Stmt\ClassMethod + ) { + throw new DocblockParseException('Badly-formatted @method string ' . $method_entry); + } + + /** @var \PhpParser\Comment\Doc */ + $node_doc_comment = $node->getDocComment(); + + $statements[0]->stmts[0]->setAttribute('startLine', $node_doc_comment->getStartLine()); + $statements[0]->stmts[0]->setAttribute('startFilePos', $node_doc_comment->getStartFilePos()); + $statements[0]->stmts[0]->setAttribute('endFilePos', $node->getAttribute('startFilePos')); + + if ($doc_comment = $statements[0]->stmts[0]->getDocComment()) { + $statements[0]->stmts[0]->setDocComment( + new \PhpParser\Comment\Doc( + $doc_comment->getText(), + $comment->getStartLine() + substr_count($comment->getText(), "\n", 0, $offset), + $node_doc_comment->getStartFilePos() + ) + ); + } + + $info->methods[] = $statements[0]->stmts[0]; + } + } + + if (isset($parsed_docblock->tags['psalm-stub-override'])) { + $info->stub_override = true; + } + + self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'property'); + self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'psalm-property'); + self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'property-read'); + self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'psalm-property-read'); + self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'property-write'); + self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'psalm-property-write'); + + return $info; + } + + /** + * @param array> $specials + * @param 'property'|'psalm-property'|'property-read'| + * 'psalm-property-read'|'property-write'|'psalm-property-write' $property_tag + * + * @throws DocblockParseException + * + */ + protected static function addMagicPropertyToInfo( + PhpParser\Comment\Doc $comment, + ClassLikeDocblockComment $info, + array $specials, + string $property_tag + ) : void { + $magic_property_comments = isset($specials[$property_tag]) ? $specials[$property_tag] : []; + + foreach ($magic_property_comments as $offset => $property) { + $line_parts = self::splitDocLine($property); + + if (count($line_parts) === 1 && isset($line_parts[0][0]) && $line_parts[0][0] === '$') { + continue; + } + + if (count($line_parts) > 1) { + if (preg_match('/^&?\$[A-Za-z0-9_]+,?$/', $line_parts[1]) + && $line_parts[0][0] !== '{' + ) { + $line_parts[1] = str_replace('&', '', $line_parts[1]); + + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + + $start = $offset + $comment->getStartFilePos(); + $end = $start + strlen($line_parts[0]); + + $line_parts[0] = str_replace("\n", '', preg_replace('@^[ \t]*\*@m', '', $line_parts[0])); + + if ($line_parts[0] === '' + || ($line_parts[0][0] === '$' + && !preg_match('/^\$this(\||$)/', $line_parts[0])) + ) { + throw new IncorrectDocblockException('Misplaced variable'); + } + + $name = trim($line_parts[1]); + + if (!preg_match('/^\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$/', $name)) { + throw new DocblockParseException('Badly-formatted @property name'); + } + + $info->properties[] = [ + 'name' => $name, + 'type' => $line_parts[0], + 'line_number' => $comment->getStartLine() + substr_count($comment->getText(), "\n", 0, $offset), + 'tag' => $property_tag, + 'start' => $start, + 'end' => $end, + ]; + } + } else { + throw new DocblockParseException('Badly-formatted @property'); + } + } + } + + /** + * @throws DocblockParseException if an invalid string is found + * + * @return list + * + * @psalm-pure + */ + public static function splitDocLine(string $return_block): array + { + $brackets = ''; + + $type = ''; + + $expects_callable_return = false; + + $return_block = str_replace("\t", ' ', $return_block); + + $quote_char = null; + $escaped = false; + + for ($i = 0, $l = strlen($return_block); $i < $l; ++$i) { + $char = $return_block[$i]; + $next_char = $i < $l - 1 ? $return_block[$i + 1] : null; + $last_char = $i > 0 ? $return_block[$i - 1] : null; + + if ($quote_char) { + if ($char === $quote_char && $i > 1 && !$escaped) { + $quote_char = null; + + $type .= $char; + + continue; + } + + if ($char === '\\' && !$escaped && ($next_char === $quote_char || $next_char === '\\')) { + $escaped = true; + + $type .= $char; + + continue; + } + + $escaped = false; + + $type .= $char; + + continue; + } + + if ($char === '"' || $char === '\'') { + $quote_char = $char; + + $type .= $char; + + continue; + } + + if ($char === ':' && $last_char === ')') { + $expects_callable_return = true; + + $type .= $char; + + continue; + } + + if ($char === '[' || $char === '{' || $char === '(' || $char === '<') { + $brackets .= $char; + } elseif ($char === ']' || $char === '}' || $char === ')' || $char === '>') { + $last_bracket = substr($brackets, -1); + $brackets = substr($brackets, 0, -1); + + if (($char === ']' && $last_bracket !== '[') + || ($char === '}' && $last_bracket !== '{') + || ($char === ')' && $last_bracket !== '(') + || ($char === '>' && $last_bracket !== '<') + ) { + throw new DocblockParseException('Invalid string ' . $return_block); + } + } elseif ($char === ' ') { + if ($brackets) { + $expects_callable_return = false; + $type .= ' '; + continue; + } + + if ($next_char === '|' || $next_char === '&') { + $nexter_char = $i < $l - 2 ? $return_block[$i + 2] : null; + + if ($nexter_char === ' ') { + ++$i; + $type .= $next_char . ' '; + continue; + } + } + + if ($last_char === '|' || $last_char === '&') { + $type .= ' '; + continue; + } + + if ($next_char === ':') { + ++$i; + $type .= ' :'; + $expects_callable_return = true; + continue; + } + + if ($expects_callable_return) { + $type .= ' '; + $expects_callable_return = false; + continue; + } + + $remaining = trim(preg_replace('@^[ \t]*\* *@m', ' ', substr($return_block, $i + 1))); + + if ($remaining) { + return array_merge([rtrim($type)], preg_split('/[ \s]+/', $remaining)); + } + + return [$type]; + } + + $expects_callable_return = false; + + $type .= $char; + } + + return [$type]; + } + + /** + * @throws DocblockParseException if a duplicate is found + */ + private static function checkDuplicatedTags(ParsedDocblock $parsed_docblock): void + { + if (count($parsed_docblock->tags['return'] ?? []) > 1 + || count($parsed_docblock->tags['psalm-return'] ?? []) > 1 + || count($parsed_docblock->tags['phpstan-return'] ?? []) > 1 + ) { + throw new DocblockParseException('Found duplicated @return or prefixed @return tag'); + } + + self::checkDuplicatedParams($parsed_docblock->tags['param'] ?? []); + self::checkDuplicatedParams($parsed_docblock->tags['psalm-param'] ?? []); + self::checkDuplicatedParams($parsed_docblock->tags['phpstan-param'] ?? []); + } + + /** + * @param array $param + * + * + * @throws DocblockParseException if a duplicate is found + */ + private static function checkDuplicatedParams(array $param): void + { + $list_names = self::extractAllParamNames($param); + + if (count($list_names) !== count(array_unique($list_names))) { + throw new DocblockParseException('Found duplicated @param or prefixed @param tag'); + } + } + + /** + * @param array $lines + * + * @return list + * + * @psalm-pure + */ + private static function extractAllParamNames(array $lines): array + { + $names = []; + + foreach ($lines as $line) { + $split_by_dollar = explode('$', $line, 2); + if (count($split_by_dollar) > 1) { + $split_by_space = explode(' ', $split_by_dollar[1], 2); + $names[] = $split_by_space[0]; + } + } + + return $names; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/DataFlowNodeData.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/DataFlowNodeData.php new file mode 100644 index 0000000000000000000000000000000000000000..a8504b0535747f8bf4f10143d67613c7e9588d37 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/DataFlowNodeData.php @@ -0,0 +1,118 @@ +label = $label; + $this->entry_path_type = $entry_path_type; + $this->entry_path_description = $entry_path_description; + $this->line_from = $line_from; + $this->line_to = $line_to; + $this->file_name = $file_name; + $this->file_path = $file_path; + $this->snippet = $snippet; + $this->selected_text = $selected_text; + $this->from = $from; + $this->to = $to; + $this->snippet_from = $snippet_from; + $this->snippet_to = $snippet_to; + $this->column_from = $column_from; + $this->column_to = $column_to; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..ca779b341b3ed9dc3ba4d0c1aac09ab35aad5ccf --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php @@ -0,0 +1,664 @@ + + */ + private $required_file_paths = []; + + /** + * @var array + */ + private $parent_file_paths = []; + + /** + * @var array + */ + private $suppressed_issues = []; + + /** + * @var array> + */ + private $namespace_aliased_classes = []; + + /** + * @var array> + */ + private $namespace_aliased_classes_flipped = []; + + /** + * @var array> + */ + private $namespace_aliased_classes_flipped_replaceable = []; + + /** + * @var array + */ + public $interface_analyzers_to_analyze = []; + + /** + * @var array + */ + public $class_analyzers_to_analyze = []; + + /** + * @var null|Context + */ + public $context; + + /** + * @var ProjectAnalyzer + */ + public $project_analyzer; + + /** + * @var Codebase + */ + public $codebase; + + /** + * @var int + */ + private $first_statement_offset = -1; + + /** @var ?\Psalm\Internal\Provider\NodeDataProvider */ + private $node_data; + + /** @var ?Type\Union */ + private $return_type; + + public function __construct(ProjectAnalyzer $project_analyzer, string $file_path, string $file_name) + { + $this->source = $this; + $this->file_path = $file_path; + $this->file_name = $file_name; + $this->project_analyzer = $project_analyzer; + $this->codebase = $project_analyzer->getCodebase(); + } + + public function analyze( + ?Context $file_context = null, + bool $preserve_analyzers = false, + ?Context $global_context = null + ): void { + $codebase = $this->project_analyzer->getCodebase(); + + $file_storage = $codebase->file_storage_provider->get($this->file_path); + + if (!$file_storage->deep_scan && !$codebase->server_mode) { + throw new UnpreparedAnalysisException('File ' . $this->file_path . ' has not been properly scanned'); + } + + if ($file_storage->has_visitor_issues) { + return; + } + + if ($file_context) { + $this->context = $file_context; + } + + if (!$this->context) { + $this->context = new Context(); + } + + if ($codebase->config->useStrictTypesForFile($this->file_path)) { + $this->context->strict_types = true; + } + + $this->context->is_global = true; + $this->context->defineGlobals(); + $this->context->collect_exceptions = $codebase->config->check_for_throws_in_global_scope; + + try { + $stmts = $codebase->getStatementsForFile($this->file_path); + } catch (PhpParser\Error $e) { + return; + } + foreach ($codebase->config->before_file_checks as $plugin_class) { + $plugin_class::beforeAnalyzeFile($this, $this->context, $file_storage, $codebase); + } + + if ($codebase->alter_code) { + foreach ($stmts as $stmt) { + if (!$stmt instanceof PhpParser\Node\Stmt\Declare_) { + $this->first_statement_offset = (int) $stmt->getAttribute('startFilePos'); + break; + } + } + } + + $leftover_stmts = $this->populateCheckers($stmts); + + $this->node_data = new \Psalm\Internal\Provider\NodeDataProvider(); + $statements_analyzer = new StatementsAnalyzer($this, $this->node_data); + + foreach ($file_storage->docblock_issues as $docblock_issue) { + IssueBuffer::add($docblock_issue); + } + + // if there are any leftover statements, evaluate them, + // in turn causing the classes/interfaces be evaluated + if ($leftover_stmts) { + $statements_analyzer->analyze($leftover_stmts, $this->context, $global_context, true); + + foreach ($leftover_stmts as $leftover_stmt) { + if ($leftover_stmt instanceof PhpParser\Node\Stmt\Return_) { + if ($leftover_stmt->expr) { + $this->return_type = $statements_analyzer->node_data->getType($leftover_stmt->expr) + ?: Type::getMixed(); + } else { + $this->return_type = Type::getVoid(); + } + + break; + } + } + } + + // check any leftover interfaces not already evaluated + foreach ($this->interface_analyzers_to_analyze as $interface_analyzer) { + $interface_analyzer->analyze(); + } + + // check any leftover classes not already evaluated + + foreach ($this->class_analyzers_to_analyze as $class_analyzer) { + $class_analyzer->analyze(null, $this->context); + } + + if (!$preserve_analyzers) { + $this->class_analyzers_to_analyze = []; + $this->interface_analyzers_to_analyze = []; + } + + if ($codebase->config->check_for_throws_in_global_scope) { + $uncaught_throws = $statements_analyzer->getUncaughtThrows($this->context); + foreach ($uncaught_throws as $possibly_thrown_exception => $codelocations) { + foreach ($codelocations as $codelocation) { + // issues are suppressed in ThrowAnalyzer, CallAnalyzer, etc. + if (IssueBuffer::accepts( + new UncaughtThrowInGlobalScope( + $possibly_thrown_exception . ' is thrown but not caught in global scope', + $codelocation + ) + )) { + // fall through + } + } + } + } + + // validate type imports + if ($file_storage->type_aliases) { + foreach ($file_storage->type_aliases as $alias) { + if ($alias instanceof LinkableTypeAlias) { + $location = new DocblockTypeLocation( + $this->getSource(), + $alias->start_offset, + $alias->end_offset, + $alias->line_number + ); + $fq_source_classlike = $alias->declaring_fq_classlike_name; + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $this->getSource(), + $fq_source_classlike, + $location, + null, + null, + $this->suppressed_issues, + true, + false, + true, + true + ) === false) { + continue; + } + + $referenced_class_storage = $codebase->classlike_storage_provider->get($fq_source_classlike); + if (!isset($referenced_class_storage->type_aliases[$alias->alias_name])) { + IssueBuffer::accepts( + new InvalidTypeImport( + 'Type alias ' . $alias->alias_name + . ' imported from ' . $fq_source_classlike + . ' is not defined on the source class', + $location + ) + ); + } + } + } + } + + foreach ($codebase->config->after_file_checks as $plugin_class) { + $plugin_class::afterAnalyzeFile($this, $this->context, $file_storage, $codebase); + } + } + + /** + * @param array $stmts + * + * @return list + */ + public function populateCheckers(array $stmts): array + { + $leftover_stmts = []; + + foreach ($stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\ClassLike) { + $this->populateClassLikeAnalyzers($stmt); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Namespace_) { + $namespace_name = $stmt->name ? implode('\\', $stmt->name->parts) : ''; + + $namespace_analyzer = new NamespaceAnalyzer($stmt, $this); + $namespace_analyzer->collectAnalyzableInformation(); + + $this->namespace_aliased_classes[$namespace_name] = $namespace_analyzer->getAliases()->uses; + $this->namespace_aliased_classes_flipped[$namespace_name] = + $namespace_analyzer->getAliasedClassesFlipped(); + $this->namespace_aliased_classes_flipped_replaceable[$namespace_name] = + $namespace_analyzer->getAliasedClassesFlippedReplaceable(); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Use_) { + $this->visitUse($stmt); + } elseif ($stmt instanceof PhpParser\Node\Stmt\GroupUse) { + $this->visitGroupUse($stmt); + } else { + if ($stmt instanceof PhpParser\Node\Stmt\If_) { + foreach ($stmt->stmts as $if_stmt) { + if ($if_stmt instanceof PhpParser\Node\Stmt\ClassLike) { + $this->populateClassLikeAnalyzers($if_stmt); + } + } + } + + $leftover_stmts[] = $stmt; + } + } + + return $leftover_stmts; + } + + private function populateClassLikeAnalyzers(PhpParser\Node\Stmt\ClassLike $stmt): void + { + if ($stmt instanceof PhpParser\Node\Stmt\Class_) { + if (!$stmt->name) { + return; + } + + // this can happen when stubbing + if (!$this->codebase->classExists($stmt->name->name)) { + return; + } + + + $class_analyzer = new ClassAnalyzer($stmt, $this, $stmt->name->name); + + $fq_class_name = $class_analyzer->getFQCLN(); + + $this->class_analyzers_to_analyze[strtolower($fq_class_name)] = $class_analyzer; + } elseif ($stmt instanceof PhpParser\Node\Stmt\Interface_) { + if (!$stmt->name) { + return; + } + + // this can happen when stubbing + if (!$this->codebase->interfaceExists($stmt->name->name)) { + return; + } + + $class_analyzer = new InterfaceAnalyzer($stmt, $this, $stmt->name->name); + + $fq_class_name = $class_analyzer->getFQCLN(); + + $this->interface_analyzers_to_analyze[$fq_class_name] = $class_analyzer; + } + } + + public function addNamespacedClassAnalyzer(string $fq_class_name, ClassAnalyzer $class_analyzer): void + { + $this->class_analyzers_to_analyze[strtolower($fq_class_name)] = $class_analyzer; + } + + public function addNamespacedInterfaceAnalyzer(string $fq_class_name, InterfaceAnalyzer $interface_analyzer): void + { + $this->interface_analyzers_to_analyze[strtolower($fq_class_name)] = $interface_analyzer; + } + + public function getMethodMutations( + \Psalm\Internal\MethodIdentifier $method_id, + Context $this_context, + bool $from_project_analyzer = false + ): void { + $fq_class_name = $method_id->fq_class_name; + $method_name = $method_id->method_name; + $fq_class_name_lc = strtolower($fq_class_name); + + if (isset($this->class_analyzers_to_analyze[$fq_class_name_lc])) { + $class_analyzer_to_examine = $this->class_analyzers_to_analyze[$fq_class_name_lc]; + } else { + if (!$from_project_analyzer) { + $this->project_analyzer->getMethodMutations( + $method_id, + $this_context, + $this->getRootFilePath(), + $this->getRootFileName() + ); + } + + return; + } + + $call_context = new Context($this_context->self); + $call_context->collect_mutations = $this_context->collect_mutations; + $call_context->collect_initializations = $this_context->collect_initializations; + $call_context->collect_nonprivate_initializations = $this_context->collect_nonprivate_initializations; + $call_context->initialized_methods = $this_context->initialized_methods; + $call_context->include_location = $this_context->include_location; + $call_context->calling_method_id = $this_context->calling_method_id; + + foreach ($this_context->vars_possibly_in_scope as $var => $_) { + if (strpos($var, '$this->') === 0) { + $call_context->vars_possibly_in_scope[$var] = true; + } + } + + foreach ($this_context->vars_in_scope as $var => $type) { + if (strpos($var, '$this->') === 0) { + $call_context->vars_in_scope[$var] = $type; + } + } + + if (!isset($this_context->vars_in_scope['$this'])) { + throw new \UnexpectedValueException('Should exist'); + } + + $call_context->vars_in_scope['$this'] = $this_context->vars_in_scope['$this']; + + $class_analyzer_to_examine->getMethodMutations($method_name, $call_context); + + foreach ($call_context->vars_possibly_in_scope as $var => $_) { + $this_context->vars_possibly_in_scope[$var] = true; + } + + foreach ($call_context->vars_in_scope as $var => $type) { + $this_context->vars_in_scope[$var] = $type; + } + } + + public function getFunctionLikeAnalyzer(\Psalm\Internal\MethodIdentifier $method_id) : ?MethodAnalyzer + { + $fq_class_name = $method_id->fq_class_name; + $method_name = $method_id->method_name; + + $fq_class_name_lc = strtolower($fq_class_name); + + if (!isset($this->class_analyzers_to_analyze[$fq_class_name_lc])) { + return null; + } + + $class_analyzer_to_examine = $this->class_analyzers_to_analyze[$fq_class_name_lc]; + + return $class_analyzer_to_examine->getFunctionLikeAnalyzer($method_name); + } + + public function getNamespace(): ?string + { + return null; + } + + /** + * @return array + */ + public function getAliasedClassesFlipped(?string $namespace_name = null): array + { + if ($namespace_name && isset($this->namespace_aliased_classes_flipped[$namespace_name])) { + return $this->namespace_aliased_classes_flipped[$namespace_name]; + } + + return $this->aliased_classes_flipped; + } + + /** + * @return array + */ + public function getAliasedClassesFlippedReplaceable(?string $namespace_name = null): array + { + if ($namespace_name && isset($this->namespace_aliased_classes_flipped_replaceable[$namespace_name])) { + return $this->namespace_aliased_classes_flipped_replaceable[$namespace_name]; + } + + return $this->aliased_classes_flipped_replaceable; + } + + public static function clearCache(): void + { + \Psalm\Internal\Type\TypeTokenizer::clearCache(); + \Psalm\Internal\Codebase\Reflection::clearCache(); + \Psalm\Internal\Codebase\Functions::clearCache(); + IssueBuffer::clearCache(); + FileManipulationBuffer::clearCache(); + FunctionLikeAnalyzer::clearCache(); + \Psalm\Internal\Provider\ClassLikeStorageProvider::deleteAll(); + \Psalm\Internal\Provider\FileStorageProvider::deleteAll(); + \Psalm\Internal\Provider\FileReferenceProvider::clearCache(); + \Psalm\Internal\Codebase\InternalCallMapHandler::clearCache(); + } + + public function getFileName(): string + { + return $this->file_name; + } + + public function getFilePath(): string + { + return $this->file_path; + } + + public function getRootFileName(): string + { + return $this->root_file_name ?: $this->file_name; + } + + public function getRootFilePath(): string + { + return $this->root_file_path ?: $this->file_path; + } + + public function setRootFilePath(string $file_path, string $file_name): void + { + $this->root_file_name = $file_name; + $this->root_file_path = $file_path; + } + + public function addRequiredFilePath(string $file_path): void + { + $this->required_file_paths[$file_path] = true; + } + + public function addParentFilePath(string $file_path): void + { + $this->parent_file_paths[$file_path] = true; + } + + public function hasParentFilePath(string $file_path): bool + { + return $this->file_path === $file_path || isset($this->parent_file_paths[$file_path]); + } + + public function hasAlreadyRequiredFilePath(string $file_path): bool + { + return isset($this->required_file_paths[$file_path]); + } + + /** + * @return list + */ + public function getRequiredFilePaths(): array + { + return array_keys($this->required_file_paths); + } + + /** + * @return list + */ + public function getParentFilePaths(): array + { + return array_keys($this->parent_file_paths); + } + + public function getRequireNesting(): int + { + return count($this->parent_file_paths); + } + + /** + * @return array + */ + public function getSuppressedIssues(): array + { + return $this->suppressed_issues; + } + + /** + * @param array $new_issues + */ + public function addSuppressedIssues(array $new_issues): void + { + if (isset($new_issues[0])) { + $new_issues = \array_combine($new_issues, $new_issues); + } + + $this->suppressed_issues = $new_issues + $this->suppressed_issues; + } + + /** + * @param array $new_issues + */ + public function removeSuppressedIssues(array $new_issues): void + { + if (isset($new_issues[0])) { + $new_issues = \array_combine($new_issues, $new_issues); + } + + $this->suppressed_issues = \array_diff_key($this->suppressed_issues, $new_issues); + } + + public function getFQCLN(): ?string + { + return null; + } + + public function getParentFQCLN(): ?string + { + return null; + } + + public function getClassName(): ?string + { + return null; + } + + /** + * @return array>|null + */ + public function getTemplateTypeMap(): ?array + { + return null; + } + + public function isStatic(): bool + { + return false; + } + + /** + * @psalm-mutation-free + */ + public function getFileAnalyzer() : FileAnalyzer + { + return $this; + } + + /** + * @psalm-mutation-free + */ + public function getProjectAnalyzer() : ProjectAnalyzer + { + return $this->project_analyzer; + } + + public function getCodebase() : Codebase + { + return $this->codebase; + } + + public function getFirstStatementOffset() : int + { + return $this->first_statement_offset; + } + + public function getNodeTypeProvider() : \Psalm\NodeTypeProvider + { + if (!$this->node_data) { + throw new \UnexpectedValueException('There should be a node type provider'); + } + + return $this->node_data; + } + + public function getReturnType() : ?Type\Union + { + return $this->return_type; + } + + public function clearSourceBeforeDestruction() : void + { + /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ + $this->source = null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..8139af70117a7f541774104afb59b043af0b5bf5 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php @@ -0,0 +1,123 @@ +getCodebase(); + + $file_storage_provider = $codebase->file_storage_provider; + + $file_storage = $file_storage_provider->get($source->getFilePath()); + + $namespace = $source->getNamespace(); + + $function_id = ($namespace ? strtolower($namespace) . '\\' : '') . strtolower($function->name->name); + + if (!isset($file_storage->functions[$function_id])) { + throw new \UnexpectedValueException( + 'Function ' . $function_id . ' should be defined in ' . $source->getFilePath() + ); + } + + $storage = $file_storage->functions[$function_id]; + + parent::__construct($function, $source, $storage); + } + + /** + * @return non-empty-lowercase-string + */ + public function getFunctionId(): string + { + $namespace = $this->source->getNamespace(); + + /** @var non-empty-lowercase-string */ + return ($namespace ? strtolower($namespace) . '\\' : '') . strtolower($this->function->name->name); + } + + public static function analyzeStatement( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Stmt\Function_ $stmt, + Context $context + ) : void { + foreach ($stmt->stmts as $function_stmt) { + if ($function_stmt instanceof PhpParser\Node\Stmt\Global_) { + foreach ($function_stmt->vars as $var) { + if ($var instanceof PhpParser\Node\Expr\Variable) { + if (is_string($var->name)) { + $var_id = '$' . $var->name; + + // registers variable in global context + $context->hasVariable($var_id); + } + } + } + } elseif (!$function_stmt instanceof PhpParser\Node\Stmt\Nop) { + break; + } + } + + $codebase = $statements_analyzer->getCodebase(); + + if (!$codebase->register_stub_files + && !$codebase->register_autoload_files + ) { + $function_name = strtolower($stmt->name->name); + + if ($ns = $statements_analyzer->getNamespace()) { + $fq_function_name = strtolower($ns) . '\\' . $function_name; + } else { + $fq_function_name = $function_name; + } + + $function_context = new Context($context->self); + $function_context->strict_types = $context->strict_types; + $config = \Psalm\Config::getInstance(); + $function_context->collect_exceptions = $config->check_for_throws_docblock; + + if ($function_analyzer = $statements_analyzer->getFunctionAnalyzer($fq_function_name)) { + $function_analyzer->analyze( + $function_context, + $statements_analyzer->node_data, + $context + ); + + if ($config->reportIssueInFile('InvalidReturnType', $statements_analyzer->getFilePath())) { + $method_id = $function_analyzer->getId(); + + $function_storage = $codebase->functions->getStorage( + $statements_analyzer, + strtolower($method_id) + ); + + $return_type = $function_storage->return_type; + $return_type_location = $function_storage->return_type_location; + + $function_analyzer->verifyReturnType( + $stmt->getStmts(), + $statements_analyzer, + $return_type, + $statements_analyzer->getFQCLN(), + $return_type_location, + $function_context->has_returned + ); + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..ee7bbd8994fd214d6865b421cd5214e4ff8b168b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php @@ -0,0 +1,896 @@ +getSuppressedIssues(); + $codebase = $source->getCodebase(); + $project_analyzer = $source->getProjectAnalyzer(); + + $function_like_storage = null; + + if ($source instanceof StatementsAnalyzer) { + $function_like_storage = $function_like_analyzer->getFunctionLikeStorage($source); + } elseif ($source instanceof \Psalm\Internal\Analyzer\ClassAnalyzer + || $source instanceof \Psalm\Internal\Analyzer\TraitAnalyzer + ) { + $function_like_storage = $function_like_analyzer->getFunctionLikeStorage(); + } + + $cased_method_id = $function_like_analyzer->getCorrectlyCasedMethodId(); + + if (!$function->getStmts() && + ( + $function instanceof ClassMethod && + ($source instanceof InterfaceAnalyzer || $function->isAbstract()) + ) + ) { + if (!$return_type) { + if (IssueBuffer::accepts( + new MissingReturnType( + 'Method ' . $cased_method_id . ' does not have a return type', + new CodeLocation($function_like_analyzer, $function->name, null, true) + ), + $suppressed_issues + )) { + // fall through + } + } + + return null; + } + + $is_to_string = $function instanceof ClassMethod && strtolower($function->name->name) === '__tostring'; + + if ($function instanceof ClassMethod + && substr($function->name->name, 0, 2) === '__' + && !$is_to_string + && !$return_type + ) { + // do not check __construct, __set, __get, __call etc. + return null; + } + + if (!$return_type_location) { + $return_type_location = new CodeLocation( + $function_like_analyzer, + $function instanceof Closure || $function instanceof ArrowFunction ? $function : $function->name + ); + } + + $inferred_yield_types = []; + + $inferred_return_type_parts = ReturnTypeCollector::getReturnTypes( + $codebase, + $type_provider, + $function_stmts, + $inferred_yield_types, + true + ); + + if (!$inferred_return_type_parts) { + $did_explicitly_return = true; + } + + if ((!$return_type || $return_type->from_docblock) + && ScopeAnalyzer::getControlActions( + $function_stmts, + $type_provider, + $codebase->config->exit_functions + ) !== [ScopeAnalyzer::ACTION_END] + && !$inferred_yield_types + && count($inferred_return_type_parts) + && !$did_explicitly_return + ) { + // only add null if we have a return statement elsewhere and it wasn't void + foreach ($inferred_return_type_parts as $inferred_return_type_part) { + if (!$inferred_return_type_part->isVoid()) { + $atomic_null = new Type\Atomic\TNull(); + $atomic_null->from_docblock = true; + $inferred_return_type_parts[] = new Type\Union([$atomic_null]); + break; + } + } + } + + /** @psalm-suppress PossiblyUndefinedStringArrayOffset */ + if ($return_type + && (!$return_type->from_docblock + || ($return_type->isNullable() + && !$return_type->hasTemplate() + && !$return_type->getAtomicTypes()['null']->from_docblock + ) + ) + && !$return_type->isVoid() + && !$inferred_yield_types + && (!$function_like_storage || !$function_like_storage->has_yield) + && ScopeAnalyzer::getControlActions( + $function_stmts, + $type_provider, + $codebase->config->exit_functions + ) !== [ScopeAnalyzer::ACTION_END] + ) { + if (IssueBuffer::accepts( + new InvalidReturnType( + 'Not all code paths of ' . $cased_method_id . ' end in a return statement, return type ' + . $return_type . ' expected', + $return_type_location + ), + $suppressed_issues + )) { + return false; + } + + return null; + } + + if ($return_type + && $return_type->isNever() + && !$inferred_yield_types + && ScopeAnalyzer::getControlActions( + $function_stmts, + $type_provider, + $codebase->config->exit_functions, + [], + false + ) !== [ScopeAnalyzer::ACTION_END] + ) { + if (IssueBuffer::accepts( + new InvalidReturnType( + $cased_method_id . ' is not expected to return any values but it does, ' + . 'either implicitly or explicitly', + $return_type_location + ), + $suppressed_issues + )) { + return false; + } + + return null; + } + + $inferred_return_type = $inferred_return_type_parts + ? \Psalm\Type::combineUnionTypeArray($inferred_return_type_parts, $codebase) + : Type::getVoid(); + $inferred_yield_type = $inferred_yield_types + ? \Psalm\Type::combineUnionTypeArray($inferred_yield_types, $codebase) + : null; + + if ($inferred_yield_type) { + $inferred_return_type = $inferred_yield_type; + } + + $unsafe_return_type = false; + + // prevent any return types that do not return a value from being used in PHP typehints + if ($codebase->alter_code + && $inferred_return_type->isNullable() + && !$inferred_yield_types + ) { + foreach ($inferred_return_type_parts as $inferred_return_type_part) { + if ($inferred_return_type_part->isVoid()) { + $unsafe_return_type = true; + break; + } + } + } + + $inferred_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $inferred_return_type, + $source->getFQCLN(), + $source->getFQCLN(), + $source->getParentFQCLN() + ); + + // hack until we have proper yield type collection + if ($function_like_storage + && $function_like_storage->has_yield + && !$inferred_yield_type + && !$inferred_return_type->isVoid() + ) { + $inferred_return_type = new Type\Union([new Type\Atomic\TNamedObject('Generator')]); + } + + if ($is_to_string) { + if (!$inferred_return_type->hasMixed() && + !UnionTypeComparator::isContainedBy( + $codebase, + $inferred_return_type, + Type::getString(), + $inferred_return_type->ignore_nullable_issues, + $inferred_return_type->ignore_falsable_issues + ) + ) { + if (IssueBuffer::accepts( + new InvalidToString( + '__toString methods must return a string, ' . $inferred_return_type . ' returned', + $return_type_location + ), + $suppressed_issues + )) { + return false; + } + } + + return null; + } + + if (!$return_type) { + if ($function instanceof Closure || $function instanceof ArrowFunction) { + if (!$closure_inside_call || $inferred_return_type->isMixed()) { + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['MissingClosureReturnType']) + && !in_array('MissingClosureReturnType', $suppressed_issues) + ) { + if ($inferred_return_type->hasMixed() || $inferred_return_type->isNull()) { + return null; + } + + self::addOrUpdateReturnType( + $function, + $project_analyzer, + $inferred_return_type, + $source, + ($project_analyzer->only_replace_php_types_with_non_docblock_types + || $unsafe_return_type) + && $inferred_return_type->from_docblock, + $function_like_storage + ); + + return null; + } + + if (IssueBuffer::accepts( + new MissingClosureReturnType( + 'Closure does not have a return type, expecting ' . $inferred_return_type->getId(), + new CodeLocation($function_like_analyzer, $function, null, true) + ), + $suppressed_issues, + !$inferred_return_type->hasMixed() && !$inferred_return_type->isNull() + )) { + // fall through + } + } + + return null; + } + + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['MissingReturnType']) + && !in_array('MissingReturnType', $suppressed_issues) + ) { + if ($inferred_return_type->hasMixed() || $inferred_return_type->isNull()) { + return null; + } + + self::addOrUpdateReturnType( + $function, + $project_analyzer, + $inferred_return_type, + $source, + $compatible_method_ids + || !$did_explicitly_return + || (($project_analyzer->only_replace_php_types_with_non_docblock_types + || $unsafe_return_type) + && $inferred_return_type->from_docblock), + $function_like_storage + ); + + return null; + } + + if (IssueBuffer::accepts( + new MissingReturnType( + 'Method ' . $cased_method_id . ' does not have a return type' . + (!$inferred_return_type->hasMixed() ? ', expecting ' . $inferred_return_type->getId() : ''), + new CodeLocation($function_like_analyzer, $function->name, null, true) + ), + $suppressed_issues, + !$inferred_return_type->hasMixed() && !$inferred_return_type->isNull() + )) { + // fall through + } + + return null; + } + + $self_fq_class_name = $fq_class_name ?: $source->getFQCLN(); + + $parent_class = null; + + if ($self_fq_class_name) { + $classlike_storage = $codebase->classlike_storage_provider->get($self_fq_class_name); + $parent_class = $classlike_storage->parent_class; + } + + // passing it through fleshOutTypes eradicates errant $ vars + $declared_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $return_type, + $self_fq_class_name, + $self_fq_class_name, + $parent_class, + true, + true, + $function_like_storage instanceof MethodStorage && $function_like_storage->final + ); + + if (!$inferred_return_type_parts + && !$inferred_yield_types + && (!$function_like_storage || !$function_like_storage->has_yield) + ) { + if ($declared_return_type->isVoid() || $declared_return_type->isNever()) { + return null; + } + + if (ScopeAnalyzer::onlyThrowsOrExits($type_provider, $function_stmts)) { + // if there's a single throw statement, it's presumably an exception saying this method is not to be + // used + return null; + } + + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['InvalidReturnType']) + && !in_array('InvalidReturnType', $suppressed_issues) + ) { + self::addOrUpdateReturnType( + $function, + $project_analyzer, + Type::getVoid(), + $source, + $compatible_method_ids + || (($project_analyzer->only_replace_php_types_with_non_docblock_types + || $unsafe_return_type) + && $inferred_return_type->from_docblock) + ); + + return null; + } + + if (!$declared_return_type->from_docblock || !$declared_return_type->isNullable()) { + if (IssueBuffer::accepts( + new InvalidReturnType( + 'No return statements were found for method ' . $cased_method_id . + ' but return type \'' . $declared_return_type . '\' was expected', + $return_type_location + ), + $suppressed_issues, + true + )) { + return false; + } + } + + return null; + } + + if (!$declared_return_type->hasMixed()) { + if ($inferred_return_type->isVoid() + && ($declared_return_type->isVoid() || ($function_like_storage && $function_like_storage->has_yield)) + ) { + return null; + } + + if ($inferred_return_type->hasMixed() || $inferred_return_type->isEmpty()) { + if (IssueBuffer::accepts( + new MixedInferredReturnType( + 'Could not verify return type \'' . $declared_return_type . '\' for ' . + $cased_method_id, + $return_type_location + ), + $suppressed_issues + )) { + return false; + } + + return null; + } + + $union_comparison_results = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $inferred_return_type, + $declared_return_type, + true, + true, + $union_comparison_results + )) { + // is the declared return type more specific than the inferred one? + if ($union_comparison_results->type_coerced) { + if ($union_comparison_results->type_coerced_from_mixed) { + if (!$union_comparison_results->type_coerced_from_as_mixed) { + if (IssueBuffer::accepts( + new MixedReturnTypeCoercion( + 'The declared return type \'' . $declared_return_type->getId() . '\' for ' + . $cased_method_id . ' is more specific than the inferred return type ' + . '\'' . $inferred_return_type->getId() . '\'', + $return_type_location + ), + $suppressed_issues + )) { + return false; + } + } + } else { + if (IssueBuffer::accepts( + new MoreSpecificReturnType( + 'The declared return type \'' . $declared_return_type->getId() . '\' for ' + . $cased_method_id . ' is more specific than the inferred return type ' + . '\'' . $inferred_return_type->getId() . '\'', + $return_type_location + ), + $suppressed_issues + )) { + return false; + } + } + } else { + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['InvalidReturnType']) + && !in_array('InvalidReturnType', $suppressed_issues) + ) { + self::addOrUpdateReturnType( + $function, + $project_analyzer, + $inferred_return_type, + $source, + ($project_analyzer->only_replace_php_types_with_non_docblock_types + || $unsafe_return_type) + && $inferred_return_type->from_docblock, + $function_like_storage + ); + + return null; + } + + if (IssueBuffer::accepts( + new InvalidReturnType( + 'The declared return type \'' + . $declared_return_type->getId() + . '\' for ' . $cased_method_id + . ' is incorrect, got \'' + . $inferred_return_type->getId() . '\'', + $return_type_location + ), + $suppressed_issues, + true + )) { + return false; + } + } + } elseif ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['LessSpecificReturnType']) + && !in_array('LessSpecificReturnType', $suppressed_issues) + && !($function_like_storage instanceof MethodStorage && $function_like_storage->inheritdoc) + ) { + if (!UnionTypeComparator::isContainedBy( + $codebase, + $declared_return_type, + $inferred_return_type, + false, + false + )) { + self::addOrUpdateReturnType( + $function, + $project_analyzer, + $inferred_return_type, + $source, + $compatible_method_ids + || (($project_analyzer->only_replace_php_types_with_non_docblock_types + || $unsafe_return_type) + && $inferred_return_type->from_docblock), + $function_like_storage + ); + + return null; + } + } elseif (!$inferred_return_type->hasMixed() + && ((!$inferred_return_type->isNullable() && $declared_return_type->isNullable()) + || (!$inferred_return_type->isFalsable() && $declared_return_type->isFalsable())) + ) { + if ($function instanceof Function_ + || $function instanceof Closure + || $function instanceof ArrowFunction + || $function->isPrivate() + ) { + $check_for_less_specific_type = true; + } elseif ($source instanceof StatementsAnalyzer) { + if ($function_like_storage instanceof MethodStorage) { + $check_for_less_specific_type = !$function_like_storage->overridden_somewhere; + } else { + $check_for_less_specific_type = false; + } + } else { + $check_for_less_specific_type = false; + } + + if ($check_for_less_specific_type) { + if (IssueBuffer::accepts( + new LessSpecificReturnType( + 'The inferred return type \'' . $inferred_return_type . '\' for ' . $cased_method_id . + ' is more specific than the declared return type \'' . $declared_return_type . '\'', + $return_type_location + ), + $suppressed_issues, + !($function_like_storage instanceof MethodStorage && $function_like_storage->inheritdoc) + )) { + return false; + } + } + } + + if (!$inferred_return_type->ignore_nullable_issues + && $inferred_return_type->isNullable() + && !$declared_return_type->isNullable() + && !$declared_return_type->hasTemplate() + && !$declared_return_type->isVoid() + ) { + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['InvalidNullableReturnType']) + && !in_array('InvalidNullableReturnType', $suppressed_issues) + && !$inferred_return_type->isNull() + ) { + self::addOrUpdateReturnType( + $function, + $project_analyzer, + $inferred_return_type, + $source, + ($project_analyzer->only_replace_php_types_with_non_docblock_types + || $unsafe_return_type) + && $inferred_return_type->from_docblock, + $function_like_storage + ); + + return null; + } + + if (IssueBuffer::accepts( + new InvalidNullableReturnType( + 'The declared return type \'' . $declared_return_type . '\' for ' . $cased_method_id . + ' is not nullable, but \'' . $inferred_return_type . '\' contains null', + $return_type_location + ), + $suppressed_issues, + !$inferred_return_type->isNull() + )) { + return false; + } + } + + if (!$inferred_return_type->ignore_falsable_issues + && $inferred_return_type->isFalsable() + && !$declared_return_type->isFalsable() + && !$declared_return_type->hasBool() + && !$declared_return_type->hasScalar() + ) { + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['InvalidFalsableReturnType']) + ) { + self::addOrUpdateReturnType( + $function, + $project_analyzer, + $inferred_return_type, + $source, + ($project_analyzer->only_replace_php_types_with_non_docblock_types + || $unsafe_return_type) + && $inferred_return_type->from_docblock, + $function_like_storage + ); + + return null; + } + + if (IssueBuffer::accepts( + new InvalidFalsableReturnType( + 'The declared return type \'' . $declared_return_type . '\' for ' . $cased_method_id . + ' does not allow false, but \'' . $inferred_return_type . '\' contains false', + $return_type_location + ), + $suppressed_issues, + true + )) { + return false; + } + } + } + + return null; + } + + /** + * @param Closure|Function_|ClassMethod|ArrowFunction $function + * + * @return false|null + */ + public static function checkReturnType( + FunctionLike $function, + ProjectAnalyzer $project_analyzer, + FunctionLikeAnalyzer $function_like_analyzer, + FunctionLikeStorage $storage, + Context $context + ): ?bool { + $codebase = $project_analyzer->getCodebase(); + + if (!$storage->return_type || !$storage->return_type_location) { + return null; + } + + $parent_class = null; + + $classlike_storage = null; + + if ($context->self) { + $classlike_storage = $codebase->classlike_storage_provider->get($context->self); + $parent_class = $classlike_storage->parent_class; + } + + if (!$storage->signature_return_type || $storage->signature_return_type === $storage->return_type) { + foreach ($storage->return_type->getAtomicTypes() as $type) { + if ($type instanceof Type\Atomic\TNamedObject + && 'parent' === $type->value + && null === $parent_class + ) { + if (IssueBuffer::accepts( + new InvalidParent( + 'Cannot use parent as a return type when class has no parent', + $storage->return_type_location + ), + $storage->suppressed_issues + )) { + return false; + } + return null; + } + } + + $fleshed_out_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $storage->return_type, + $classlike_storage ? $classlike_storage->name : null, + $classlike_storage ? $classlike_storage->name : null, + $parent_class + ); + + $fleshed_out_return_type->check( + $function_like_analyzer, + $storage->return_type_location, + $storage->suppressed_issues, + [], + false + ); + + return null; + } + + $fleshed_out_signature_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $storage->signature_return_type, + $classlike_storage ? $classlike_storage->name : null, + $classlike_storage ? $classlike_storage->name : null, + $parent_class + ); + + if ($fleshed_out_signature_type->check( + $function_like_analyzer, + $storage->signature_return_type_location ?: $storage->return_type_location, + $storage->suppressed_issues, + [], + false + ) === false) { + return false; + } + + if ($function instanceof Closure || $function instanceof ArrowFunction) { + return null; + } + + $fleshed_out_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $storage->return_type, + $classlike_storage ? $classlike_storage->name : null, + $classlike_storage ? $classlike_storage->name : null, + $parent_class, + true, + true + ); + + if ($fleshed_out_return_type->check( + $function_like_analyzer, + $storage->return_type_location, + $storage->suppressed_issues, + [], + false, + $storage instanceof MethodStorage && $storage->inherited_return_type + ) === false) { + return false; + } + + if ($classlike_storage && $context->self) { + $class_template_params = ClassTemplateParamCollector::collect( + $codebase, + $classlike_storage, + $codebase->classlike_storage_provider->get($context->self), + strtolower($function->name->name), + new Type\Atomic\TNamedObject($context->self), + '$this' + ); + + $class_template_params = $class_template_params ?: []; + + if ($class_template_params) { + $template_result = new \Psalm\Internal\Type\TemplateResult( + $class_template_params, + [] + ); + + $fleshed_out_return_type = \Psalm\Internal\Type\UnionTemplateHandler::replaceTemplateTypesWithStandins( + $fleshed_out_return_type, + $template_result, + $codebase, + null, + null, + null + ); + } + } + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $fleshed_out_return_type, + $fleshed_out_signature_type + ) + ) { + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['MismatchingDocblockReturnType']) + ) { + self::addOrUpdateReturnType( + $function, + $project_analyzer, + $storage->signature_return_type, + $function_like_analyzer->getSource() + ); + + return null; + } + + if (IssueBuffer::accepts( + new MismatchingDocblockReturnType( + 'Docblock has incorrect return type \'' . $storage->return_type->getId() . + '\', should be \'' . $storage->signature_return_type->getId() . '\'', + $storage->return_type_location + ), + $storage->suppressed_issues, + true + )) { + return false; + } + } + + return null; + } + + /** + * @param Closure|Function_|ClassMethod|ArrowFunction $function + * + */ + private static function addOrUpdateReturnType( + FunctionLike $function, + ProjectAnalyzer $project_analyzer, + Type\Union $inferred_return_type, + StatementsSource $source, + bool $docblock_only = false, + ?FunctionLikeStorage $function_like_storage = null + ): void { + $manipulator = FunctionDocblockManipulator::getForFunction( + $project_analyzer, + $source->getFilePath(), + $function + ); + + $codebase = $project_analyzer->getCodebase(); + $is_final = true; + $fqcln = $source->getFQCLN(); + + if ($fqcln !== null && $function instanceof ClassMethod) { + $class_storage = $codebase->classlike_storage_provider->get($fqcln); + $is_final = $function->isFinal() || $class_storage->final; + } + + $allow_native_type = !$docblock_only + && $codebase->php_major_version >= 7 + && ( + $codebase->allow_backwards_incompatible_changes + || $is_final + || !$function instanceof PhpParser\Node\Stmt\ClassMethod + ); + + $manipulator->setReturnType( + $allow_native_type + ? (string) $inferred_return_type->toPhpString( + $source->getNamespace(), + $source->getAliasedClassesFlipped(), + $source->getFQCLN(), + $codebase->php_major_version, + $codebase->php_minor_version + ) : null, + $inferred_return_type->toNamespacedString( + $source->getNamespace(), + $source->getAliasedClassesFlipped(), + $source->getFQCLN(), + false + ), + $inferred_return_type->toNamespacedString( + $source->getNamespace(), + $source->getAliasedClassesFlipped(), + $source->getFQCLN(), + true + ), + $inferred_return_type->canBeFullyExpressedInPhp(), + $function_like_storage ? $function_like_storage->return_type_description : null + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php new file mode 100644 index 0000000000000000000000000000000000000000..17fbf701b1ee882b23a74e2ae25f53987fa5cfe3 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php @@ -0,0 +1,314 @@ + $stmts + * @param list $yield_types + * + * @return list a list of return types + */ + public static function getReturnTypes( + \Psalm\Codebase $codebase, + \Psalm\Internal\Provider\NodeDataProvider $nodes, + array $stmts, + array &$yield_types, + bool $collapse_types = false + ): array { + $return_types = []; + + foreach ($stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\Return_) { + if (!$stmt->expr) { + $return_types[] = Type::getVoid(); + } elseif ($stmt_type = $nodes->getType($stmt)) { + $return_types[] = $stmt_type; + + $yield_types = array_merge($yield_types, self::getYieldTypeFromExpression($stmt->expr, $nodes)); + } else { + $return_types[] = Type::getMixed(); + } + + break; + } + + if ($stmt instanceof PhpParser\Node\Stmt\Throw_ + || $stmt instanceof PhpParser\Node\Stmt\Break_ + || $stmt instanceof PhpParser\Node\Stmt\Continue_ + ) { + break; + } + + if ($stmt instanceof PhpParser\Node\Stmt\Expression) { + if ($stmt->expr instanceof PhpParser\Node\Expr\Assign) { + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + [$stmt->expr->expr], + $yield_types + ) + ); + } + + $yield_types = array_merge($yield_types, self::getYieldTypeFromExpression($stmt->expr, $nodes)); + } elseif ($stmt instanceof PhpParser\Node\Stmt\If_) { + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + $stmt->stmts, + $yield_types + ) + ); + + foreach ($stmt->elseifs as $elseif) { + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + $elseif->stmts, + $yield_types + ) + ); + } + + if ($stmt->else) { + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + $stmt->else->stmts, + $yield_types + ) + ); + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\TryCatch) { + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + $stmt->stmts, + $yield_types + ) + ); + + foreach ($stmt->catches as $catch) { + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + $catch->stmts, + $yield_types + ) + ); + } + + if ($stmt->finally) { + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + $stmt->finally->stmts, + $yield_types + ) + ); + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\For_) { + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + $stmt->stmts, + $yield_types + ) + ); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Foreach_) { + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + $stmt->stmts, + $yield_types + ) + ); + } elseif ($stmt instanceof PhpParser\Node\Stmt\While_) { + $yield_types = array_merge($yield_types, self::getYieldTypeFromExpression($stmt->cond, $nodes)); + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + $stmt->stmts, + $yield_types + ) + ); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Do_) { + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + $stmt->stmts, + $yield_types + ) + ); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Switch_) { + foreach ($stmt->cases as $case) { + $return_types = array_merge( + $return_types, + self::getReturnTypes( + $codebase, + $nodes, + $case->stmts, + $yield_types + ) + ); + } + } + } + + // if we're at the top level and we're not ending in a return, make sure to add possible null + if ($collapse_types) { + // if it's a generator, boil everything down to a single generator return type + if ($yield_types) { + $key_type = null; + $value_type = null; + + $yield_type = Type::combineUnionTypeArray($yield_types, null); + + foreach ($yield_type->getAtomicTypes() as $type) { + if ($type instanceof Type\Atomic\TKeyedArray) { + $type = $type->getGenericArrayType(); + } + + if ($type instanceof Type\Atomic\TList) { + $type = new Type\Atomic\TArray([Type::getInt(), $type->type_param]); + } + + if ($type instanceof Type\Atomic\TArray) { + [$key_type_param, $value_type_param] = $type->type_params; + + if (!$key_type) { + $key_type = clone $key_type_param; + } else { + $key_type = Type::combineUnionTypes($key_type_param, $key_type); + } + + if (!$value_type) { + $value_type = clone $value_type_param; + } else { + $value_type = Type::combineUnionTypes($value_type_param, $value_type); + } + } elseif ($type instanceof Type\Atomic\TIterable + || $type instanceof Type\Atomic\TNamedObject + ) { + ForeachAnalyzer::getKeyValueParamsForTraversableObject( + $type, + $codebase, + $key_type, + $value_type + ); + } + } + + $yield_types = [ + new Type\Union([ + new Atomic\TGenericObject( + 'Generator', + [ + $key_type ?: Type::getMixed(), + $value_type ?: Type::getMixed(), + Type::getMixed(), + $return_types ? Type::combineUnionTypeArray($return_types, null) : Type::getVoid() + ] + ), + ]) + ]; + } + } + + return $return_types; + } + + /** + * @return list + */ + protected static function getYieldTypeFromExpression( + PhpParser\Node\Expr $stmt, + \Psalm\Internal\Provider\NodeDataProvider $nodes + ): array { + if ($stmt instanceof PhpParser\Node\Expr\Yield_) { + $key_type = null; + + if ($stmt->key && ($stmt_key_type = $nodes->getType($stmt->key))) { + $key_type = $stmt_key_type; + } + + if ($stmt->value + && $value_type = $nodes->getType($stmt->value) + ) { + $generator_type = new Atomic\TGenericObject( + 'Generator', + [ + $key_type ? clone $key_type : Type::getInt(), + clone $value_type, + Type::getMixed(), + Type::getMixed() + ] + ); + + return [new Type\Union([$generator_type])]; + } + + return [Type::getMixed()]; + } elseif ($stmt instanceof PhpParser\Node\Expr\YieldFrom) { + if ($stmt_expr_type = $nodes->getType($stmt->expr)) { + return [$stmt_expr_type]; + } + + return [Type::getMixed()]; + } elseif ($stmt instanceof PhpParser\Node\Expr\BinaryOp) { + return array_merge( + self::getYieldTypeFromExpression($stmt->left, $nodes), + self::getYieldTypeFromExpression($stmt->right, $nodes) + ); + } elseif ($stmt instanceof PhpParser\Node\Expr\Assign) { + return self::getYieldTypeFromExpression($stmt->expr, $nodes); + } elseif ($stmt instanceof PhpParser\Node\Expr\MethodCall + || $stmt instanceof PhpParser\Node\Expr\FuncCall + || $stmt instanceof PhpParser\Node\Expr\StaticCall + ) { + $yield_types = []; + + foreach ($stmt->args as $arg) { + $yield_types = array_merge($yield_types, self::getYieldTypeFromExpression($arg->value, $nodes)); + } + + return $yield_types; + } + + return []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..21e6d65777769eca121a779712852b46e4680f16 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -0,0 +1,1915 @@ + + */ + protected $suppressed_issues; + + /** + * @var bool + */ + protected $is_static = false; + + /** + * @var StatementsSource + */ + protected $source; + + /** + * @var ?array + */ + protected $return_vars_in_scope = []; + + /** + * @var ?array + */ + protected $return_vars_possibly_in_scope = []; + + /** + * @var Type\Union|null + */ + private $local_return_type; + + /** + * @var array + */ + protected static $no_effects_hashes = []; + + /** + * @var bool + */ + public $track_mutations = false; + + /** + * @var bool + */ + public $inferred_impure = false; + + /** + * @var bool + */ + public $inferred_has_mutation = false; + + /** + * Holds param nodes for functions with func_get_args calls + * + * @var array + */ + public $param_nodes = []; + + /** + * @var FunctionLikeStorage + */ + protected $storage; + + /** + * @param Closure|Function_|ClassMethod|ArrowFunction $function + */ + public function __construct($function, SourceAnalyzer $source, FunctionLikeStorage $storage) + { + $this->function = $function; + $this->source = $source; + $this->suppressed_issues = $source->getSuppressedIssues(); + $this->codebase = $source->getCodebase(); + $this->storage = $storage; + } + + /** + * @param bool $add_mutations whether or not to add mutations to this method + * @param ?array $byref_uses + * + * @return false|null + */ + public function analyze( + Context $context, + \Psalm\Internal\Provider\NodeDataProvider $type_provider, + ?Context $global_context = null, + bool $add_mutations = false, + ?array $byref_uses = null + ): ?bool { + $storage = $this->storage; + + $function_stmts = $this->function->getStmts() ?: []; + + if ($this->function instanceof ArrowFunction + && isset($function_stmts[0]) + && $function_stmts[0] instanceof PhpParser\Node\Stmt\Return_ + && $function_stmts[0]->expr + ) { + $function_stmts[0]->setAttributes($function_stmts[0]->expr->getAttributes()); + } + + $hash = null; + $real_method_id = null; + $method_id = null; + + $cased_method_id = null; + + $appearing_class_storage = null; + + if ($global_context) { + foreach ($global_context->constants as $const_name => $var_type) { + if (!$context->hasVariable($const_name)) { + $context->vars_in_scope[$const_name] = clone $var_type; + } + } + } + + $codebase = $this->codebase; + $project_analyzer = $this->getProjectAnalyzer(); + + $implemented_docblock_param_types = []; + + $classlike_storage_provider = $codebase->classlike_storage_provider; + + if ($codebase->track_unused_suppressions && !isset($storage->suppressed_issues[0])) { + foreach ($storage->suppressed_issues as $offset => $issue_name) { + IssueBuffer::addUnusedSuppression($this->getFilePath(), $offset, $issue_name); + } + } + + foreach ($storage->docblock_issues as $docblock_issue) { + IssueBuffer::add($docblock_issue); + } + + $overridden_method_ids = []; + + if ($this->function instanceof ClassMethod) { + if (!$storage instanceof MethodStorage || !$this instanceof MethodAnalyzer) { + throw new \UnexpectedValueException('$storage must be MethodStorage'); + } + + $real_method_id = $this->getMethodId(); + + $method_id = $this->getMethodId($context->self); + + $fq_class_name = (string)$context->self; + $appearing_class_storage = $classlike_storage_provider->get($fq_class_name); + + if ($add_mutations) { + if (!$context->collect_initializations) { + $hash = md5($real_method_id . '::' . $context->getScopeSummary()); + + // if we know that the function has no effects on vars, we don't bother rechecking + if (isset(self::$no_effects_hashes[$hash])) { + return null; + } + } + } elseif ($context->self) { + if ($appearing_class_storage->template_types) { + $template_params = []; + + foreach ($appearing_class_storage->template_types as $param_name => $template_map) { + $key = array_keys($template_map)[0]; + + $template_params[] = new Type\Union([ + new Type\Atomic\TTemplateParam( + $param_name, + \reset($template_map)[0], + $key + ) + ]); + } + + $this_object_type = new Type\Atomic\TGenericObject( + $context->self, + $template_params + ); + $this_object_type->was_static = !$storage->final; + } else { + $this_object_type = new TNamedObject($context->self); + $this_object_type->was_static = !$storage->final; + } + + $context->vars_in_scope['$this'] = new Type\Union([$this_object_type]); + + if ($storage->external_mutation_free + && !$storage->mutation_free_inferred + ) { + $context->vars_in_scope['$this']->reference_free = true; + + if ($this->function->name->name !== '__construct') { + $context->vars_in_scope['$this']->allow_mutations = false; + } + } + + $context->vars_possibly_in_scope['$this'] = true; + } + + if ($appearing_class_storage->has_visitor_issues) { + return null; + } + + $cased_method_id = $fq_class_name . '::' . $storage->cased_name; + + $overridden_method_ids = $codebase->methods->getOverriddenMethodIds($method_id); + + if ($this->function->name->name === '__construct') { + $context->inside_constructor = true; + } + + $codeLocation = new CodeLocation( + $this, + $this->function, + null, + true + ); + + if ($overridden_method_ids + && !$context->collect_initializations + && !$context->collect_mutations + ) { + foreach ($overridden_method_ids as $overridden_method_id) { + $parent_method_storage = $codebase->methods->getStorage($overridden_method_id); + + $overridden_fq_class_name = $overridden_method_id->fq_class_name; + + $parent_storage = $classlike_storage_provider->get($overridden_fq_class_name); + + if ($this->function->name->name === '__construct' + && !$parent_storage->preserve_constructor_signature + ) { + continue; + } + + $implementer_visibility = $storage->visibility; + + $implementer_appearing_method_id = $codebase->methods->getAppearingMethodId($method_id); + $implementer_declaring_method_id = $real_method_id; + + $declaring_class_storage = $appearing_class_storage; + + if ($implementer_appearing_method_id + && $implementer_appearing_method_id !== $implementer_declaring_method_id + ) { + $appearing_fq_class_name = $implementer_appearing_method_id->fq_class_name; + $appearing_method_name = $implementer_appearing_method_id->method_name; + + $declaring_fq_class_name = $implementer_declaring_method_id->fq_class_name; + + $appearing_class_storage = $classlike_storage_provider->get( + $appearing_fq_class_name + ); + + $declaring_class_storage = $classlike_storage_provider->get( + $declaring_fq_class_name + ); + + if (isset($appearing_class_storage->trait_visibility_map[$appearing_method_name])) { + $implementer_visibility + = $appearing_class_storage->trait_visibility_map[$appearing_method_name]; + } + } + + // we've already checked this in the class checker + if (!isset($appearing_class_storage->class_implements[strtolower($overridden_fq_class_name)])) { + MethodComparator::compare( + $codebase, + \count($overridden_method_ids) === 1 ? $this->function : null, + $declaring_class_storage, + $parent_storage, + $storage, + $parent_method_storage, + $fq_class_name, + $implementer_visibility, + $codeLocation, + $storage->suppressed_issues + ); + } + + foreach ($parent_method_storage->params as $i => $guide_param) { + if ($guide_param->type + && (!$guide_param->signature_type + || ($guide_param->signature_type !== $guide_param->type + && $storage->inheritdoc) + || !$parent_storage->user_defined + ) + ) { + if (!isset($implemented_docblock_param_types[$i])) { + $implemented_docblock_param_types[$i] = $guide_param->type; + } + } + } + } + } + + MethodAnalyzer::checkMethodSignatureMustOmitReturnType($storage, $codeLocation); + + if (!$context->calling_method_id || !$context->collect_initializations) { + $context->calling_method_id = strtolower((string) $method_id); + } + } elseif ($this->function instanceof Function_) { + $function_name = $this->function->name->name; + $namespace_prefix = $this->getNamespace(); + $cased_method_id = ($namespace_prefix !== null ? $namespace_prefix . '\\' : '') . $function_name; + $context->calling_function_id = strtolower($cased_method_id); + } else { // Closure + if ($storage->return_type) { + $closure_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $storage->return_type, + $context->self, + $context->self, + $this->getParentFQCLN() + ); + } else { + $closure_return_type = Type::getMixed(); + } + + $closure_type = new Type\Atomic\TClosure( + 'Closure', + $storage->params, + $closure_return_type + ); + + if ($storage instanceof FunctionStorage) { + $closure_type->byref_uses = $storage->byref_uses; + $closure_type->is_pure = $storage->pure; + } + + $type_provider->setType( + $this->function, + new Type\Union([ + $closure_type, + ]) + ); + } + + $this->suppressed_issues = $this->getSource()->getSuppressedIssues() + $storage->suppressed_issues; + + if ($storage instanceof MethodStorage && $storage->is_static) { + $this->is_static = true; + } + + $statements_analyzer = new StatementsAnalyzer($this, $type_provider); + + if ($byref_uses) { + $statements_analyzer->setByRefUses($byref_uses); + } + + if ($storage->template_types) { + foreach ($storage->template_types as $param_name => $_) { + $fq_classlike_name = Type::getFQCLNFromString( + $param_name, + $this->getAliases() + ); + + if ($codebase->classOrInterfaceExists($fq_classlike_name)) { + if (IssueBuffer::accepts( + new ReservedWord( + 'Cannot use ' . $param_name . ' as template name since the class already exists', + new CodeLocation($this, $this->function), + 'resource' + ), + $this->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + $template_types = $storage->template_types; + + if ($appearing_class_storage && $appearing_class_storage->template_types) { + $template_types = array_merge($template_types ?: [], $appearing_class_storage->template_types); + } + + $params = $storage->params; + + if ($storage instanceof MethodStorage) { + $non_null_param_types = array_filter( + $storage->params, + function (FunctionLikeParameter $p): bool { + return $p->type !== null && $p->has_docblock_type; + } + ); + } else { + $non_null_param_types = array_filter( + $storage->params, + function (FunctionLikeParameter $p): bool { + return $p->type !== null; + } + ); + } + + if ($storage instanceof MethodStorage + && $method_id instanceof \Psalm\Internal\MethodIdentifier + && $overridden_method_ids + ) { + $types_without_docblocks = array_filter( + $storage->params, + function (FunctionLikeParameter $p): bool { + return !$p->type || !$p->has_docblock_type; + } + ); + + if ($types_without_docblocks) { + $params = $codebase->methods->getMethodParams( + $method_id, + $this + ); + } + } + + if ($codebase->alter_code) { + $this->alterParams($codebase, $storage, $params, $context); + } + + foreach ($codebase->methods_to_rename as $original_method_id => $new_method_name) { + if ($this->function instanceof ClassMethod + && $this instanceof MethodAnalyzer + && strtolower((string) $this->getMethodId()) === $original_method_id + ) { + $file_manipulations = [ + new \Psalm\FileManipulation( + (int) $this->function->name->getAttribute('startFilePos'), + (int) $this->function->name->getAttribute('endFilePos') + 1, + $new_method_name + ) + ]; + + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $this->getFilePath(), + $file_manipulations + ); + } + } + + $check_stmts = $this->processParams( + $statements_analyzer, + $storage, + $cased_method_id, + $params, + $context, + $implemented_docblock_param_types, + (bool) $non_null_param_types, + (bool) $template_types + ); + + if ($storage->pure) { + $context->pure = true; + } + + if ($storage->mutation_free + && $cased_method_id + && !strpos($cased_method_id, '__construct') + && !($storage instanceof MethodStorage && $storage->mutation_free_inferred) + ) { + $context->mutation_free = true; + } + + if ($storage instanceof MethodStorage + && $storage->external_mutation_free + && !$storage->mutation_free_inferred + ) { + $context->external_mutation_free = true; + } + + if ($storage->unused_docblock_params) { + foreach ($storage->unused_docblock_params as $param_name => $param_location) { + if (IssueBuffer::accepts( + new InvalidDocblockParamName( + 'Incorrect param name $' . $param_name . ' in docblock for ' . $cased_method_id, + $param_location + ) + )) { + } + } + } + + if ($storage->signature_return_type && $storage->signature_return_type_location) { + [$start, $end] = $storage->signature_return_type_location->getSelectionBounds(); + + $codebase->analyzer->addOffsetReference( + $this->getFilePath(), + $start, + $end, + (string) $storage->signature_return_type + ); + } + + if (ReturnTypeAnalyzer::checkReturnType( + $this->function, + $project_analyzer, + $this, + $storage, + $context + ) === false) { + $check_stmts = false; + } + + if (!$check_stmts) { + return false; + } + + if ($context->collect_initializations || $context->collect_mutations) { + $statements_analyzer->addSuppressedIssues([ + 'DocblockTypeContradiction', + 'InvalidReturnStatement', + 'RedundantCondition', + 'RedundantConditionGivenDocblockType', + 'TypeDoesNotContainNull', + 'TypeDoesNotContainType', + 'LoopInvalidation', + ]); + + if ($context->collect_initializations) { + $statements_analyzer->addSuppressedIssues([ + 'UndefinedInterfaceMethod', + 'UndefinedMethod', + 'PossiblyUndefinedMethod', + ]); + } + } elseif ($cased_method_id && strpos($cased_method_id, '__destruct')) { + $statements_analyzer->addSuppressedIssues([ + 'InvalidPropertyAssignmentValue', + 'PossiblyNullPropertyAssignmentValue', + ]); + } + + $time = \microtime(true); + + $project_analyzer = $statements_analyzer->getProjectAnalyzer(); + + if ($codebase->alter_code + && (isset($project_analyzer->getIssuesToFix()['MissingPureAnnotation']) + || isset($project_analyzer->getIssuesToFix()['MissingImmutableAnnotation'])) + ) { + $this->track_mutations = true; + } elseif ($this->function instanceof Closure + || $this->function instanceof ArrowFunction + ) { + $this->track_mutations = true; + } + + $statements_analyzer->analyze($function_stmts, $context, $global_context, true); + + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['MissingPureAnnotation']) + && !$this->inferred_impure + && ($this->function instanceof Function_ + || $this->function instanceof ClassMethod) + && $storage->params + && !$overridden_method_ids + ) { + $manipulator = FunctionDocblockManipulator::getForFunction( + $project_analyzer, + $this->source->getFilePath(), + $this->function + ); + + $yield_types = []; + + $inferred_return_types = ReturnTypeCollector::getReturnTypes( + $codebase, + $type_provider, + $function_stmts, + $yield_types, + true + ); + + $inferred_return_type = $inferred_return_types + ? \Psalm\Type::combineUnionTypeArray( + $inferred_return_types, + $codebase + ) + : null; + + if ($inferred_return_type + && !$inferred_return_type->isVoid() + && !$inferred_return_type->isFalse() + && !$inferred_return_type->isNull() + && !$inferred_return_type->isSingleIntLiteral() + && !$inferred_return_type->isSingleStringLiteral() + && !$inferred_return_type->isTrue() + && $inferred_return_type->getId() !== 'array' + ) { + $manipulator->makePure(); + } + } + + if ($this->inferred_has_mutation && $context->self) { + $this->codebase->analyzer->addMutableClass($context->self); + } + + if (!$context->collect_initializations + && !$context->collect_mutations + && $project_analyzer->debug_performance + && $cased_method_id + ) { + $traverser = new PhpParser\NodeTraverser; + + $node_counter = new \Psalm\Internal\PhpVisitor\NodeCounterVisitor(); + $traverser->addVisitor($node_counter); + $traverser->traverse($function_stmts); + + if ($node_counter->count > 5) { + $time_taken = \microtime(true) - $time; + $codebase->analyzer->addFunctionTiming($cased_method_id, $time_taken / $node_counter->count); + } + } + + $this->examineParamTypes($statements_analyzer, $context, $codebase); + + foreach ($storage->params as $offset => $function_param) { + // only complain if there's no type defined by a parent type + if (!$function_param->type + && $function_param->location + && !isset($implemented_docblock_param_types[$offset]) + ) { + if ($this->function instanceof Closure + || $this->function instanceof ArrowFunction + ) { + IssueBuffer::accepts( + new MissingClosureParamType( + 'Parameter $' . $function_param->name . ' has no provided type', + $function_param->location + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + ); + } else { + IssueBuffer::accepts( + new MissingParamType( + 'Parameter $' . $function_param->name . ' has no provided type', + $function_param->location + ), + $storage->suppressed_issues + $this->getSuppressedIssues() + ); + } + } + } + + if ($this->function instanceof Closure + || $this->function instanceof ArrowFunction + ) { + $this->verifyReturnType( + $function_stmts, + $statements_analyzer, + $storage->return_type, + $this->source->getFQCLN(), + $storage->return_type_location, + $context->has_returned, + $global_context && $global_context->inside_call + ); + + $closure_yield_types = []; + + $closure_return_types = ReturnTypeCollector::getReturnTypes( + $codebase, + $type_provider, + $function_stmts, + $closure_yield_types, + true + ); + + $closure_return_type = $closure_return_types + ? \Psalm\Type::combineUnionTypeArray( + $closure_return_types, + $codebase + ) + : null; + + $closure_yield_type = $closure_yield_types + ? \Psalm\Type::combineUnionTypeArray( + $closure_yield_types, + $codebase + ) + : null; + + if ($closure_return_type || $closure_yield_type) { + if ($closure_yield_type) { + $closure_return_type = $closure_yield_type; + } + + if ($function_type = $statements_analyzer->node_data->getType($this->function)) { + /** + * @var Type\Atomic\TClosure + */ + $closure_atomic = \array_values($function_type->getAtomicTypes())[0]; + + if (($storage->return_type === $storage->signature_return_type) + && (!$storage->return_type + || $storage->return_type->hasMixed() + || UnionTypeComparator::isContainedBy( + $codebase, + $closure_return_type, + $storage->return_type + )) + ) { + $closure_atomic->return_type = $closure_return_type; + } + + $closure_atomic->is_pure = !$this->inferred_impure; + } + } + } + + if ($codebase->collect_references + && !$context->collect_initializations + && !$context->collect_mutations + && $codebase->find_unused_variables + && $context->check_variables + ) { + $this->checkParamReferences( + $statements_analyzer, + $storage, + $appearing_class_storage, + $context + ); + } + + foreach ($storage->throws as $expected_exception => $_) { + if (($expected_exception === 'self' + || $expected_exception === 'static') + && $context->self + ) { + $expected_exception = $context->self; + } + + if (isset($storage->throw_locations[$expected_exception])) { + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $expected_exception, + $storage->throw_locations[$expected_exception], + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues(), + false, + false, + true, + true + )) { + $input_type = new Type\Union([new TNamedObject($expected_exception)]); + $container_type = new Type\Union([new TNamedObject('Exception'), new TNamedObject('Throwable')]); + + if (!UnionTypeComparator::isContainedBy($codebase, $input_type, $container_type)) { + if (IssueBuffer::accepts( + new \Psalm\Issue\InvalidThrow( + 'Class supplied for @throws ' . $expected_exception + . ' does not implement Throwable', + $storage->throw_locations[$expected_exception], + $expected_exception + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($codebase->alter_code) { + $codebase->classlikes->handleDocblockTypeInMigration( + $codebase, + $this, + $input_type, + $storage->throw_locations[$expected_exception], + $context->calling_method_id + ); + } + } + } + } + + foreach ($statements_analyzer->getUncaughtThrows($context) as $possibly_thrown_exception => $codelocations) { + $is_expected = false; + + foreach ($storage->throws as $expected_exception => $_) { + if ($expected_exception === $possibly_thrown_exception + || $codebase->classExtendsOrImplements($possibly_thrown_exception, $expected_exception) + ) { + $is_expected = true; + break; + } + } + + if (!$is_expected) { + foreach ($codelocations as $codelocation) { + // issues are suppressed in ThrowAnalyzer, CallAnalyzer, etc. + if (IssueBuffer::accepts( + new MissingThrowsDocblock( + $possibly_thrown_exception . ' is thrown but not caught - please either catch' + . ' or add a @throws annotation', + $codelocation + ) + )) { + // fall through + } + } + } + } + + if ($codebase->taint_flow_graph + && $this->function instanceof ClassMethod + && $cased_method_id + && $storage->specialize_call + && isset($context->vars_in_scope['$this']) + && $context->vars_in_scope['$this']->parent_nodes + ) { + $method_source = DataFlowNode::getForMethodReturn( + (string) $method_id, + $cased_method_id, + $storage->location + ); + + $codebase->taint_flow_graph->addNode($method_source); + + foreach ($context->vars_in_scope['$this']->parent_nodes as $parent_node) { + $codebase->taint_flow_graph->addPath( + $parent_node, + $method_source, + '$this' + ); + } + } + + if ($add_mutations) { + if ($this->return_vars_in_scope !== null) { + $context->vars_in_scope = TypeAnalyzer::combineKeyedTypes( + $context->vars_in_scope, + $this->return_vars_in_scope + ); + } + + if ($this->return_vars_possibly_in_scope !== null) { + $context->vars_possibly_in_scope = array_merge( + $context->vars_possibly_in_scope, + $this->return_vars_possibly_in_scope + ); + } + + foreach ($context->vars_in_scope as $var => $_) { + if (strpos($var, '$this->') !== 0 && $var !== '$this') { + unset($context->vars_in_scope[$var]); + } + } + + foreach ($context->vars_possibly_in_scope as $var => $_) { + if (strpos($var, '$this->') !== 0 && $var !== '$this') { + unset($context->vars_possibly_in_scope[$var]); + } + } + + if ($hash + && $real_method_id + && $this instanceof MethodAnalyzer + && !$context->collect_initializations + ) { + $new_hash = md5($real_method_id . '::' . $context->getScopeSummary()); + + if ($new_hash === $hash) { + self::$no_effects_hashes[$hash] = true; + } + } + } + + $plugin_classes = $codebase->config->after_functionlike_checks; + + if ($plugin_classes) { + $file_manipulations = []; + + foreach ($plugin_classes as $plugin_fq_class_name) { + if ($plugin_fq_class_name::afterStatementAnalysis( + $this->function, + $storage, + $this, + $codebase, + $file_manipulations + ) === false) { + return false; + } + } + + if ($file_manipulations) { + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $this->getFilePath(), + $file_manipulations + ); + } + } + + return null; + } + + private function checkParamReferences( + StatementsAnalyzer $statements_analyzer, + FunctionLikeStorage $storage, + ?\Psalm\Storage\ClassLikeStorage $class_storage, + Context $context + ) : void { + $codebase = $statements_analyzer->getCodebase(); + + $unused_params = []; + + foreach ($statements_analyzer->getUnusedVarLocations() as [$var_name, $original_location]) { + if (!array_key_exists(substr($var_name, 1), $storage->param_lookup)) { + continue; + } + + if (strpos($var_name, '$_') === 0 || (strpos($var_name, '$unused') === 0 && $var_name !== '$unused')) { + continue; + } + + $position = array_search(substr($var_name, 1), array_keys($storage->param_lookup), true); + + if ($position === false) { + throw new \UnexpectedValueException('$position should not be false here'); + } + + if ($storage->params[$position]->by_ref) { + continue; + } + + $did_match_param = false; + + foreach ($this->function->params as $param) { + if ($param->var->getAttribute('endFilePos') === $original_location->raw_file_end) { + $did_match_param = true; + break; + } + } + + if (!$did_match_param) { + continue; + } + + $assignment_node = DataFlowNode::getForAssignment($var_name, $original_location); + + if ($statements_analyzer->data_flow_graph instanceof \Psalm\Internal\Codebase\VariableUseGraph + && $statements_analyzer->data_flow_graph->isVariableUsed($assignment_node) + ) { + continue; + } + + if (!($storage instanceof MethodStorage) + || !$storage->cased_name + || $storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE + ) { + if ($this->function instanceof Closure + || $this->function instanceof ArrowFunction + ) { + if (IssueBuffer::accepts( + new UnusedClosureParam( + 'Param ' . $var_name . ' is never referenced in this method', + $original_location + ), + $this->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new UnusedParam( + 'Param ' . $var_name . ' is never referenced in this method', + $original_location + ), + $this->getSuppressedIssues() + )) { + // fall through + } + } + } else { + $fq_class_name = (string)$context->self; + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + $method_name_lc = strtolower($storage->cased_name); + + if ($storage->abstract) { + continue; + } + + if (isset($class_storage->overridden_method_ids[$method_name_lc])) { + $parent_method_id = end($class_storage->overridden_method_ids[$method_name_lc]); + + if ($parent_method_id) { + $parent_method_storage = $codebase->methods->getStorage($parent_method_id); + + // if the parent method has a param at that position and isn't abstract + if (!$parent_method_storage->abstract + && isset($parent_method_storage->params[$position]) + ) { + continue; + } + } + } + + $unused_params[$position] = $original_location; + } + } + + if ($storage instanceof MethodStorage + && $this instanceof MethodAnalyzer + && $class_storage + && $storage->cased_name + && $storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PRIVATE + ) { + $method_id_lc = strtolower((string) $this->getMethodId()); + + foreach ($storage->params as $i => $_) { + if (!isset($unused_params[$i])) { + $codebase->file_reference_provider->addMethodParamUse( + $method_id_lc, + $i, + $method_id_lc + ); + + $method_name_lc = strtolower($storage->cased_name); + + if (!isset($class_storage->overridden_method_ids[$method_name_lc])) { + continue; + } + + foreach ($class_storage->overridden_method_ids[$method_name_lc] as $parent_method_id) { + $codebase->file_reference_provider->addMethodParamUse( + strtolower((string) $parent_method_id), + $i, + $method_id_lc + ); + } + } + } + } + } + + /** + * @param array $params + * @param array $implemented_docblock_param_types + */ + private function processParams( + StatementsAnalyzer $statements_analyzer, + FunctionLikeStorage $storage, + ?string $cased_method_id, + array $params, + Context $context, + array $implemented_docblock_param_types, + bool $non_null_param_types, + bool $has_template_types + ) : bool { + $check_stmts = true; + $codebase = $statements_analyzer->getCodebase(); + $project_analyzer = $statements_analyzer->getProjectAnalyzer(); + + foreach ($params as $offset => $function_param) { + $signature_type = $function_param->signature_type; + $signature_type_location = $function_param->signature_type_location; + + if ($signature_type && $signature_type_location && $signature_type->hasObjectType()) { + $referenced_type = $signature_type; + if ($referenced_type->isNullable()) { + $referenced_type = clone $referenced_type; + $referenced_type->removeType('null'); + } + [$start, $end] = $signature_type_location->getSelectionBounds(); + $codebase->analyzer->addOffsetReference( + $this->getFilePath(), + $start, + $end, + (string) $referenced_type + ); + } + + if ($signature_type) { + $signature_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $signature_type, + $context->self, + $context->self, + $this->getParentFQCLN() + ); + } + + if ($function_param->type) { + $is_signature_type = $function_param->type === $function_param->signature_type; + + if ($is_signature_type + && $storage instanceof MethodStorage + && $storage->inheritdoc + && isset($implemented_docblock_param_types[$offset]) + ) { + $param_type = clone $implemented_docblock_param_types[$offset]; + } else { + $param_type = clone $function_param->type; + } + + $param_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $param_type, + $context->self, + $context->self, + $this->getParentFQCLN() + ); + + if ($function_param->type_location) { + if ($param_type->check( + $this, + $function_param->type_location, + $storage->suppressed_issues, + [], + false, + false, + $this->function instanceof ClassMethod + && strtolower($this->function->name->name) !== '__construct' + ) === false) { + $check_stmts = false; + } + } + } else { + if (!$non_null_param_types && isset($implemented_docblock_param_types[$offset])) { + $param_type = clone $implemented_docblock_param_types[$offset]; + + $param_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $param_type, + $context->self, + $context->self, + $this->getParentFQCLN() + ); + } else { + $param_type = Type::getMixed(); + } + } + + if ($param_type->hasTemplate() && $param_type->isSingle()) { + /** @var Type\Atomic\TTemplateParam */ + $template_type = \array_values($param_type->getAtomicTypes())[0]; + + if ($template_type->as->getTemplateTypes()) { + $param_type = $template_type->as; + } + } + + $var_type = $param_type; + + if ($function_param->is_variadic) { + $var_type = new Type\Union([ + new Type\Atomic\TList($param_type), + ]); + } + + if ($statements_analyzer->data_flow_graph + && $function_param->location + ) { + $param_assignment = DataFlowNode::getForAssignment( + '$' . $function_param->name, + $function_param->location + ); + + $statements_analyzer->data_flow_graph->addNode($param_assignment); + + if ($cased_method_id) { + $type_source = DataFlowNode::getForMethodArgument( + $cased_method_id, + $cased_method_id, + $offset, + $function_param->location, + null + ); + + $statements_analyzer->data_flow_graph->addPath($type_source, $param_assignment, 'param'); + } + + if ($function_param->by_ref + && $codebase->find_unused_variables + ) { + $statements_analyzer->data_flow_graph->addPath( + $param_assignment, + new DataFlowNode('variable-use', 'variable use', null), + 'variable-use' + ); + } + + if ($storage->variadic) { + $this->param_nodes += [$param_assignment->id => $param_assignment]; + } + + $var_type->parent_nodes += [$param_assignment->id => $param_assignment]; + } + + $context->vars_in_scope['$' . $function_param->name] = $var_type; + $context->vars_possibly_in_scope['$' . $function_param->name] = true; + + if ($function_param->by_ref) { + $context->vars_in_scope['$' . $function_param->name]->by_ref = true; + } + + $parser_param = $this->function->getParams()[$offset]; + + if ($function_param->location) { + $statements_analyzer->registerVariable( + '$' . $function_param->name, + $function_param->location, + null + ); + } + + if (!$function_param->type_location || !$function_param->location) { + if ($parser_param->default) { + ExpressionAnalyzer::analyze($statements_analyzer, $parser_param->default, $context); + } + + continue; + } + + if ($signature_type) { + $union_comparison_result = new TypeComparisonResult(); + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $param_type, + $signature_type, + false, + false, + $union_comparison_result + ) && !$union_comparison_result->type_coerced_from_mixed + ) { + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['MismatchingDocblockParamType']) + ) { + $this->addOrUpdateParamType($project_analyzer, $function_param->name, $signature_type, true); + + continue; + } + + if (IssueBuffer::accepts( + new MismatchingDocblockParamType( + 'Parameter $' . $function_param->name . ' has wrong type \'' . $param_type . + '\', should be \'' . $signature_type . '\'', + $function_param->type_location + ), + $storage->suppressed_issues, + true + )) { + // do nothing + } + + if ($signature_type->check( + $this, + $function_param->type_location, + $storage->suppressed_issues, + [], + false + ) === false) { + $check_stmts = false; + } + + continue; + } + } + + if ($parser_param->default) { + ExpressionAnalyzer::analyze($statements_analyzer, $parser_param->default, $context); + + $default_type = $statements_analyzer->node_data->getType($parser_param->default); + + if ($default_type + && !$default_type->hasMixed() + && !UnionTypeComparator::isContainedBy( + $codebase, + $default_type, + $param_type, + false, + false, + null, + true + ) + ) { + if (IssueBuffer::accepts( + new InvalidParamDefault( + 'Default value type ' . $default_type->getId() . ' for argument ' . ($offset + 1) + . ' of method ' . $cased_method_id + . ' does not match the given type ' . $param_type->getId(), + $function_param->type_location + ) + )) { + // fall through + } + } + } + + if ($has_template_types) { + $substituted_type = clone $param_type; + if ($substituted_type->check( + $this->source, + $function_param->type_location, + $this->suppressed_issues, + [], + false + ) === false) { + $check_stmts = false; + } + } else { + if ($param_type->isVoid()) { + if (IssueBuffer::accepts( + new ReservedWord( + 'Parameter cannot be void', + $function_param->type_location, + 'void' + ), + $this->suppressed_issues + )) { + // fall through + } + } + + if ($param_type->check( + $this->source, + $function_param->type_location, + $this->suppressed_issues, + [], + false + ) === false) { + $check_stmts = false; + } + } + + if ($codebase->collect_locations) { + if ($function_param->type_location !== $function_param->signature_type_location && + $function_param->signature_type_location && + $function_param->signature_type + ) { + if ($function_param->signature_type->check( + $this->source, + $function_param->signature_type_location, + $this->suppressed_issues, + [], + false + ) === false) { + $check_stmts = false; + } + } + } + + if ($function_param->by_ref) { + // register by ref params as having been used, to avoid false positives + // @todo change the assignment analysis *just* for byref params + // so that we don't have to do this + $context->hasVariable('$' . $function_param->name); + } + } + + return $check_stmts; + } + + /** + * @param \Psalm\Storage\FunctionLikeParameter[] $params + */ + private function alterParams( + Codebase $codebase, + FunctionLikeStorage $storage, + array $params, + Context $context + ) : void { + foreach ($this->function->params as $param) { + $param_name_node = null; + + if ($param->type instanceof PhpParser\Node\Name) { + $param_name_node = $param->type; + } elseif ($param->type instanceof PhpParser\Node\NullableType + && $param->type->type instanceof PhpParser\Node\Name + ) { + $param_name_node = $param->type->type; + } + + if ($param_name_node) { + $resolved_name = ClassLikeAnalyzer::getFQCLNFromNameObject($param_name_node, $this->getAliases()); + + $parent_fqcln = $this->getParentFQCLN(); + + if ($resolved_name === 'self' && $context->self) { + $resolved_name = (string) $context->self; + } elseif ($resolved_name === 'parent' && $parent_fqcln) { + $resolved_name = $parent_fqcln; + } + + $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $this, + $param_name_node, + $resolved_name, + $context->calling_method_id, + false, + true + ); + } + } + + if ($this->function->returnType) { + $return_name_node = null; + + if ($this->function->returnType instanceof PhpParser\Node\Name) { + $return_name_node = $this->function->returnType; + } elseif ($this->function->returnType instanceof PhpParser\Node\NullableType + && $this->function->returnType->type instanceof PhpParser\Node\Name + ) { + $return_name_node = $this->function->returnType->type; + } + + if ($return_name_node) { + $resolved_name = ClassLikeAnalyzer::getFQCLNFromNameObject($return_name_node, $this->getAliases()); + + $parent_fqcln = $this->getParentFQCLN(); + + if ($resolved_name === 'self' && $context->self) { + $resolved_name = (string) $context->self; + } elseif ($resolved_name === 'parent' && $parent_fqcln) { + $resolved_name = $parent_fqcln; + } + + $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $this, + $return_name_node, + $resolved_name, + $context->calling_method_id, + false, + true + ); + } + } + + if ($storage->return_type + && $storage->return_type_location + && $storage->return_type_location !== $storage->signature_return_type_location + ) { + $replace_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $storage->return_type, + $context->self, + 'static', + $this->getParentFQCLN(), + false + ); + + $codebase->classlikes->handleDocblockTypeInMigration( + $codebase, + $this, + $replace_type, + $storage->return_type_location, + $context->calling_method_id + ); + } + + foreach ($params as $function_param) { + if ($function_param->type + && $function_param->type_location + && $function_param->type_location !== $function_param->signature_type_location + && $function_param->type_location->file_path === $this->getFilePath() + ) { + $replace_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $function_param->type, + $context->self, + 'static', + $this->getParentFQCLN(), + false + ); + + $codebase->classlikes->handleDocblockTypeInMigration( + $codebase, + $this, + $replace_type, + $function_param->type_location, + $context->calling_method_id + ); + } + } + } + + /** + * @param array $function_stmts + */ + public function verifyReturnType( + array $function_stmts, + StatementsAnalyzer $statements_analyzer, + ?Type\Union $return_type = null, + ?string $fq_class_name = null, + ?CodeLocation $return_type_location = null, + bool $did_explicitly_return = false, + bool $closure_inside_call = false + ): void { + ReturnTypeAnalyzer::verifyReturnType( + $this->function, + $function_stmts, + $statements_analyzer, + $statements_analyzer->node_data, + $this, + $return_type, + $fq_class_name, + $return_type_location, + [], + $did_explicitly_return, + $closure_inside_call + ); + } + + public function addOrUpdateParamType( + ProjectAnalyzer $project_analyzer, + string $param_name, + Type\Union $inferred_return_type, + bool $docblock_only = false + ): void { + $manipulator = FunctionDocblockManipulator::getForFunction( + $project_analyzer, + $this->source->getFilePath(), + $this->function + ); + + $codebase = $project_analyzer->getCodebase(); + $is_final = true; + $fqcln = $this->source->getFQCLN(); + + if ($fqcln !== null && $this->function instanceof ClassMethod) { + $class_storage = $codebase->classlike_storage_provider->get($fqcln); + $is_final = $this->function->isFinal() || $class_storage->final; + } + + $allow_native_type = !$docblock_only + && $codebase->php_major_version >= 7 + && ( + $codebase->allow_backwards_incompatible_changes + || $is_final + || !$this->function instanceof PhpParser\Node\Stmt\ClassMethod + ); + + $manipulator->setParamType( + $param_name, + $allow_native_type + ? $inferred_return_type->toPhpString( + $this->source->getNamespace(), + $this->source->getAliasedClassesFlipped(), + $this->source->getFQCLN(), + $project_analyzer->getCodebase()->php_major_version, + $project_analyzer->getCodebase()->php_minor_version + ) : null, + $inferred_return_type->toNamespacedString( + $this->source->getNamespace(), + $this->source->getAliasedClassesFlipped(), + $this->source->getFQCLN(), + false + ), + $inferred_return_type->toNamespacedString( + $this->source->getNamespace(), + $this->source->getAliasedClassesFlipped(), + $this->source->getFQCLN(), + true + ) + ); + } + + /** + * Adds return types for the given function + * + * @param string $return_type + * + */ + public function addReturnTypes(Context $context): void + { + if ($this->return_vars_in_scope !== null) { + $this->return_vars_in_scope = TypeAnalyzer::combineKeyedTypes( + $context->vars_in_scope, + $this->return_vars_in_scope + ); + } else { + $this->return_vars_in_scope = $context->vars_in_scope; + } + + if ($this->return_vars_possibly_in_scope !== null) { + $this->return_vars_possibly_in_scope = array_merge( + $context->vars_possibly_in_scope, + $this->return_vars_possibly_in_scope + ); + } else { + $this->return_vars_possibly_in_scope = $context->vars_possibly_in_scope; + } + } + + public function examineParamTypes( + StatementsAnalyzer $statements_analyzer, + Context $context, + Codebase $codebase, + PhpParser\Node $stmt = null + ): void { + $storage = $this->getFunctionLikeStorage($statements_analyzer); + + foreach ($storage->params as $param) { + if ($param->by_ref && isset($context->vars_in_scope['$' . $param->name]) && !$param->is_variadic) { + $actual_type = $context->vars_in_scope['$' . $param->name]; + $param_out_type = $param->out_type ?: $param->type; + + if ($param_out_type && !$actual_type->hasMixed() && $param->location) { + if (!UnionTypeComparator::isContainedBy( + $codebase, + $actual_type, + $param_out_type, + $actual_type->ignore_nullable_issues, + $actual_type->ignore_falsable_issues + ) + ) { + if (IssueBuffer::accepts( + new ReferenceConstraintViolation( + 'Variable ' . '$' . $param->name . ' is limited to values of type ' + . $param_out_type->getId() + . ' because it is passed by reference, ' + . $actual_type->getId() . ' type found. Use @param-out to specify ' + . 'a different output type', + $stmt + ? new CodeLocation($this, $stmt) + : $param->location + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + } + + public function getMethodName(): ?string + { + if ($this->function instanceof ClassMethod) { + return (string)$this->function->name; + } + + return null; + } + + public function getCorrectlyCasedMethodId(?string $context_self = null): string + { + if ($this->function instanceof ClassMethod) { + $function_name = (string)$this->function->name; + + return ($context_self ?: $this->source->getFQCLN()) . '::' . $function_name; + } + + if ($this->function instanceof Function_) { + $namespace = $this->source->getNamespace(); + + return ($namespace ? $namespace . '\\' : '') . $this->function->name; + } + + if (!$this instanceof ClosureAnalyzer) { + throw new \UnexpectedValueException('This is weird'); + } + + return $this->getClosureId(); + } + + public function getFunctionLikeStorage(?StatementsAnalyzer $statements_analyzer = null): FunctionLikeStorage + { + $codebase = $this->codebase; + + if ($this->function instanceof ClassMethod && $this instanceof MethodAnalyzer) { + $method_id = $this->getMethodId(); + $codebase_methods = $codebase->methods; + + try { + return $codebase_methods->getStorage($method_id); + } catch (\UnexpectedValueException $e) { + $declaring_method_id = $codebase_methods->getDeclaringMethodId($method_id); + + if ($declaring_method_id === null) { + throw new \UnexpectedValueException('Cannot get storage for function that doesn‘t exist'); + } + + // happens for fake constructors + return $codebase_methods->getStorage($declaring_method_id); + } + } + + if ($this instanceof FunctionAnalyzer) { + $function_id = $this->getFunctionId(); + } elseif ($this instanceof ClosureAnalyzer) { + $function_id = $this->getClosureId(); + } else { + throw new \UnexpectedValueException('This is weird'); + } + + return $codebase->functions->getStorage($statements_analyzer, $function_id); + } + + /** @return non-empty-string */ + public function getId() : string + { + if ($this instanceof MethodAnalyzer) { + return (string) $this->getMethodId(); + } + + if ($this instanceof FunctionAnalyzer) { + return $this->getFunctionId(); + } + + if ($this instanceof ClosureAnalyzer) { + return $this->getClosureId(); + } + + throw new \UnexpectedValueException('This is weird'); + } + + /** + * @return array + */ + public function getAliasedClassesFlipped(): array + { + if ($this->source instanceof NamespaceAnalyzer || + $this->source instanceof FileAnalyzer || + $this->source instanceof ClassLikeAnalyzer + ) { + return $this->source->getAliasedClassesFlipped(); + } + + return []; + } + + /** + * @return array + */ + public function getAliasedClassesFlippedReplaceable(): array + { + if ($this->source instanceof NamespaceAnalyzer || + $this->source instanceof FileAnalyzer || + $this->source instanceof ClassLikeAnalyzer + ) { + return $this->source->getAliasedClassesFlippedReplaceable(); + } + + return []; + } + + public function getFQCLN(): ?string + { + return $this->source->getFQCLN(); + } + + public function getClassName(): ?string + { + return $this->source->getClassName(); + } + + /** + * @return array>|null + */ + public function getTemplateTypeMap(): ?array + { + if ($this->source instanceof ClassLikeAnalyzer) { + return ($this->source->getTemplateTypeMap() ?: []) + + ($this->storage->template_types ?: []); + } + + return $this->storage->template_types; + } + + public function getParentFQCLN(): ?string + { + return $this->source->getParentFQCLN(); + } + + public function getNodeTypeProvider() : \Psalm\NodeTypeProvider + { + return $this->source->getNodeTypeProvider(); + } + + public function isStatic(): bool + { + return $this->is_static; + } + + public function getSource(): StatementsSource + { + return $this->source; + } + + public function getCodebase() : Codebase + { + return $this->codebase; + } + + /** + * Get a list of suppressed issues + * + * @return array + */ + public function getSuppressedIssues(): array + { + return $this->suppressed_issues; + } + + /** + * @param array $new_issues + */ + public function addSuppressedIssues(array $new_issues): void + { + if (isset($new_issues[0])) { + $new_issues = \array_combine($new_issues, $new_issues); + } + + $this->suppressed_issues = $new_issues + $this->suppressed_issues; + } + + /** + * @param array $new_issues + */ + public function removeSuppressedIssues(array $new_issues): void + { + if (isset($new_issues[0])) { + $new_issues = \array_combine($new_issues, $new_issues); + } + + $this->suppressed_issues = \array_diff_key($this->suppressed_issues, $new_issues); + } + + /** + * Adds a suppressed issue, useful when creating a method checker from scratch + * + */ + public function addSuppressedIssue(string $issue_name): void + { + $this->suppressed_issues[] = $issue_name; + } + + public static function clearCache(): void + { + self::$no_effects_hashes = []; + } + + public function getLocalReturnType(Type\Union $storage_return_type, bool $final = false): Type\Union + { + if ($this->local_return_type) { + return $this->local_return_type; + } + + $this->local_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $this->codebase, + $storage_return_type, + $this->getFQCLN(), + $this->getFQCLN(), + $this->getParentFQCLN(), + true, + true, + $final + ); + + return $this->local_return_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..d77cfde5276b8722c41e901a366eae6790c3987f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php @@ -0,0 +1,131 @@ +class instanceof PhpParser\Node\Stmt\Interface_) { + throw new \LogicException('Something went badly wrong'); + } + + $project_analyzer = $this->file_analyzer->project_analyzer; + $codebase = $project_analyzer->getCodebase(); + $config = $project_analyzer->getConfig(); + + if ($this->class->extends) { + foreach ($this->class->extends as $extended_interface) { + $extended_interface_name = self::getFQCLNFromNameObject( + $extended_interface, + $this->getAliases() + ); + + $parent_reference_location = new CodeLocation($this, $extended_interface); + + if (!$codebase->classOrInterfaceExists( + $extended_interface_name, + $parent_reference_location + )) { + // we should not normally get here + return; + } + + try { + $extended_interface_storage = $codebase->classlike_storage_provider->get($extended_interface_name); + } catch (\InvalidArgumentException $e) { + continue; + } + + if (!$extended_interface_storage->is_interface) { + $code_location = new CodeLocation( + $this, + $extended_interface + ); + + if (\Psalm\IssueBuffer::accepts( + new UndefinedInterface( + $extended_interface_name . ' is not an interface', + $code_location, + $extended_interface_name + ), + $this->getSuppressedIssues() + )) { + // fall through + } + } + + if ($codebase->store_node_types && $extended_interface_name) { + $bounds = $parent_reference_location->getSelectionBounds(); + + $codebase->analyzer->addOffsetReference( + $this->getFilePath(), + $bounds[0], + $bounds[1], + $extended_interface_name + ); + } + } + } + + $fq_interface_name = $this->getFQCLN(); + + if (!$fq_interface_name) { + throw new \UnexpectedValueException('bad'); + } + + $class_storage = $codebase->classlike_storage_provider->get($fq_interface_name); + + foreach ($this->class->stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod) { + $method_analyzer = new MethodAnalyzer($stmt, $this); + + $type_provider = new \Psalm\Internal\Provider\NodeDataProvider(); + + $method_analyzer->analyze(new \Psalm\Context($this->getFQCLN()), $type_provider); + + $actual_method_id = $method_analyzer->getMethodId(); + + if ($stmt->name->name !== '__construct' + && $config->reportIssueInFile('InvalidReturnType', $this->getFilePath()) + ) { + ClassAnalyzer::analyzeClassMethodReturnType( + $stmt, + $method_analyzer, + $this, + $type_provider, + $codebase, + $class_storage, + $fq_interface_name, + $actual_method_id, + $actual_method_id, + false + ); + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\Property) { + \Psalm\IssueBuffer::add( + new \Psalm\Issue\ParseError( + 'Interfaces cannot have properties', + new CodeLocation($this, $stmt) + ) + ); + + return; + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/IssueData.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/IssueData.php new file mode 100644 index 0000000000000000000000000000000000000000..9b2be54fa20e40404291ae6df1812b6b0140720c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/IssueData.php @@ -0,0 +1,169 @@ + + */ + public $taint_trace; + + /** + * @var ?string + * @readonly + */ + public $dupe_key; + + /** + * @param ?list $taint_trace + */ + public function __construct( + string $severity, + int $line_from, + int $line_to, + string $type, + string $message, + string $file_name, + string $file_path, + string $snippet, + string $selected_text, + int $from, + int $to, + int $snippet_from, + int $snippet_to, + int $column_from, + int $column_to, + int $shortcode = 0, + int $error_level = -1, + ?array $taint_trace = null, + ?string $dupe_key = null + ) { + $this->severity = $severity; + $this->line_from = $line_from; + $this->line_to = $line_to; + $this->type = $type; + $this->message = $message; + $this->file_name = $file_name; + $this->file_path = $file_path; + $this->snippet = $snippet; + $this->selected_text = $selected_text; + $this->from = $from; + $this->to = $to; + $this->snippet_from = $snippet_from; + $this->snippet_to = $snippet_to; + $this->column_from = $column_from; + $this->column_to = $column_to; + $this->shortcode = $shortcode; + $this->error_level = $error_level; + $this->link = 'https://psalm.dev/' . \str_pad((string) $shortcode, 3, "0", \STR_PAD_LEFT); + $this->taint_trace = $taint_trace; + $this->dupe_key = $dupe_key; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/MethodAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/MethodAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..d54a7d4209c1ea60d86a794535b9d23c3beeef4d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/MethodAnalyzer.php @@ -0,0 +1,290 @@ +getCodebase(); + + $method_name_lc = strtolower((string) $function->name); + + $source_fqcln = (string) $source->getFQCLN(); + + $source_fqcln_lc = strtolower($source_fqcln); + + $method_id = new \Psalm\Internal\MethodIdentifier($source_fqcln, $method_name_lc); + + if (!$storage) { + try { + $storage = $codebase->methods->getStorage($method_id); + } catch (\UnexpectedValueException $e) { + $class_storage = $codebase->classlike_storage_provider->get($source_fqcln_lc); + + if (!$class_storage->parent_classes) { + throw $e; + } + + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + if (!$declaring_method_id) { + throw $e; + } + + // happens for fake constructors + $storage = $codebase->methods->getStorage($declaring_method_id); + } + } + + parent::__construct($function, $source, $storage); + } + + /** + * Determines whether a given method is static or not + * @param array $suppressed_issues + */ + public static function checkStatic( + \Psalm\Internal\MethodIdentifier $method_id, + bool $self_call, + bool $is_context_dynamic, + Codebase $codebase, + CodeLocation $code_location, + array $suppressed_issues, + ?bool &$is_dynamic_this_method = false + ): bool { + $codebase_methods = $codebase->methods; + + if ($method_id->fq_class_name === 'Closure' + && $method_id->method_name === 'fromcallable' + ) { + return true; + } + + $original_method_id = $method_id; + + $method_id = $codebase_methods->getDeclaringMethodId($method_id); + + if (!$method_id) { + throw new \LogicException('Declaring method for ' . $original_method_id . ' should not be null'); + } + + $storage = $codebase_methods->getStorage($method_id); + + if (!$storage->is_static) { + if ($self_call) { + if (!$is_context_dynamic) { + if (IssueBuffer::accepts( + new NonStaticSelfCall( + 'Method ' . $codebase_methods->getCasedMethodId($method_id) . + ' is not static, but is called ' . + 'using self::', + $code_location + ), + $suppressed_issues + )) { + return false; + } + } else { + $is_dynamic_this_method = true; + } + } else { + if (IssueBuffer::accepts( + new InvalidStaticInvocation( + 'Method ' . $codebase_methods->getCasedMethodId($method_id) . + ' is not static, but is called ' . + 'statically', + $code_location + ), + $suppressed_issues + )) { + return false; + } + } + } + + return true; + } + + /** + * @param string[] $suppressed_issues + * @param lowercase-string|null $calling_method_id + * + */ + public static function checkMethodExists( + Codebase $codebase, + \Psalm\Internal\MethodIdentifier $method_id, + CodeLocation $code_location, + array $suppressed_issues, + ?string $calling_method_id = null + ): ?bool { + if ($codebase->methods->methodExists( + $method_id, + $calling_method_id, + !$calling_method_id + || $calling_method_id !== strtolower((string) $method_id) + ? $code_location + : null, + null, + $code_location->file_path + )) { + return true; + } + + if (IssueBuffer::accepts( + new UndefinedMethod('Method ' . $method_id . ' does not exist', $code_location, (string) $method_id), + $suppressed_issues + )) { + return false; + } + + return null; + } + + public static function isMethodVisible( + \Psalm\Internal\MethodIdentifier $method_id, + Context $context, + StatementsSource $source + ): bool { + $codebase = $source->getCodebase(); + + $fq_classlike_name = $method_id->fq_class_name; + $method_name = $method_id->method_name; + + if ($codebase->methods->visibility_provider->has($fq_classlike_name)) { + $method_visible = $codebase->methods->visibility_provider->isMethodVisible( + $source, + $fq_classlike_name, + $method_name, + $context, + null + ); + + if ($method_visible !== null) { + return $method_visible; + } + } + + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + if (!$declaring_method_id) { + // this can happen for methods in the callmap that were not reflected + return true; + } + + $appearing_method_id = $codebase->methods->getAppearingMethodId($method_id); + + $appearing_method_class = null; + + if ($appearing_method_id) { + $appearing_method_class = $appearing_method_id->fq_class_name; + + // if the calling class is the same, we know the method exists, so it must be visible + if ($appearing_method_class === $context->self) { + return true; + } + } + + $declaring_method_class = $declaring_method_id->fq_class_name; + + if ($source->getSource() instanceof TraitAnalyzer + && strtolower($declaring_method_class) === strtolower((string) $source->getFQCLN()) + ) { + return true; + } + + $storage = $codebase->methods->getStorage($declaring_method_id); + + switch ($storage->visibility) { + case ClassLikeAnalyzer::VISIBILITY_PUBLIC: + return true; + + case ClassLikeAnalyzer::VISIBILITY_PRIVATE: + return $context->self && $appearing_method_class === $context->self; + + case ClassLikeAnalyzer::VISIBILITY_PROTECTED: + if (!$context->self) { + return false; + } + + if ($appearing_method_class + && $codebase->classExtends($appearing_method_class, $context->self) + ) { + return true; + } + + if ($appearing_method_class + && !$codebase->classExtends($context->self, $appearing_method_class) + ) { + return false; + } + } + + return true; + } + + /** + * Check that __clone, __construct, and __destruct do not have a return type + * hint in their signature. + * + * @return false|null + */ + public static function checkMethodSignatureMustOmitReturnType( + MethodStorage $method_storage, + CodeLocation $code_location + ): ?bool { + if ($method_storage->signature_return_type === null) { + return null; + } + + $cased_method_name = $method_storage->cased_name; + $methodsOfInterest = ['__clone', '__construct', '__destruct']; + if (in_array($cased_method_name, $methodsOfInterest)) { + if (IssueBuffer::accepts( + new MethodSignatureMustOmitReturnType( + 'Method ' . $cased_method_name . ' must not declare a return type', + $code_location + ) + )) { + return false; + } + } + + return null; + } + + public function getMethodId(?string $context_self = null): \Psalm\Internal\MethodIdentifier + { + $function_name = (string)$this->function->name; + + return new \Psalm\Internal\MethodIdentifier( + $context_self ?: (string) $this->source->getFQCLN(), + strtolower($function_name) + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/MethodComparator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/MethodComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..e3a6326920a2653ecdf77aac753615214cbd60b7 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -0,0 +1,1072 @@ +methods->getDeclaringMethodId( + new MethodIdentifier( + $implementer_classlike_storage->name, + strtolower($guide_method_storage->cased_name ?: '') + ) + ); + + $cased_implementer_method_id = $implementer_classlike_storage->name . '::' + . $implementer_method_storage->cased_name; + + $cased_guide_method_id = $guide_classlike_storage->name . '::' . $guide_method_storage->cased_name; + + self::checkForObviousMethodMismatches( + $guide_classlike_storage, + $implementer_classlike_storage, + $guide_method_storage, + $implementer_method_storage, + $guide_method_storage->visibility, + $implementer_visibility, + $cased_guide_method_id, + $cased_implementer_method_id, + $prevent_method_signature_mismatch, + $prevent_abstract_override, + $codebase->php_major_version >= 8, + $code_location, + $suppressed_issues + ); + + if ($guide_method_storage->signature_return_type && $prevent_method_signature_mismatch) { + self::compareMethodSignatureReturnTypes( + $codebase, + $guide_classlike_storage, + $implementer_classlike_storage, + $guide_method_storage, + $implementer_method_storage, + $guide_method_storage->signature_return_type, + $cased_guide_method_id, + $implementer_called_class_name, + $cased_implementer_method_id, + $code_location, + $suppressed_issues + ); + } + + if ($guide_method_storage->return_type + && $implementer_method_storage->return_type + && !$implementer_method_storage->inherited_return_type + && ($guide_method_storage->signature_return_type !== $guide_method_storage->return_type + || $implementer_method_storage->signature_return_type !== $implementer_method_storage->return_type) + && $implementer_classlike_storage->user_defined + && (!$guide_classlike_storage->stubbed || $guide_classlike_storage->template_types) + ) { + self::compareMethodDocblockReturnTypes( + $codebase, + $guide_classlike_storage, + $implementer_classlike_storage, + $implementer_method_storage, + $guide_method_storage->return_type, + $implementer_method_storage->return_type, + $cased_guide_method_id, + $implementer_called_class_name, + $implementer_declaring_method_id, + $code_location, + $suppressed_issues + ); + } + + foreach ($guide_method_storage->params as $i => $guide_param) { + if (!isset($implementer_method_storage->params[$i])) { + if (!$prevent_abstract_override && $i >= $guide_method_storage->required_param_count) { + continue; + } + + if (IssueBuffer::accepts( + new MethodSignatureMismatch( + 'Method ' . $cased_implementer_method_id . ' has fewer parameters than parent method ' . + $cased_guide_method_id, + $code_location + ) + )) { + return false; + } + + return null; + } + + self::compareMethodParams( + $codebase, + $stmt, + $implementer_classlike_storage, + $guide_classlike_storage, + $implementer_called_class_name, + $guide_method_storage, + $implementer_method_storage, + $guide_param, + $implementer_method_storage->params[$i], + $i, + $cased_guide_method_id, + $cased_implementer_method_id, + $prevent_method_signature_mismatch, + $code_location, + $suppressed_issues + ); + } + + if ($guide_classlike_storage->user_defined + && ($guide_classlike_storage->is_interface + || $guide_classlike_storage->preserve_constructor_signature + || $implementer_method_storage->cased_name !== '__construct') + && $implementer_method_storage->required_param_count > $guide_method_storage->required_param_count + ) { + if ($implementer_method_storage->cased_name !== '__construct') { + if (IssueBuffer::accepts( + new MethodSignatureMismatch( + 'Method ' . $cased_implementer_method_id . ' has more required parameters than parent method ' . + $cased_guide_method_id, + $code_location + ) + )) { + return false; + } + } else { + if (IssueBuffer::accepts( + new ConstructorSignatureMismatch( + 'Method ' . $cased_implementer_method_id . ' has more required parameters than parent method ' . + $cased_guide_method_id, + $code_location + ) + )) { + return false; + } + } + + + return null; + } + + return null; + } + + /** + * @param string[] $suppressed_issues + */ + private static function checkForObviousMethodMismatches( + ClassLikeStorage $guide_classlike_storage, + ClassLikeStorage $implementer_classlike_storage, + MethodStorage $guide_method_storage, + MethodStorage $implementer_method_storage, + int $guide_visibility, + int $implementer_visibility, + string $cased_guide_method_id, + string $cased_implementer_method_id, + bool $prevent_method_signature_mismatch, + bool $prevent_abstract_override, + bool $trait_mismatches_are_fatal, + CodeLocation $code_location, + array $suppressed_issues + ) : void { + if ($implementer_visibility > $guide_visibility) { + if ($trait_mismatches_are_fatal + || $guide_classlike_storage->is_trait === $implementer_classlike_storage->is_trait + || !in_array($guide_classlike_storage->name, $implementer_classlike_storage->used_traits) + || $implementer_method_storage->defining_fqcln !== $implementer_classlike_storage->name + || (!$implementer_method_storage->abstract + && !$guide_method_storage->abstract) + ) { + if (IssueBuffer::accepts( + new OverriddenMethodAccess( + 'Method ' . $cased_implementer_method_id . ' has different access level than ' + . $cased_guide_method_id, + $code_location + ) + )) { + // fall through + } + } elseif (IssueBuffer::accepts( + new TraitMethodSignatureMismatch( + 'Method ' . $cased_implementer_method_id . ' has different access level than ' + . $cased_guide_method_id, + $code_location + ) + )) { + // fall through + } + } + + if ($guide_method_storage->final + && $prevent_method_signature_mismatch + && $prevent_abstract_override + ) { + if (IssueBuffer::accepts( + new MethodSignatureMismatch( + 'Method ' . $cased_guide_method_id . ' is declared final and cannot be overridden', + $code_location + ) + )) { + // fall through + } + } + + if ($prevent_abstract_override + && !$guide_method_storage->abstract + && $implementer_method_storage->abstract + && !$guide_classlike_storage->abstract + && !$guide_classlike_storage->is_interface + ) { + if (IssueBuffer::accepts( + new MethodSignatureMismatch( + 'Method ' . $cased_implementer_method_id . ' cannot be abstract when inherited method ' + . $cased_guide_method_id . ' is non-abstract', + $code_location + ) + )) { + // fall through + } + } + + if ($guide_method_storage->external_mutation_free + && !$implementer_method_storage->external_mutation_free + && !$guide_method_storage->mutation_free_inferred + && $prevent_method_signature_mismatch + ) { + if (IssueBuffer::accepts( + new MissingImmutableAnnotation( + $cased_guide_method_id . ' is marked immutable, but ' + . $implementer_classlike_storage->name . '::' + . ($guide_method_storage->cased_name ?: '') + . ' is not marked immutable', + $code_location + ), + $suppressed_issues + )) { + // fall through + } + } + } + + /** + * @param string[] $suppressed_issues + */ + private static function compareMethodParams( + Codebase $codebase, + ?ClassMethod $stmt, + ClassLikeStorage $implementer_classlike_storage, + ClassLikeStorage $guide_classlike_storage, + string $implementer_called_class_name, + MethodStorage $guide_method_storage, + MethodStorage $implementer_method_storage, + FunctionLikeParameter $guide_param, + FunctionLikeParameter $implementer_param, + int $i, + string $cased_guide_method_id, + string $cased_implementer_method_id, + bool $prevent_method_signature_mismatch, + CodeLocation $code_location, + array $suppressed_issues + ) : void { + if ($prevent_method_signature_mismatch) { + if (!$guide_classlike_storage->user_defined + && $guide_param->type + ) { + $implementer_param_type = $implementer_param->signature_type; + + $guide_param_signature_type = $guide_param->type; + + $or_null_guide_param_signature_type = $guide_param->signature_type + ? clone $guide_param->signature_type + : null; + + if ($or_null_guide_param_signature_type) { + $or_null_guide_param_signature_type->addType(new Type\Atomic\TNull); + } + + if ($cased_guide_method_id === 'Serializable::unserialize') { + $guide_param_signature_type = null; + $or_null_guide_param_signature_type = null; + } + + if (!$guide_param->type->hasMixed() + && !$guide_param->type->from_docblock + && ($implementer_param_type || $guide_param_signature_type) + ) { + $config = \Psalm\Config::getInstance(); + + if ($implementer_param_type + && (!$guide_param_signature_type + || strtolower($implementer_param_type->getId()) + !== strtolower($guide_param_signature_type->getId())) + && (!$or_null_guide_param_signature_type + || strtolower($implementer_param_type->getId()) + !== strtolower($or_null_guide_param_signature_type->getId())) + ) { + if ($implementer_method_storage->cased_name === '__construct') { + if (IssueBuffer::accepts( + new ConstructorSignatureMismatch( + 'Argument ' . ($i + 1) . ' of ' + . $cased_implementer_method_id . ' has wrong type \'' + . $implementer_param_type . '\', expecting \'' + . $guide_param_signature_type . '\' as defined by ' + . $cased_guide_method_id, + $implementer_param->location + && $config->isInProjectDirs( + $implementer_param->location->file_path + ) + ? $implementer_param->location + : $code_location + ) + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new MethodSignatureMismatch( + 'Argument ' . ($i + 1) . ' of ' + . $cased_implementer_method_id . ' has wrong type \'' + . $implementer_param_type . '\', expecting \'' + . $guide_param_signature_type . '\' as defined by ' + . $cased_guide_method_id, + $implementer_param->location + && $config->isInProjectDirs( + $implementer_param->location->file_path + ) + ? $implementer_param->location + : $code_location + ) + )) { + // fall through + } + } + + + return; + } + } + } + + $config = \Psalm\Config::getInstance(); + + if ($guide_param->name !== $implementer_param->name + && $guide_method_storage->allow_named_arg_calls + && count($implementer_method_storage->params) > 1 + && $guide_classlike_storage->user_defined + && $implementer_classlike_storage->user_defined + && $implementer_param->location + && $guide_method_storage->cased_name + && substr($guide_method_storage->cased_name, 0, 2) !== '__' + && $config->isInProjectDirs( + $implementer_param->location->file_path + ) + ) { + if ($config->allow_named_arg_calls + || ($guide_classlike_storage->location + && !$config->isInProjectDirs($guide_classlike_storage->location->file_path) + ) + ) { + if ($codebase->alter_code) { + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + + if ($stmt && isset($project_analyzer->getIssuesToFix()['ParamNameMismatch'])) { + $param_replacer = new ParamReplacementVisitor( + $implementer_param->name, + $guide_param->name + ); + + $traverser = new \PhpParser\NodeTraverser(); + $traverser->addVisitor($param_replacer); + $traverser->traverse([$stmt]); + + if ($replacements = $param_replacer->getReplacements()) { + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $implementer_param->location->file_path, + $replacements + ); + } + } + } else { + if (IssueBuffer::accepts( + new ParamNameMismatch( + 'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id . ' has wrong name $' + . $implementer_param->name . ', expecting $' + . $guide_param->name . ' as defined by ' + . $cased_guide_method_id, + $implementer_param->location + ), + $suppressed_issues + )) { + // fall through + } + } + } + } + + if ($guide_classlike_storage->user_defined + && $implementer_param->signature_type + ) { + self::compareMethodSignatureParams( + $codebase, + $i, + $guide_classlike_storage, + $implementer_classlike_storage, + $guide_method_storage, + $implementer_method_storage, + $guide_param, + $implementer_param->signature_type, + $cased_guide_method_id, + $cased_implementer_method_id, + $code_location, + $suppressed_issues + ); + } + } + + if ($implementer_param->type + && $guide_param->type + && $implementer_param->type->getId() !== $guide_param->type->getId() + ) { + self::compareMethodDocblockParams( + $codebase, + $i, + $guide_classlike_storage, + $implementer_classlike_storage, + $implementer_called_class_name, + $guide_method_storage, + $implementer_method_storage, + $cased_guide_method_id, + $cased_implementer_method_id, + $guide_param->type, + $implementer_param->type, + $code_location, + $suppressed_issues + ); + } + + if ($guide_classlike_storage->user_defined && $implementer_param->by_ref !== $guide_param->by_ref) { + $config = \Psalm\Config::getInstance(); + + if (IssueBuffer::accepts( + new MethodSignatureMismatch( + 'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id . ' is' . + ($implementer_param->by_ref ? '' : ' not') . ' passed by reference, but argument ' . + ($i + 1) . ' of ' . $cased_guide_method_id . ' is' . ($guide_param->by_ref ? '' : ' not'), + $implementer_param->location + && $config->isInProjectDirs( + $implementer_param->location->file_path + ) + ? $implementer_param->location + : $code_location + ) + )) { + // fall through + } + } + } + + /** + * @param string[] $suppressed_issues + */ + private static function compareMethodSignatureParams( + Codebase $codebase, + int $i, + ClassLikeStorage $guide_classlike_storage, + ClassLikeStorage $implementer_classlike_storage, + MethodStorage $guide_method_storage, + MethodStorage $implementer_method_storage, + FunctionLikeParameter $guide_param, + Type\Union $implementer_param_signature_type, + string $cased_guide_method_id, + string $cased_implementer_method_id, + CodeLocation $code_location, + array $suppressed_issues + ) : void { + $guide_param_signature_type = $guide_param->signature_type + ? \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $guide_param->signature_type, + $guide_classlike_storage->is_trait && $guide_method_storage->abstract + ? $implementer_classlike_storage->name + : $guide_classlike_storage->name, + $guide_classlike_storage->is_trait && $guide_method_storage->abstract + ? $implementer_classlike_storage->name + : $guide_classlike_storage->name, + $guide_classlike_storage->is_trait && $guide_method_storage->abstract + ? $implementer_classlike_storage->parent_class + : $guide_classlike_storage->parent_class + ) + : null; + + $implementer_param_signature_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $implementer_param_signature_type, + $implementer_classlike_storage->name, + $implementer_classlike_storage->name, + $implementer_classlike_storage->parent_class + ); + + $is_contained_by = (($codebase->php_major_version === 7 + && $codebase->php_minor_version === 4) + || $codebase->php_major_version >= 8) + && $guide_param_signature_type + ? UnionTypeComparator::isContainedBy( + $codebase, + $guide_param_signature_type, + $implementer_param_signature_type + ) + : UnionTypeComparator::isContainedByInPhp( + $guide_param_signature_type, + $implementer_param_signature_type + ); + if (!$is_contained_by) { + $config = \Psalm\Config::getInstance(); + + if ($codebase->php_major_version >= 8 + || $guide_classlike_storage->is_trait === $implementer_classlike_storage->is_trait + || !in_array($guide_classlike_storage->name, $implementer_classlike_storage->used_traits) + || $implementer_method_storage->defining_fqcln !== $implementer_classlike_storage->name + || (!$implementer_method_storage->abstract + && !$guide_method_storage->abstract) + ) { + if ($implementer_method_storage->cased_name === '__construct') { + if (IssueBuffer::accepts( + new ConstructorSignatureMismatch( + 'Argument ' . ($i + 1) . ' of ' + . $cased_implementer_method_id + . ' has wrong type \'' + . $implementer_param_signature_type . '\', expecting \'' + . $guide_param_signature_type . '\' as defined by ' + . $cased_guide_method_id, + $implementer_method_storage->params[$i]->location + && $config->isInProjectDirs( + $implementer_method_storage->params[$i]->location->file_path + ) + ? $implementer_method_storage->params[$i]->location + : $code_location + ) + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new MethodSignatureMismatch( + 'Argument ' . ($i + 1) . ' of ' + . $cased_implementer_method_id + . ' has wrong type \'' + . $implementer_param_signature_type . '\', expecting \'' + . $guide_param_signature_type . '\' as defined by ' + . $cased_guide_method_id, + $implementer_method_storage->params[$i]->location + && $config->isInProjectDirs( + $implementer_method_storage->params[$i]->location->file_path + ) + ? $implementer_method_storage->params[$i]->location + : $code_location + ) + )) { + // fall through + } + } + } else { + if (IssueBuffer::accepts( + new TraitMethodSignatureMismatch( + 'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id . ' has wrong type \'' . + $implementer_param_signature_type . '\', expecting \'' . + $guide_param_signature_type . '\' as defined by ' . + $cased_guide_method_id, + $implementer_method_storage->params[$i]->location + && $config->isInProjectDirs( + $implementer_method_storage->params[$i]->location->file_path + ) + ? $implementer_method_storage->params[$i]->location + : $code_location + ), + $suppressed_issues + )) { + // fall through + } + } + } + } + + /** + * @param string[] $suppressed_issues + */ + private static function compareMethodDocblockParams( + Codebase $codebase, + int $i, + ClassLikeStorage $guide_classlike_storage, + ClassLikeStorage $implementer_classlike_storage, + string $implementer_called_class_name, + MethodStorage $guide_method_storage, + MethodStorage $implementer_method_storage, + string $cased_guide_method_id, + string $cased_implementer_method_id, + Type\Union $guide_param_type, + Type\Union $implementer_param_type, + CodeLocation $code_location, + array $suppressed_issues + ) : void { + $implementer_method_storage_param_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $implementer_param_type, + $implementer_classlike_storage->name, + $implementer_called_class_name, + $implementer_classlike_storage->parent_class + ); + + $guide_method_storage_param_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $guide_param_type, + $guide_classlike_storage->is_trait && $guide_method_storage->abstract + ? $implementer_classlike_storage->name + : $guide_classlike_storage->name, + $guide_classlike_storage->is_trait && $guide_method_storage->abstract + ? $implementer_classlike_storage->name + : $guide_classlike_storage->name, + $guide_classlike_storage->is_trait && $guide_method_storage->abstract + ? $implementer_classlike_storage->parent_class + : $guide_classlike_storage->parent_class + ); + + $guide_class_name = $guide_classlike_storage->name; + + if ($implementer_classlike_storage->template_type_extends) { + self::transformTemplates( + $implementer_classlike_storage->template_type_extends, + $guide_class_name, + $guide_method_storage_param_type, + $codebase + ); + } + + if ($implementer_classlike_storage->is_trait) { + $implementer_called_class_storage = $codebase->classlike_storage_provider->get( + $implementer_called_class_name + ); + + if (isset( + $implementer_called_class_storage->template_type_extends[$implementer_classlike_storage->name] + )) { + self::transformTemplates( + $implementer_called_class_storage->template_type_extends, + $implementer_classlike_storage->name, + $implementer_method_storage_param_type, + $codebase + ); + + self::transformTemplates( + $implementer_called_class_storage->template_type_extends, + $guide_class_name, + $guide_method_storage_param_type, + $codebase + ); + } + } + + foreach ($implementer_method_storage_param_type->getAtomicTypes() as $k => $t) { + if ($t instanceof Type\Atomic\TTemplateParam + && \strpos($t->defining_class, 'fn-') === 0 + ) { + $implementer_method_storage_param_type->removeType($k); + + foreach ($t->as->getAtomicTypes() as $as_t) { + $implementer_method_storage_param_type->addType($as_t); + } + } + } + + foreach ($guide_method_storage_param_type->getAtomicTypes() as $k => $t) { + if ($t instanceof Type\Atomic\TTemplateParam + && \strpos($t->defining_class, 'fn-') === 0 + ) { + $guide_method_storage_param_type->removeType($k); + + foreach ($t->as->getAtomicTypes() as $as_t) { + $guide_method_storage_param_type->addType($as_t); + } + } + } + + $union_comparison_results = new TypeComparisonResult(); + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $guide_method_storage_param_type, + $implementer_method_storage_param_type, + !$guide_classlike_storage->user_defined, + !$guide_classlike_storage->user_defined, + $union_comparison_results + )) { + // is the declared return type more specific than the inferred one? + if ($union_comparison_results->type_coerced) { + if ($guide_classlike_storage->user_defined) { + if (IssueBuffer::accepts( + new MoreSpecificImplementedParamType( + 'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id + . ' has the more specific type \'' . + $implementer_method_storage_param_type->getId() . '\', expecting \'' . + $guide_method_storage_param_type->getId() . '\' as defined by ' . + $cased_guide_method_id, + $implementer_method_storage->params[$i]->location + ?: $code_location + ), + $suppressed_issues + )) { + // fall through + } + } + } else { + if (UnionTypeComparator::isContainedBy( + $codebase, + $implementer_method_storage_param_type, + $guide_method_storage_param_type, + !$guide_classlike_storage->user_defined, + !$guide_classlike_storage->user_defined + )) { + if (IssueBuffer::accepts( + new MoreSpecificImplementedParamType( + 'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id + . ' has the more specific type \'' . + $implementer_method_storage_param_type->getId() . '\', expecting \'' . + $guide_method_storage_param_type->getId() . '\' as defined by ' . + $cased_guide_method_id, + $implementer_method_storage->params[$i]->location + ?: $code_location + ), + $suppressed_issues + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new ImplementedParamTypeMismatch( + 'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id + . ' has wrong type \'' . + $implementer_method_storage_param_type->getId() . '\', expecting \'' . + $guide_method_storage_param_type->getId() . '\' as defined by ' . + $cased_guide_method_id, + $implementer_method_storage->params[$i]->location + ?: $code_location + ), + $suppressed_issues + )) { + // fall through + } + } + } + } + } + + /** + * @param string[] $suppressed_issues + */ + private static function compareMethodSignatureReturnTypes( + Codebase $codebase, + ClassLikeStorage $guide_classlike_storage, + ClassLikeStorage $implementer_classlike_storage, + MethodStorage $guide_method_storage, + MethodStorage $implementer_method_storage, + Type\Union $guide_signature_return_type, + string $cased_guide_method_id, + string $implementer_called_class_name, + string $cased_implementer_method_id, + CodeLocation $code_location, + array $suppressed_issues + ) : void { + $guide_signature_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $guide_signature_return_type, + $guide_classlike_storage->is_trait && $guide_method_storage->abstract + ? $implementer_classlike_storage->name + : $guide_classlike_storage->name, + ($guide_classlike_storage->is_trait && $guide_method_storage->abstract) + || $guide_classlike_storage->final + ? $implementer_classlike_storage->name + : $guide_classlike_storage->name, + $guide_classlike_storage->is_trait && $guide_method_storage->abstract + ? $implementer_classlike_storage->parent_class + : $guide_classlike_storage->parent_class, + true, + true, + $implementer_method_storage->final + ); + + $implementer_signature_return_type = $implementer_method_storage->signature_return_type + ? \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $implementer_method_storage->signature_return_type, + $implementer_classlike_storage->is_trait + ? $implementer_called_class_name + : $implementer_classlike_storage->name, + $implementer_classlike_storage->is_trait + ? $implementer_called_class_name + : $implementer_classlike_storage->name, + $implementer_classlike_storage->parent_class + ) : null; + + $is_contained_by = (($codebase->php_major_version === 7 + && $codebase->php_minor_version === 4) + || $codebase->php_major_version >= 8) + && $implementer_signature_return_type + ? UnionTypeComparator::isContainedBy( + $codebase, + $implementer_signature_return_type, + $guide_signature_return_type + ) + : UnionTypeComparator::isContainedByInPhp($implementer_signature_return_type, $guide_signature_return_type); + + if (!$is_contained_by) { + if ($codebase->php_major_version >= 8 + || $guide_classlike_storage->is_trait === $implementer_classlike_storage->is_trait + || !in_array($guide_classlike_storage->name, $implementer_classlike_storage->used_traits) + || $implementer_method_storage->defining_fqcln !== $implementer_classlike_storage->name + || (!$implementer_method_storage->abstract + && !$guide_method_storage->abstract) + ) { + if (IssueBuffer::accepts( + new MethodSignatureMismatch( + 'Method ' . $cased_implementer_method_id . ' with return type \'' + . $implementer_signature_return_type . '\' is different to return type \'' + . $guide_signature_return_type . '\' of inherited method ' . $cased_guide_method_id, + $code_location + ), + $suppressed_issues + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TraitMethodSignatureMismatch( + 'Method ' . $cased_implementer_method_id . ' with return type \'' + . $implementer_signature_return_type . '\' is different to return type \'' + . $guide_signature_return_type . '\' of inherited method ' . $cased_guide_method_id, + $code_location + ), + $suppressed_issues + )) { + // fall through + } + } + } + } + + /** + * @param string[] $suppressed_issues + */ + private static function compareMethodDocblockReturnTypes( + Codebase $codebase, + ClassLikeStorage $guide_classlike_storage, + ClassLikeStorage $implementer_classlike_storage, + MethodStorage $implementer_method_storage, + Type\Union $guide_return_type, + Type\Union $implementer_return_type, + string $cased_guide_method_id, + string $implementer_called_class_name, + ?MethodIdentifier $implementer_declaring_method_id, + CodeLocation $code_location, + array $suppressed_issues + ) : void { + $implementer_method_storage_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $implementer_return_type, + $implementer_classlike_storage->name, + $implementer_called_class_name, + $implementer_classlike_storage->parent_class + ); + + $guide_method_storage_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $guide_return_type, + $guide_classlike_storage->is_trait + ? $implementer_classlike_storage->name + : $guide_classlike_storage->name, + $guide_classlike_storage->is_trait + || $implementer_method_storage->final + ? $implementer_called_class_name + : $guide_classlike_storage->name, + $guide_classlike_storage->parent_class, + true, + true, + $implementer_method_storage->final + ); + + $guide_class_name = $guide_classlike_storage->name; + + if ($implementer_classlike_storage->template_type_extends) { + self::transformTemplates( + $implementer_classlike_storage->template_type_extends, + $guide_class_name, + $guide_method_storage_return_type, + $codebase + ); + } + + if ($implementer_classlike_storage->is_trait) { + $implementer_called_class_storage = $codebase->classlike_storage_provider->get( + $implementer_called_class_name + ); + + if (isset( + $implementer_called_class_storage->template_type_extends[$implementer_classlike_storage->name] + )) { + self::transformTemplates( + $implementer_called_class_storage->template_type_extends, + $implementer_classlike_storage->name, + $implementer_method_storage_return_type, + $codebase + ); + + self::transformTemplates( + $implementer_called_class_storage->template_type_extends, + $guide_class_name, + $guide_method_storage_return_type, + $codebase + ); + } + } + + // treat void as null when comparing against docblock implementer + if ($implementer_method_storage_return_type->isVoid()) { + $implementer_method_storage_return_type = Type::getNull(); + } + + if ($guide_method_storage_return_type->isVoid()) { + $guide_method_storage_return_type = Type::getNull(); + } + + $union_comparison_results = new TypeComparisonResult(); + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $implementer_method_storage_return_type, + $guide_method_storage_return_type, + false, + false, + $union_comparison_results + )) { + // is the declared return type more specific than the inferred one? + if ($union_comparison_results->type_coerced) { + if (IssueBuffer::accepts( + new LessSpecificImplementedReturnType( + 'The inherited return type \'' . $guide_method_storage_return_type->getId() + . '\' for ' . $cased_guide_method_id . ' is more specific than the implemented ' + . 'return type for ' . $implementer_declaring_method_id . ' \'' + . $implementer_method_storage_return_type->getId() . '\'', + $implementer_method_storage->return_type_location + ?: $code_location + ), + $suppressed_issues + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new ImplementedReturnTypeMismatch( + 'The inherited return type \'' . $guide_method_storage_return_type->getId() + . '\' for ' . $cased_guide_method_id . ' is different to the implemented ' + . 'return type for ' . $implementer_declaring_method_id . ' \'' + . $implementer_method_storage_return_type->getId() . '\'', + $implementer_method_storage->return_type_location + ?: $code_location + ), + $suppressed_issues + )) { + // fall through + } + } + } + } + + /** + * @param array> $template_type_extends + */ + private static function transformTemplates( + array $template_type_extends, + string $base_class_name, + Type\Union $templated_type, + Codebase $codebase + ) : void { + if (isset($template_type_extends[$base_class_name])) { + $map = $template_type_extends[$base_class_name]; + + $template_types = []; + + foreach ($map as $key => $mapped_type) { + if (is_string($key)) { + $new_bases = []; + + foreach ($mapped_type->getTemplateTypes() as $mapped_atomic_type) { + if ($mapped_atomic_type->defining_class === $base_class_name) { + continue; + } + + $new_bases[] = $mapped_atomic_type->defining_class; + } + + if ($new_bases) { + $mapped_type = clone $mapped_type; + + foreach ($new_bases as $new_base_class_name) { + self::transformTemplates( + $template_type_extends, + $new_base_class_name, + $mapped_type, + $codebase + ); + } + } + + $template_types[$key][$base_class_name] = [$mapped_type]; + } + } + + $template_result = new \Psalm\Internal\Type\TemplateResult([], $template_types); + + $templated_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..05e8083a3803923b5c05e50e640bce7b2ec92501 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php @@ -0,0 +1,177 @@ +> + */ + protected static $public_namespace_constants = []; + + public function __construct(Namespace_ $namespace, FileAnalyzer $source) + { + $this->source = $source; + $this->namespace = $namespace; + $this->namespace_name = $this->namespace->name ? implode('\\', $this->namespace->name->parts) : ''; + } + + public function collectAnalyzableInformation(): void + { + $leftover_stmts = []; + + if (!isset(self::$public_namespace_constants[$this->namespace_name])) { + self::$public_namespace_constants[$this->namespace_name] = []; + } + + $codebase = $this->getCodebase(); + + foreach ($this->namespace->stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\ClassLike) { + $this->collectAnalyzableClassLike($stmt); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Use_) { + $this->visitUse($stmt); + } elseif ($stmt instanceof PhpParser\Node\Stmt\GroupUse) { + $this->visitGroupUse($stmt); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Const_) { + foreach ($stmt->consts as $const) { + self::$public_namespace_constants[$this->namespace_name][$const->name->name] = Type::getMixed(); + } + + $leftover_stmts[] = $stmt; + } else { + $leftover_stmts[] = $stmt; + } + } + + if ($leftover_stmts) { + $statements_analyzer = new StatementsAnalyzer($this, new \Psalm\Internal\Provider\NodeDataProvider()); + $context = new Context(); + $context->is_global = true; + $context->defineGlobals(); + $context->collect_exceptions = $codebase->config->check_for_throws_in_global_scope; + $statements_analyzer->analyze($leftover_stmts, $context, null, true); + + $file_context = $this->source->context; + if ($file_context) { + $file_context->mergeExceptions($context); + } + } + } + + public function collectAnalyzableClassLike(PhpParser\Node\Stmt\ClassLike $stmt): void + { + if (!$stmt->name) { + throw new \UnexpectedValueException('Did not expect anonymous class here'); + } + + $fq_class_name = Type::getFQCLNFromString($stmt->name->name, $this->getAliases()); + + if ($stmt instanceof PhpParser\Node\Stmt\Class_) { + $this->source->addNamespacedClassAnalyzer( + $fq_class_name, + new ClassAnalyzer($stmt, $this, $fq_class_name) + ); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Interface_) { + $this->source->addNamespacedInterfaceAnalyzer( + $fq_class_name, + new InterfaceAnalyzer($stmt, $this, $fq_class_name) + ); + } + } + + public function getNamespace(): string + { + return $this->namespace_name; + } + + public function setConstType(string $const_name, Type\Union $const_type): void + { + self::$public_namespace_constants[$this->namespace_name][$const_name] = $const_type; + } + + /** + * @return array + */ + public static function getConstantsForNamespace(string $namespace_name, int $visibility): array + { + // @todo this does not allow for loading in namespace constants not already defined in the current sweep + if (!isset(self::$public_namespace_constants[$namespace_name])) { + self::$public_namespace_constants[$namespace_name] = []; + } + + if ($visibility === \ReflectionProperty::IS_PUBLIC) { + return self::$public_namespace_constants[$namespace_name]; + } + + throw new \InvalidArgumentException('Given $visibility not supported'); + } + + public function getFileAnalyzer() : FileAnalyzer + { + return $this->source; + } + + /** + * Returns true if $className is the same as, or starts with $namespace, in a case-insensitive comparison. + * + * + * @psalm-pure + */ + public static function isWithin(string $calling_namespace, string $namespace): bool + { + if ($namespace === '') { + return true; // required to prevent a warning from strpos with empty needle in PHP < 8 + } + + $calling_namespace = strtolower(trim($calling_namespace, '\\') . '\\'); + $namespace = strtolower(trim($namespace, '\\') . '\\'); + + return $calling_namespace === $namespace + || strpos($calling_namespace, $namespace) === 0; + } + + /** + * @param string $fullyQualifiedClassName, e.g. '\Psalm\Internal\Analyzer\NamespaceAnalyzer' + * + * @return string , e.g. 'Psalm' + * + * @psalm-pure + */ + public static function getNameSpaceRoot(string $fullyQualifiedClassName): string + { + return preg_replace('/^([^\\\]+).*/', '$1', $fullyQualifiedClassName); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..c2703ee5dd04153d24e04a7d006fbadc772453ed --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php @@ -0,0 +1,1490 @@ + + */ + private $issues_to_fix = []; + + /** + * @var bool + */ + public $dry_run = false; + + /** + * @var bool + */ + public $full_run = false; + + /** + * @var bool + */ + public $only_replace_php_types_with_non_docblock_types = false; + + /** + * @var ?int + */ + public $onchange_line_limit; + + /** + * @var bool + */ + public $provide_completion = false; + + /** + * @var array + */ + private $project_files = []; + + /** + * @var array + */ + private $extra_files = []; + + /** + * @var array + */ + private $to_refactor = []; + + /** + * @var ?ReportOptions + */ + public $stdout_report_options; + + /** + * @var array + */ + public $generated_report_options; + + /** + * @var array> + */ + private const SUPPORTED_ISSUES_TO_FIX = [ + InvalidFalsableReturnType::class, + InvalidNullableReturnType::class, + InvalidReturnType::class, + LessSpecificReturnType::class, + MismatchingDocblockParamType::class, + MismatchingDocblockReturnType::class, + MissingClosureReturnType::class, + MissingParamType::class, + MissingPropertyType::class, + MissingReturnType::class, + ParamNameMismatch::class, + PossiblyUndefinedGlobalVariable::class, + PossiblyUndefinedVariable::class, + PossiblyUnusedMethod::class, + PossiblyUnusedProperty::class, + UnusedMethod::class, + UnusedProperty::class, + UnusedVariable::class, + UnnecessaryVarAnnotation::class, + ]; + + /** + * When this is true, the language server will send the diagnostic code with a help link. + * + * @var bool + */ + public $language_server_use_extended_diagnostic_codes = false; + + /** + * If this is true then the language server will send log messages to the client with additional information. + * + * @var bool + */ + public $language_server_verbose = false; + + /** + * @param array $generated_report_options + * @param string $reports + */ + public function __construct( + Config $config, + Providers $providers, + ?ReportOptions $stdout_report_options = null, + array $generated_report_options = [], + int $threads = 1, + ?Progress $progress = null + ) { + if ($progress === null) { + $progress = new VoidProgress(); + } + + $this->parser_cache_provider = $providers->parser_cache_provider; + $this->project_cache_provider = $providers->project_cache_provider; + $this->file_provider = $providers->file_provider; + $this->classlike_storage_provider = $providers->classlike_storage_provider; + $this->file_reference_provider = $providers->file_reference_provider; + + $this->progress = $progress; + $this->threads = $threads; + $this->config = $config; + + $this->clearCacheDirectoryIfConfigOrComposerLockfileChanged(); + + $this->codebase = new Codebase( + $config, + $providers, + $progress + ); + + $this->stdout_report_options = $stdout_report_options; + $this->generated_report_options = $generated_report_options; + + $file_extensions = $this->config->getFileExtensions(); + + foreach ($this->config->getProjectDirectories() as $dir_name) { + $file_paths = $this->file_provider->getFilesInDir($dir_name, $file_extensions); + + foreach ($file_paths as $file_path) { + if ($this->config->isInProjectDirs($file_path)) { + $this->addProjectFile($file_path); + } + } + } + + foreach ($this->config->getExtraDirectories() as $dir_name) { + $file_paths = $this->file_provider->getFilesInDir($dir_name, $file_extensions); + + foreach ($file_paths as $file_path) { + if ($this->config->isInExtraDirs($file_path)) { + $this->addExtraFile($file_path); + } + } + } + + foreach ($this->config->getProjectFiles() as $file_path) { + $this->addProjectFile($file_path); + } + + self::$instance = $this; + } + + private function clearCacheDirectoryIfConfigOrComposerLockfileChanged() : void + { + if ($this->project_cache_provider + && $this->project_cache_provider->hasLockfileChanged() + ) { + $this->progress->debug( + 'Composer lockfile change detected, clearing cache' . "\n" + ); + + $cache_directory = $this->config->getCacheDirectory(); + if ($cache_directory !== null) { + Config::removeCacheDirectory($cache_directory); + } + + if ($this->file_reference_provider->cache) { + $this->file_reference_provider->cache->hasConfigChanged(); + } + + $this->project_cache_provider->updateComposerLockHash(); + } elseif ($this->file_reference_provider->cache + && $this->file_reference_provider->cache->hasConfigChanged() + ) { + $this->progress->debug( + 'Config change detected, clearing cache' . "\n" + ); + + $cache_directory = $this->config->getCacheDirectory(); + if ($cache_directory !== null) { + Config::removeCacheDirectory($cache_directory); + } + + if ($this->project_cache_provider) { + $this->project_cache_provider->hasLockfileChanged(); + } + } + } + + /** + * @param array $report_file_paths + * @return list + */ + public static function getFileReportOptions(array $report_file_paths, bool $show_info = true): array + { + $report_options = []; + + $mapping = [ + 'checkstyle.xml' => Report::TYPE_CHECKSTYLE, + 'sonarqube.json' => Report::TYPE_SONARQUBE, + 'summary.json' => Report::TYPE_JSON_SUMMARY, + 'junit.xml' => Report::TYPE_JUNIT, + '.xml' => Report::TYPE_XML, + '.json' => Report::TYPE_JSON, + '.txt' => Report::TYPE_TEXT, + '.emacs' => Report::TYPE_EMACS, + '.pylint' => Report::TYPE_PYLINT, + '.console' => Report::TYPE_CONSOLE, + ]; + + foreach ($report_file_paths as $report_file_path) { + foreach ($mapping as $extension => $type) { + if (substr($report_file_path, -strlen($extension)) === $extension) { + $o = new ReportOptions(); + + $o->format = $type; + $o->show_info = $show_info; + $o->output_path = $report_file_path; + $o->use_color = false; + $report_options[] = $o; + continue 2; + } + } + + throw new \UnexpectedValueException('Unknown report format ' . $report_file_path); + } + + return $report_options; + } + + private function visitAutoloadFiles() : void + { + $start_time = microtime(true); + + $this->config->visitComposerAutoloadFiles($this, $this->progress); + + $now_time = microtime(true); + + $this->progress->debug( + 'Visiting autoload files took ' . \number_format($now_time - $start_time, 3) . 's' . "\n" + ); + } + + public function server(?string $address = '127.0.0.1:12345', bool $socket_server_mode = false): void + { + $this->visitAutoloadFiles(); + $this->codebase->diff_methods = true; + $this->file_reference_provider->loadReferenceCache(); + $this->codebase->enterServerMode(); + + if (\ini_get('pcre.jit') === '1' + && \PHP_OS === 'Darwin' + && \version_compare(\PHP_VERSION, '7.3.0') >= 0 + && \version_compare(\PHP_VERSION, '7.4.0') < 0 + ) { + // do nothing + } else { + $cpu_count = self::getCpuCount(); + + // let's not go crazy + $usable_cpus = $cpu_count - 2; + + if ($usable_cpus > 1) { + $this->threads = $usable_cpus; + } + } + + $this->config->initializePlugins($this); + + foreach ($this->config->getProjectDirectories() as $dir_name) { + $this->checkDirWithConfig($dir_name, $this->config); + } + + @cli_set_process_title('Psalm ' . PSALM_VERSION . ' - PHP Language Server'); + + if (!$socket_server_mode && $address) { + // Connect to a TCP server + $socket = stream_socket_client('tcp://' . $address, $errno, $errstr); + if ($socket === false) { + fwrite(STDERR, "Could not connect to language client. Error $errno\n$errstr"); + exit(1); + } + stream_set_blocking($socket, false); + new LanguageServer( + new ProtocolStreamReader($socket), + new ProtocolStreamWriter($socket), + $this + ); + \Amp\Loop::run(); + } elseif ($socket_server_mode && $address) { + // Run a TCP Server + $tcpServer = stream_socket_server('tcp://' . $address, $errno, $errstr); + if ($tcpServer === false) { + fwrite(STDERR, "Could not listen on $address. Error $errno\n$errstr"); + exit(1); + } + fwrite(STDOUT, "Server listening on $address\n"); + + $fork_available = true; + if (!extension_loaded('pcntl')) { + fwrite(STDERR, "PCNTL is not available. Only a single connection will be accepted\n"); + $fork_available = false; + } + + $disabled_functions = array_map('trim', explode(',', ini_get('disable_functions'))); + if (in_array('pcntl_fork', $disabled_functions)) { + fwrite( + STDERR, + "pcntl_fork() is disabled by php configuration (disable_functions directive)." + . " Only a single connection will be accepted\n" + ); + $fork_available = false; + } + + while ($socket = stream_socket_accept($tcpServer, -1)) { + fwrite(STDOUT, "Connection accepted\n"); + stream_set_blocking($socket, false); + if ($fork_available) { + // If PCNTL is available, fork a child process for the connection + // An exit notification will only terminate the child process + $pid = pcntl_fork(); + if ($pid === -1) { + fwrite(STDERR, "Could not fork\n"); + exit(1); + } + + if ($pid === 0) { + // Child process + $reader = new ProtocolStreamReader($socket); + $reader->on( + 'close', + function (): void { + fwrite(STDOUT, "Connection closed\n"); + } + ); + new LanguageServer( + $reader, + new ProtocolStreamWriter($socket), + $this + ); + // Just for safety + exit(0); + } + } else { + // If PCNTL is not available, we only accept one connection. + // An exit notification will terminate the server + new LanguageServer( + new ProtocolStreamReader($socket), + new ProtocolStreamWriter($socket), + $this + ); + \Amp\Loop::run(); + } + } + } else { + // Use STDIO + stream_set_blocking(STDIN, false); + new LanguageServer( + new ProtocolStreamReader(STDIN), + new ProtocolStreamWriter(STDOUT), + $this + ); + \Amp\Loop::run(); + } + } + + public static function getInstance(): ProjectAnalyzer + { + return self::$instance; + } + + public function canReportIssues(string $file_path): bool + { + return isset($this->project_files[$file_path]); + } + + public function check(string $base_dir, bool $is_diff = false): void + { + $start_checks = (int)microtime(true); + + if (!$base_dir) { + throw new \InvalidArgumentException('Cannot work with empty base_dir'); + } + + $diff_files = null; + $deleted_files = null; + + $this->full_run = true; + + $reference_cache = $this->file_reference_provider->loadReferenceCache(true); + + $this->codebase->diff_methods = $is_diff; + + if ($is_diff + && $reference_cache + && $this->project_cache_provider + && $this->project_cache_provider->canDiffFiles() + ) { + $deleted_files = $this->file_reference_provider->getDeletedReferencedFiles(); + $diff_files = $deleted_files; + + foreach ($this->config->getProjectDirectories() as $dir_name) { + $diff_files = array_merge($diff_files, $this->getDiffFilesInDir($dir_name, $this->config)); + } + } + + $this->progress->startScanningFiles(); + + $diff_no_files = false; + + if ($diff_files === null + || $deleted_files === null + || count($diff_files) > 200 + ) { + $this->visitAutoloadFiles(); + + $this->codebase->scanner->addFilesToShallowScan($this->extra_files); + $this->codebase->scanner->addFilesToDeepScan($this->project_files); + $this->codebase->analyzer->addFilesToAnalyze($this->project_files); + + $this->config->initializePlugins($this); + + $this->codebase->scanFiles($this->threads); + + $this->codebase->infer_types_from_usage = true; + } else { + $this->progress->debug(count($diff_files) . ' changed files: ' . "\n"); + $this->progress->debug(' ' . implode("\n ", $diff_files) . "\n"); + + $this->codebase->analyzer->addFilesToShowResults($this->project_files); + + if ($diff_files) { + $file_list = $this->getReferencedFilesFromDiff($diff_files); + + // strip out deleted files + $file_list = array_diff($file_list, $deleted_files); + + if ($file_list) { + $this->visitAutoloadFiles(); + + $this->checkDiffFilesWithConfig($this->config, $file_list); + + $this->config->initializePlugins($this); + + $this->codebase->scanFiles($this->threads); + } else { + $diff_no_files = true; + } + } else { + $diff_no_files = true; + } + } + + if (!$diff_no_files) { + $this->config->visitStubFiles($this->codebase, $this->progress); + + $plugin_classes = $this->config->after_codebase_populated; + + if ($plugin_classes) { + foreach ($plugin_classes as $plugin_fq_class_name) { + $plugin_fq_class_name::afterCodebasePopulated($this->codebase); + } + } + } + + $this->progress->startAnalyzingFiles(); + + $this->codebase->analyzer->analyzeFiles( + $this, + $this->threads, + $this->codebase->alter_code, + true + ); + + if ($this->project_cache_provider && $this->parser_cache_provider) { + $removed_parser_files = $this->parser_cache_provider->deleteOldParserCaches( + $is_diff ? $this->project_cache_provider->getLastRun() : $start_checks + ); + + if ($removed_parser_files) { + $this->progress->debug('Removed ' . $removed_parser_files . ' old parser caches' . "\n"); + } + + if ($is_diff) { + $this->parser_cache_provider->touchParserCaches($this->getAllFiles($this->config), $start_checks); + } + } + } + + public function consolidateAnalyzedData(): void + { + $this->codebase->classlikes->consolidateAnalyzedData( + $this->codebase->methods, + $this->progress, + !!$this->codebase->find_unused_code + ); + } + + public function trackTaintedInputs(): void + { + $this->codebase->taint_flow_graph = new TaintFlowGraph(); + } + + public function trackUnusedSuppressions(): void + { + $this->codebase->track_unused_suppressions = true; + } + + public function interpretRefactors() : void + { + if (!$this->codebase->alter_code) { + throw new \UnexpectedValueException('Should not be checking references'); + } + + // interpret wildcards + foreach ($this->to_refactor as $source => $destination) { + if (($source_pos = strpos($source, '*')) + && ($destination_pos = strpos($destination, '*')) + && $source_pos === (strlen($source) - 1) + && $destination_pos === (strlen($destination) - 1) + ) { + foreach ($this->codebase->classlike_storage_provider->getAll() as $class_storage) { + if (substr($class_storage->name, 0, $source_pos) === substr($source, 0, -1)) { + $this->to_refactor[$class_storage->name] + = substr($destination, 0, -1) . substr($class_storage->name, $source_pos); + } + } + + unset($this->to_refactor[$source]); + } + } + + foreach ($this->to_refactor as $source => $destination) { + $source_parts = explode('::', $source); + $destination_parts = explode('::', $destination); + + if (!$this->codebase->classlikes->hasFullyQualifiedClassName($source_parts[0])) { + throw new \Psalm\Exception\RefactorException( + 'Source class ' . $source_parts[0] . ' doesn’t exist' + ); + } + + if (count($source_parts) === 1 && count($destination_parts) === 1) { + if ($this->codebase->classlikes->hasFullyQualifiedClassName($destination_parts[0])) { + throw new \Psalm\Exception\RefactorException( + 'Destination class ' . $destination_parts[0] . ' already exists' + ); + } + + $source_class_storage = $this->codebase->classlike_storage_provider->get($source_parts[0]); + + $destination_parts = explode('\\', $destination, -1); + $destination_ns = implode('\\', $destination_parts); + + $this->codebase->classes_to_move[strtolower($source)] = $destination; + + $destination_class_storage = $this->codebase->classlike_storage_provider->create($destination); + + $destination_class_storage->name = $destination; + + if ($source_class_storage->aliases) { + $destination_class_storage->aliases = clone $source_class_storage->aliases; + $destination_class_storage->aliases->namespace = $destination_ns; + } + + $destination_class_storage->location = $source_class_storage->location; + $destination_class_storage->stmt_location = $source_class_storage->stmt_location; + $destination_class_storage->populated = true; + + $this->codebase->class_transforms[strtolower($source)] = $destination; + + continue; + } + + $source_method_id = new \Psalm\Internal\MethodIdentifier( + $source_parts[0], + strtolower($source_parts[1]) + ); + + if ($this->codebase->methods->methodExists($source_method_id)) { + if ($this->codebase->methods->methodExists( + new \Psalm\Internal\MethodIdentifier( + $destination_parts[0], + strtolower($destination_parts[1]) + ) + )) { + throw new \Psalm\Exception\RefactorException( + 'Destination method ' . $destination . ' already exists' + ); + } + + if (!$this->codebase->classlikes->classExists($destination_parts[0])) { + throw new \Psalm\Exception\RefactorException( + 'Destination class ' . $destination_parts[0] . ' doesn’t exist' + ); + } + + if (strtolower($source_parts[0]) !== strtolower($destination_parts[0])) { + $source_method_storage = $this->codebase->methods->getStorage($source_method_id); + $destination_class_storage + = $this->codebase->classlike_storage_provider->get($destination_parts[0]); + + if (!$source_method_storage->is_static + && !isset( + $destination_class_storage->parent_classes[strtolower($source_method_id->fq_class_name)] + ) + ) { + throw new \Psalm\Exception\RefactorException( + 'Cannot move non-static method ' . $source + . ' into unrelated class ' . $destination_parts[0] + ); + } + + $this->codebase->methods_to_move[strtolower($source)]= $destination; + } else { + $this->codebase->methods_to_rename[strtolower($source)] = $destination_parts[1]; + } + + $this->codebase->call_transforms[strtolower($source) . '\((.*\))'] = $destination . '($1)'; + continue; + } + + if ($source_parts[1][0] === '$') { + if ($destination_parts[1][0] !== '$') { + throw new \Psalm\Exception\RefactorException( + 'Destination property must be of the form Foo::$bar' + ); + } + + if (!$this->codebase->properties->propertyExists($source, true)) { + throw new \Psalm\Exception\RefactorException( + 'Property ' . $source . ' does not exist' + ); + } + + if ($this->codebase->properties->propertyExists($destination, true)) { + throw new \Psalm\Exception\RefactorException( + 'Destination property ' . $destination . ' already exists' + ); + } + + if (!$this->codebase->classlikes->classExists($destination_parts[0])) { + throw new \Psalm\Exception\RefactorException( + 'Destination class ' . $destination_parts[0] . ' doesn’t exist' + ); + } + + $source_id = strtolower($source_parts[0]) . '::' . $source_parts[1]; + + if (strtolower($source_parts[0]) !== strtolower($destination_parts[0])) { + $source_storage = $this->codebase->properties->getStorage($source); + + if (!$source_storage->is_static) { + throw new \Psalm\Exception\RefactorException( + 'Cannot move non-static property ' . $source + ); + } + + $this->codebase->properties_to_move[$source_id] = $destination; + } else { + $this->codebase->properties_to_rename[$source_id] = substr($destination_parts[1], 1); + } + + $this->codebase->property_transforms[$source_id] = $destination; + continue; + } + + $source_class_constants = $this->codebase->classlikes->getConstantsForClass( + $source_parts[0], + \ReflectionProperty::IS_PRIVATE + ); + + if (isset($source_class_constants[$source_parts[1]])) { + if (!$this->codebase->classlikes->hasFullyQualifiedClassName($destination_parts[0])) { + throw new \Psalm\Exception\RefactorException( + 'Destination class ' . $destination_parts[0] . ' doesn’t exist' + ); + } + + $destination_class_constants = $this->codebase->classlikes->getConstantsForClass( + $destination_parts[0], + \ReflectionProperty::IS_PRIVATE + ); + + if (isset($destination_class_constants[$destination_parts[1]])) { + throw new \Psalm\Exception\RefactorException( + 'Destination constant ' . $destination . ' already exists' + ); + } + + $source_id = strtolower($source_parts[0]) . '::' . $source_parts[1]; + + if (strtolower($source_parts[0]) !== strtolower($destination_parts[0])) { + $this->codebase->class_constants_to_move[$source_id] = $destination; + } else { + $this->codebase->class_constants_to_rename[$source_id] = $destination_parts[1]; + } + + $this->codebase->class_constant_transforms[$source_id] = $destination; + continue; + } + + throw new \Psalm\Exception\RefactorException( + 'Psalm cannot locate ' . $source + ); + } + } + + public function prepareMigration() : void + { + if (!$this->codebase->alter_code) { + throw new \UnexpectedValueException('Should not be checking references'); + } + + $this->codebase->classlikes->moveMethods( + $this->codebase->methods, + $this->progress + ); + + $this->codebase->classlikes->moveProperties( + $this->codebase->properties, + $this->progress + ); + + $this->codebase->classlikes->moveClassConstants( + $this->progress + ); + } + + public function migrateCode() : void + { + if (!$this->codebase->alter_code) { + throw new \UnexpectedValueException('Should not be checking references'); + } + + $migration_manipulations = \Psalm\Internal\FileManipulation\FileManipulationBuffer::getMigrationManipulations( + $this->codebase->file_provider + ); + + if ($migration_manipulations) { + foreach ($migration_manipulations as $file_path => $file_manipulations) { + usort( + $file_manipulations, + function (FileManipulation $a, FileManipulation $b): int { + if ($a->start === $b->start) { + if ($b->end === $a->end) { + return $b->insertion_text > $a->insertion_text ? 1 : -1; + } + + return $b->end > $a->end ? 1 : -1; + } + + return $b->start > $a->start ? 1 : -1; + } + ); + + $existing_contents = $this->codebase->file_provider->getContents($file_path); + + foreach ($file_manipulations as $manipulation) { + $existing_contents = $manipulation->transform($existing_contents); + } + + $this->codebase->file_provider->setContents($file_path, $existing_contents); + } + } + + if ($this->codebase->classes_to_move) { + foreach ($this->codebase->classes_to_move as $source => $destination) { + $source_class_storage = $this->codebase->classlike_storage_provider->get($source); + + if (!$source_class_storage->location) { + continue; + } + + $potential_file_path = $this->config->getPotentialComposerFilePathForClassLike($destination); + + if ($potential_file_path && !file_exists($potential_file_path)) { + $containing_dir = dirname($potential_file_path); + + if (!file_exists($containing_dir)) { + mkdir($containing_dir, 0777, true); + } + + rename($source_class_storage->location->file_path, $potential_file_path); + } + } + } + } + + public function findReferencesTo(string $symbol): void + { + if (!$this->stdout_report_options) { + throw new \UnexpectedValueException('Not expecting to emit output'); + } + + $locations = $this->codebase->findReferencesToSymbol($symbol); + + foreach ($locations as $location) { + $snippet = $location->getSnippet(); + + $snippet_bounds = $location->getSnippetBounds(); + $selection_bounds = $location->getSelectionBounds(); + + $selection_start = $selection_bounds[0] - $snippet_bounds[0]; + $selection_length = $selection_bounds[1] - $selection_bounds[0]; + + echo $location->file_name . ':' . $location->getLineNumber() . "\n" . + ( + $this->stdout_report_options->use_color + ? substr($snippet, 0, $selection_start) . + "\e[97;42m" . substr($snippet, $selection_start, $selection_length) . + "\e[0m" . substr($snippet, $selection_length + $selection_start) + : $snippet + ) . "\n" . "\n"; + } + } + + public function checkDir(string $dir_name): void + { + $this->file_reference_provider->loadReferenceCache(); + + $this->checkDirWithConfig($dir_name, $this->config, true); + + $this->progress->startScanningFiles(); + + $this->config->initializePlugins($this); + + $this->codebase->scanFiles($this->threads); + + $this->config->visitStubFiles($this->codebase, $this->progress); + + $this->progress->startAnalyzingFiles(); + + $this->codebase->analyzer->analyzeFiles( + $this, + $this->threads, + $this->codebase->alter_code, + $this->codebase->find_unused_code === 'always' + ); + } + + private function checkDirWithConfig(string $dir_name, Config $config, bool $allow_non_project_files = false): void + { + $file_extensions = $config->getFileExtensions(); + + $file_paths = $this->file_provider->getFilesInDir($dir_name, $file_extensions); + + $files_to_scan = []; + + foreach ($file_paths as $file_path) { + if ($allow_non_project_files || $config->isInProjectDirs($file_path)) { + $files_to_scan[$file_path] = $file_path; + } + } + + $this->codebase->addFilesToAnalyze($files_to_scan); + } + + /** + * @return list + */ + private function getAllFiles(Config $config): array + { + $file_extensions = $config->getFileExtensions(); + $file_paths = []; + + foreach ($config->getProjectDirectories() as $dir_name) { + $file_paths = array_merge( + $file_paths, + $this->file_provider->getFilesInDir($dir_name, $file_extensions) + ); + } + + return $file_paths; + } + + /** + * @param string $dir_name + * + */ + public function addProjectFile(string $file_path): void + { + $this->project_files[$file_path] = $file_path; + } + + public function addExtraFile(string $file_path) : void + { + $this->extra_files[$file_path] = $file_path; + } + + /** + * @return list + */ + protected function getDiffFilesInDir(string $dir_name, Config $config): array + { + $file_extensions = $config->getFileExtensions(); + + if (!$this->parser_cache_provider || !$this->project_cache_provider) { + throw new \UnexpectedValueException('Parser cache provider cannot be null here'); + } + + $diff_files = []; + + $last_run = $this->project_cache_provider->getLastRun(); + + $file_paths = $this->file_provider->getFilesInDir($dir_name, $file_extensions); + + foreach ($file_paths as $file_path) { + if ($config->isInProjectDirs($file_path)) { + if ($this->file_provider->getModifiedTime($file_path) > $last_run + && $this->parser_cache_provider->loadExistingFileContentsFromCache($file_path) + !== $this->file_provider->getContents($file_path) + ) { + $diff_files[] = $file_path; + } + } + } + + return $diff_files; + } + + /** + * @param array $file_list + * + */ + private function checkDiffFilesWithConfig(Config $config, array $file_list = []): void + { + $files_to_scan = []; + + foreach ($file_list as $file_path) { + if (!$this->file_provider->fileExists($file_path)) { + continue; + } + + if (!$config->isInProjectDirs($file_path)) { + $this->progress->debug('skipping ' . $file_path . "\n"); + + continue; + } + + $files_to_scan[$file_path] = $file_path; + } + + $this->codebase->addFilesToAnalyze($files_to_scan); + } + + public function checkFile(string $file_path): void + { + $this->progress->debug('Checking ' . $file_path . "\n"); + + $this->config->hide_external_errors = $this->config->isInProjectDirs($file_path); + + $this->codebase->addFilesToAnalyze([$file_path => $file_path]); + + $this->file_reference_provider->loadReferenceCache(); + + $this->progress->startScanningFiles(); + + $this->config->initializePlugins($this); + + $this->codebase->scanFiles($this->threads); + + $this->config->visitStubFiles($this->codebase, $this->progress); + + $this->progress->startAnalyzingFiles(); + + $this->codebase->analyzer->analyzeFiles( + $this, + $this->threads, + $this->codebase->alter_code, + $this->codebase->find_unused_code === 'always' + ); + } + + /** + * @param string[] $paths_to_check + */ + public function checkPaths(array $paths_to_check): void + { + $this->visitAutoloadFiles(); + + foreach ($paths_to_check as $path) { + $this->progress->debug('Checking ' . $path . "\n"); + + if (is_dir($path)) { + $this->checkDirWithConfig($path, $this->config, true); + } elseif (is_file($path)) { + $this->codebase->addFilesToAnalyze([$path => $path]); + $this->config->hide_external_errors = $this->config->isInProjectDirs($path); + } + } + + $this->file_reference_provider->loadReferenceCache(); + + $this->progress->startScanningFiles(); + + $this->config->initializePlugins($this); + + $this->codebase->scanFiles($this->threads); + + $this->config->visitStubFiles($this->codebase, $this->progress); + + $this->progress->startAnalyzingFiles(); + + $this->codebase->analyzer->analyzeFiles( + $this, + $this->threads, + $this->codebase->alter_code, + $this->codebase->find_unused_code === 'always' + ); + + if ($this->stdout_report_options + && in_array( + $this->stdout_report_options->format, + [\Psalm\Report::TYPE_CONSOLE, \Psalm\Report::TYPE_PHP_STORM] + ) + && $this->codebase->collect_references + ) { + fwrite( + STDERR, + PHP_EOL . 'To whom it may concern: Psalm cannot detect unused classes, methods and properties' + . PHP_EOL . 'when analyzing individual files and folders. Run on the full project to enable' + . PHP_EOL . 'complete unused code detection.' . PHP_EOL + ); + } + } + + public function getConfig(): Config + { + return $this->config; + } + + /** + * @param array $diff_files + * + * @return array + */ + public function getReferencedFilesFromDiff(array $diff_files, bool $include_referencing_files = true): array + { + $all_inherited_files_to_check = $diff_files; + + while ($diff_files) { + $diff_file = array_shift($diff_files); + + $dependent_files = $this->file_reference_provider->getFilesInheritingFromFile($diff_file); + + $new_dependent_files = array_diff($dependent_files, $all_inherited_files_to_check); + + $all_inherited_files_to_check = array_merge($all_inherited_files_to_check, $new_dependent_files); + $diff_files = array_merge($diff_files, $new_dependent_files); + } + + $all_files_to_check = $all_inherited_files_to_check; + + if ($include_referencing_files) { + foreach ($all_inherited_files_to_check as $file_name) { + $dependent_files = $this->file_reference_provider->getFilesReferencingFile($file_name); + $all_files_to_check = array_merge($dependent_files, $all_files_to_check); + } + } + + return array_combine($all_files_to_check, $all_files_to_check); + } + + public function fileExists(string $file_path): bool + { + return $this->file_provider->fileExists($file_path); + } + + public function alterCodeAfterCompletion( + bool $dry_run = false, + bool $safe_types = false + ): void { + $this->codebase->alter_code = true; + $this->codebase->infer_types_from_usage = true; + $this->show_issues = false; + $this->dry_run = $dry_run; + $this->only_replace_php_types_with_non_docblock_types = $safe_types; + } + + /** + * @param array $to_refactor + * + */ + public function refactorCodeAfterCompletion(array $to_refactor): void + { + $this->to_refactor = $to_refactor; + $this->codebase->alter_code = true; + $this->show_issues = false; + } + + public function setPhpVersion(string $version): void + { + if (!preg_match('/^(5\.[456]|7\.[01234]|8\.[0])(\..*)?$/', $version)) { + throw new \UnexpectedValueException('Expecting a version number in the format x.y'); + } + + [$php_major_version, $php_minor_version] = explode('.', $version); + + $php_major_version = (int) $php_major_version; + $php_minor_version = (int) $php_minor_version; + + if ($this->codebase->php_major_version !== $php_major_version + || $this->codebase->php_minor_version !== $php_minor_version + ) { + // reset lexer and parser when php version changes + \Psalm\Internal\Provider\StatementsProvider::clearLexer(); + \Psalm\Internal\Provider\StatementsProvider::clearParser(); + } + + $this->codebase->php_major_version = $php_major_version; + $this->codebase->php_minor_version = $php_minor_version; + } + + /** + * @param array $issues + * @throws UnsupportedIssueToFixException + * + */ + public function setIssuesToFix(array $issues): void + { + $supported_issues_to_fix = static::getSupportedIssuesToFix(); + + $supported_issues_to_fix[] = 'MissingImmutableAnnotation'; + $supported_issues_to_fix[] = 'MissingPureAnnotation'; + + $unsupportedIssues = array_diff(array_keys($issues), $supported_issues_to_fix); + + if (! empty($unsupportedIssues)) { + throw new UnsupportedIssueToFixException( + 'Psalm doesn\'t know how to fix issue(s): ' . implode(', ', $unsupportedIssues) . PHP_EOL + . 'Supported issues to fix are: ' . implode(',', $supported_issues_to_fix) + ); + } + + $this->issues_to_fix = $issues; + } + + public function setAllIssuesToFix(): void + { + $keyed_issues = array_fill_keys(static::getSupportedIssuesToFix(), true); + + $this->setIssuesToFix($keyed_issues); + } + + /** + * @return array + */ + public function getIssuesToFix(): array + { + return $this->issues_to_fix; + } + + public function getCodebase(): Codebase + { + return $this->codebase; + } + + public function getFileAnalyzerForClassLike(string $fq_class_name): FileAnalyzer + { + $fq_class_name_lc = strtolower($fq_class_name); + + $file_path = $this->codebase->scanner->getClassLikeFilePath($fq_class_name_lc); + + $file_analyzer = new FileAnalyzer( + $this, + $file_path, + $this->config->shortenFileName($file_path) + ); + + return $file_analyzer; + } + + public function getMethodMutations( + \Psalm\Internal\MethodIdentifier $original_method_id, + Context $this_context, + string $root_file_path, + string $root_file_name + ): void { + $fq_class_name = $original_method_id->fq_class_name; + + $appearing_method_id = $this->codebase->methods->getAppearingMethodId($original_method_id); + + if (!$appearing_method_id) { + // this can happen for some abstract classes implementing (but not fully) interfaces + return; + } + + $appearing_fq_class_name = $appearing_method_id->fq_class_name; + + $appearing_class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name); + + if (!$appearing_class_storage->user_defined) { + return; + } + + $file_analyzer = $this->getFileAnalyzerForClassLike($fq_class_name); + + $file_analyzer->setRootFilePath($root_file_path, $root_file_name); + + if ($appearing_fq_class_name !== $fq_class_name) { + $file_analyzer = $this->getFileAnalyzerForClassLike($appearing_fq_class_name); + } + + $stmts = $this->codebase->getStatementsForFile( + $file_analyzer->getFilePath() + ); + + $file_analyzer->populateCheckers($stmts); + + if (!$this_context->self) { + $this_context->self = $fq_class_name; + $this_context->vars_in_scope['$this'] = Type::parseString($fq_class_name); + } + + $file_analyzer->getMethodMutations($appearing_method_id, $this_context, true); + + $file_analyzer->class_analyzers_to_analyze = []; + $file_analyzer->interface_analyzers_to_analyze = []; + $file_analyzer->clearSourceBeforeDestruction(); + } + + public function getFunctionLikeAnalyzer( + \Psalm\Internal\MethodIdentifier $method_id, + string $file_path + ) : ?FunctionLikeAnalyzer { + $file_analyzer = new FileAnalyzer( + $this, + $file_path, + $this->config->shortenFileName($file_path) + ); + + $stmts = $this->codebase->getStatementsForFile( + $file_analyzer->getFilePath() + ); + + $file_analyzer->populateCheckers($stmts); + + $function_analyzer = $file_analyzer->getFunctionLikeAnalyzer($method_id); + + $file_analyzer->class_analyzers_to_analyze = []; + $file_analyzer->interface_analyzers_to_analyze = []; + + return $function_analyzer; + } + + /** + * Adapted from https://gist.github.com/divinity76/01ef9ca99c111565a72d3a8a6e42f7fb + * returns number of cpu cores + * Copyleft 2018, license: WTFPL + * @throws \RuntimeException + * @throws \LogicException + * @psalm-suppress ForbiddenCode + */ + public static function getCpuCount(): int + { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + /* + $str = trim((string) shell_exec('wmic cpu get NumberOfCores 2>&1')); + if (!preg_match('/(\d+)/', $str, $matches)) { + throw new \RuntimeException('wmic failed to get number of cpu cores on windows!'); + } + return ((int) $matches [1]); + */ + return 1; + } + + if (\ini_get('pcre.jit') === '1' + && \PHP_OS === 'Darwin' + && \version_compare(\PHP_VERSION, '7.3.0') >= 0 + && \version_compare(\PHP_VERSION, '7.4.0') < 0 + ) { + return 1; + } + + if (!extension_loaded('pcntl')) { + return 1; + } + + $has_nproc = trim((string) @shell_exec('command -v nproc')); + if ($has_nproc) { + $ret = @shell_exec('nproc'); + if (is_string($ret)) { + $ret = trim($ret); + $tmp = filter_var($ret, FILTER_VALIDATE_INT); + if (is_int($tmp)) { + return $tmp; + } + } + } + + $ret = @shell_exec('sysctl -n hw.ncpu'); + if (is_string($ret)) { + $ret = trim($ret); + $tmp = filter_var($ret, FILTER_VALIDATE_INT); + if (is_int($tmp)) { + return $tmp; + } + } + + if (is_readable('/proc/cpuinfo')) { + $cpuinfo = file_get_contents('/proc/cpuinfo'); + $count = substr_count($cpuinfo, 'processor'); + if ($count > 0) { + return $count; + } + } + + throw new \LogicException('failed to detect number of CPUs!'); + } + + /** + * @return array + * + * @psalm-pure + */ + public static function getSupportedIssuesToFix(): array + { + return array_map( + /** @param class-string $issue_class */ + function (string $issue_class): string { + $parts = explode('\\', $issue_class); + return end($parts); + }, + self::SUPPORTED_ISSUES_TO_FIX + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..4e2ce2035b409c4c893b5ecdbd003e296c120ffd --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php @@ -0,0 +1,411 @@ + $stmts + * + */ + public static function doesEverBreak(array $stmts): bool + { + if (empty($stmts)) { + return false; + } + + for ($i = count($stmts) - 1; $i >= 0; --$i) { + $stmt = $stmts[$i]; + + if ($stmt instanceof PhpParser\Node\Stmt\Break_) { + return true; + } + + if ($stmt instanceof PhpParser\Node\Stmt\If_) { + if (self::doesEverBreak($stmt->stmts)) { + return true; + } + + if ($stmt->else && self::doesEverBreak($stmt->else->stmts)) { + return true; + } + + foreach ($stmt->elseifs as $elseif) { + if (self::doesEverBreak($elseif->stmts)) { + return true; + } + } + } + } + + return false; + } + + /** + * @param array $stmts + * @param bool $return_is_exit Exit and Throw statements are treated differently from return if this is false + * @param list<'loop'|'switch'> $break_types + * + * @return list> + */ + public static function getControlActions( + array $stmts, + ?\Psalm\Internal\Provider\NodeDataProvider $nodes, + array $exit_functions, + array $break_types = [], + bool $return_is_exit = true + ): array { + if (empty($stmts)) { + return [self::ACTION_NONE]; + } + + $control_actions = []; + + for ($i = 0, $c = count($stmts); $i < $c; ++$i) { + $stmt = $stmts[$i]; + + if ($stmt instanceof PhpParser\Node\Stmt\Return_ || + $stmt instanceof PhpParser\Node\Stmt\Throw_ || + ($stmt instanceof PhpParser\Node\Stmt\Expression && $stmt->expr instanceof PhpParser\Node\Expr\Exit_) + ) { + if (!$return_is_exit && $stmt instanceof PhpParser\Node\Stmt\Return_) { + return array_merge($control_actions, [self::ACTION_RETURN]); + } + + return [self::ACTION_END]; + } + + if ($stmt instanceof PhpParser\Node\Stmt\Expression) { + if ($stmt->expr instanceof PhpParser\Node\Expr\FuncCall + && $stmt->expr->name instanceof PhpParser\Node\Name + && $stmt->expr->name->parts === ['trigger_error'] + && isset($stmt->expr->args[1]) + && $stmt->expr->args[1]->value instanceof PhpParser\Node\Expr\ConstFetch + && in_array( + end($stmt->expr->args[1]->value->name->parts), + ['E_ERROR', 'E_PARSE', 'E_CORE_ERROR', 'E_COMPILE_ERROR', 'E_USER_ERROR'] + ) + ) { + return [self::ACTION_END]; + } + + // This allows calls to functions that always exit to act as exit statements themselves + if ($nodes + && ($stmt_expr_type = $nodes->getType($stmt->expr)) + && $stmt_expr_type->isNever() + ) { + return [self::ACTION_END]; + } + + if ($exit_functions) { + if ($stmt->expr instanceof PhpParser\Node\Expr\FuncCall + || $stmt->expr instanceof PhpParser\Node\Expr\StaticCall + ) { + if ($stmt->expr instanceof PhpParser\Node\Expr\FuncCall) { + /** @var string|null */ + $resolved_name = $stmt->expr->name->getAttribute('resolvedName'); + + if ($resolved_name && isset($exit_functions[strtolower($resolved_name)])) { + return [self::ACTION_END]; + } + } elseif ($stmt->expr->class instanceof PhpParser\Node\Name + && $stmt->expr->name instanceof PhpParser\Node\Identifier + ) { + /** @var string|null */ + $resolved_class_name = $stmt->expr->class->getAttribute('resolvedName'); + + if ($resolved_class_name + && isset($exit_functions[strtolower($resolved_class_name . '::' . $stmt->expr->name)]) + ) { + return [self::ACTION_END]; + } + } + } + } + + continue; + } + + if ($stmt instanceof PhpParser\Node\Stmt\Continue_) { + if ($break_types + && end($break_types) === 'switch' + && (!$stmt->num || !$stmt->num instanceof PhpParser\Node\Scalar\LNumber || $stmt->num->value < 2) + ) { + return array_merge($control_actions, [self::ACTION_LEAVE_SWITCH]); + } + + return \array_values(array_unique(array_merge($control_actions, [self::ACTION_CONTINUE]))); + } + + if ($stmt instanceof PhpParser\Node\Stmt\Break_) { + if ($break_types + && end($break_types) === 'switch' + && (!$stmt->num || !$stmt->num instanceof PhpParser\Node\Scalar\LNumber || $stmt->num->value < 2) + ) { + return [self::ACTION_LEAVE_SWITCH]; + } + + return \array_values(array_unique(array_merge($control_actions, [self::ACTION_BREAK]))); + } + + if ($stmt instanceof PhpParser\Node\Stmt\If_) { + $if_statement_actions = self::getControlActions( + $stmt->stmts, + $nodes, + $exit_functions, + $break_types + ); + + $else_statement_actions = $stmt->else + ? self::getControlActions($stmt->else->stmts, $nodes, $exit_functions, $break_types) + : []; + + $all_same = count($if_statement_actions) === 1 + && $if_statement_actions == $else_statement_actions + && $if_statement_actions !== [self::ACTION_NONE]; + + $all_elseif_actions = []; + + if ($stmt->elseifs) { + foreach ($stmt->elseifs as $elseif) { + $elseif_control_actions = self::getControlActions( + $elseif->stmts, + $nodes, + $exit_functions, + $break_types + ); + + $all_same = $all_same && $elseif_control_actions == $if_statement_actions; + + if (!$all_same) { + $all_elseif_actions = array_merge($elseif_control_actions, $all_elseif_actions); + } + } + } + + if ($all_same) { + return $if_statement_actions; + } + + $control_actions = array_filter( + array_merge( + $control_actions, + $if_statement_actions, + $else_statement_actions, + $all_elseif_actions + ), + function ($action) { + return $action !== self::ACTION_NONE; + } + ); + } + + if ($stmt instanceof PhpParser\Node\Stmt\Switch_) { + $has_ended = false; + $has_non_breaking_default = false; + $has_default_terminator = false; + + // iterate backwards in a case statement + for ($d = count($stmt->cases) - 1; $d >= 0; --$d) { + $case = $stmt->cases[$d]; + + $case_actions = self::getControlActions($case->stmts, $nodes, $exit_functions, ['switch']); + + if (array_intersect([ + self::ACTION_LEAVE_SWITCH, + self::ACTION_BREAK, + self::ACTION_CONTINUE + ], $case_actions) + ) { + continue 2; + } + + if (!$case->cond) { + $has_non_breaking_default = true; + } + + $case_does_end = $case_actions == [self::ACTION_END]; + + if ($case_does_end) { + $has_ended = true; + } + + if (!$case_does_end && !$has_ended) { + continue 2; + } + + if ($has_non_breaking_default && $case_does_end) { + $has_default_terminator = true; + } + } + + if ($has_default_terminator || isset($stmt->allMatched)) { + return \array_values(array_unique(array_merge($control_actions, [self::ACTION_END]))); + } + } + + if ($stmt instanceof PhpParser\Node\Stmt\Do_ + || $stmt instanceof PhpParser\Node\Stmt\While_ + || $stmt instanceof PhpParser\Node\Stmt\Foreach_ + || $stmt instanceof PhpParser\Node\Stmt\For_ + ) { + $do_actions = self::getControlActions( + $stmt->stmts, + $nodes, + $exit_functions, + array_merge($break_types, ['loop']) + ); + + $control_actions = array_filter( + array_merge($control_actions, $do_actions), + function ($action) use ($break_types) { + return $action !== self::ACTION_NONE + && ($break_types + || ($action !== self::ACTION_CONTINUE + && $action !== self::ACTION_BREAK)); + } + ); + } + + if ($stmt instanceof PhpParser\Node\Stmt\TryCatch) { + $try_statement_actions = self::getControlActions( + $stmt->stmts, + $nodes, + $exit_functions, + $break_types + ); + + if ($stmt->catches) { + $all_same = count($try_statement_actions) === 1; + + foreach ($stmt->catches as $catch) { + $catch_actions = self::getControlActions( + $catch->stmts, + $nodes, + $exit_functions, + $break_types + ); + + $all_same = $all_same && $try_statement_actions == $catch_actions; + + if (!$all_same) { + $control_actions = array_merge($control_actions, $catch_actions); + } + } + + if ($all_same && $try_statement_actions !== [self::ACTION_NONE]) { + return \array_values(array_unique(array_merge($control_actions, $try_statement_actions))); + } + } elseif (!in_array(self::ACTION_NONE, $try_statement_actions, true)) { + return \array_values(array_unique(array_merge($control_actions, $try_statement_actions))); + } + + if ($stmt->finally) { + if ($stmt->finally->stmts) { + $finally_statement_actions = self::getControlActions( + $stmt->finally->stmts, + $nodes, + $exit_functions, + $break_types + ); + + if (!in_array(self::ACTION_NONE, $finally_statement_actions, true)) { + return array_merge( + array_filter( + $control_actions, + function ($action) { + return $action !== self::ACTION_NONE; + } + ), + $finally_statement_actions + ); + } + } + + if (!$stmt->catches && !in_array(self::ACTION_NONE, $try_statement_actions, true)) { + return array_merge( + array_filter( + $control_actions, + function ($action) { + return $action !== self::ACTION_NONE; + } + ), + $try_statement_actions + ); + } + } + + $control_actions = array_filter( + \array_merge($control_actions, $try_statement_actions), + function ($action) { + return $action !== self::ACTION_NONE; + } + ); + } + } + + $control_actions[] = self::ACTION_NONE; + + return \array_values(array_unique($control_actions)); + } + + /** + * @param array $stmts + * + */ + public static function onlyThrowsOrExits(\Psalm\NodeTypeProvider $type_provider, array $stmts): bool + { + if (empty($stmts)) { + return false; + } + + for ($i = count($stmts) - 1; $i >= 0; --$i) { + $stmt = $stmts[$i]; + + if ($stmt instanceof PhpParser\Node\Stmt\Throw_ + || ($stmt instanceof PhpParser\Node\Stmt\Expression + && $stmt->expr instanceof PhpParser\Node\Expr\Exit_) + ) { + return true; + } + + if ($stmt instanceof PhpParser\Node\Stmt\Expression) { + $stmt_type = $type_provider->getType($stmt->expr); + + if ($stmt_type && $stmt_type->isNever()) { + return true; + } + } + } + + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/SourceAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/SourceAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..5cbc91af39f6b985be033928fa644d239d53e1df --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/SourceAnalyzer.php @@ -0,0 +1,180 @@ +source = null; + } + + public function getAliases(): Aliases + { + return $this->source->getAliases(); + } + + /** + * @return array + */ + public function getAliasedClassesFlipped(): array + { + return $this->source->getAliasedClassesFlipped(); + } + + /** + * @return array + */ + public function getAliasedClassesFlippedReplaceable(): array + { + return $this->source->getAliasedClassesFlippedReplaceable(); + } + + public function getFQCLN(): ?string + { + return $this->source->getFQCLN(); + } + + public function getClassName(): ?string + { + return $this->source->getClassName(); + } + + public function getParentFQCLN(): ?string + { + return $this->source->getParentFQCLN(); + } + + public function getFileName(): string + { + return $this->source->getFileName(); + } + + public function getFilePath(): string + { + return $this->source->getFilePath(); + } + + public function getRootFileName(): string + { + return $this->source->getRootFileName(); + } + + public function getRootFilePath(): string + { + return $this->source->getRootFilePath(); + } + + public function setRootFilePath(string $file_path, string $file_name): void + { + $this->source->setRootFilePath($file_path, $file_name); + } + + public function hasParentFilePath(string $file_path): bool + { + return $this->source->hasParentFilePath($file_path); + } + + public function hasAlreadyRequiredFilePath(string $file_path): bool + { + return $this->source->hasAlreadyRequiredFilePath($file_path); + } + + public function getRequireNesting(): int + { + return $this->source->getRequireNesting(); + } + + /** + * @psalm-mutation-free + */ + public function getSource(): StatementsSource + { + return $this->source; + } + + /** + * Get a list of suppressed issues + * + * @return array + */ + public function getSuppressedIssues(): array + { + return $this->source->getSuppressedIssues(); + } + + /** + * @param array $new_issues + */ + public function addSuppressedIssues(array $new_issues): void + { + $this->source->addSuppressedIssues($new_issues); + } + + /** + * @param array $new_issues + */ + public function removeSuppressedIssues(array $new_issues): void + { + $this->source->removeSuppressedIssues($new_issues); + } + + public function getNamespace(): ?string + { + return $this->source->getNamespace(); + } + + public function isStatic(): bool + { + return $this->source->isStatic(); + } + + /** + * @psalm-mutation-free + */ + public function getCodebase() : Codebase + { + return $this->source->getCodebase(); + } + + /** + * @psalm-mutation-free + */ + public function getProjectAnalyzer() : ProjectAnalyzer + { + return $this->source->getProjectAnalyzer(); + } + + /** + * @psalm-mutation-free + */ + public function getFileAnalyzer() : FileAnalyzer + { + return $this->source->getFileAnalyzer(); + } + + /** + * @return array>|null + */ + public function getTemplateTypeMap(): ?array + { + return $this->source->getTemplateTypeMap(); + } + + public function getNodeTypeProvider() : \Psalm\NodeTypeProvider + { + return $this->source->getNodeTypeProvider(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..94d66a78180910d33127e20ec1af4650aeaacdbd --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php @@ -0,0 +1,198 @@ +break_types[] = 'loop'; + + $codebase = $statements_analyzer->getCodebase(); + + if ($codebase->alter_code) { + $do_context->branch_point = $do_context->branch_point ?: (int) $stmt->getAttribute('startFilePos'); + } + + $loop_scope = new LoopScope($do_context, $context); + $loop_scope->protected_var_ids = $context->protected_var_ids; + + self::analyzeDoNaively($statements_analyzer, $stmt, $do_context, $loop_scope); + + $mixed_var_ids = []; + + foreach ($do_context->vars_in_scope as $var_id => $type) { + if ($type->hasMixed()) { + $mixed_var_ids[] = $var_id; + } + } + + $cond_id = \spl_object_id($stmt->cond); + + $while_clauses = Algebra::getFormula( + $cond_id, + $cond_id, + $stmt->cond, + $context->self, + $statements_analyzer, + $codebase + ); + + $while_clauses = array_values( + array_filter( + $while_clauses, + function (Clause $c) use ($mixed_var_ids): bool { + $keys = array_keys($c->possibilities); + + $mixed_var_ids = \array_diff($mixed_var_ids, $keys); + + foreach ($keys as $key) { + foreach ($mixed_var_ids as $mixed_var_id) { + if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { + return false; + } + } + } + + return true; + } + ) + ); + + if (!$while_clauses) { + $while_clauses = [new Clause([], $cond_id, $cond_id, true)]; + } + + LoopAnalyzer::analyze( + $statements_analyzer, + $stmt->stmts, + [$stmt->cond], + [], + $loop_scope, + $inner_loop_context, + true, + true + ); + + // because it's a do {} while, inner loop vars belong to the main context + if (!$inner_loop_context) { + throw new \UnexpectedValueException('Should never be null'); + } + + $negated_while_clauses = Algebra::negateFormula($while_clauses); + + $negated_while_types = Algebra::getTruthsFromFormula( + Algebra::simplifyCNF( + array_merge($context->clauses, $negated_while_clauses) + ) + ); + + //var_dump($do_context->vars_in_scope); + + if ($negated_while_types) { + $changed_var_ids = []; + + $inner_loop_context->vars_in_scope = + Type\Reconciler::reconcileKeyedTypes( + $negated_while_types, + [], + $inner_loop_context->vars_in_scope, + $changed_var_ids, + [], + $statements_analyzer, + [], + true, + new \Psalm\CodeLocation($statements_analyzer->getSource(), $stmt->cond) + ); + } + + foreach ($inner_loop_context->vars_in_scope as $var_id => $type) { + // if there are break statements in the loop it's not certain + // that the loop has finished executing, so the assertions at the end + // the loop in the while conditional may not hold + if (in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true)) { + if (isset($loop_scope->possibly_defined_loop_parent_vars[$var_id])) { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $type, + $loop_scope->possibly_defined_loop_parent_vars[$var_id] + ); + } + } else { + $context->vars_in_scope[$var_id] = $type; + } + } + + $do_context->loop_scope = null; + + $context->vars_possibly_in_scope = array_merge( + $context->vars_possibly_in_scope, + $do_context->vars_possibly_in_scope + ); + + $context->referenced_var_ids = array_merge( + $context->referenced_var_ids, + $do_context->referenced_var_ids + ); + + if ($context->collect_exceptions) { + $context->mergeExceptions($inner_loop_context); + } + } + + private static function analyzeDoNaively( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Stmt\Do_ $stmt, + Context $context, + LoopScope $loop_scope + ) : void { + $do_context = clone $context; + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('RedundantCondition', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['RedundantCondition']); + } + if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['RedundantConditionGivenDocblockType']); + } + if (!in_array('TypeDoesNotContainType', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['TypeDoesNotContainType']); + } + + $do_context->loop_scope = $loop_scope; + + $statements_analyzer->analyze($stmt->stmts, $do_context); + + if (!in_array('RedundantCondition', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['RedundantCondition']); + } + if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['RedundantConditionGivenDocblockType']); + } + if (!in_array('TypeDoesNotContainType', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['TypeDoesNotContainType']); + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..f1dc1b49274146b113037b6e3e8fbfc3ef3442a1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php @@ -0,0 +1,186 @@ +assigned_var_ids; + $context->assigned_var_ids = []; + + $init_var_types = []; + + foreach ($stmt->init as $init) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $init, $context) === false) { + return false; + } + + if ($init instanceof PhpParser\Node\Expr\Assign + && $init->var instanceof PhpParser\Node\Expr\Variable + && \is_string($init->var->name) + && ($init_var_type = $statements_analyzer->node_data->getType($init->expr)) + ) { + $init_var_types[$init->var->name] = $init_var_type; + } + } + + $assigned_var_ids = $context->assigned_var_ids; + + $context->assigned_var_ids = array_merge( + $pre_assigned_var_ids, + $assigned_var_ids + ); + + $while_true = !$stmt->cond && !$stmt->init && !$stmt->loop; + + $pre_context = null; + + if ($while_true) { + $pre_context = clone $context; + } + + $for_context = clone $context; + + $for_context->inside_loop = true; + $for_context->break_types[] = 'loop'; + + $codebase = $statements_analyzer->getCodebase(); + + if ($codebase->alter_code) { + $for_context->branch_point = $for_context->branch_point ?: (int) $stmt->getAttribute('startFilePos'); + } + + $loop_scope = new LoopScope($for_context, $context); + + $loop_scope->protected_var_ids = array_merge( + $assigned_var_ids, + $context->protected_var_ids + ); + + LoopAnalyzer::analyze( + $statements_analyzer, + $stmt->stmts, + $stmt->cond, + $stmt->loop, + $loop_scope, + $inner_loop_context + ); + + if (!$inner_loop_context) { + throw new \UnexpectedValueException('There should be an inner loop context'); + } + + $always_enters_loop = false; + + foreach ($stmt->cond as $cond) { + if ($cond_type = $statements_analyzer->node_data->getType($cond)) { + foreach ($cond_type->getAtomicTypes() as $iterator_type) { + $always_enters_loop = $iterator_type instanceof Type\Atomic\TTrue; + + break; + } + } + + if (\count($stmt->init) === 1 + && \count($stmt->cond) === 1 + && $cond instanceof PhpParser\Node\Expr\BinaryOp + && $cond->right instanceof PhpParser\Node\Scalar\LNumber + && $cond->left instanceof PhpParser\Node\Expr\Variable + && \is_string($cond->left->name) + && isset($init_var_types[$cond->left->name]) + && $init_var_types[$cond->left->name]->isSingleIntLiteral() + ) { + $init_value = $init_var_types[$cond->left->name]->getSingleIntLiteral()->value; + $cond_value = $cond->right->value; + + if ($cond instanceof PhpParser\Node\Expr\BinaryOp\Smaller && $init_value < $cond_value) { + $always_enters_loop = true; + break; + } + + if ($cond instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual && $init_value <= $cond_value) { + $always_enters_loop = true; + break; + } + + if ($cond instanceof PhpParser\Node\Expr\BinaryOp\Greater && $init_value > $cond_value) { + $always_enters_loop = true; + break; + } + + if ($cond instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual && $init_value >= $cond_value) { + $always_enters_loop = true; + break; + } + } + } + + if ($while_true) { + $always_enters_loop = true; + } + + $can_leave_loop = !$while_true + || in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true); + + if ($always_enters_loop && $can_leave_loop) { + foreach ($inner_loop_context->vars_in_scope as $var_id => $type) { + // if there are break statements in the loop it's not certain + // that the loop has finished executing + if (in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true) + || in_array(ScopeAnalyzer::ACTION_CONTINUE, $loop_scope->final_actions, true) + ) { + if (isset($loop_scope->possibly_defined_loop_parent_vars[$var_id])) { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $type, + $loop_scope->possibly_defined_loop_parent_vars[$var_id] + ); + } + } else { + $context->vars_in_scope[$var_id] = $type; + } + } + } + + $for_context->loop_scope = null; + + if ($can_leave_loop) { + $context->vars_possibly_in_scope = array_merge( + $context->vars_possibly_in_scope, + $for_context->vars_possibly_in_scope + ); + } elseif ($pre_context) { + $context->vars_possibly_in_scope = $pre_context->vars_possibly_in_scope; + } + + $context->referenced_var_ids = array_intersect_key( + $for_context->referenced_var_ids, + $context->referenced_var_ids + ); + + if ($context->collect_exceptions) { + $context->mergeExceptions($for_context); + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..8a4af61cf007a6c1941dea25c0a4d37e4feb1054 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php @@ -0,0 +1,1171 @@ +getDocComment(); + + $codebase = $statements_analyzer->getCodebase(); + $file_path = $statements_analyzer->getRootFilePath(); + $type_aliases = $codebase->file_storage_provider->get($file_path)->type_aliases; + + if ($doc_comment) { + try { + $var_comments = CommentAnalyzer::getTypeFromComment( + $doc_comment, + $statements_analyzer->getSource(), + $statements_analyzer->getSource()->getAliases(), + $statements_analyzer->getTemplateTypeMap() ?: [], + $type_aliases + ); + } catch (DocblockParseException $e) { + if (IssueBuffer::accepts( + new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($statements_analyzer, $stmt) + ) + )) { + // fall through + } + } + } + + $safe_var_ids = []; + + if ($stmt->keyVar instanceof PhpParser\Node\Expr\Variable && is_string($stmt->keyVar->name)) { + $safe_var_ids['$' . $stmt->keyVar->name] = true; + } + + if ($stmt->valueVar instanceof PhpParser\Node\Expr\Variable && is_string($stmt->valueVar->name)) { + $safe_var_ids['$' . $stmt->valueVar->name] = true; + } elseif ($stmt->valueVar instanceof PhpParser\Node\Expr\List_) { + foreach ($stmt->valueVar->items as $list_item) { + if (!$list_item) { + continue; + } + + $list_item_key = $list_item->key; + $list_item_value = $list_item->value; + + if ($list_item_value instanceof PhpParser\Node\Expr\Variable && is_string($list_item_value->name)) { + $safe_var_ids['$' . $list_item_value->name] = true; + } + + if ($list_item_key instanceof PhpParser\Node\Expr\Variable && is_string($list_item_key->name)) { + $safe_var_ids['$' . $list_item_key->name] = true; + } + } + } + + foreach ($var_comments as $var_comment) { + if (!$var_comment->var_id || !$var_comment->type) { + continue; + } + + if (isset($safe_var_ids[$var_comment->var_id])) { + continue; + } + + $comment_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $var_comment->type, + $context->self, + $context->self, + $statements_analyzer->getParentFQCLN() + ); + + $type_location = null; + + if ($var_comment->type_start + && $var_comment->type_end + && $var_comment->line_number + ) { + $type_location = new CodeLocation\DocblockTypeLocation( + $statements_analyzer, + $var_comment->type_start, + $var_comment->type_end, + $var_comment->line_number + ); + + if ($codebase->alter_code) { + $codebase->classlikes->handleDocblockTypeInMigration( + $codebase, + $statements_analyzer, + $comment_type, + $type_location, + $context->calling_method_id + ); + } + } + + if (isset($context->vars_in_scope[$var_comment->var_id]) + || VariableFetchAnalyzer::isSuperGlobal($var_comment->var_id) + ) { + if ($codebase->find_unused_variables + && $doc_comment + && $type_location + && isset($context->vars_in_scope[$var_comment->var_id]) + && $context->vars_in_scope[$var_comment->var_id]->getId() === $comment_type->getId() + && !$comment_type->isMixed() + ) { + $project_analyzer = $statements_analyzer->getProjectAnalyzer(); + + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['UnnecessaryVarAnnotation']) + ) { + FileManipulationBuffer::addVarAnnotationToRemove($type_location); + } elseif (IssueBuffer::accepts( + new UnnecessaryVarAnnotation( + 'The @var ' . $comment_type . ' annotation for ' + . $var_comment->var_id . ' is unnecessary', + $type_location + ), + [], + true + )) { + // fall through + } + } + + if (isset($context->vars_in_scope[$var_comment->var_id])) { + $comment_type->parent_nodes = $context->vars_in_scope[$var_comment->var_id]->parent_nodes; + } + + $context->vars_in_scope[$var_comment->var_id] = $comment_type; + } + } + + $was_inside_use = $context->inside_use; + $context->inside_use = true; + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + $context->inside_use = $was_inside_use; + + $key_type = null; + $value_type = null; + $always_non_empty_array = true; + + $var_id = ExpressionIdentifier::getVarId( + $stmt->expr, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr)) { + $iterator_type = $stmt_expr_type; + } elseif ($var_id && $context->hasVariable($var_id)) { + $iterator_type = $context->vars_in_scope[$var_id]; + } else { + $iterator_type = null; + } + + if ($iterator_type) { + if (self::checkIteratorType( + $statements_analyzer, + $stmt, + $iterator_type, + $codebase, + $context, + $key_type, + $value_type, + $always_non_empty_array + ) === false + ) { + return false; + } + } + + $foreach_context = clone $context; + + foreach ($foreach_context->vars_in_scope as $context_var_id => $context_type) { + $foreach_context->vars_in_scope[$context_var_id] = clone $context_type; + } + + $foreach_context->inside_loop = true; + $foreach_context->break_types[] = 'loop'; + + if ($codebase->alter_code) { + $foreach_context->branch_point = + $foreach_context->branch_point ?: (int) $stmt->getAttribute('startFilePos'); + } + + if ($stmt->keyVar && $stmt->keyVar instanceof PhpParser\Node\Expr\Variable && is_string($stmt->keyVar->name)) { + $key_type = $key_type ?: Type::getMixed(); + + AssignmentAnalyzer::analyze( + $statements_analyzer, + $stmt->keyVar, + $stmt->expr, + $key_type, + $foreach_context, + $doc_comment, + ['$' . $stmt->keyVar->name => true] + ); + } + + $value_type = $value_type ?: Type::getMixed(); + + if ($stmt->byRef) { + $value_type->by_ref = true; + } + + AssignmentAnalyzer::analyze( + $statements_analyzer, + $stmt->valueVar, + $stmt->expr, + $value_type, + $foreach_context, + $doc_comment, + $stmt->valueVar instanceof PhpParser\Node\Expr\Variable + && is_string($stmt->valueVar->name) + ? ['$' . $stmt->valueVar->name => true] + : [] + ); + + foreach ($var_comments as $var_comment) { + if (!$var_comment->var_id || !$var_comment->type) { + continue; + } + + $comment_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $var_comment->type, + $context->self, + $context->self, + $statements_analyzer->getParentFQCLN() + ); + + if (isset($foreach_context->vars_in_scope[$var_comment->var_id])) { + $existing_var_type = $foreach_context->vars_in_scope[$var_comment->var_id]; + $comment_type->parent_nodes = $existing_var_type->parent_nodes; + $comment_type->by_ref = $existing_var_type->by_ref; + } + + $foreach_context->vars_in_scope[$var_comment->var_id] = $comment_type; + } + + $loop_scope = new LoopScope($foreach_context, $context); + + $loop_scope->protected_var_ids = $context->protected_var_ids; + + LoopAnalyzer::analyze( + $statements_analyzer, + $stmt->stmts, + [], + [], + $loop_scope, + $inner_loop_context, + false, + $always_non_empty_array + ); + + if (!$inner_loop_context) { + throw new \UnexpectedValueException('There should be an inner loop context'); + } + + $foreach_context->loop_scope = null; + + $context->vars_possibly_in_scope = array_merge( + $foreach_context->vars_possibly_in_scope, + $context->vars_possibly_in_scope + ); + + $context->referenced_var_ids = array_intersect_key( + $foreach_context->referenced_var_ids, + $context->referenced_var_ids + ); + + if ($context->collect_exceptions) { + $context->mergeExceptions($foreach_context); + } + + return null; + } + + /** + * @return false|null + */ + public static function checkIteratorType( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Stmt\Foreach_ $stmt, + Type\Union $iterator_type, + Codebase $codebase, + Context $context, + ?Type\Union &$key_type, + ?Type\Union &$value_type, + bool &$always_non_empty_array + ): ?bool { + if ($iterator_type->isNull()) { + if (IssueBuffer::accepts( + new NullIterator( + 'Cannot iterate over null', + new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } elseif ($iterator_type->isNullable() && !$iterator_type->ignore_nullable_issues) { + if (IssueBuffer::accepts( + new PossiblyNullIterator( + 'Cannot iterate over nullable var ' . $iterator_type, + new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } elseif ($iterator_type->isFalsable() && !$iterator_type->ignore_falsable_issues) { + if (IssueBuffer::accepts( + new PossiblyFalseIterator( + 'Cannot iterate over falsable var ' . $iterator_type, + new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + + $has_valid_iterator = false; + $invalid_iterator_types = []; + $raw_object_types = []; + + foreach ($iterator_type->getAtomicTypes() as $iterator_atomic_type) { + if ($iterator_atomic_type instanceof Type\Atomic\TTemplateParam) { + $iterator_atomic_type = array_values($iterator_atomic_type->as->getAtomicTypes())[0]; + } + + // if it's an empty array, we cannot iterate over it + if ($iterator_atomic_type instanceof Type\Atomic\TArray + && $iterator_atomic_type->type_params[1]->isEmpty() + ) { + $always_non_empty_array = false; + $has_valid_iterator = true; + continue; + } + + if ($iterator_atomic_type instanceof Type\Atomic\TNull + || $iterator_atomic_type instanceof Type\Atomic\TFalse + ) { + $always_non_empty_array = false; + continue; + } + + if ($iterator_atomic_type instanceof Type\Atomic\TArray + || $iterator_atomic_type instanceof Type\Atomic\TKeyedArray + || $iterator_atomic_type instanceof Type\Atomic\TList + ) { + if ($iterator_atomic_type instanceof Type\Atomic\TKeyedArray) { + if (!$iterator_atomic_type->sealed) { + $always_non_empty_array = false; + } + $iterator_atomic_type = $iterator_atomic_type->getGenericArrayType(); + } elseif ($iterator_atomic_type instanceof Type\Atomic\TList) { + if (!$iterator_atomic_type instanceof Type\Atomic\TNonEmptyList) { + $always_non_empty_array = false; + } + + $iterator_atomic_type = new Type\Atomic\TArray([ + Type::getInt(), + $iterator_atomic_type->type_param + ]); + } elseif (!$iterator_atomic_type instanceof Type\Atomic\TNonEmptyArray) { + $always_non_empty_array = false; + } + + if (!$value_type) { + $value_type = clone $iterator_atomic_type->type_params[1]; + } else { + $value_type = Type::combineUnionTypes($value_type, $iterator_atomic_type->type_params[1]); + } + + ArrayFetchAnalyzer::taintArrayFetch( + $statements_analyzer, + $stmt->expr, + null, + $value_type, + Type::getMixed() + ); + + $key_type_part = $iterator_atomic_type->type_params[0]; + + if (!$key_type) { + $key_type = $key_type_part; + } else { + $key_type = Type::combineUnionTypes($key_type, $key_type_part); + } + + ArrayFetchAnalyzer::taintArrayFetch( + $statements_analyzer, + $stmt->expr, + null, + $key_type, + Type::getMixed() + ); + + $has_valid_iterator = true; + continue; + } + + $always_non_empty_array = false; + + if ($iterator_atomic_type instanceof Type\Atomic\Scalar || + $iterator_atomic_type instanceof Type\Atomic\TVoid + ) { + $invalid_iterator_types[] = $iterator_atomic_type->getKey(); + + $value_type = Type::getMixed(); + } elseif ($iterator_atomic_type instanceof Type\Atomic\TObject || + $iterator_atomic_type instanceof Type\Atomic\TMixed || + $iterator_atomic_type instanceof Type\Atomic\TEmpty + ) { + $has_valid_iterator = true; + $value_type = Type::getMixed(); + + ArrayFetchAnalyzer::taintArrayFetch( + $statements_analyzer, + $stmt->expr, + null, + $value_type, + Type::getMixed() + ); + + if (!$context->pure) { + if ($statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } + } else { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call a possibly-mutating iterator from a pure context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } elseif ($iterator_atomic_type instanceof Type\Atomic\TIterable) { + if ($iterator_atomic_type->extra_types) { + $iterator_atomic_type_copy = clone $iterator_atomic_type; + $iterator_atomic_type_copy->extra_types = []; + $iterator_atomic_types = [$iterator_atomic_type_copy]; + $iterator_atomic_types = array_merge( + $iterator_atomic_types, + $iterator_atomic_type->extra_types + ); + } else { + $iterator_atomic_types = [$iterator_atomic_type]; + } + + $intersection_value_type = null; + $intersection_key_type = null; + + foreach ($iterator_atomic_types as $iat) { + if (!$iat instanceof Type\Atomic\TIterable) { + continue; + } + + [$key_type_part, $value_type_part] = $iat->type_params; + + if (!$intersection_value_type) { + $intersection_value_type = $value_type_part; + } else { + $intersection_value_type = Type::intersectUnionTypes( + $intersection_value_type, + $value_type_part, + $codebase + ) ?: Type::getMixed(); + } + + if (!$intersection_key_type) { + $intersection_key_type = $key_type_part; + } else { + $intersection_key_type = Type::intersectUnionTypes( + $intersection_key_type, + $key_type_part, + $codebase + ) ?: Type::getMixed(); + } + } + + if (!$intersection_value_type || !$intersection_key_type) { + throw new \UnexpectedValueException('Should not happen'); + } + + if (!$value_type) { + $value_type = $intersection_value_type; + } else { + $value_type = Type::combineUnionTypes($value_type, $intersection_value_type); + } + + if (!$key_type) { + $key_type = $intersection_key_type; + } else { + $key_type = Type::combineUnionTypes($key_type, $intersection_key_type); + } + + $has_valid_iterator = true; + + if (!$context->pure) { + if ($statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } + } else { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call a possibly-mutating Traversable::getIterator from a pure context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } elseif ($iterator_atomic_type instanceof Type\Atomic\TNamedObject) { + if ($iterator_atomic_type->value !== 'Traversable' && + $iterator_atomic_type->value !== $statements_analyzer->getClassName() + ) { + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $iterator_atomic_type->value, + new CodeLocation($statements_analyzer->getSource(), $stmt->expr), + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues() + ) === false) { + return false; + } + } + + if (AtomicTypeComparator::isContainedBy( + $codebase, + $iterator_atomic_type, + new Type\Atomic\TIterable([Type::getMixed(), Type::getMixed()]) + )) { + self::handleIterable( + $statements_analyzer, + $iterator_atomic_type, + $stmt->expr, + $codebase, + $context, + $key_type, + $value_type, + $has_valid_iterator + ); + } else { + $raw_object_types[] = $iterator_atomic_type->value; + } + + if (!$context->pure) { + if ($statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } + } else { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call a possibly-mutating iterator from a pure context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + if ($raw_object_types) { + if ($has_valid_iterator) { + if (IssueBuffer::accepts( + new PossibleRawObjectIteration( + 'Possibly undesired iteration over regular object ' . \reset($raw_object_types), + new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new RawObjectIteration( + 'Possibly undesired iteration over regular object ' . \reset($raw_object_types), + new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($invalid_iterator_types) { + if ($has_valid_iterator) { + if (IssueBuffer::accepts( + new PossiblyInvalidIterator( + 'Cannot iterate over ' . $invalid_iterator_types[0], + new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidIterator( + 'Cannot iterate over ' . $invalid_iterator_types[0], + new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + return null; + } + + public static function handleIterable( + StatementsAnalyzer $statements_analyzer, + Type\Atomic\TNamedObject $iterator_atomic_type, + PhpParser\Node\Expr $foreach_expr, + Codebase $codebase, + Context $context, + ?Type\Union &$key_type, + ?Type\Union &$value_type, + bool &$has_valid_iterator + ): void { + if ($iterator_atomic_type->extra_types) { + $iterator_atomic_type_copy = clone $iterator_atomic_type; + $iterator_atomic_type_copy->extra_types = []; + $iterator_atomic_types = [$iterator_atomic_type_copy]; + $iterator_atomic_types = array_merge($iterator_atomic_types, $iterator_atomic_type->extra_types); + } else { + $iterator_atomic_types = [$iterator_atomic_type]; + } + + foreach ($iterator_atomic_types as $iterator_atomic_type) { + if ($iterator_atomic_type instanceof Type\Atomic\TTemplateParam + || $iterator_atomic_type instanceof Type\Atomic\TObjectWithProperties + ) { + throw new \UnexpectedValueException('Shouldn’t get a generic param here'); + } + + + $has_valid_iterator = true; + + if ($iterator_atomic_type instanceof Type\Atomic\TNamedObject + && strtolower($iterator_atomic_type->value) === 'simplexmlelement' + ) { + if ($value_type) { + $value_type = Type::combineUnionTypes( + $value_type, + new Type\Union([clone $iterator_atomic_type]) + ); + } else { + $value_type = new Type\Union([clone $iterator_atomic_type]); + } + + if ($key_type) { + $key_type = Type::combineUnionTypes( + $key_type, + Type::getString() + ); + } else { + $key_type = Type::getString(); + } + } + + if ($iterator_atomic_type instanceof Type\Atomic\TIterable + || (strtolower($iterator_atomic_type->value) === 'traversable' + || $codebase->classImplements( + $iterator_atomic_type->value, + 'Traversable' + ) || + ( + $codebase->interfaceExists($iterator_atomic_type->value) + && $codebase->interfaceExtends( + $iterator_atomic_type->value, + 'Traversable' + ) + )) + ) { + if (strtolower($iterator_atomic_type->value) === 'iteratoraggregate' + || $codebase->classImplements( + $iterator_atomic_type->value, + 'IteratorAggregate' + ) + || ($codebase->interfaceExists($iterator_atomic_type->value) + && $codebase->interfaceExtends( + $iterator_atomic_type->value, + 'IteratorAggregate' + ) + ) + ) { + $old_data_provider = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $fake_method_call = new PhpParser\Node\Expr\MethodCall( + $foreach_expr, + new PhpParser\Node\Identifier('getIterator', $foreach_expr->getAttributes()) + ); + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + if (!in_array('PossiblyUndefinedMethod', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['PossiblyUndefinedMethod']); + } + + \Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_method_call, + $context + ); + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + if (!in_array('PossiblyUndefinedMethod', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['PossiblyUndefinedMethod']); + } + + $iterator_class_type = $statements_analyzer->node_data->getType($fake_method_call) ?: null; + + $statements_analyzer->node_data = $old_data_provider; + + if ($iterator_class_type) { + foreach ($iterator_class_type->getAtomicTypes() as $array_atomic_type) { + $key_type_part = null; + $value_type_part = null; + + if ($array_atomic_type instanceof Type\Atomic\TArray + || $array_atomic_type instanceof Type\Atomic\TKeyedArray + ) { + if ($array_atomic_type instanceof Type\Atomic\TKeyedArray) { + $array_atomic_type = $array_atomic_type->getGenericArrayType(); + } + + [$key_type_part, $value_type_part] = $array_atomic_type->type_params; + } else { + if ($array_atomic_type instanceof Type\Atomic\TNamedObject + && $codebase->classExists($array_atomic_type->value) + && $codebase->classImplements( + $array_atomic_type->value, + 'Traversable' + ) + ) { + $generic_storage = $codebase->classlike_storage_provider->get( + $array_atomic_type->value + ); + + // The collection might be an iterator, in which case + // we want to call the iterator function + /** @psalm-suppress PossiblyUndefinedStringArrayOffset */ + if (!isset($generic_storage->template_type_extends['Traversable']) + || ($generic_storage + ->template_type_extends['Traversable']['TKey']->isMixed() + && $generic_storage + ->template_type_extends['Traversable']['TValue']->isMixed()) + ) { + self::handleIterable( + $statements_analyzer, + $array_atomic_type, + $fake_method_call, + $codebase, + $context, + $key_type, + $value_type, + $has_valid_iterator + ); + + continue; + } + } + + if ($array_atomic_type instanceof Type\Atomic\TIterable + || ($array_atomic_type instanceof Type\Atomic\TNamedObject + && ($array_atomic_type->value === 'Traversable' + || ($codebase->classOrInterfaceExists($array_atomic_type->value) + && $codebase->classImplements( + $array_atomic_type->value, + 'Traversable' + )))) + ) { + self::getKeyValueParamsForTraversableObject( + $array_atomic_type, + $codebase, + $key_type_part, + $value_type_part + ); + } + } + + if (!$key_type_part || !$value_type_part) { + break; + } + + if (!$key_type) { + $key_type = $key_type_part; + } else { + $key_type = Type::combineUnionTypes($key_type, $key_type_part); + } + + if (!$value_type) { + $value_type = $value_type_part; + } else { + $value_type = Type::combineUnionTypes($value_type, $value_type_part); + } + } + } + } elseif ($codebase->classImplements( + $iterator_atomic_type->value, + 'Iterator' + ) || + ( + $codebase->interfaceExists($iterator_atomic_type->value) + && $codebase->interfaceExtends( + $iterator_atomic_type->value, + 'Iterator' + ) + ) + ) { + $iterator_value_type = self::getFakeMethodCallType( + $statements_analyzer, + $foreach_expr, + $context, + 'current' + ); + + $iterator_key_type = self::getFakeMethodCallType( + $statements_analyzer, + $foreach_expr, + $context, + 'key' + ); + + if ($iterator_value_type && !$iterator_value_type->isMixed()) { + if (!$value_type) { + $value_type = $iterator_value_type; + } else { + $value_type = Type::combineUnionTypes($value_type, $iterator_value_type); + } + } + + if ($iterator_key_type && !$iterator_key_type->isMixed()) { + if (!$key_type) { + $key_type = $iterator_key_type; + } else { + $key_type = Type::combineUnionTypes($key_type, $iterator_key_type); + } + } + } + + if (!$key_type && !$value_type) { + self::getKeyValueParamsForTraversableObject( + $iterator_atomic_type, + $codebase, + $key_type, + $value_type + ); + } + + return; + } + + if (!$codebase->classlikes->classOrInterfaceExists($iterator_atomic_type->value)) { + return; + } + } + } + + public static function getKeyValueParamsForTraversableObject( + Type\Atomic $iterator_atomic_type, + Codebase $codebase, + ?Type\Union &$key_type, + ?Type\Union &$value_type + ): void { + if ($iterator_atomic_type instanceof Type\Atomic\TIterable + || ($iterator_atomic_type instanceof Type\Atomic\TGenericObject + && strtolower($iterator_atomic_type->value) === 'traversable') + ) { + $value_type_part = $iterator_atomic_type->type_params[1]; + + if (!$value_type) { + $value_type = $value_type_part; + } else { + $value_type = Type::combineUnionTypes($value_type, $value_type_part); + } + + $key_type_part = $iterator_atomic_type->type_params[0]; + + if (!$key_type) { + $key_type = $key_type_part; + } else { + $key_type = Type::combineUnionTypes($key_type, $key_type_part); + } + return; + } + + if ($iterator_atomic_type instanceof Type\Atomic\TNamedObject + && ( + $codebase->classImplements( + $iterator_atomic_type->value, + 'Traversable' + ) + || $codebase->interfaceExtends( + $iterator_atomic_type->value, + 'Traversable' + ) + ) + ) { + $generic_storage = $codebase->classlike_storage_provider->get( + $iterator_atomic_type->value + ); + + if (!isset($generic_storage->template_type_extends['Traversable'])) { + return; + } + + if ($generic_storage->template_types + || $iterator_atomic_type instanceof Type\Atomic\TGenericObject + ) { + // if we're just being passed the non-generic class itself, assume + // that it's inside the calling class + $passed_type_params = $iterator_atomic_type instanceof Type\Atomic\TGenericObject + ? $iterator_atomic_type->type_params + : array_values( + array_map( + /** @param array $arr */ + function (array $arr) use ($iterator_atomic_type) : Type\Union { + if (isset($arr[$iterator_atomic_type->value])) { + return $arr[$iterator_atomic_type->value][0]; + } + + return Type::getMixed(); + }, + $generic_storage->template_types + ) + ); + } else { + $passed_type_params = null; + } + + $key_type = self::getExtendedType( + 'TKey', + 'Traversable', + $generic_storage->name, + $generic_storage->template_type_extends, + $generic_storage->template_types, + $passed_type_params + ); + + $value_type = self::getExtendedType( + 'TValue', + 'Traversable', + $generic_storage->name, + $generic_storage->template_type_extends, + $generic_storage->template_types, + $passed_type_params + ); + + return; + } + } + + private static function getFakeMethodCallType( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr $foreach_expr, + Context $context, + string $method_name + ) : ?Type\Union { + $old_data_provider = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $fake_method_call = new PhpParser\Node\Expr\MethodCall( + $foreach_expr, + new PhpParser\Node\Identifier($method_name, $foreach_expr->getAttributes()) + ); + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + if (!in_array('PossiblyUndefinedMethod', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['PossiblyUndefinedMethod']); + } + + $was_inside_call = $context->inside_call; + + $context->inside_call = true; + + \Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_method_call, + $context + ); + + $context->inside_call = $was_inside_call; + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + if (!in_array('PossiblyUndefinedMethod', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['PossiblyUndefinedMethod']); + } + + $iterator_class_type = $statements_analyzer->node_data->getType($fake_method_call) ?: null; + + $statements_analyzer->node_data = $old_data_provider; + + return $iterator_class_type; + } + + /** + * @param array> $template_type_extends + * @param array> $class_template_types + * @param array $calling_type_params + */ + private static function getExtendedType( + string $template_name, + string $template_class, + string $calling_class, + array $template_type_extends, + ?array $class_template_types = null, + ?array $calling_type_params = null + ): ?Type\Union { + if ($calling_class === $template_class) { + if (isset($class_template_types[$template_name]) && $calling_type_params) { + $offset = array_search($template_name, array_keys($class_template_types)); + + if ($offset !== false && isset($calling_type_params[$offset])) { + return $calling_type_params[$offset]; + } + } + + return null; + } + + if (isset($template_type_extends[$template_class][$template_name])) { + $extended_type = $template_type_extends[$template_class][$template_name]; + + $return_type = null; + + foreach ($extended_type->getAtomicTypes() as $extended_atomic_type) { + if (!$extended_atomic_type instanceof Type\Atomic\TTemplateParam) { + if (!$return_type) { + $return_type = $extended_type; + } else { + $return_type = Type::combineUnionTypes( + $return_type, + $extended_type + ); + } + + continue; + } + + $candidate_type = self::getExtendedType( + $extended_atomic_type->param_name, + $extended_atomic_type->defining_class, + $calling_class, + $template_type_extends, + $class_template_types, + $calling_type_params + ); + + if ($candidate_type) { + if (!$return_type) { + $return_type = $candidate_type; + } else { + $return_type = Type::combineUnionTypes( + $return_type, + $candidate_type + ); + } + } + } + + if ($return_type) { + return $return_type; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..f61178d952541435d60dc70f91ae4455248691a9 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfAnalyzer.php @@ -0,0 +1,1922 @@ +getCodebase(); + + $if_scope = new IfScope(); + + // We need to clone the original context for later use if we're exiting in this if conditional + if (!$stmt->else && !$stmt->elseifs && $stmt->cond instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) { + $final_actions = ScopeAnalyzer::getControlActions( + $stmt->stmts, + $statements_analyzer->node_data, + $codebase->config->exit_functions, + $context->break_types + ); + + $has_leaving_statements = $final_actions === [ScopeAnalyzer::ACTION_END] + || (count($final_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $final_actions, true)); + + if ($has_leaving_statements) { + $if_scope->mic_drop_context = clone $context; + } + } + + try { + $if_conditional_scope = self::analyzeIfConditional( + $statements_analyzer, + $stmt->cond, + $context, + $codebase, + $if_scope, + $context->branch_point ?: (int) $stmt->getAttribute('startFilePos') + ); + + $if_context = $if_conditional_scope->if_context; + + $original_context = $if_conditional_scope->original_context; + $cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids; + $cond_assigned_var_ids = $if_conditional_scope->cond_assigned_var_ids; + } catch (\Psalm\Exception\ScopeAnalysisException $e) { + return false; + } + + $mixed_var_ids = []; + + foreach ($if_context->vars_in_scope as $var_id => $type) { + if ($type->hasMixed() && isset($context->vars_in_scope[$var_id])) { + $mixed_var_ids[] = $var_id; + } + } + + $cond_object_id = \spl_object_id($stmt->cond); + + $if_clauses = Algebra::getFormula( + $cond_object_id, + $cond_object_id, + $stmt->cond, + $context->self, + $statements_analyzer, + $codebase + ); + + if (count($if_clauses) > 200) { + $if_clauses = []; + } + + $if_clauses = array_values( + array_map( + /** + * @return Clause + */ + function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { + $keys = array_keys($c->possibilities); + + $mixed_var_ids = \array_diff($mixed_var_ids, $keys); + + foreach ($keys as $key) { + foreach ($mixed_var_ids as $mixed_var_id) { + if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { + return new Clause([], $cond_object_id, $cond_object_id, true); + } + } + } + + return $c; + }, + $if_clauses + ) + ); + + $entry_clauses = $context->clauses; + + // this will see whether any of the clauses in set A conflict with the clauses in set B + AlgebraAnalyzer::checkForParadox( + $context->clauses, + $if_clauses, + $statements_analyzer, + $stmt->cond, + $cond_assigned_var_ids + ); + + // if we have assignments in the if, we may have duplicate clauses + if ($cond_assigned_var_ids) { + $if_clauses = Algebra::simplifyCNF($if_clauses); + } + + $if_context_clauses = array_merge($entry_clauses, $if_clauses); + + $if_context->clauses = Algebra::simplifyCNF($if_context_clauses); + + if ($if_context->reconciled_expression_clauses) { + $reconciled_expression_clauses = $if_context->reconciled_expression_clauses; + + $if_context->clauses = array_values( + array_filter( + $if_context->clauses, + function ($c) use ($reconciled_expression_clauses): bool { + return !in_array($c->hash, $reconciled_expression_clauses); + } + ) + ); + + if (count($if_context->clauses) === 1 + && $if_context->clauses[0]->wedge + && !$if_context->clauses[0]->possibilities + ) { + $if_context->clauses = []; + $if_context->reconciled_expression_clauses = []; + } + } + + // define this before we alter local claues after reconciliation + $if_scope->reasonable_clauses = $if_context->clauses; + + try { + $if_scope->negated_clauses = Algebra::negateFormula($if_clauses); + } catch (\Psalm\Exception\ComplicatedExpressionException $e) { + try { + $if_scope->negated_clauses = Algebra::getFormula( + $cond_object_id, + $cond_object_id, + new PhpParser\Node\Expr\BooleanNot($stmt->cond), + $context->self, + $statements_analyzer, + $codebase, + false + ); + } catch (\Psalm\Exception\ComplicatedExpressionException $e) { + $if_scope->negated_clauses = []; + } + } + + $if_scope->negated_types = Algebra::getTruthsFromFormula( + Algebra::simplifyCNF( + array_merge($context->clauses, $if_scope->negated_clauses) + ) + ); + + $active_if_types = []; + + $reconcilable_if_types = Algebra::getTruthsFromFormula( + $if_context->clauses, + \spl_object_id($stmt->cond), + $cond_referenced_var_ids, + $active_if_types + ); + + if (array_filter( + $context->clauses, + function ($clause): bool { + return !!$clause->possibilities; + } + )) { + $omit_keys = array_reduce( + $context->clauses, + /** + * @param array $carry + * @return array + */ + function (array $carry, Clause $clause): array { + return array_merge($carry, array_keys($clause->possibilities)); + }, + [] + ); + + $omit_keys = array_combine($omit_keys, $omit_keys); + $omit_keys = array_diff_key($omit_keys, Algebra::getTruthsFromFormula($context->clauses)); + + $cond_referenced_var_ids = array_diff_key( + $cond_referenced_var_ids, + $omit_keys + ); + } + + // if the if has an || in the conditional, we cannot easily reason about it + if ($reconcilable_if_types) { + $changed_var_ids = []; + + $if_vars_in_scope_reconciled = + Reconciler::reconcileKeyedTypes( + $reconcilable_if_types, + $active_if_types, + $if_context->vars_in_scope, + $changed_var_ids, + $cond_referenced_var_ids, + $statements_analyzer, + $statements_analyzer->getTemplateTypeMap() ?: [], + $if_context->inside_loop, + $context->check_variables + ? new CodeLocation( + $statements_analyzer->getSource(), + $stmt->cond instanceof PhpParser\Node\Expr\BooleanNot + ? $stmt->cond->expr + : $stmt->cond, + $context->include_location + ) : null + ); + + $if_context->vars_in_scope = $if_vars_in_scope_reconciled; + + foreach ($reconcilable_if_types as $var_id => $_) { + $if_context->vars_possibly_in_scope[$var_id] = true; + } + + if ($changed_var_ids) { + $if_context->clauses = Context::removeReconciledClauses($if_context->clauses, $changed_var_ids)[0]; + } + + $if_scope->if_cond_changed_var_ids = $changed_var_ids; + } + + $old_if_context = clone $if_context; + $context->vars_possibly_in_scope = array_merge( + $if_context->vars_possibly_in_scope, + $context->vars_possibly_in_scope + ); + + $context->referenced_var_ids = array_merge( + $if_context->referenced_var_ids, + $context->referenced_var_ids + ); + + $temp_else_context = clone $original_context; + + $changed_var_ids = []; + + if ($if_scope->negated_types) { + $else_vars_reconciled = Reconciler::reconcileKeyedTypes( + $if_scope->negated_types, + [], + $temp_else_context->vars_in_scope, + $changed_var_ids, + [], + $statements_analyzer, + $statements_analyzer->getTemplateTypeMap() ?: [], + $context->inside_loop, + $context->check_variables + ? new CodeLocation( + $statements_analyzer->getSource(), + $stmt->cond instanceof PhpParser\Node\Expr\BooleanNot + ? $stmt->cond->expr + : $stmt->cond, + $context->include_location + ) : null + ); + + $temp_else_context->vars_in_scope = $else_vars_reconciled; + } + + // we calculate the vars redefined in a hypothetical else statement to determine + // which vars of the if we can safely change + $pre_assignment_else_redefined_vars = array_intersect_key( + $temp_else_context->getRedefinedVars($context->vars_in_scope, true), + $changed_var_ids + ); + + // check the if + if (self::analyzeIfBlock( + $statements_analyzer, + $stmt, + $if_scope, + $if_conditional_scope, + $if_context, + $old_if_context, + $context, + $pre_assignment_else_redefined_vars + ) === false) { + return false; + } + + // check the else + $else_context = clone $original_context; + + // check the elseifs + foreach ($stmt->elseifs as $elseif) { + if (self::analyzeElseIfBlock( + $statements_analyzer, + $elseif, + $if_scope, + $else_context, + $context, + $codebase, + $else_context->branch_point ?: (int) $stmt->getAttribute('startFilePos') + ) === false) { + return false; + } + } + + if ($stmt->else) { + if ($codebase->alter_code) { + $else_context->branch_point = + $else_context->branch_point ?: (int) $stmt->getAttribute('startFilePos'); + } + } + + if (self::analyzeElseBlock( + $statements_analyzer, + $stmt->else, + $if_scope, + $else_context, + $context + ) === false) { + return false; + } + + if ($context->loop_scope) { + $context->loop_scope->final_actions = array_unique( + array_merge( + $context->loop_scope->final_actions, + $if_scope->final_actions + ) + ); + } + + $context->vars_possibly_in_scope = array_merge( + $context->vars_possibly_in_scope, + $if_scope->new_vars_possibly_in_scope + ); + + $context->possibly_assigned_var_ids = array_merge( + $context->possibly_assigned_var_ids, + $if_scope->possibly_assigned_var_ids ?: [] + ); + + // vars can only be defined/redefined if there was an else (defined in every block) + $context->assigned_var_ids = array_merge( + $context->assigned_var_ids, + $if_scope->assigned_var_ids ?: [] + ); + + if ($if_scope->new_vars) { + foreach ($if_scope->new_vars as $var_id => $type) { + if (isset($context->vars_possibly_in_scope[$var_id]) + && $statements_analyzer->data_flow_graph + ) { + $type->parent_nodes += $statements_analyzer->getParentNodesForPossiblyUndefinedVariable($var_id); + } + + $context->vars_in_scope[$var_id] = $type; + } + } + + if ($if_scope->redefined_vars) { + foreach ($if_scope->redefined_vars as $var_id => $type) { + $context->vars_in_scope[$var_id] = $type; + $if_scope->updated_vars[$var_id] = true; + + if ($if_scope->reasonable_clauses) { + $if_scope->reasonable_clauses = Context::filterClauses( + $var_id, + $if_scope->reasonable_clauses, + isset($context->vars_in_scope[$var_id]) + ? $context->vars_in_scope[$var_id] + : null, + $statements_analyzer + ); + } + } + } + + if ($if_scope->possible_param_types) { + foreach ($if_scope->possible_param_types as $var => $type) { + $context->possible_param_types[$var] = $type; + } + } + + if ($if_scope->reasonable_clauses + && (count($if_scope->reasonable_clauses) > 1 || !$if_scope->reasonable_clauses[0]->wedge) + ) { + $context->clauses = Algebra::simplifyCNF( + array_merge( + $if_scope->reasonable_clauses, + $context->clauses + ) + ); + } + + if ($if_scope->possibly_redefined_vars) { + foreach ($if_scope->possibly_redefined_vars as $var_id => $type) { + if (isset($context->vars_in_scope[$var_id])) { + if (!$type->failed_reconciliation + && !isset($if_scope->updated_vars[$var_id]) + ) { + $combined_type = Type::combineUnionTypes( + $context->vars_in_scope[$var_id], + $type, + $codebase + ); + + if (!$combined_type->equals($context->vars_in_scope[$var_id])) { + $context->removeDescendents($var_id, $combined_type); + } + + $context->vars_in_scope[$var_id] = $combined_type; + } else { + $context->vars_in_scope[$var_id]->parent_nodes += $type->parent_nodes; + } + } + } + } + + $context->possibly_assigned_var_ids += $if_scope->possibly_assigned_var_ids; + + if (!in_array(ScopeAnalyzer::ACTION_NONE, $if_scope->final_actions, true)) { + $context->has_returned = true; + } + + return null; + } + + public static function analyzeIfConditional( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr $cond, + Context $outer_context, + Codebase $codebase, + IfScope $if_scope, + ?int $branch_point + ): IfConditionalScope { + $entry_clauses = []; + + // used when evaluating elseifs + if ($if_scope->negated_clauses) { + $entry_clauses = array_merge($outer_context->clauses, $if_scope->negated_clauses); + + $changed_var_ids = []; + + if ($if_scope->negated_types) { + $vars_reconciled = Reconciler::reconcileKeyedTypes( + $if_scope->negated_types, + [], + $outer_context->vars_in_scope, + $changed_var_ids, + [], + $statements_analyzer, + [], + $outer_context->inside_loop, + new CodeLocation( + $statements_analyzer->getSource(), + $cond instanceof PhpParser\Node\Expr\BooleanNot + ? $cond->expr + : $cond, + $outer_context->include_location, + false + ) + ); + + if ($changed_var_ids) { + $outer_context = clone $outer_context; + $outer_context->vars_in_scope = $vars_reconciled; + + $entry_clauses = array_values( + array_filter( + $entry_clauses, + function (Clause $c) use ($changed_var_ids): bool { + return count($c->possibilities) > 1 + || $c->wedge + || !isset($changed_var_ids[array_keys($c->possibilities)[0]]); + } + ) + ); + } + } + } + + // get the first expression in the if, which should be evaluated on its own + // this allows us to update the context of $matches in + // if (!preg_match('/a/', 'aa', $matches)) { + // exit + // } + // echo $matches[0]; + $externally_applied_if_cond_expr = self::getDefinitelyEvaluatedExpressionAfterIf($cond); + + $internally_applied_if_cond_expr = self::getDefinitelyEvaluatedExpressionInsideIf($cond); + + $was_inside_conditional = $outer_context->inside_conditional; + + $outer_context->inside_conditional = true; + + $pre_condition_vars_in_scope = $outer_context->vars_in_scope; + + $referenced_var_ids = $outer_context->referenced_var_ids; + $outer_context->referenced_var_ids = []; + + $pre_assigned_var_ids = $outer_context->assigned_var_ids; + $outer_context->assigned_var_ids = []; + + $if_context = null; + + if ($internally_applied_if_cond_expr !== $externally_applied_if_cond_expr) { + $if_context = clone $outer_context; + } + + if ($externally_applied_if_cond_expr) { + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $externally_applied_if_cond_expr, + $outer_context + ) === false) { + throw new \Psalm\Exception\ScopeAnalysisException(); + } + } + + $first_cond_assigned_var_ids = $outer_context->assigned_var_ids; + $outer_context->assigned_var_ids = array_merge( + $pre_assigned_var_ids, + $first_cond_assigned_var_ids + ); + + $first_cond_referenced_var_ids = $outer_context->referenced_var_ids; + $outer_context->referenced_var_ids = array_merge( + $referenced_var_ids, + $first_cond_referenced_var_ids + ); + + if (!$was_inside_conditional) { + $outer_context->inside_conditional = false; + } + + if (!$if_context) { + $if_context = clone $outer_context; + } + + $if_conditional_context = clone $if_context; + $if_conditional_context->if_context = $if_context; + $if_conditional_context->if_scope = $if_scope; + + if ($codebase->alter_code) { + $if_context->branch_point = $branch_point; + } + + // we need to clone the current context so our ongoing updates + // to $outer_context don't mess with elseif/else blocks + $original_context = clone $outer_context; + + if ($internally_applied_if_cond_expr !== $cond + || $externally_applied_if_cond_expr !== $cond + ) { + $assigned_var_ids = $first_cond_assigned_var_ids; + $if_conditional_context->assigned_var_ids = []; + + $referenced_var_ids = $first_cond_referenced_var_ids; + $if_conditional_context->referenced_var_ids = []; + + $if_conditional_context->inside_conditional = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $cond, $if_conditional_context) === false) { + throw new \Psalm\Exception\ScopeAnalysisException(); + } + + $if_conditional_context->inside_conditional = false; + + /** @var array */ + $more_cond_referenced_var_ids = $if_conditional_context->referenced_var_ids; + $if_conditional_context->referenced_var_ids = array_merge( + $more_cond_referenced_var_ids, + $referenced_var_ids + ); + + $cond_referenced_var_ids = array_merge( + $first_cond_referenced_var_ids, + $more_cond_referenced_var_ids + ); + + /** @var array */ + $more_cond_assigned_var_ids = $if_conditional_context->assigned_var_ids; + $if_conditional_context->assigned_var_ids = array_merge( + $more_cond_assigned_var_ids, + $assigned_var_ids + ); + + $cond_assigned_var_ids = array_merge( + $first_cond_assigned_var_ids, + $more_cond_assigned_var_ids + ); + } else { + $cond_referenced_var_ids = $first_cond_referenced_var_ids; + + $cond_assigned_var_ids = $first_cond_assigned_var_ids; + } + + $newish_var_ids = array_map( + /** + * @param Type\Union $_ + * + * @return true + */ + function (Type\Union $_): bool { + return true; + }, + array_diff_key( + $if_conditional_context->vars_in_scope, + $pre_condition_vars_in_scope, + $cond_referenced_var_ids, + $cond_assigned_var_ids + ) + ); + + $cond_type = $statements_analyzer->node_data->getType($cond); + + if ($cond_type !== null) { + if ($cond_type->isFalse()) { + if ($cond_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + 'if (false) is impossible', + new CodeLocation($statements_analyzer, $cond), + 'false falsy' + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + 'if (false) is impossible', + new CodeLocation($statements_analyzer, $cond), + 'false falsy' + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } elseif ($cond_type->isTrue()) { + if ($cond_type->from_docblock) { + if (IssueBuffer::accepts( + new RedundantConditionGivenDocblockType( + 'if (true) is redundant', + new CodeLocation($statements_analyzer, $cond), + 'true falsy' + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new RedundantCondition( + 'if (true) is redundant', + new CodeLocation($statements_analyzer, $cond), + 'true falsy' + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + // get all the var ids that were referened in the conditional, but not assigned in it + $cond_referenced_var_ids = array_diff_key($cond_referenced_var_ids, $cond_assigned_var_ids); + + $cond_referenced_var_ids = array_merge($newish_var_ids, $cond_referenced_var_ids); + + return new \Psalm\Internal\Scope\IfConditionalScope( + $if_context, + $original_context, + $cond_referenced_var_ids, + $cond_assigned_var_ids, + $entry_clauses + ); + } + + /** + * @param array $pre_assignment_else_redefined_vars + * + * @return false|null + */ + protected static function analyzeIfBlock( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Stmt\If_ $stmt, + IfScope $if_scope, + IfConditionalScope $if_conditional_scope, + Context $if_context, + Context $old_if_context, + Context $outer_context, + array $pre_assignment_else_redefined_vars + ): ?bool { + $codebase = $statements_analyzer->getCodebase(); + + $if_context->parent_context = $outer_context; + + $assigned_var_ids = $if_context->assigned_var_ids; + $possibly_assigned_var_ids = $if_context->possibly_assigned_var_ids; + $if_context->assigned_var_ids = []; + $if_context->possibly_assigned_var_ids = []; + + if ($statements_analyzer->analyze( + $stmt->stmts, + $if_context + ) === false + ) { + return false; + } + + $final_actions = ScopeAnalyzer::getControlActions( + $stmt->stmts, + $statements_analyzer->node_data, + $codebase->config->exit_functions, + $outer_context->break_types + ); + + $has_ending_statements = $final_actions === [ScopeAnalyzer::ACTION_END]; + + $has_leaving_statements = $has_ending_statements + || (count($final_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $final_actions, true)); + + $has_break_statement = $final_actions === [ScopeAnalyzer::ACTION_BREAK]; + $has_continue_statement = $final_actions === [ScopeAnalyzer::ACTION_CONTINUE]; + + $if_scope->final_actions = $final_actions; + + /** @var array */ + $new_assigned_var_ids = $if_context->assigned_var_ids; + /** @var array */ + $new_possibly_assigned_var_ids = $if_context->possibly_assigned_var_ids; + + $if_context->assigned_var_ids = array_merge($assigned_var_ids, $new_assigned_var_ids); + $if_context->possibly_assigned_var_ids = array_merge( + $possibly_assigned_var_ids, + $new_possibly_assigned_var_ids + ); + + foreach ($if_context->byref_constraints as $var_id => $byref_constraint) { + if (isset($outer_context->byref_constraints[$var_id]) + && $byref_constraint->type + && ($outer_constraint_type = $outer_context->byref_constraints[$var_id]->type) + && !UnionTypeComparator::isContainedBy( + $codebase, + $byref_constraint->type, + $outer_constraint_type + ) + ) { + if (IssueBuffer::accepts( + new ConflictingReferenceConstraint( + 'There is more than one pass-by-reference constraint on ' . $var_id, + new CodeLocation($statements_analyzer, $stmt, $outer_context->include_location, true) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + $outer_context->byref_constraints[$var_id] = $byref_constraint; + } + } + + $mic_drop = false; + + if (!$has_leaving_statements) { + $if_scope->new_vars = array_diff_key($if_context->vars_in_scope, $outer_context->vars_in_scope); + + $if_scope->redefined_vars = $if_context->getRedefinedVars($outer_context->vars_in_scope); + $if_scope->possibly_redefined_vars = $if_scope->redefined_vars; + $if_scope->assigned_var_ids = $new_assigned_var_ids; + $if_scope->possibly_assigned_var_ids = $new_possibly_assigned_var_ids; + + $changed_var_ids = $new_assigned_var_ids; + + // if the variable was only set in the conditional, it's not possibly redefined + foreach ($if_scope->possibly_redefined_vars as $var_id => $_) { + if (!isset($new_possibly_assigned_var_ids[$var_id]) + && isset($if_scope->if_cond_changed_var_ids[$var_id]) + ) { + unset($if_scope->possibly_redefined_vars[$var_id]); + } + } + + if ($if_scope->reasonable_clauses) { + // remove all reasonable clauses that would be negated by the if stmts + foreach ($changed_var_ids as $var_id => $_) { + $if_scope->reasonable_clauses = Context::filterClauses( + $var_id, + $if_scope->reasonable_clauses, + isset($if_context->vars_in_scope[$var_id]) ? $if_context->vars_in_scope[$var_id] : null, + $statements_analyzer + ); + } + } + } else { + if (!$has_break_statement) { + $if_scope->reasonable_clauses = []; + } + } + + if ($has_leaving_statements && !$has_break_statement && !$stmt->else && !$stmt->elseifs) { + // If we're assigning inside + if ($if_conditional_scope->cond_assigned_var_ids + && $stmt->cond instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr + && $if_scope->mic_drop_context + ) { + self::addConditionallyAssignedVarsToContext( + $statements_analyzer, + $stmt->cond, + $if_scope->mic_drop_context, + $outer_context, + $if_conditional_scope->cond_assigned_var_ids + ); + } + + if ($if_scope->negated_types) { + $changed_var_ids = []; + + $outer_context_vars_reconciled = Reconciler::reconcileKeyedTypes( + $if_scope->negated_types, + [], + $outer_context->vars_in_scope, + $changed_var_ids, + [], + $statements_analyzer, + $statements_analyzer->getTemplateTypeMap() ?: [], + $outer_context->inside_loop, + new CodeLocation( + $statements_analyzer->getSource(), + $stmt->cond instanceof PhpParser\Node\Expr\BooleanNot + ? $stmt->cond->expr + : $stmt->cond, + $outer_context->include_location, + false + ) + ); + + foreach ($changed_var_ids as $changed_var_id => $_) { + $outer_context->removeVarFromConflictingClauses($changed_var_id); + } + + $changed_var_ids += $new_assigned_var_ids; + + foreach ($changed_var_ids as $var_id => $_) { + $if_scope->negated_clauses = Context::filterClauses( + $var_id, + $if_scope->negated_clauses + ); + } + + foreach ($changed_var_ids as $var_id => $_) { + $first_appearance = $statements_analyzer->getFirstAppearance($var_id); + + if ($first_appearance + && isset($outer_context->vars_in_scope[$var_id]) + && isset($outer_context_vars_reconciled[$var_id]) + && $outer_context->vars_in_scope[$var_id]->hasMixed() + && !$outer_context_vars_reconciled[$var_id]->hasMixed() + ) { + if (!$outer_context->collect_initializations + && !$outer_context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->decrementMixedCount($statements_analyzer->getFilePath()); + } + + IssueBuffer::remove( + $statements_analyzer->getFilePath(), + 'MixedAssignment', + $first_appearance->raw_file_start + ); + } + } + + $outer_context->vars_in_scope = $outer_context_vars_reconciled; + $mic_drop = true; + } + + $outer_context->clauses = Algebra::simplifyCNF( + array_merge($outer_context->clauses, $if_scope->negated_clauses) + ); + } + + // update the parent context as necessary, but only if we can safely reason about type negation. + // We only update vars that changed both at the start of the if block and then again by an assignment + // in the if statement. + if ($if_scope->negated_types && !$mic_drop) { + $vars_to_update = array_intersect( + array_keys($pre_assignment_else_redefined_vars), + array_keys($if_scope->negated_types) + ); + + $extra_vars_to_update = []; + + // if there's an object-like array in there, we also need to update the root array variable + foreach ($vars_to_update as $var_id) { + $bracked_pos = strpos($var_id, '['); + if ($bracked_pos !== false) { + $extra_vars_to_update[] = substr($var_id, 0, $bracked_pos); + } + } + + if ($extra_vars_to_update) { + $vars_to_update = array_unique(array_merge($extra_vars_to_update, $vars_to_update)); + } + + //update $if_context vars to include the pre-assignment else vars + if (!$stmt->else && !$has_leaving_statements) { + foreach ($pre_assignment_else_redefined_vars as $var_id => $type) { + if (isset($if_context->vars_in_scope[$var_id])) { + $if_context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $if_context->vars_in_scope[$var_id], + $type, + $codebase + ); + } + } + } + + $outer_context->update( + $old_if_context, + $if_context, + $has_leaving_statements, + $vars_to_update, + $if_scope->updated_vars + ); + } + + if (!$has_ending_statements) { + $vars_possibly_in_scope = array_diff_key( + $if_context->vars_possibly_in_scope, + $outer_context->vars_possibly_in_scope + ); + + if ($if_context->loop_scope) { + if (!$has_continue_statement && !$has_break_statement) { + $if_scope->new_vars_possibly_in_scope = $vars_possibly_in_scope; + } + + $if_context->loop_scope->vars_possibly_in_scope = array_merge( + $vars_possibly_in_scope, + $if_context->loop_scope->vars_possibly_in_scope + ); + } elseif (!$has_leaving_statements) { + $if_scope->new_vars_possibly_in_scope = $vars_possibly_in_scope; + } + } + + if ($outer_context->collect_exceptions) { + $outer_context->mergeExceptions($if_context); + } + + return null; + } + + /** + * @param Context $elseif_context + * + * @return false|null + */ + protected static function analyzeElseIfBlock( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Stmt\ElseIf_ $elseif, + IfScope $if_scope, + Context $else_context, + Context $outer_context, + Codebase $codebase, + ?int $branch_point + ): ?bool { + $pre_conditional_context = clone $else_context; + + try { + $if_conditional_scope = self::analyzeIfConditional( + $statements_analyzer, + $elseif->cond, + $else_context, + $codebase, + $if_scope, + $branch_point + ); + + $elseif_context = $if_conditional_scope->if_context; + $cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids; + $cond_assigned_var_ids = $if_conditional_scope->cond_assigned_var_ids; + $entry_clauses = $if_conditional_scope->entry_clauses; + } catch (\Psalm\Exception\ScopeAnalysisException $e) { + return false; + } + + $mixed_var_ids = []; + + foreach ($elseif_context->vars_in_scope as $var_id => $type) { + if ($type->hasMixed()) { + $mixed_var_ids[] = $var_id; + } + } + + $elseif_cond_id = \spl_object_id($elseif->cond); + + $elseif_clauses = Algebra::getFormula( + $elseif_cond_id, + $elseif_cond_id, + $elseif->cond, + $else_context->self, + $statements_analyzer, + $codebase + ); + + $elseif_clauses = array_map( + /** + * @return Clause + */ + function (Clause $c) use ($mixed_var_ids, $elseif_cond_id): Clause { + $keys = array_keys($c->possibilities); + + $mixed_var_ids = \array_diff($mixed_var_ids, $keys); + + foreach ($keys as $key) { + foreach ($mixed_var_ids as $mixed_var_id) { + if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { + return new Clause([], $elseif_cond_id, $elseif_cond_id, true); + } + } + } + + return $c; + }, + $elseif_clauses + ); + + $entry_clauses = array_map( + /** + * @return Clause + */ + function (Clause $c) use ($cond_assigned_var_ids, $elseif_cond_id): Clause { + $keys = array_keys($c->possibilities); + + foreach ($keys as $key) { + foreach ($cond_assigned_var_ids as $conditional_assigned_var_id => $_) { + if (preg_match('/^' . preg_quote($conditional_assigned_var_id, '/') . '(\[|-|$)/', $key)) { + return new Clause([], $elseif_cond_id, $elseif_cond_id, true); + } + } + } + + return $c; + }, + $entry_clauses + ); + + // this will see whether any of the clauses in set A conflict with the clauses in set B + AlgebraAnalyzer::checkForParadox( + $entry_clauses, + $elseif_clauses, + $statements_analyzer, + $elseif->cond, + $cond_assigned_var_ids + ); + + $elseif_context_clauses = array_merge($entry_clauses, $elseif_clauses); + + if ($elseif_context->reconciled_expression_clauses) { + $reconciled_expression_clauses = $elseif_context->reconciled_expression_clauses; + + $elseif_context_clauses = array_values( + array_filter( + $elseif_context_clauses, + function ($c) use ($reconciled_expression_clauses): bool { + return !in_array($c->hash, $reconciled_expression_clauses); + } + ) + ); + } + + $elseif_context->clauses = Algebra::simplifyCNF($elseif_context_clauses); + + $active_elseif_types = []; + + try { + if (array_filter( + $entry_clauses, + function ($clause): bool { + return !!$clause->possibilities; + } + )) { + $omit_keys = array_reduce( + $entry_clauses, + /** + * @param array $carry + * @return array + */ + function (array $carry, Clause $clause): array { + return array_merge($carry, array_keys($clause->possibilities)); + }, + [] + ); + + $omit_keys = array_combine($omit_keys, $omit_keys); + $omit_keys = array_diff_key($omit_keys, Algebra::getTruthsFromFormula($entry_clauses)); + + $cond_referenced_var_ids = array_diff_key( + $cond_referenced_var_ids, + $omit_keys + ); + } + $reconcilable_elseif_types = Algebra::getTruthsFromFormula( + $elseif_context->clauses, + \spl_object_id($elseif->cond), + $cond_referenced_var_ids, + $active_elseif_types + ); + $negated_elseif_types = Algebra::getTruthsFromFormula( + Algebra::negateFormula($elseif_clauses) + ); + } catch (\Psalm\Exception\ComplicatedExpressionException $e) { + $reconcilable_elseif_types = []; + $negated_elseif_types = []; + } + + $all_negated_vars = array_unique( + array_merge( + array_keys($negated_elseif_types), + array_keys($if_scope->negated_types) + ) + ); + + foreach ($all_negated_vars as $var_id) { + if (isset($negated_elseif_types[$var_id])) { + if (isset($if_scope->negated_types[$var_id])) { + $if_scope->negated_types[$var_id] = array_merge( + $if_scope->negated_types[$var_id], + $negated_elseif_types[$var_id] + ); + } else { + $if_scope->negated_types[$var_id] = $negated_elseif_types[$var_id]; + } + } + } + + $changed_var_ids = []; + + // if the elseif has an || in the conditional, we cannot easily reason about it + if ($reconcilable_elseif_types) { + $elseif_vars_reconciled = Reconciler::reconcileKeyedTypes( + $reconcilable_elseif_types, + $active_elseif_types, + $elseif_context->vars_in_scope, + $changed_var_ids, + $cond_referenced_var_ids, + $statements_analyzer, + $statements_analyzer->getTemplateTypeMap() ?: [], + $elseif_context->inside_loop, + new CodeLocation( + $statements_analyzer->getSource(), + $elseif->cond instanceof PhpParser\Node\Expr\BooleanNot + ? $elseif->cond->expr + : $elseif->cond, + $outer_context->include_location + ) + ); + + $elseif_context->vars_in_scope = $elseif_vars_reconciled; + + if ($changed_var_ids) { + $elseif_context->clauses = Context::removeReconciledClauses( + $elseif_context->clauses, + $changed_var_ids + )[0]; + } + } + + $pre_stmts_assigned_var_ids = $elseif_context->assigned_var_ids; + $elseif_context->assigned_var_ids = []; + $pre_stmts_possibly_assigned_var_ids = $elseif_context->possibly_assigned_var_ids; + $elseif_context->possibly_assigned_var_ids = []; + + if ($statements_analyzer->analyze( + $elseif->stmts, + $elseif_context + ) === false + ) { + return false; + } + + /** @var array */ + $new_stmts_assigned_var_ids = $elseif_context->assigned_var_ids; + $elseif_context->assigned_var_ids = $pre_stmts_assigned_var_ids + $new_stmts_assigned_var_ids; + + /** @var array */ + $new_stmts_possibly_assigned_var_ids = $elseif_context->possibly_assigned_var_ids; + $elseif_context->possibly_assigned_var_ids = + $pre_stmts_possibly_assigned_var_ids + $new_stmts_possibly_assigned_var_ids; + + foreach ($elseif_context->byref_constraints as $var_id => $byref_constraint) { + if (isset($outer_context->byref_constraints[$var_id]) + && ($outer_constraint_type = $outer_context->byref_constraints[$var_id]->type) + && $byref_constraint->type + && !UnionTypeComparator::isContainedBy( + $codebase, + $byref_constraint->type, + $outer_constraint_type + ) + ) { + if (IssueBuffer::accepts( + new ConflictingReferenceConstraint( + 'There is more than one pass-by-reference constraint on ' . $var_id, + new CodeLocation($statements_analyzer, $elseif, $outer_context->include_location, true) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + $outer_context->byref_constraints[$var_id] = $byref_constraint; + } + } + + $final_actions = ScopeAnalyzer::getControlActions( + $elseif->stmts, + $statements_analyzer->node_data, + $codebase->config->exit_functions, + $outer_context->break_types + ); + // has a return/throw at end + $has_ending_statements = $final_actions === [ScopeAnalyzer::ACTION_END]; + $has_leaving_statements = $has_ending_statements + || (count($final_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $final_actions, true)); + + $has_break_statement = $final_actions === [ScopeAnalyzer::ACTION_BREAK]; + $has_continue_statement = $final_actions === [ScopeAnalyzer::ACTION_CONTINUE]; + + $if_scope->final_actions = array_merge($final_actions, $if_scope->final_actions); + + // update the parent context as necessary + $elseif_redefined_vars = $elseif_context->getRedefinedVars($outer_context->vars_in_scope); + + if (!$has_leaving_statements) { + if ($if_scope->new_vars === null) { + $if_scope->new_vars = array_diff_key($elseif_context->vars_in_scope, $outer_context->vars_in_scope); + } else { + foreach ($if_scope->new_vars as $new_var => $type) { + if (!$elseif_context->hasVariable($new_var)) { + unset($if_scope->new_vars[$new_var]); + } else { + $if_scope->new_vars[$new_var] = Type::combineUnionTypes( + $type, + $elseif_context->vars_in_scope[$new_var], + $codebase + ); + } + } + } + + $possibly_redefined_vars = $elseif_redefined_vars; + + foreach ($possibly_redefined_vars as $var_id => $_) { + if (!isset($new_stmts_assigned_var_ids[$var_id]) + && isset($changed_var_ids[$var_id]) + ) { + unset($possibly_redefined_vars[$var_id]); + } + } + + $assigned_var_ids = array_merge($new_stmts_assigned_var_ids, $cond_assigned_var_ids); + + if ($if_scope->assigned_var_ids === null) { + $if_scope->assigned_var_ids = $assigned_var_ids; + } else { + $if_scope->assigned_var_ids = array_intersect_key($assigned_var_ids, $if_scope->assigned_var_ids); + } + + if ($if_scope->redefined_vars === null) { + $if_scope->redefined_vars = $elseif_redefined_vars; + $if_scope->possibly_redefined_vars = $possibly_redefined_vars; + } else { + foreach ($if_scope->redefined_vars as $redefined_var => $type) { + if (!isset($elseif_redefined_vars[$redefined_var])) { + unset($if_scope->redefined_vars[$redefined_var]); + } else { + $if_scope->redefined_vars[$redefined_var] = Type::combineUnionTypes( + $elseif_redefined_vars[$redefined_var], + $type, + $codebase + ); + + if (isset($outer_context->vars_in_scope[$redefined_var]) + && $if_scope->redefined_vars[$redefined_var]->equals( + $outer_context->vars_in_scope[$redefined_var] + ) + ) { + unset($if_scope->redefined_vars[$redefined_var]); + } + } + } + + foreach ($possibly_redefined_vars as $var => $type) { + if (isset($if_scope->possibly_redefined_vars[$var])) { + $if_scope->possibly_redefined_vars[$var] = Type::combineUnionTypes( + $type, + $if_scope->possibly_redefined_vars[$var], + $codebase + ); + } else { + $if_scope->possibly_redefined_vars[$var] = $type; + } + } + } + + $reasonable_clause_count = count($if_scope->reasonable_clauses); + + if ($reasonable_clause_count && $reasonable_clause_count < 20000 && $elseif_clauses) { + $if_scope->reasonable_clauses = Algebra::combineOredClauses( + $if_scope->reasonable_clauses, + $elseif_clauses, + \spl_object_id($elseif->cond) + ); + } else { + $if_scope->reasonable_clauses = []; + } + } else { + $if_scope->reasonable_clauses = []; + } + + if ($negated_elseif_types) { + if ($has_leaving_statements) { + $changed_var_ids = []; + + $leaving_vars_reconciled = Reconciler::reconcileKeyedTypes( + $negated_elseif_types, + [], + $pre_conditional_context->vars_in_scope, + $changed_var_ids, + [], + $statements_analyzer, + $statements_analyzer->getTemplateTypeMap() ?: [], + $elseif_context->inside_loop, + new CodeLocation($statements_analyzer->getSource(), $elseif, $outer_context->include_location) + ); + + $implied_outer_context = clone $elseif_context; + $implied_outer_context->vars_in_scope = $leaving_vars_reconciled; + + $outer_context->update( + $elseif_context, + $implied_outer_context, + false, + array_keys($negated_elseif_types), + $if_scope->updated_vars + ); + } + } + + if (!$has_ending_statements) { + $vars_possibly_in_scope = array_diff_key( + $elseif_context->vars_possibly_in_scope, + $outer_context->vars_possibly_in_scope + ); + + $possibly_assigned_var_ids = $new_stmts_possibly_assigned_var_ids; + + if ($has_leaving_statements && $elseif_context->loop_scope) { + if (!$has_continue_statement && !$has_break_statement) { + $if_scope->new_vars_possibly_in_scope = array_merge( + $vars_possibly_in_scope, + $if_scope->new_vars_possibly_in_scope + ); + $if_scope->possibly_assigned_var_ids = array_merge( + $possibly_assigned_var_ids, + $if_scope->possibly_assigned_var_ids + ); + } + + $elseif_context->loop_scope->vars_possibly_in_scope = array_merge( + $vars_possibly_in_scope, + $elseif_context->loop_scope->vars_possibly_in_scope + ); + } elseif (!$has_leaving_statements) { + $if_scope->new_vars_possibly_in_scope = array_merge( + $vars_possibly_in_scope, + $if_scope->new_vars_possibly_in_scope + ); + $if_scope->possibly_assigned_var_ids = array_merge( + $possibly_assigned_var_ids, + $if_scope->possibly_assigned_var_ids + ); + } + } + + if ($outer_context->collect_exceptions) { + $outer_context->mergeExceptions($elseif_context); + } + + try { + $if_scope->negated_clauses = Algebra::simplifyCNF( + array_merge( + $if_scope->negated_clauses, + Algebra::negateFormula($elseif_clauses) + ) + ); + } catch (\Psalm\Exception\ComplicatedExpressionException $e) { + $if_scope->negated_clauses = []; + } + + return null; + } + + /** + * @return false|null + */ + protected static function analyzeElseBlock( + StatementsAnalyzer $statements_analyzer, + ?PhpParser\Node\Stmt\Else_ $else, + IfScope $if_scope, + Context $else_context, + Context $outer_context + ): ?bool { + $codebase = $statements_analyzer->getCodebase(); + + if (!$else && !$if_scope->negated_clauses && !$else_context->clauses) { + $if_scope->final_actions = array_merge([ScopeAnalyzer::ACTION_NONE], $if_scope->final_actions); + $if_scope->assigned_var_ids = []; + $if_scope->new_vars = []; + $if_scope->redefined_vars = []; + $if_scope->reasonable_clauses = []; + + return null; + } + + $else_context->clauses = Algebra::simplifyCNF( + array_merge( + $else_context->clauses, + $if_scope->negated_clauses + ) + ); + + $else_types = Algebra::getTruthsFromFormula($else_context->clauses); + + if (!$else && !$else_types) { + $if_scope->final_actions = array_merge([ScopeAnalyzer::ACTION_NONE], $if_scope->final_actions); + $if_scope->assigned_var_ids = []; + $if_scope->new_vars = []; + $if_scope->redefined_vars = []; + $if_scope->reasonable_clauses = []; + + return null; + } + + $original_context = clone $else_context; + + if ($else_types) { + $changed_var_ids = []; + + $else_vars_reconciled = Reconciler::reconcileKeyedTypes( + $else_types, + [], + $else_context->vars_in_scope, + $changed_var_ids, + [], + $statements_analyzer, + [], + $else_context->inside_loop, + $else + ? new CodeLocation($statements_analyzer->getSource(), $else, $outer_context->include_location) + : null + ); + + $else_context->vars_in_scope = $else_vars_reconciled; + + $else_context->clauses = Context::removeReconciledClauses($else_context->clauses, $changed_var_ids)[0]; + } + + $old_else_context = clone $else_context; + + $pre_stmts_assigned_var_ids = $else_context->assigned_var_ids; + $else_context->assigned_var_ids = []; + + $pre_possibly_assigned_var_ids = $else_context->possibly_assigned_var_ids; + $else_context->possibly_assigned_var_ids = []; + + if ($else) { + if ($statements_analyzer->analyze( + $else->stmts, + $else_context + ) === false + ) { + return false; + } + } + + /** @var array */ + $new_assigned_var_ids = $else_context->assigned_var_ids; + $else_context->assigned_var_ids = $pre_stmts_assigned_var_ids; + + /** @var array */ + $new_possibly_assigned_var_ids = $else_context->possibly_assigned_var_ids; + $else_context->possibly_assigned_var_ids = $pre_possibly_assigned_var_ids + $new_possibly_assigned_var_ids; + + if ($else) { + foreach ($else_context->byref_constraints as $var_id => $byref_constraint) { + if (isset($outer_context->byref_constraints[$var_id]) + && ($outer_constraint_type = $outer_context->byref_constraints[$var_id]->type) + && $byref_constraint->type + && !UnionTypeComparator::isContainedBy( + $codebase, + $byref_constraint->type, + $outer_constraint_type + ) + ) { + if (IssueBuffer::accepts( + new ConflictingReferenceConstraint( + 'There is more than one pass-by-reference constraint on ' . $var_id, + new CodeLocation($statements_analyzer, $else, $outer_context->include_location, true) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + $outer_context->byref_constraints[$var_id] = $byref_constraint; + } + } + } + + $final_actions = $else + ? ScopeAnalyzer::getControlActions( + $else->stmts, + $statements_analyzer->node_data, + $codebase->config->exit_functions, + $outer_context->break_types + ) + : [ScopeAnalyzer::ACTION_NONE]; + // has a return/throw at end + $has_ending_statements = $final_actions === [ScopeAnalyzer::ACTION_END]; + $has_leaving_statements = $has_ending_statements + || (count($final_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $final_actions, true)); + + $has_break_statement = $final_actions === [ScopeAnalyzer::ACTION_BREAK]; + $has_continue_statement = $final_actions === [ScopeAnalyzer::ACTION_CONTINUE]; + + $if_scope->final_actions = array_merge($final_actions, $if_scope->final_actions); + + $else_redefined_vars = $else_context->getRedefinedVars($original_context->vars_in_scope); + + // if it doesn't end in a return + if (!$has_leaving_statements) { + if ($if_scope->new_vars === null && $else) { + $if_scope->new_vars = array_diff_key($else_context->vars_in_scope, $outer_context->vars_in_scope); + } elseif ($if_scope->new_vars !== null) { + foreach ($if_scope->new_vars as $new_var => $type) { + if (!$else_context->hasVariable($new_var)) { + unset($if_scope->new_vars[$new_var]); + } else { + $if_scope->new_vars[$new_var] = Type::combineUnionTypes( + $type, + $else_context->vars_in_scope[$new_var], + $codebase + ); + } + } + } + + if ($if_scope->assigned_var_ids === null) { + $if_scope->assigned_var_ids = $new_assigned_var_ids; + } else { + $if_scope->assigned_var_ids = array_intersect_key($new_assigned_var_ids, $if_scope->assigned_var_ids); + } + + if ($if_scope->redefined_vars === null) { + $if_scope->redefined_vars = $else_redefined_vars; + $if_scope->possibly_redefined_vars = $if_scope->redefined_vars; + } else { + foreach ($if_scope->redefined_vars as $redefined_var => $type) { + if (!isset($else_redefined_vars[$redefined_var])) { + unset($if_scope->redefined_vars[$redefined_var]); + } else { + $if_scope->redefined_vars[$redefined_var] = Type::combineUnionTypes( + $else_redefined_vars[$redefined_var], + $type, + $codebase + ); + } + } + + foreach ($else_redefined_vars as $var => $type) { + if (isset($if_scope->possibly_redefined_vars[$var])) { + $if_scope->possibly_redefined_vars[$var] = Type::combineUnionTypes( + $type, + $if_scope->possibly_redefined_vars[$var], + $codebase + ); + } else { + $if_scope->possibly_redefined_vars[$var] = $type; + } + } + } + + $if_scope->reasonable_clauses = []; + } + + // update the parent context as necessary + if ($if_scope->negatable_if_types) { + $outer_context->update( + $old_else_context, + $else_context, + $has_leaving_statements, + array_keys($if_scope->negatable_if_types), + $if_scope->updated_vars + ); + } + + if (!$has_ending_statements) { + $vars_possibly_in_scope = array_diff_key( + $else_context->vars_possibly_in_scope, + $outer_context->vars_possibly_in_scope + ); + + $possibly_assigned_var_ids = $new_possibly_assigned_var_ids; + + if ($has_leaving_statements && $else_context->loop_scope) { + if (!$has_continue_statement && !$has_break_statement) { + $if_scope->new_vars_possibly_in_scope = array_merge( + $vars_possibly_in_scope, + $if_scope->new_vars_possibly_in_scope + ); + + $if_scope->possibly_assigned_var_ids = array_merge( + $possibly_assigned_var_ids, + $if_scope->possibly_assigned_var_ids + ); + } + + $else_context->loop_scope->vars_possibly_in_scope = array_merge( + $vars_possibly_in_scope, + $else_context->loop_scope->vars_possibly_in_scope + ); + } elseif (!$has_leaving_statements) { + $if_scope->new_vars_possibly_in_scope = array_merge( + $vars_possibly_in_scope, + $if_scope->new_vars_possibly_in_scope + ); + + $if_scope->possibly_assigned_var_ids = array_merge( + $possibly_assigned_var_ids, + $if_scope->possibly_assigned_var_ids + ); + } + } + + if ($outer_context->collect_exceptions) { + $outer_context->mergeExceptions($else_context); + } + + return null; + } + + /** + * Returns statements that are definitely evaluated before any statements after the end of the + * if/elseif/else blocks + */ + private static function getDefinitelyEvaluatedExpressionAfterIf(PhpParser\Node\Expr $stmt): ?PhpParser\Node\Expr + { + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical + ) { + if ($stmt->left instanceof PhpParser\Node\Expr\ConstFetch + && $stmt->left->name->parts === ['true'] + ) { + return self::getDefinitelyEvaluatedExpressionAfterIf($stmt->right); + } + + if ($stmt->right instanceof PhpParser\Node\Expr\ConstFetch + && $stmt->right->name->parts === ['true'] + ) { + return self::getDefinitelyEvaluatedExpressionAfterIf($stmt->left); + } + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) { + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalAnd + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalXor + ) { + return self::getDefinitelyEvaluatedExpressionAfterIf($stmt->left); + } + + return $stmt; + } + + if ($stmt instanceof PhpParser\Node\Expr\BooleanNot) { + $inner_stmt = self::getDefinitelyEvaluatedExpressionInsideIf($stmt->expr); + + if ($inner_stmt !== $stmt->expr) { + return $inner_stmt; + } + } + + return $stmt; + } + + /** + * Returns statements that are definitely evaluated before any statements inside + * the if block + */ + private static function getDefinitelyEvaluatedExpressionInsideIf(PhpParser\Node\Expr $stmt): ?PhpParser\Node\Expr + { + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical + ) { + if ($stmt->left instanceof PhpParser\Node\Expr\ConstFetch + && $stmt->left->name->parts === ['true'] + ) { + return self::getDefinitelyEvaluatedExpressionInsideIf($stmt->right); + } + + if ($stmt->right instanceof PhpParser\Node\Expr\ConstFetch + && $stmt->right->name->parts === ['true'] + ) { + return self::getDefinitelyEvaluatedExpressionInsideIf($stmt->left); + } + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) { + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalOr + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalXor + ) { + return self::getDefinitelyEvaluatedExpressionInsideIf($stmt->left); + } + + return $stmt; + } + + if ($stmt instanceof PhpParser\Node\Expr\BooleanNot) { + $inner_stmt = self::getDefinitelyEvaluatedExpressionAfterIf($stmt->expr); + + if ($inner_stmt !== $stmt->expr) { + return $inner_stmt; + } + } + + return $stmt; + } + + /** + * Returns all expressions inside an ored expression + * @return non-empty-list + */ + private static function getDefinitelyEvaluatedOredExpressions(PhpParser\Node\Expr $stmt): array + { + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalOr + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalXor + ) { + return array_merge( + self::getDefinitelyEvaluatedOredExpressions($stmt->left), + self::getDefinitelyEvaluatedOredExpressions($stmt->right) + ); + } + + return [$stmt]; + } + + /** + * @param array $cond_assigned_var_ids + */ + public static function addConditionallyAssignedVarsToContext( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr $cond, + Context $mic_drop_context, + Context $outer_context, + array $cond_assigned_var_ids + ) : void { + // this filters out coercions to expeccted types in ArgumentAnalyzer + $cond_assigned_var_ids = \array_filter($cond_assigned_var_ids); + + if (!$cond_assigned_var_ids) { + return; + } + + $exprs = self::getDefinitelyEvaluatedOredExpressions($cond); + + // if there was no assignment in the first expression it's safe to proceed + $old_node_data = $statements_analyzer->node_data; + $statements_analyzer->node_data = clone $old_node_data; + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('RedundantCondition', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['RedundantCondition']); + } + if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['RedundantConditionGivenDocblockType']); + } + if (!in_array('TypeDoesNotContainType', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['TypeDoesNotContainType']); + } + + foreach ($exprs as $expr) { + $fake_negated_expr = new PhpParser\Node\Expr\FuncCall( + new PhpParser\Node\Name\FullyQualified('assert'), + [new PhpParser\Node\Arg( + new PhpParser\Node\Expr\BooleanNot($expr, $expr->getAttributes()), + false, + false, + $expr->getAttributes() + )], + $expr->getAttributes() + ); + + $mic_drop_context->inside_negation = !$mic_drop_context->inside_negation; + + ExpressionAnalyzer::analyze( + $statements_analyzer, + $fake_negated_expr, + $mic_drop_context + ); + + $mic_drop_context->inside_negation = !$mic_drop_context->inside_negation; + } + + if (!in_array('RedundantCondition', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['RedundantCondition']); + } + if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['RedundantConditionGivenDocblockType']); + } + if (!in_array('TypeDoesNotContainType', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['TypeDoesNotContainType']); + } + + $statements_analyzer->node_data = $old_node_data; + + foreach ($cond_assigned_var_ids as $var_id => $_) { + if (isset($mic_drop_context->vars_in_scope[$var_id])) { + $outer_context->vars_in_scope[$var_id] = clone $mic_drop_context->vars_in_scope[$var_id]; + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..f62b50c6f3a1bf4582f8c078df6b08698ebfdca9 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php @@ -0,0 +1,722 @@ + $stmts + * @param PhpParser\Node\Expr[] $pre_conditions + * @param PhpParser\Node\Expr[] $post_expressions + * + * @return false|null + */ + public static function analyze( + StatementsAnalyzer $statements_analyzer, + array $stmts, + array $pre_conditions, + array $post_expressions, + LoopScope $loop_scope, + Context &$inner_context = null, + bool $is_do = false, + bool $always_enters_loop = false + ): ?bool { + $traverser = new PhpParser\NodeTraverser; + + $assignment_mapper = new \Psalm\Internal\PhpVisitor\AssignmentMapVisitor($loop_scope->loop_context->self); + $traverser->addVisitor($assignment_mapper); + + $traverser->traverse(array_merge($pre_conditions, $stmts, $post_expressions)); + + $assignment_map = $assignment_mapper->getAssignmentMap(); + + $assignment_depth = 0; + + $always_assigned_before_loop_body_vars = []; + + $pre_condition_clauses = []; + + $original_protected_var_ids = $loop_scope->loop_parent_context->protected_var_ids; + + $codebase = $statements_analyzer->getCodebase(); + + $inner_do_context = null; + + if ($pre_conditions) { + foreach ($pre_conditions as $i => $pre_condition) { + $pre_condition_id = \spl_object_id($pre_condition); + + $pre_condition_clauses[$i] = Algebra::getFormula( + $pre_condition_id, + $pre_condition_id, + $pre_condition, + $loop_scope->loop_context->self, + $statements_analyzer, + $codebase + ); + } + } else { + $always_assigned_before_loop_body_vars = Context::getNewOrUpdatedVarIds( + $loop_scope->loop_parent_context, + $loop_scope->loop_context + ); + } + + $final_actions = ScopeAnalyzer::getControlActions( + $stmts, + $statements_analyzer->node_data, + Config::getInstance()->exit_functions, + $loop_scope->loop_context->break_types + ); + + $does_always_break = $final_actions === [ScopeAnalyzer::ACTION_BREAK]; + + $has_continue = in_array(ScopeAnalyzer::ACTION_CONTINUE, $final_actions, true); + + if ($assignment_map) { + $first_var_id = array_keys($assignment_map)[0]; + + $assignment_depth = self::getAssignmentMapDepth($first_var_id, $assignment_map); + } + + if ($has_continue) { + // this intuuitively feels right to me – if there's a continue statement, + // maybe more assignment intrigue is possible + $assignment_depth++; + } + + $loop_scope->loop_context->parent_context = $loop_scope->loop_parent_context; + + $pre_outer_context = $loop_scope->loop_parent_context; + + if ($assignment_depth === 0 || $does_always_break) { + $inner_context = clone $loop_scope->loop_context; + + foreach ($inner_context->vars_in_scope as $context_var_id => $context_type) { + $inner_context->vars_in_scope[$context_var_id] = clone $context_type; + } + + $inner_context->loop_scope = $loop_scope; + + $inner_context->parent_context = $loop_scope->loop_context; + $old_referenced_var_ids = $inner_context->referenced_var_ids; + $inner_context->referenced_var_ids = []; + + foreach ($pre_conditions as $condition_offset => $pre_condition) { + self::applyPreConditionToLoopContext( + $statements_analyzer, + $pre_condition, + $pre_condition_clauses[$condition_offset], + $inner_context, + $loop_scope->loop_parent_context, + $is_do + ); + } + + $inner_context->protected_var_ids = $loop_scope->protected_var_ids; + + $statements_analyzer->analyze($stmts, $inner_context); + self::updateLoopScopeContexts($loop_scope, $loop_scope->loop_parent_context); + + foreach ($post_expressions as $post_expression) { + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $post_expression, + $loop_scope->loop_context + ) === false + ) { + return false; + } + } + + $inner_context->referenced_var_ids = $old_referenced_var_ids + $inner_context->referenced_var_ids; + + $loop_scope->loop_parent_context->vars_possibly_in_scope = array_merge( + $inner_context->vars_possibly_in_scope, + $loop_scope->loop_parent_context->vars_possibly_in_scope + ); + } else { + $pre_outer_context = clone $loop_scope->loop_parent_context; + + $analyzer = $statements_analyzer->getCodebase()->analyzer; + + $original_mixed_counts = $analyzer->getMixedCountsForFile($statements_analyzer->getFilePath()); + + $pre_condition_vars_in_scope = $loop_scope->loop_context->vars_in_scope; + + IssueBuffer::startRecording(); + + if (!$is_do) { + foreach ($pre_conditions as $condition_offset => $pre_condition) { + self::applyPreConditionToLoopContext( + $statements_analyzer, + $pre_condition, + $pre_condition_clauses[$condition_offset], + $loop_scope->loop_context, + $loop_scope->loop_parent_context, + $is_do + ); + } + } + + // record all the vars that existed before we did the first pass through the loop + $pre_loop_context = clone $loop_scope->loop_context; + + $inner_context = clone $loop_scope->loop_context; + + foreach ($inner_context->vars_in_scope as $context_var_id => $context_type) { + $inner_context->vars_in_scope[$context_var_id] = clone $context_type; + } + + $inner_context->parent_context = $loop_scope->loop_context; + $inner_context->loop_scope = $loop_scope; + + $old_referenced_var_ids = $inner_context->referenced_var_ids; + $inner_context->referenced_var_ids = []; + + $inner_context->protected_var_ids = $loop_scope->protected_var_ids; + + $statements_analyzer->analyze($stmts, $inner_context); + + self::updateLoopScopeContexts($loop_scope, $pre_outer_context); + + $inner_context->protected_var_ids = $original_protected_var_ids; + + if ($is_do) { + $inner_do_context = clone $inner_context; + + foreach ($pre_conditions as $condition_offset => $pre_condition) { + $always_assigned_before_loop_body_vars = array_merge( + self::applyPreConditionToLoopContext( + $statements_analyzer, + $pre_condition, + $pre_condition_clauses[$condition_offset], + $inner_context, + $loop_scope->loop_parent_context, + $is_do + ), + $always_assigned_before_loop_body_vars + ); + } + } + + $always_assigned_before_loop_body_vars = array_unique($always_assigned_before_loop_body_vars); + + foreach ($post_expressions as $post_expression) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $post_expression, $inner_context) === false) { + return false; + } + } + + $inner_context->referenced_var_ids = array_intersect_key( + $old_referenced_var_ids, + $inner_context->referenced_var_ids + ); + + $recorded_issues = IssueBuffer::clearRecordingLevel(); + IssueBuffer::stopRecording(); + + for ($i = 0; $i < $assignment_depth; ++$i) { + $vars_to_remove = []; + + $loop_scope->iteration_count++; + + $has_changes = false; + + // reset the $inner_context to what it was before we started the analysis, + // but union the types with what's in the loop scope + + foreach ($inner_context->vars_in_scope as $var_id => $type) { + if (in_array($var_id, $always_assigned_before_loop_body_vars, true)) { + // set the vars to whatever the while/foreach loop expects them to be + if (!isset($pre_loop_context->vars_in_scope[$var_id]) + || !$type->equals($pre_loop_context->vars_in_scope[$var_id]) + ) { + $has_changes = true; + } + } elseif (isset($pre_outer_context->vars_in_scope[$var_id])) { + if (!$type->equals($pre_outer_context->vars_in_scope[$var_id])) { + $has_changes = true; + + // widen the foreach context type with the initial context type + $inner_context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $inner_context->vars_in_scope[$var_id], + $pre_outer_context->vars_in_scope[$var_id] + ); + + // if there's a change, invalidate related clauses + $pre_loop_context->removeVarFromConflictingClauses($var_id); + + $loop_scope->loop_parent_context->possibly_assigned_var_ids[$var_id] = true; + } + + if (isset($loop_scope->loop_context->vars_in_scope[$var_id]) + && !$type->equals($loop_scope->loop_context->vars_in_scope[$var_id]) + ) { + $has_changes = true; + + // widen the foreach context type with the initial context type + $inner_context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $inner_context->vars_in_scope[$var_id], + $loop_scope->loop_context->vars_in_scope[$var_id] + ); + + // if there's a change, invalidate related clauses + $pre_loop_context->removeVarFromConflictingClauses($var_id); + } + } else { + // give an opportunity to redeemed UndefinedVariable issues + if ($recorded_issues) { + $has_changes = true; + } + + // if we're in a do block we don't want to remove vars before evaluating + // the where conditional + if (!$is_do) { + $vars_to_remove[] = $var_id; + } + } + } + + $inner_context->has_returned = false; + + $loop_scope->loop_parent_context->vars_possibly_in_scope = array_merge( + $inner_context->vars_possibly_in_scope, + $loop_scope->loop_parent_context->vars_possibly_in_scope + ); + + // if there are no changes to the types, no need to re-examine + if (!$has_changes) { + break; + } + + // remove vars that were defined in the foreach + foreach ($vars_to_remove as $var_id) { + unset($inner_context->vars_in_scope[$var_id]); + } + + $inner_context->clauses = $pre_loop_context->clauses; + + $analyzer->setMixedCountsForFile($statements_analyzer->getFilePath(), $original_mixed_counts); + IssueBuffer::startRecording(); + + foreach ($pre_loop_context->vars_in_scope as $var_id => $_) { + if (!isset($pre_condition_vars_in_scope[$var_id]) + && isset($inner_context->vars_in_scope[$var_id]) + && \strpos($var_id, '->') === false + && \strpos($var_id, '[') === false + ) { + $inner_context->vars_in_scope[$var_id]->possibly_undefined = true; + } + } + + if (!$is_do) { + foreach ($pre_conditions as $condition_offset => $pre_condition) { + self::applyPreConditionToLoopContext( + $statements_analyzer, + $pre_condition, + $pre_condition_clauses[$condition_offset], + $inner_context, + $loop_scope->loop_parent_context, + false + ); + } + } + + foreach ($always_assigned_before_loop_body_vars as $var_id) { + if ((!isset($inner_context->vars_in_scope[$var_id]) + || $inner_context->vars_in_scope[$var_id]->getId() + !== $pre_loop_context->vars_in_scope[$var_id]->getId() + || $inner_context->vars_in_scope[$var_id]->from_docblock + !== $pre_loop_context->vars_in_scope[$var_id]->from_docblock + ) + ) { + if (isset($pre_loop_context->vars_in_scope[$var_id])) { + $inner_context->vars_in_scope[$var_id] = clone $pre_loop_context->vars_in_scope[$var_id]; + } else { + unset($inner_context->vars_in_scope[$var_id]); + } + } + } + + $inner_context->clauses = $pre_loop_context->clauses; + + $inner_context->protected_var_ids = $loop_scope->protected_var_ids; + + $traverser = new PhpParser\NodeTraverser; + + $traverser->addVisitor( + new \Psalm\Internal\PhpVisitor\NodeCleanerVisitor( + $statements_analyzer->node_data + ) + ); + $traverser->traverse($stmts); + + $statements_analyzer->analyze($stmts, $inner_context); + + self::updateLoopScopeContexts($loop_scope, $pre_outer_context); + + $inner_context->protected_var_ids = $original_protected_var_ids; + + if ($is_do) { + $inner_do_context = clone $inner_context; + + foreach ($pre_conditions as $condition_offset => $pre_condition) { + self::applyPreConditionToLoopContext( + $statements_analyzer, + $pre_condition, + $pre_condition_clauses[$condition_offset], + $inner_context, + $loop_scope->loop_parent_context, + $is_do + ); + } + } + + foreach ($post_expressions as $post_expression) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $post_expression, $inner_context) === false) { + return false; + } + } + + $recorded_issues = IssueBuffer::clearRecordingLevel(); + + IssueBuffer::stopRecording(); + } + + if ($recorded_issues) { + foreach ($recorded_issues as $recorded_issue) { + // if we're not in any loops then this will just result in the issue being emitted + IssueBuffer::bubbleUp($recorded_issue); + } + } + } + + $does_sometimes_break = in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true); + $does_always_break = $loop_scope->final_actions === [ScopeAnalyzer::ACTION_BREAK]; + + if ($does_sometimes_break) { + if ($loop_scope->possibly_redefined_loop_parent_vars !== null) { + foreach ($loop_scope->possibly_redefined_loop_parent_vars as $var => $type) { + $loop_scope->loop_parent_context->vars_in_scope[$var] = Type::combineUnionTypes( + $type, + $loop_scope->loop_parent_context->vars_in_scope[$var] + ); + + $loop_scope->loop_parent_context->possibly_assigned_var_ids[$var] = true; + } + } + } + + foreach ($loop_scope->loop_parent_context->vars_in_scope as $var_id => $type) { + if (!isset($loop_scope->loop_context->vars_in_scope[$var_id])) { + continue; + } + + if ($loop_scope->loop_context->vars_in_scope[$var_id]->getId() !== $type->getId()) { + $loop_scope->loop_parent_context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $loop_scope->loop_parent_context->vars_in_scope[$var_id], + $loop_scope->loop_context->vars_in_scope[$var_id] + ); + + $loop_scope->loop_parent_context->removeVarFromConflictingClauses($var_id); + } else { + $loop_scope->loop_parent_context->vars_in_scope[$var_id]->parent_nodes + += $loop_scope->loop_context->vars_in_scope[$var_id]->parent_nodes; + } + } + + if (!$does_always_break) { + foreach ($loop_scope->loop_parent_context->vars_in_scope as $var_id => $type) { + if (!isset($inner_context->vars_in_scope[$var_id])) { + unset($loop_scope->loop_parent_context->vars_in_scope[$var_id]); + continue; + } + + if ($inner_context->vars_in_scope[$var_id]->hasMixed()) { + $inner_context->vars_in_scope[$var_id]->parent_nodes + += $loop_scope->loop_parent_context->vars_in_scope[$var_id]->parent_nodes; + + $loop_scope->loop_parent_context->vars_in_scope[$var_id] = + $inner_context->vars_in_scope[$var_id]; + $loop_scope->loop_parent_context->removeVarFromConflictingClauses($var_id); + + continue; + } + + if ($inner_context->vars_in_scope[$var_id]->getId() !== $type->getId()) { + $loop_scope->loop_parent_context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $loop_scope->loop_parent_context->vars_in_scope[$var_id], + $inner_context->vars_in_scope[$var_id] + ); + + $loop_scope->loop_parent_context->removeVarFromConflictingClauses($var_id); + } else { + $loop_scope->loop_parent_context->vars_in_scope[$var_id]->parent_nodes = array_merge( + $loop_scope->loop_parent_context->vars_in_scope[$var_id]->parent_nodes, + $inner_context->vars_in_scope[$var_id]->parent_nodes + ); + } + } + } + + if ($pre_conditions && $pre_condition_clauses && !ScopeAnalyzer::doesEverBreak($stmts)) { + // if the loop contains an assertion and there are no break statements, we can negate that assertion + // and apply it to the current context + + try { + $negated_pre_condition_clauses = Algebra::negateFormula(array_merge(...$pre_condition_clauses)); + } catch (\Psalm\Exception\ComplicatedExpressionException $e) { + $negated_pre_condition_clauses = []; + } + + $negated_pre_condition_types = Algebra::getTruthsFromFormula($negated_pre_condition_clauses); + + if ($negated_pre_condition_types) { + $changed_var_ids = []; + + $vars_in_scope_reconciled = Reconciler::reconcileKeyedTypes( + $negated_pre_condition_types, + [], + $inner_context->vars_in_scope, + $changed_var_ids, + [], + $statements_analyzer, + [], + true, + new CodeLocation($statements_analyzer->getSource(), $pre_conditions[0]) + ); + + foreach ($changed_var_ids as $var_id => $_) { + if (isset($vars_in_scope_reconciled[$var_id]) + && isset($loop_scope->loop_parent_context->vars_in_scope[$var_id]) + ) { + $loop_scope->loop_parent_context->vars_in_scope[$var_id] = $vars_in_scope_reconciled[$var_id]; + } + + $loop_scope->loop_parent_context->removeVarFromConflictingClauses($var_id); + } + } + } + + $loop_scope->loop_context->referenced_var_ids = array_merge( + array_intersect_key( + $inner_context->referenced_var_ids, + $pre_outer_context->vars_in_scope + ), + $loop_scope->loop_context->referenced_var_ids + ); + + if ($always_enters_loop) { + foreach ($inner_context->vars_in_scope as $var_id => $type) { + // if there are break statements in the loop it's not certain + // that the loop has finished executing, so the assertions at the end + // the loop in the while conditional may not hold + if (in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true) + || in_array(ScopeAnalyzer::ACTION_CONTINUE, $loop_scope->final_actions, true) + ) { + if (isset($loop_scope->possibly_defined_loop_parent_vars[$var_id])) { + $loop_scope->loop_parent_context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $type, + $loop_scope->possibly_defined_loop_parent_vars[$var_id] + ); + } + } else { + $loop_scope->loop_parent_context->vars_in_scope[$var_id] = $type; + } + } + } + + if ($inner_do_context) { + $inner_context = $inner_do_context; + } + + return null; + } + + private static function updateLoopScopeContexts( + LoopScope $loop_scope, + Context $pre_outer_context + ): void { + $updated_loop_vars = []; + + if (!in_array(ScopeAnalyzer::ACTION_CONTINUE, $loop_scope->final_actions, true)) { + $loop_scope->loop_context->vars_in_scope = $pre_outer_context->vars_in_scope; + } else { + if ($loop_scope->redefined_loop_vars !== null) { + foreach ($loop_scope->redefined_loop_vars as $var => $type) { + $loop_scope->loop_context->vars_in_scope[$var] = $type; + $updated_loop_vars[$var] = true; + } + } + + if ($loop_scope->possibly_redefined_loop_vars) { + foreach ($loop_scope->possibly_redefined_loop_vars as $var => $type) { + if ($loop_scope->loop_context->hasVariable($var)) { + if (!isset($updated_loop_vars[$var])) { + $loop_scope->loop_context->vars_in_scope[$var] = Type::combineUnionTypes( + $loop_scope->loop_context->vars_in_scope[$var], + $type + ); + } else { + $loop_scope->loop_context->vars_in_scope[$var]->parent_nodes + += $type->parent_nodes; + } + } + } + } + } + + // merge vars possibly in scope at the end of each loop + $loop_scope->loop_context->vars_possibly_in_scope = array_merge( + $loop_scope->loop_context->vars_possibly_in_scope, + $loop_scope->vars_possibly_in_scope + ); + } + + /** + * @param list $pre_condition_clauses + * + * @return list + */ + private static function applyPreConditionToLoopContext( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr $pre_condition, + array $pre_condition_clauses, + Context $loop_context, + Context $outer_context, + bool $is_do + ): array { + $pre_referenced_var_ids = $loop_context->referenced_var_ids; + $loop_context->referenced_var_ids = []; + + $loop_context->inside_conditional = true; + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('RedundantCondition', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['RedundantCondition']); + } + if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['RedundantConditionGivenDocblockType']); + } + if (!in_array('TypeDoesNotContainType', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['TypeDoesNotContainType']); + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $pre_condition, $loop_context) === false) { + return []; + } + + $loop_context->inside_conditional = false; + + $new_referenced_var_ids = $loop_context->referenced_var_ids; + $loop_context->referenced_var_ids = array_merge($pre_referenced_var_ids, $new_referenced_var_ids); + + $always_assigned_before_loop_body_vars = Context::getNewOrUpdatedVarIds($outer_context, $loop_context); + + $loop_context->clauses = Algebra::simplifyCNF( + array_merge($outer_context->clauses, $pre_condition_clauses) + ); + + $active_while_types = []; + + $reconcilable_while_types = Algebra::getTruthsFromFormula( + $loop_context->clauses, + \spl_object_id($pre_condition), + $new_referenced_var_ids + ); + + $changed_var_ids = []; + + if ($reconcilable_while_types) { + $pre_condition_vars_in_scope_reconciled = Reconciler::reconcileKeyedTypes( + $reconcilable_while_types, + $active_while_types, + $loop_context->vars_in_scope, + $changed_var_ids, + $new_referenced_var_ids, + $statements_analyzer, + [], + true, + new CodeLocation($statements_analyzer->getSource(), $pre_condition) + ); + + $loop_context->vars_in_scope = $pre_condition_vars_in_scope_reconciled; + } + + if (!in_array('RedundantCondition', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['RedundantCondition']); + } + if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['RedundantConditionGivenDocblockType']); + } + if (!in_array('TypeDoesNotContainType', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['TypeDoesNotContainType']); + } + + if ($is_do) { + return []; + } + + foreach ($always_assigned_before_loop_body_vars as $var_id) { + $loop_context->clauses = Context::filterClauses( + $var_id, + $loop_context->clauses, + null, + $statements_analyzer + ); + } + + return $always_assigned_before_loop_body_vars; + } + + /** + * @param array> $assignment_map + * + */ + private static function getAssignmentMapDepth(string $first_var_id, array $assignment_map): int + { + $max_depth = 0; + + $assignment_var_ids = $assignment_map[$first_var_id]; + unset($assignment_map[$first_var_id]); + + foreach ($assignment_var_ids as $assignment_var_id => $_) { + $depth = 1; + + if (isset($assignment_map[$assignment_var_id])) { + $depth = 1 + self::getAssignmentMapDepth($assignment_var_id, $assignment_map); + } + + if ($depth > $max_depth) { + $max_depth = $depth; + } + } + + return $max_depth; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..5fec920e67eb75d4b2f9bd332e4f1575b25e9640 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php @@ -0,0 +1,214 @@ +getCodebase(); + + $context->inside_conditional = true; + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->cond, $context) === false) { + return false; + } + $context->inside_conditional = false; + + $switch_var_id = ExpressionIdentifier::getArrayVarId( + $stmt->cond, + null, + $statements_analyzer + ); + + if (!$switch_var_id + && ($stmt->cond instanceof PhpParser\Node\Expr\FuncCall + || $stmt->cond instanceof PhpParser\Node\Expr\MethodCall + || $stmt->cond instanceof PhpParser\Node\Expr\StaticCall + ) + ) { + $switch_var_id = '$__tmp_switch__' . (int) $stmt->cond->getAttribute('startFilePos'); + + $condition_type = $statements_analyzer->node_data->getType($stmt->cond) ?: Type::getMixed(); + + $context->vars_in_scope[$switch_var_id] = $condition_type; + } + + $original_context = clone $context; + + // the last statement always breaks, by default + $last_case_exit_type = 'break'; + + $case_exit_types = new \SplFixedArray(count($stmt->cases)); + + $has_default = false; + + $case_action_map = []; + + $config = \Psalm\Config::getInstance(); + + // create a map of case statement -> ultimate exit type + for ($i = count($stmt->cases) - 1; $i >= 0; --$i) { + $case = $stmt->cases[$i]; + + $case_actions = $case_action_map[$i] = ScopeAnalyzer::getControlActions( + $case->stmts, + $statements_analyzer->node_data, + $config->exit_functions, + ['switch'] + ); + + if (!in_array(ScopeAnalyzer::ACTION_NONE, $case_actions, true)) { + if ($case_actions === [ScopeAnalyzer::ACTION_END]) { + $last_case_exit_type = 'return_throw'; + } elseif ($case_actions === [ScopeAnalyzer::ACTION_CONTINUE]) { + $last_case_exit_type = 'continue'; + } elseif (in_array(ScopeAnalyzer::ACTION_LEAVE_SWITCH, $case_actions, true)) { + $last_case_exit_type = 'break'; + } + } + + $case_exit_types[$i] = $last_case_exit_type; + } + + $switch_scope = new SwitchScope(); + + $was_caching_assertions = $statements_analyzer->node_data->cache_assertions; + + $statements_analyzer->node_data->cache_assertions = false; + + for ($i = 0, $l = count($stmt->cases); $i < $l; $i++) { + $case = $stmt->cases[$i]; + + /** @var string */ + $case_exit_type = $case_exit_types[$i]; + + $case_actions = $case_action_map[$i]; + + if (!$case->cond) { + $has_default = true; + } + + if (SwitchCaseAnalyzer::analyze( + $statements_analyzer, + $codebase, + $stmt, + $switch_var_id, + $case, + $context, + $original_context, + $case_exit_type, + $case_actions, + $i === $l - 1, + $switch_scope + ) === false + ) { + return false; + } + } + + $all_options_matched = $has_default; + + if (!$has_default && $switch_scope->negated_clauses && $switch_var_id) { + $entry_clauses = Algebra::simplifyCNF( + array_merge( + $original_context->clauses, + $switch_scope->negated_clauses + ) + ); + + $reconcilable_if_types = Algebra::getTruthsFromFormula($entry_clauses); + + // if the if has an || in the conditional, we cannot easily reason about it + if ($reconcilable_if_types && isset($reconcilable_if_types[$switch_var_id])) { + $changed_var_ids = []; + + $case_vars_in_scope_reconciled = + Reconciler::reconcileKeyedTypes( + $reconcilable_if_types, + [], + $original_context->vars_in_scope, + $changed_var_ids, + [], + $statements_analyzer, + [], + $original_context->inside_loop + ); + + if (isset($case_vars_in_scope_reconciled[$switch_var_id]) + && $case_vars_in_scope_reconciled[$switch_var_id]->isEmpty() + ) { + $all_options_matched = true; + } + } + } + + if ($was_caching_assertions) { + $statements_analyzer->node_data->cache_assertions = true; + } + + // only update vars if there is a default or all possible cases accounted for + // if the default has a throw/return/continue, that should be handled above + if ($all_options_matched) { + if ($switch_scope->new_vars_in_scope) { + $context->vars_in_scope = array_merge($context->vars_in_scope, $switch_scope->new_vars_in_scope); + } + + if ($switch_scope->redefined_vars) { + $context->vars_in_scope = array_merge($context->vars_in_scope, $switch_scope->redefined_vars); + } + + if ($switch_scope->possibly_redefined_vars) { + foreach ($switch_scope->possibly_redefined_vars as $var_id => $type) { + if (!isset($switch_scope->redefined_vars[$var_id]) + && !isset($switch_scope->new_vars_in_scope[$var_id]) + ) { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $type, + $context->vars_in_scope[$var_id] + ); + } + } + } + + /** @psalm-suppress UndefinedPropertyAssignment */ + $stmt->allMatched = true; + } elseif ($switch_scope->possibly_redefined_vars) { + foreach ($switch_scope->possibly_redefined_vars as $var_id => $type) { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes($type, $context->vars_in_scope[$var_id]); + } + } + + if ($switch_scope->new_assigned_var_ids) { + $context->assigned_var_ids += $switch_scope->new_assigned_var_ids; + } + + $context->vars_possibly_in_scope = array_merge( + $context->vars_possibly_in_scope, + $switch_scope->new_vars_possibly_in_scope + ); + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..8de860e2334a05f514485de09fea60824bf3194f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php @@ -0,0 +1,713 @@ +alter_code) { + $case_context->branch_point = $case_context->branch_point ?: (int) $stmt->getAttribute('startFilePos'); + } + + $case_context->parent_context = $context; + $case_scope = $case_context->case_scope = new CaseScope($case_context); + + $case_equality_expr = null; + + $old_node_data = $statements_analyzer->node_data; + + $fake_switch_condition = false; + + if ($switch_var_id && substr($switch_var_id, 0, 15) === '$__tmp_switch__') { + $switch_condition = new PhpParser\Node\Expr\Variable( + substr($switch_var_id, 1), + $stmt->cond->getAttributes() + ); + + $fake_switch_condition = true; + } else { + $switch_condition = $stmt->cond; + } + + if ($case->cond) { + $was_inside_conditional = $case_context->inside_conditional; + $case_context->inside_conditional = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $case->cond, $case_context) === false) { + /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ + $case_scope->parent_context = null; + $case_context->case_scope = null; + $case_context->parent_context = null; + + return false; + } + + if (!$was_inside_conditional) { + $case_context->inside_conditional = false; + } + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $traverser = new PhpParser\NodeTraverser; + $traverser->addVisitor( + new \Psalm\Internal\PhpVisitor\ConditionCloningVisitor( + $statements_analyzer->node_data + ) + ); + + /** @var PhpParser\Node\Expr */ + $switch_condition = $traverser->traverse([$switch_condition])[0]; + + if ($fake_switch_condition) { + $statements_analyzer->node_data->setType( + $switch_condition, + $case_context->vars_in_scope[$switch_var_id] ?? Type::getMixed() + ); + } + + if ($switch_condition instanceof PhpParser\Node\Expr\Variable + && is_string($switch_condition->name) + && isset($context->vars_in_scope['$' . $switch_condition->name]) + ) { + $switch_var_type = $context->vars_in_scope['$' . $switch_condition->name]; + + $type_statements = []; + + foreach ($switch_var_type->getAtomicTypes() as $type) { + if ($type instanceof Type\Atomic\TDependentGetClass) { + $type_statements[] = new PhpParser\Node\Expr\FuncCall( + new PhpParser\Node\Name(['get_class']), + [ + new PhpParser\Node\Arg( + new PhpParser\Node\Expr\Variable(substr($type->typeof, 1)) + ), + ] + ); + } elseif ($type instanceof Type\Atomic\TDependentGetType) { + $type_statements[] = new PhpParser\Node\Expr\FuncCall( + new PhpParser\Node\Name(['gettype']), + [ + new PhpParser\Node\Arg( + new PhpParser\Node\Expr\Variable(substr($type->typeof, 1)) + ), + ] + ); + } elseif ($type instanceof Type\Atomic\TDependentGetDebugType) { + $type_statements[] = new PhpParser\Node\Expr\FuncCall( + new PhpParser\Node\Name(['get_debug_type']), + [ + new PhpParser\Node\Arg( + new PhpParser\Node\Expr\Variable(substr($type->typeof, 1)) + ), + ] + ); + } else { + $type_statements = null; + break; + } + } + + if ($type_statements && count($type_statements) === 1) { + $switch_condition = $type_statements[0]; + + if ($fake_switch_condition) { + $statements_analyzer->node_data->setType( + $switch_condition, + $case_context->vars_in_scope[$switch_var_id] ?? Type::getMixed() + ); + } + } + } + + if (($switch_condition_type = $statements_analyzer->node_data->getType($switch_condition)) + && ($case_cond_type = $statements_analyzer->node_data->getType($case->cond)) + && (($switch_condition_type->isString() && $case_cond_type->isString()) + || ($switch_condition_type->isInt() && $case_cond_type->isInt()) + || ($switch_condition_type->isFloat() && $case_cond_type->isFloat()) + ) + ) { + $case_equality_expr = new PhpParser\Node\Expr\BinaryOp\Identical( + $switch_condition, + $case->cond, + $case->cond->getAttributes() + ); + } else { + $case_equality_expr = new PhpParser\Node\Expr\BinaryOp\Equal( + $switch_condition, + $case->cond, + $case->cond->getAttributes() + ); + } + } + + $continue_case_equality_expr = false; + + if ($case->stmts) { + $case_stmts = array_merge($switch_scope->leftover_statements, $case->stmts); + } else { + $continue_case_equality_expr = count($switch_scope->leftover_statements) === 1; + $case_stmts = $switch_scope->leftover_statements; + } + + if (!$has_leaving_statements && !$is_last) { + if (!$case_equality_expr) { + $case_equality_expr = new PhpParser\Node\Expr\FuncCall( + new PhpParser\Node\Name\FullyQualified(['rand']), + [ + new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(0)), + new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(1)), + ], + $case->getAttributes() + ); + } + + $switch_scope->leftover_case_equality_expr = $switch_scope->leftover_case_equality_expr + ? new PhpParser\Node\Expr\BinaryOp\BooleanOr( + $switch_scope->leftover_case_equality_expr, + $case_equality_expr, + $case->cond ? $case->cond->getAttributes() : $case->getAttributes() + ) + : $case_equality_expr; + + if ($continue_case_equality_expr + && $switch_scope->leftover_statements[0] instanceof PhpParser\Node\Stmt\If_ + ) { + $case_if_stmt = $switch_scope->leftover_statements[0]; + $case_if_stmt->cond = $switch_scope->leftover_case_equality_expr; + } else { + $case_if_stmt = new PhpParser\Node\Stmt\If_( + $switch_scope->leftover_case_equality_expr, + ['stmts' => $case_stmts] + ); + + $switch_scope->leftover_statements = [$case_if_stmt]; + } + + /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ + $case_scope->parent_context = null; + $case_context->case_scope = null; + $case_context->parent_context = null; + + $statements_analyzer->node_data = $old_node_data; + + return null; + } + + if ($switch_scope->leftover_case_equality_expr) { + $case_or_default_equality_expr = $case_equality_expr; + + if (!$case_or_default_equality_expr) { + $case_or_default_equality_expr = new PhpParser\Node\Expr\FuncCall( + new PhpParser\Node\Name\FullyQualified(['rand']), + [ + new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(0)), + new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(1)), + ], + $case->getAttributes() + ); + } + + $case_equality_expr = new PhpParser\Node\Expr\BinaryOp\BooleanOr( + $switch_scope->leftover_case_equality_expr, + $case_or_default_equality_expr, + $case_or_default_equality_expr->getAttributes() + ); + } + + if ($case_equality_expr + && $switch_condition instanceof PhpParser\Node\Expr\Variable + && is_string($switch_condition->name) + && isset($context->vars_in_scope['$' . $switch_condition->name]) + ) { + $new_case_equality_expr = self::simplifyCaseEqualityExpression( + $case_equality_expr, + $switch_condition + ); + + if ($new_case_equality_expr) { + ExpressionAnalyzer::analyze( + $statements_analyzer, + $new_case_equality_expr->args[1]->value, + $case_context + ); + + $case_equality_expr = $new_case_equality_expr; + } + } + + $case_context->break_types[] = 'switch'; + + $switch_scope->leftover_statements = []; + $switch_scope->leftover_case_equality_expr = null; + + $case_clauses = []; + + if ($case_equality_expr) { + $case_equality_expr_id = \spl_object_id($case_equality_expr); + $case_clauses = Algebra::getFormula( + $case_equality_expr_id, + $case_equality_expr_id, + $case_equality_expr, + $context->self, + $statements_analyzer, + $codebase, + false, + false + ); + } + + if ($switch_scope->negated_clauses && count($switch_scope->negated_clauses) < 50) { + $entry_clauses = Algebra::simplifyCNF( + array_merge( + $original_context->clauses, + $switch_scope->negated_clauses + ) + ); + } else { + $entry_clauses = $original_context->clauses; + } + + if ($case_clauses && $case->cond) { + // this will see whether any of the clauses in set A conflict with the clauses in set B + AlgebraAnalyzer::checkForParadox( + $entry_clauses, + $case_clauses, + $statements_analyzer, + $case->cond, + [] + ); + + if (count($entry_clauses) + count($case_clauses) < 50) { + $case_context->clauses = Algebra::simplifyCNF(array_merge($entry_clauses, $case_clauses)); + } else { + $case_context->clauses = array_merge($entry_clauses, $case_clauses); + } + } else { + $case_context->clauses = $entry_clauses; + } + + $reconcilable_if_types = Algebra::getTruthsFromFormula($case_context->clauses); + + // if the if has an || in the conditional, we cannot easily reason about it + if ($reconcilable_if_types) { + $changed_var_ids = []; + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('RedundantCondition', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['RedundantCondition']); + } + + if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['RedundantConditionGivenDocblockType']); + } + + $case_vars_in_scope_reconciled = + Reconciler::reconcileKeyedTypes( + $reconcilable_if_types, + [], + $case_context->vars_in_scope, + $changed_var_ids, + $case->cond && $switch_var_id ? [$switch_var_id => true] : [], + $statements_analyzer, + [], + $case_context->inside_loop, + new CodeLocation( + $statements_analyzer->getSource(), + $case->cond ? $case->cond : $case, + $context->include_location + ) + ); + + if (!in_array('RedundantCondition', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['RedundantCondition']); + } + + if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['RedundantConditionGivenDocblockType']); + } + + $case_context->vars_in_scope = $case_vars_in_scope_reconciled; + foreach ($reconcilable_if_types as $var_id => $_) { + $case_context->vars_possibly_in_scope[$var_id] = true; + } + + if ($changed_var_ids) { + $case_context->clauses = Context::removeReconciledClauses($case_context->clauses, $changed_var_ids)[0]; + } + } + + if ($case_clauses && $case_equality_expr) { + try { + $negated_case_clauses = Algebra::negateFormula($case_clauses); + } catch (\Psalm\Exception\ComplicatedExpressionException $e) { + $case_equality_expr_id = \spl_object_id($case_equality_expr); + + try { + $negated_case_clauses = Algebra::getFormula( + $case_equality_expr_id, + $case_equality_expr_id, + new PhpParser\Node\Expr\BooleanNot($case_equality_expr), + $context->self, + $statements_analyzer, + $codebase, + false, + false + ); + } catch (\Psalm\Exception\ComplicatedExpressionException $e) { + $negated_case_clauses = []; + } + } + + $switch_scope->negated_clauses = array_merge( + $switch_scope->negated_clauses, + $negated_case_clauses + ); + } + + $pre_possibly_assigned_var_ids = $case_context->possibly_assigned_var_ids; + $case_context->possibly_assigned_var_ids = []; + + $pre_assigned_var_ids = $case_context->assigned_var_ids; + $case_context->assigned_var_ids = []; + + $statements_analyzer->analyze($case_stmts, $case_context); + + $traverser = new PhpParser\NodeTraverser; + $traverser->addVisitor( + new \Psalm\Internal\PhpVisitor\TypeMappingVisitor( + $statements_analyzer->node_data, + $old_node_data + ) + ); + + $traverser->traverse([$case]); + + $statements_analyzer->node_data = $old_node_data; + + /** @var array */ + $new_case_assigned_var_ids = $case_context->assigned_var_ids; + $case_context->assigned_var_ids = $pre_assigned_var_ids + $new_case_assigned_var_ids; + + /** @var array */ + $new_case_possibly_assigned_var_ids = $case_context->possibly_assigned_var_ids; + $case_context->possibly_assigned_var_ids = + $pre_possibly_assigned_var_ids + $new_case_possibly_assigned_var_ids; + + $context->referenced_var_ids = array_merge( + $context->referenced_var_ids, + $case_context->referenced_var_ids + ); + + if ($case_exit_type !== 'return_throw') { + if (self::handleNonReturningCase( + $statements_analyzer, + $switch_var_id, + $case, + $context, + $case_context, + $original_context, + $case_exit_type, + $switch_scope + ) === false) { + /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ + $case_scope->parent_context = null; + $case_context->case_scope = null; + $case_context->parent_context = null; + + return false; + } + } + + // augment the information with data from break statements + if ($case_scope->break_vars !== null) { + if ($switch_scope->possibly_redefined_vars === null) { + $switch_scope->possibly_redefined_vars = array_intersect_key( + $case_scope->break_vars, + $context->vars_in_scope + ); + } else { + foreach ($case_scope->break_vars as $var_id => $type) { + if (isset($context->vars_in_scope[$var_id])) { + if (!isset($switch_scope->possibly_redefined_vars[$var_id])) { + $switch_scope->possibly_redefined_vars[$var_id] = clone $type; + } else { + $switch_scope->possibly_redefined_vars[$var_id] = Type::combineUnionTypes( + clone $type, + $switch_scope->possibly_redefined_vars[$var_id] + ); + } + } + } + } + + if ($switch_scope->new_vars_in_scope !== null) { + foreach ($switch_scope->new_vars_in_scope as $var_id => $type) { + if (isset($case_scope->break_vars[$var_id])) { + if (!isset($case_context->vars_in_scope[$var_id])) { + unset($switch_scope->new_vars_in_scope[$var_id]); + } else { + $switch_scope->new_vars_in_scope[$var_id] = Type::combineUnionTypes( + clone $case_scope->break_vars[$var_id], + $type + ); + } + } else { + unset($switch_scope->new_vars_in_scope[$var_id]); + } + } + } + + if ($switch_scope->redefined_vars !== null) { + foreach ($switch_scope->redefined_vars as $var_id => $type) { + if (isset($case_scope->break_vars[$var_id])) { + $switch_scope->redefined_vars[$var_id] = Type::combineUnionTypes( + clone $case_scope->break_vars[$var_id], + $type + ); + } else { + unset($switch_scope->redefined_vars[$var_id]); + } + } + } + } + + /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ + $case_scope->parent_context = null; + $case_context->case_scope = null; + $case_context->parent_context = null; + + return null; + } + + /** + * @param array $new_case_assigned_var_ids + * @param array $new_case_possibly_assigned_var_ids + * @return null|false + */ + private static function handleNonReturningCase( + StatementsAnalyzer $statements_analyzer, + ?string $switch_var_id, + PhpParser\Node\Stmt\Case_ $case, + Context $context, + Context $case_context, + Context $original_context, + string $case_exit_type, + SwitchScope $switch_scope + ): ?bool { + if (!$case->cond + && $switch_var_id + && isset($case_context->vars_in_scope[$switch_var_id]) + && $case_context->vars_in_scope[$switch_var_id]->isEmpty() + ) { + if (IssueBuffer::accepts( + new ParadoxicalCondition( + 'All possible case statements have been met, default is impossible here', + new CodeLocation($statements_analyzer->getSource(), $case) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + + // if we're leaving this block, add vars to outer for loop scope + if ($case_exit_type === 'continue') { + if (!$context->loop_scope) { + if (IssueBuffer::accepts( + new ContinueOutsideLoop( + 'Continue called when not in loop', + new CodeLocation($statements_analyzer->getSource(), $case) + ) + )) { + return false; + } + } + } else { + $case_redefined_vars = $case_context->getRedefinedVars($original_context->vars_in_scope); + + if ($switch_scope->possibly_redefined_vars === null) { + $switch_scope->possibly_redefined_vars = $case_redefined_vars; + } else { + foreach ($case_redefined_vars as $var_id => $type) { + if (!isset($switch_scope->possibly_redefined_vars[$var_id])) { + $switch_scope->possibly_redefined_vars[$var_id] = clone $type; + } else { + $switch_scope->possibly_redefined_vars[$var_id] = Type::combineUnionTypes( + clone $type, + $switch_scope->possibly_redefined_vars[$var_id] + ); + } + } + } + + if ($switch_scope->redefined_vars === null) { + $switch_scope->redefined_vars = $case_redefined_vars; + } else { + foreach ($switch_scope->redefined_vars as $var_id => $type) { + if (!isset($case_redefined_vars[$var_id])) { + unset($switch_scope->redefined_vars[$var_id]); + } else { + $switch_scope->redefined_vars[$var_id] = Type::combineUnionTypes( + $type, + clone $case_redefined_vars[$var_id] + ); + } + } + } + + $context_new_vars = array_diff_key($case_context->vars_in_scope, $context->vars_in_scope); + + if ($switch_scope->new_vars_in_scope === null) { + $switch_scope->new_vars_in_scope = $context_new_vars; + $switch_scope->new_vars_possibly_in_scope = array_diff_key( + $case_context->vars_possibly_in_scope, + $context->vars_possibly_in_scope + ); + } else { + foreach ($switch_scope->new_vars_in_scope as $new_var => $type) { + if (!$case_context->hasVariable($new_var)) { + unset($switch_scope->new_vars_in_scope[$new_var]); + } else { + $switch_scope->new_vars_in_scope[$new_var] = + Type::combineUnionTypes(clone $case_context->vars_in_scope[$new_var], $type); + } + } + + $switch_scope->new_vars_possibly_in_scope = array_merge( + array_diff_key( + $case_context->vars_possibly_in_scope, + $context->vars_possibly_in_scope + ), + $switch_scope->new_vars_possibly_in_scope + ); + } + } + + if ($context->collect_exceptions) { + $context->mergeExceptions($case_context); + } + + return null; + } + + private static function simplifyCaseEqualityExpression( + PhpParser\Node\Expr $case_equality_expr, + PhpParser\Node\Expr\Variable $var + ) : ?PhpParser\Node\Expr\FuncCall { + if ($case_equality_expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) { + $nested_or_options = self::getOptionsFromNestedOr($case_equality_expr, $var); + + if ($nested_or_options) { + return new PhpParser\Node\Expr\FuncCall( + new PhpParser\Node\Name\FullyQualified(['in_array']), + [ + new PhpParser\Node\Arg( + $var + ), + new PhpParser\Node\Arg( + new PhpParser\Node\Expr\Array_( + $nested_or_options + ) + ), + new PhpParser\Node\Arg( + new PhpParser\Node\Expr\ConstFetch( + new PhpParser\Node\Name\FullyQualified(['true']) + ) + ), + ] + ); + } + } + + return null; + } + + /** + * @param array $in_array_values + * @return ?array + */ + private static function getOptionsFromNestedOr( + PhpParser\Node\Expr $case_equality_expr, + PhpParser\Node\Expr\Variable $var, + array $in_array_values = [] + ) : ?array { + if ($case_equality_expr instanceof PhpParser\Node\Expr\BinaryOp\Identical + && $case_equality_expr->left instanceof PhpParser\Node\Expr\Variable + && $case_equality_expr->left->name === $var->name + ) { + $in_array_values[] = new PhpParser\Node\Expr\ArrayItem( + $case_equality_expr->right + ); + + return $in_array_values; + } + + if (!$case_equality_expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) { + return null; + } + + if (!$case_equality_expr->right instanceof PhpParser\Node\Expr\BinaryOp\Identical + || !$case_equality_expr->right->left instanceof PhpParser\Node\Expr\Variable + || $case_equality_expr->right->left->name !== $var->name + ) { + return null; + } + + $in_array_values[] = new PhpParser\Node\Expr\ArrayItem($case_equality_expr->right->right); + + return self::getOptionsFromNestedOr( + $case_equality_expr->left, + $var, + $in_array_values + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..945294a8f5207e61e77d961a86d169fdae1d901b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php @@ -0,0 +1,510 @@ +getCodebase(); + + /** @var int $i */ + foreach ($stmt->catches as $i => $catch) { + $catch_actions[$i] = ScopeAnalyzer::getControlActions( + $catch->stmts, + $statements_analyzer->node_data, + $codebase->config->exit_functions + ); + $all_catches_leave = $all_catches_leave && !in_array(ScopeAnalyzer::ACTION_NONE, $catch_actions[$i], true); + } + + $existing_thrown_exceptions = $context->possibly_thrown_exceptions; + + /** + * @var array> + */ + $context->possibly_thrown_exceptions = []; + + $old_context = clone $context; + + if ($all_catches_leave && !$stmt->finally) { + $try_context = $context; + } else { + $try_context = clone $context; + + if ($codebase->alter_code) { + $try_context->branch_point = $try_context->branch_point ?: (int) $stmt->getAttribute('startFilePos'); + } + + if ($stmt->finally) { + $try_context->finally_scope = new FinallyScope($try_context->vars_in_scope); + } + } + + $assigned_var_ids = $try_context->assigned_var_ids; + $context->assigned_var_ids = []; + + $old_referenced_var_ids = $try_context->referenced_var_ids; + + if ($statements_analyzer->analyze($stmt->stmts, $context) === false) { + return false; + } + + if ($try_context->finally_scope) { + foreach ($context->vars_in_scope as $var_id => $type) { + if (isset($try_context->finally_scope->vars_in_scope[$var_id])) { + if ($try_context->finally_scope->vars_in_scope[$var_id] !== $type) { + $try_context->finally_scope->vars_in_scope[$var_id] = Type::combineUnionTypes( + $try_context->finally_scope->vars_in_scope[$var_id], + $type, + $statements_analyzer->getCodebase() + ); + } + } else { + $try_context->finally_scope->vars_in_scope[$var_id] = $type; + } + } + } + + $context->has_returned = false; + + $stmt_control_actions = ScopeAnalyzer::getControlActions( + $stmt->stmts, + $statements_analyzer->node_data, + $codebase->config->exit_functions, + $context->break_types + ); + + /** @var array */ + $newly_assigned_var_ids = $context->assigned_var_ids; + + $context->assigned_var_ids = array_merge( + $assigned_var_ids, + $newly_assigned_var_ids + ); + + $possibly_referenced_var_ids = array_merge( + $context->referenced_var_ids, + $old_referenced_var_ids + ); + + if ($try_context !== $context) { + foreach ($context->vars_in_scope as $var_id => $type) { + if (!isset($try_context->vars_in_scope[$var_id])) { + $try_context->vars_in_scope[$var_id] = clone $type; + + $context->vars_in_scope[$var_id]->possibly_undefined = true; + $context->vars_in_scope[$var_id]->possibly_undefined_from_try = true; + } else { + $try_context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $try_context->vars_in_scope[$var_id], + $type + ); + } + } + + $try_context->vars_possibly_in_scope = $context->vars_possibly_in_scope; + $try_context->possibly_thrown_exceptions = $context->possibly_thrown_exceptions; + + $context->referenced_var_ids = array_intersect_key( + $try_context->referenced_var_ids, + $context->referenced_var_ids + ); + } + + $try_leaves_loop = $context->loop_scope + && $context->loop_scope->final_actions + && !in_array(ScopeAnalyzer::ACTION_NONE, $context->loop_scope->final_actions, true); + + if (!$all_catches_leave) { + foreach ($newly_assigned_var_ids as $assigned_var_id => $_) { + $context->removeVarFromConflictingClauses($assigned_var_id); + } + } else { + foreach ($newly_assigned_var_ids as $assigned_var_id => $_) { + $try_context->removeVarFromConflictingClauses($assigned_var_id); + } + } + + // at this point we have two contexts – $context, in which it is assumed that everything was fine, + // and $try_context - which allows all variables to have the union of the values before and after + // the try was applied + $original_context = clone $try_context; + + $issues_to_suppress = [ + 'RedundantCondition', + 'RedundantConditionGivenDocblockType', + 'TypeDoesNotContainNull', + 'TypeDoesNotContainType', + ]; + + $definitely_newly_assigned_var_ids = $newly_assigned_var_ids; + + /** @var int $i */ + foreach ($stmt->catches as $i => $catch) { + $catch_context = clone $original_context; + $catch_context->has_returned = false; + + foreach ($catch_context->vars_in_scope as $var_id => $type) { + if (!isset($old_context->vars_in_scope[$var_id])) { + $type = clone $type; + $type->possibly_undefined_from_try = true; + $catch_context->vars_in_scope[$var_id] = $type; + } else { + $catch_context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $type, + $old_context->vars_in_scope[$var_id] + ); + } + } + + $fq_catch_classes = []; + + if (!$catch->types) { + throw new \UnexpectedValueException('Very bad'); + } + + foreach ($catch->types as $catch_type) { + $fq_catch_class = ClassLikeAnalyzer::getFQCLNFromNameObject( + $catch_type, + $statements_analyzer->getAliases() + ); + + $fq_catch_class = $codebase->classlikes->getUnAliasedName($fq_catch_class); + + if ($codebase->alter_code && $fq_catch_class) { + $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $statements_analyzer, + $catch_type, + $fq_catch_class, + $context->calling_method_id + ); + } + + if ($original_context->check_classes) { + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $fq_catch_class, + new CodeLocation($statements_analyzer->getSource(), $catch_type, $context->include_location), + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues(), + false + ) === false) { + return false; + } + } + + if (($codebase->classExists($fq_catch_class) + && strtolower($fq_catch_class) !== 'exception' + && !($codebase->classExtends($fq_catch_class, 'Exception') + || $codebase->classImplements($fq_catch_class, 'Throwable'))) + || ($codebase->interfaceExists($fq_catch_class) + && strtolower($fq_catch_class) !== 'throwable' + && !$codebase->interfaceExtends($fq_catch_class, 'Throwable')) + ) { + if (IssueBuffer::accepts( + new InvalidCatch( + 'Class/interface ' . $fq_catch_class . ' cannot be caught', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $fq_catch_class + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + + $fq_catch_classes[] = $fq_catch_class; + } + + if ($catch_context->collect_exceptions) { + foreach ($fq_catch_classes as $fq_catch_class) { + $fq_catch_class_lower = strtolower($fq_catch_class); + + foreach ($catch_context->possibly_thrown_exceptions as $exception_fqcln => $_) { + $exception_fqcln_lower = strtolower($exception_fqcln); + + if ($exception_fqcln_lower === $fq_catch_class_lower + || ($codebase->classExists($exception_fqcln) + && $codebase->classExtendsOrImplements($exception_fqcln, $fq_catch_class)) + || ($codebase->interfaceExists($exception_fqcln) + && $codebase->interfaceExtends($exception_fqcln, $fq_catch_class)) + ) { + unset($original_context->possibly_thrown_exceptions[$exception_fqcln]); + unset($context->possibly_thrown_exceptions[$exception_fqcln]); + unset($catch_context->possibly_thrown_exceptions[$exception_fqcln]); + } + } + } + + /** + * @var array> + */ + $catch_context->possibly_thrown_exceptions = []; + } + + // discard all clauses because crazy stuff may have happened in try block + $catch_context->clauses = []; + + /** @psalm-suppress RedundantConditionGivenDocblockType */ + if ($catch->var && is_string($catch->var->name)) { + $catch_var_id = '$' . $catch->var->name; + + $catch_context->vars_in_scope[$catch_var_id] = new Union( + array_map( + /** + * @param string $fq_catch_class + */ + function ($fq_catch_class) use ($codebase): \Psalm\Type\Atomic\TNamedObject { + $catch_class_type = new TNamedObject($fq_catch_class); + + if (version_compare(PHP_VERSION, '7.0.0dev', '>=') + && strtolower($fq_catch_class) !== 'throwable' + && $codebase->interfaceExists($fq_catch_class) + && !$codebase->interfaceExtends($fq_catch_class, 'Throwable') + ) { + $catch_class_type->addIntersectionType(new TNamedObject('Throwable')); + } + + return $catch_class_type; + }, + $fq_catch_classes + ) + ); + + $catch_context->vars_possibly_in_scope[$catch_var_id] = true; + + $location = new CodeLocation($statements_analyzer->getSource(), $catch->var); + + if (!$statements_analyzer->hasVariable($catch_var_id)) { + $statements_analyzer->registerVariable( + $catch_var_id, + $location, + $catch_context->branch_point + ); + } else { + $statements_analyzer->registerVariableAssignment( + $catch_var_id, + $location + ); + } + + if ($statements_analyzer->data_flow_graph) { + $catch_var_node = DataFlowNode::getForAssignment($catch_var_id, $location); + + $catch_context->vars_in_scope[$catch_var_id]->parent_nodes = [ + $catch_var_node->id => $catch_var_node + ]; + + if ($statements_analyzer->data_flow_graph instanceof \Psalm\Internal\Codebase\VariableUseGraph) { + $statements_analyzer->data_flow_graph->addPath( + $catch_var_node, + new DataFlowNode('variable-use', 'variable use', null), + 'variable-use' + ); + } + } + } + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + foreach ($issues_to_suppress as $issue_to_suppress) { + if (!in_array($issue_to_suppress, $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues([$issue_to_suppress]); + } + } + + $old_catch_assigned_var_ids = $catch_context->referenced_var_ids; + + $catch_context->assigned_var_ids = []; + + $statements_analyzer->analyze($catch->stmts, $catch_context); + + // recalculate in case there's a no-return clause + $catch_actions[$i] = ScopeAnalyzer::getControlActions( + $catch->stmts, + $statements_analyzer->node_data, + $codebase->config->exit_functions, + $context->break_types + ); + + foreach ($issues_to_suppress as $issue_to_suppress) { + if (!in_array($issue_to_suppress, $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues([$issue_to_suppress]); + } + } + + /** @var array */ + $new_catch_assigned_var_ids = $catch_context->assigned_var_ids; + + $catch_context->assigned_var_ids += $old_catch_assigned_var_ids; + + $context->referenced_var_ids = array_intersect_key( + $catch_context->referenced_var_ids, + $context->referenced_var_ids + ); + + $possibly_referenced_var_ids = array_merge( + $catch_context->referenced_var_ids, + $possibly_referenced_var_ids + ); + + if ($codebase->find_unused_variables && $catch_actions[$i] !== [ScopeAnalyzer::ACTION_END]) { + // something + } + + if ($catch_context->collect_exceptions) { + $context->mergeExceptions($catch_context); + } + + if ($catch_actions[$i] !== [ScopeAnalyzer::ACTION_END] + && $catch_actions[$i] !== [ScopeAnalyzer::ACTION_CONTINUE] + && $catch_actions[$i] !== [ScopeAnalyzer::ACTION_BREAK] + ) { + $definitely_newly_assigned_var_ids = array_intersect_key( + $new_catch_assigned_var_ids, + $definitely_newly_assigned_var_ids + ); + + foreach ($catch_context->vars_in_scope as $var_id => $type) { + if ($stmt_control_actions === [ScopeAnalyzer::ACTION_END]) { + $context->vars_in_scope[$var_id] = $type; + } elseif (isset($context->vars_in_scope[$var_id])) { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $context->vars_in_scope[$var_id], + $type + ); + } + } + + $context->vars_possibly_in_scope = array_merge( + $catch_context->vars_possibly_in_scope, + $context->vars_possibly_in_scope + ); + } else { + if ($stmt->finally) { + $context->vars_possibly_in_scope = array_merge( + $catch_context->vars_possibly_in_scope, + $context->vars_possibly_in_scope + ); + } + } + + if ($try_context->finally_scope) { + foreach ($catch_context->vars_in_scope as $var_id => $type) { + if (isset($try_context->finally_scope->vars_in_scope[$var_id])) { + if ($try_context->finally_scope->vars_in_scope[$var_id] !== $type) { + $try_context->finally_scope->vars_in_scope[$var_id] = Type::combineUnionTypes( + $try_context->finally_scope->vars_in_scope[$var_id], + $type, + $statements_analyzer->getCodebase() + ); + } + } else { + $try_context->finally_scope->vars_in_scope[$var_id] = $type; + } + } + } + } + + if ($context->loop_scope + && !$try_leaves_loop + && !in_array(ScopeAnalyzer::ACTION_NONE, $context->loop_scope->final_actions, true) + ) { + $context->loop_scope->final_actions[] = ScopeAnalyzer::ACTION_NONE; + } + + if ($stmt->finally) { + if ($try_context->finally_scope) { + $finally_context = clone $context; + + $finally_context->assigned_var_ids = []; + $finally_context->possibly_assigned_var_ids = []; + + $finally_context->vars_in_scope = $try_context->finally_scope->vars_in_scope; + + $statements_analyzer->analyze($stmt->finally->stmts, $finally_context); + + if ($finally_context->has_returned) { + $context->has_returned = true; + } + + /** @var string $var_id */ + foreach ($finally_context->assigned_var_ids as $var_id => $_) { + if (isset($context->vars_in_scope[$var_id])) { + if ($context->vars_in_scope[$var_id]->possibly_undefined + && $context->vars_in_scope[$var_id]->possibly_undefined_from_try + ) { + $context->vars_in_scope[$var_id]->possibly_undefined = false; + $context->vars_in_scope[$var_id]->possibly_undefined_from_try = false; + } + + $context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $context->vars_in_scope[$var_id], + $finally_context->vars_in_scope[$var_id], + $codebase + ); + } elseif (isset($finally_context->vars_in_scope[$var_id])) { + $context->vars_in_scope[$var_id] = clone $finally_context->vars_in_scope[$var_id]; + } + } + } + } + + foreach ($definitely_newly_assigned_var_ids as $var_id => $_) { + if (isset($context->vars_in_scope[$var_id])) { + $new_type = clone $context->vars_in_scope[$var_id]; + + if ($new_type->possibly_undefined_from_try) { + $new_type->possibly_undefined = false; + $new_type->possibly_undefined_from_try = false; + } + + $context->vars_in_scope[$var_id] = $new_type; + } + } + + foreach ($existing_thrown_exceptions as $possibly_thrown_exception => $codelocations) { + foreach ($codelocations as $hash => $codelocation) { + $context->possibly_thrown_exceptions[$possibly_thrown_exception][$hash] = $codelocation; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/WhileAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/WhileAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..dac196f0ec9552214cba087b42bd9267232addfc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/WhileAnalyzer.php @@ -0,0 +1,162 @@ +cond instanceof PhpParser\Node\Expr\ConstFetch && $stmt->cond->name->parts === ['true']) + || ($stmt->cond instanceof PhpParser\Node\Scalar\LNumber && $stmt->cond->value > 0); + + $pre_context = null; + + if ($while_true) { + $pre_context = clone $context; + } + + $while_context = clone $context; + + $while_context->inside_loop = true; + $while_context->break_types[] = 'loop'; + + $codebase = $statements_analyzer->getCodebase(); + + if ($codebase->alter_code) { + $while_context->branch_point = $while_context->branch_point ?: (int) $stmt->getAttribute('startFilePos'); + } + + $loop_scope = new LoopScope($while_context, $context); + $loop_scope->protected_var_ids = $context->protected_var_ids; + + if (LoopAnalyzer::analyze( + $statements_analyzer, + $stmt->stmts, + self::getAndExpressions($stmt->cond), + [], + $loop_scope, + $inner_loop_context + ) === false) { + return false; + } + + if (!$inner_loop_context) { + throw new \UnexpectedValueException('Should always enter loop'); + } + + $always_enters_loop = false; + + if ($stmt_cond_type = $statements_analyzer->node_data->getType($stmt->cond)) { + $always_enters_loop = true; + + foreach ($stmt_cond_type->getAtomicTypes() as $iterator_type) { + if ($iterator_type instanceof Type\Atomic\TArray + || $iterator_type instanceof Type\Atomic\TKeyedArray + ) { + if ($iterator_type instanceof Type\Atomic\TKeyedArray) { + if (!$iterator_type->sealed) { + $always_enters_loop = false; + } + } elseif (!$iterator_type instanceof Type\Atomic\TNonEmptyArray) { + $always_enters_loop = false; + } + + continue; + } + + if ($iterator_type instanceof Type\Atomic\TTrue) { + continue; + } + + if ($iterator_type instanceof Type\Atomic\TLiteralString + && $iterator_type->value + ) { + continue; + } + + if ($iterator_type instanceof Type\Atomic\TLiteralInt + && $iterator_type->value + ) { + continue; + } + + $always_enters_loop = false; + break; + } + } + + $can_leave_loop = !$while_true + || in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true); + + if ($always_enters_loop && $can_leave_loop) { + foreach ($inner_loop_context->vars_in_scope as $var_id => $type) { + // if there are break statements in the loop it's not certain + // that the loop has finished executing, so the assertions at the end + // the loop in the while conditional may not hold + if (in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true) + || in_array(ScopeAnalyzer::ACTION_CONTINUE, $loop_scope->final_actions, true) + ) { + if (isset($loop_scope->possibly_defined_loop_parent_vars[$var_id])) { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $type, + $loop_scope->possibly_defined_loop_parent_vars[$var_id] + ); + } + } else { + $context->vars_in_scope[$var_id] = $type; + } + } + } + + $while_context->loop_scope = null; + + if ($can_leave_loop) { + $context->vars_possibly_in_scope = array_merge( + $context->vars_possibly_in_scope, + $while_context->vars_possibly_in_scope + ); + } elseif ($pre_context) { + $context->vars_possibly_in_scope = $pre_context->vars_possibly_in_scope; + } + + $context->referenced_var_ids = array_merge( + $context->referenced_var_ids, + $while_context->referenced_var_ids + ); + + return null; + } + + /** + * @return list + */ + private static function getAndExpressions( + PhpParser\Node\Expr $expr + ) : array { + if ($expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd) { + return array_merge( + self::getAndExpressions($expr->left), + self::getAndExpressions($expr->right) + ); + } + + return [$expr]; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..8886260a81d9b8d89fd896d2f88338e53a90b24e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php @@ -0,0 +1,114 @@ +loop_scope; + + $leaving_switch = true; + + if ($loop_scope) { + if ($context->break_types + && \end($context->break_types) === 'switch' + && (!$stmt->num + || !$stmt->num instanceof PhpParser\Node\Scalar\LNumber + || $stmt->num->value < 2 + ) + ) { + $loop_scope->final_actions[] = ScopeAnalyzer::ACTION_LEAVE_SWITCH; + } else { + $leaving_switch = false; + + $loop_scope->final_actions[] = ScopeAnalyzer::ACTION_BREAK; + } + + $redefined_vars = $context->getRedefinedVars($loop_scope->loop_parent_context->vars_in_scope); + + if ($loop_scope->possibly_redefined_loop_parent_vars === null) { + $loop_scope->possibly_redefined_loop_parent_vars = $redefined_vars; + } else { + foreach ($redefined_vars as $var => $type) { + if ($type->hasMixed()) { + if (isset($loop_scope->possibly_redefined_loop_parent_vars[$var])) { + $type->parent_nodes + += $loop_scope->possibly_redefined_loop_parent_vars[$var]->parent_nodes; + } + + $loop_scope->possibly_redefined_loop_parent_vars[$var] = $type; + } elseif (isset($loop_scope->possibly_redefined_loop_parent_vars[$var])) { + $loop_scope->possibly_redefined_loop_parent_vars[$var] = Type::combineUnionTypes( + $type, + $loop_scope->possibly_redefined_loop_parent_vars[$var] + ); + } else { + $loop_scope->possibly_redefined_loop_parent_vars[$var] = $type; + } + } + } + + if ($loop_scope->iteration_count === 0) { + foreach ($context->vars_in_scope as $var_id => $type) { + if (!isset($loop_scope->loop_parent_context->vars_in_scope[$var_id])) { + if (isset($loop_scope->possibly_defined_loop_parent_vars[$var_id])) { + $loop_scope->possibly_defined_loop_parent_vars[$var_id] = Type::combineUnionTypes( + $type, + $loop_scope->possibly_defined_loop_parent_vars[$var_id] + ); + } else { + $loop_scope->possibly_defined_loop_parent_vars[$var_id] = $type; + } + } + } + } + + if ($context->finally_scope) { + foreach ($context->vars_in_scope as $var_id => $type) { + if (isset($context->finally_scope->vars_in_scope[$var_id])) { + if ($context->finally_scope->vars_in_scope[$var_id] !== $type) { + $context->finally_scope->vars_in_scope[$var_id] = Type::combineUnionTypes( + $context->finally_scope->vars_in_scope[$var_id], + $type, + $statements_analyzer->getCodebase() + ); + } + } else { + $context->finally_scope->vars_in_scope[$var_id] = $type; + } + } + } + } + + $case_scope = $context->case_scope; + if ($case_scope && $leaving_switch) { + foreach ($context->vars_in_scope as $var_id => $type) { + if ($case_scope->parent_context !== $context) { + if ($case_scope->break_vars === null) { + $case_scope->break_vars = []; + } + + if (isset($case_scope->break_vars[$var_id])) { + $case_scope->break_vars[$var_id] = Type::combineUnionTypes( + $type, + $case_scope->break_vars[$var_id] + ); + } else { + $case_scope->break_vars[$var_id] = $type; + } + } + } + } + + $context->has_returned = true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..9ea4f004ae3a3bab77e3e35578bfc0663914cc8d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php @@ -0,0 +1,105 @@ +loop_scope; + + if ($loop_scope === null) { + if (!$context->break_types) { + if (IssueBuffer::accepts( + new ContinueOutsideLoop( + 'Continue call outside loop context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSource()->getSuppressedIssues() + )) { + return false; + } + } + } else { + if ($context->break_types + && \end($context->break_types) === 'switch' + && (!$stmt->num + || !$stmt->num instanceof PhpParser\Node\Scalar\LNumber + || $stmt->num->value < 2 + ) + ) { + $loop_scope->final_actions[] = ScopeAnalyzer::ACTION_LEAVE_SWITCH; + } else { + $loop_scope->final_actions[] = ScopeAnalyzer::ACTION_CONTINUE; + } + + $redefined_vars = $context->getRedefinedVars($loop_scope->loop_parent_context->vars_in_scope); + + if ($loop_scope->redefined_loop_vars === null) { + $loop_scope->redefined_loop_vars = $redefined_vars; + } else { + foreach ($loop_scope->redefined_loop_vars as $redefined_var => $type) { + if (!isset($redefined_vars[$redefined_var])) { + unset($loop_scope->redefined_loop_vars[$redefined_var]); + } else { + $loop_scope->redefined_loop_vars[$redefined_var] = Type::combineUnionTypes( + $redefined_vars[$redefined_var], + $type + ); + } + } + } + + foreach ($redefined_vars as $var => $type) { + if ($type->hasMixed()) { + if (isset($loop_scope->possibly_redefined_loop_vars[$var])) { + $type->parent_nodes += $loop_scope->possibly_redefined_loop_vars[$var]->parent_nodes; + } + + $loop_scope->possibly_redefined_loop_vars[$var] = $type; + } elseif (isset($loop_scope->possibly_redefined_loop_vars[$var])) { + $loop_scope->possibly_redefined_loop_vars[$var] = Type::combineUnionTypes( + $type, + $loop_scope->possibly_redefined_loop_vars[$var] + ); + } else { + $loop_scope->possibly_redefined_loop_vars[$var] = $type; + } + } + + if ($context->finally_scope) { + foreach ($context->vars_in_scope as $var_id => $type) { + if (isset($context->finally_scope->vars_in_scope[$var_id])) { + if ($context->finally_scope->vars_in_scope[$var_id] !== $type) { + $context->finally_scope->vars_in_scope[$var_id] = Type::combineUnionTypes( + $context->finally_scope->vars_in_scope[$var_id], + $type, + $statements_analyzer->getCodebase() + ); + } + } else { + $context->finally_scope->vars_in_scope[$var_id] = $type; + } + } + } + } + + $context->has_returned = true; + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..0cc1f156f9804674eaffd65edffc7ec17cff1de3 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php @@ -0,0 +1,138 @@ +getCodebase(); + + foreach ($stmt->exprs as $i => $expr) { + $context->inside_call = true; + ExpressionAnalyzer::analyze($statements_analyzer, $expr, $context); + $context->inside_call = false; + + $expr_type = $statements_analyzer->node_data->getType($expr); + + if ($statements_analyzer->data_flow_graph + && $expr_type + ) { + $expr_type = CastAnalyzer::castStringAttempt( + $statements_analyzer, + $context, + $expr_type, + $expr, + false + ); + } + + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $call_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + + $echo_param_sink = TaintSink::getForMethodArgument( + 'echo', + 'echo', + (int) $i, + null, + $call_location + ); + + $echo_param_sink->taints = [ + Type\TaintKind::INPUT_HTML, + Type\TaintKind::USER_SECRET, + Type\TaintKind::SYSTEM_SECRET + ]; + + $statements_analyzer->data_flow_graph->addSink($echo_param_sink); + } + + if ($expr_type) { + if (ArgumentAnalyzer::verifyType( + $statements_analyzer, + $expr_type, + Type::getString(), + null, + 'echo', + (int)$i, + new CodeLocation($statements_analyzer->getSource(), $expr), + $expr, + $context, + $echo_param, + false, + null, + true, + true, + new CodeLocation($statements_analyzer, $stmt) + ) === false) { + return false; + } + } + } + + if ($codebase->config->forbid_echo) { + if (IssueBuffer::accepts( + new ForbiddenEcho( + 'Use of echo', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSource()->getSuppressedIssues() + )) { + return false; + } + } elseif (isset($codebase->config->forbidden_functions['echo'])) { + if (IssueBuffer::accepts( + new ForbiddenCode( + 'Use of echo', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSource()->getSuppressedIssues() + )) { + // continue + } + } + + if (!$context->collect_initializations && !$context->collect_mutations) { + if ($context->mutation_free || $context->external_mutation_free) { + if (IssueBuffer::accepts( + new ImpureFunctionCall( + 'Cannot call echo from a mutation-free context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..097de4e2b00874fb43db75ab700439063c86da1a --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php @@ -0,0 +1,365 @@ +items)) { + $statements_analyzer->node_data->setType($stmt, Type::getEmptyArray()); + + return true; + } + + $item_key_atomic_types = []; + $item_value_atomic_types = []; + + $property_types = []; + $class_strings = []; + + $can_create_objectlike = true; + + $array_keys = []; + + $int_offset_diff = 0; + + $codebase = $statements_analyzer->getCodebase(); + + $all_list = true; + + $parent_taint_nodes = []; + + foreach ($stmt->items as $int_offset => $item) { + if ($item === null) { + \Psalm\IssueBuffer::add( + new \Psalm\Issue\ParseError( + 'Array element cannot be empty', + new CodeLocation($statements_analyzer, $stmt) + ) + ); + + return false; + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $item->value, $context) === false) { + return false; + } + + if ($item->unpack) { + $unpacked_array_type = $statements_analyzer->node_data->getType($item->value); + + if (!$unpacked_array_type) { + continue; + } + + foreach ($unpacked_array_type->getAtomicTypes() as $unpacked_atomic_type) { + if ($unpacked_atomic_type instanceof Type\Atomic\TKeyedArray) { + $unpacked_array_offset = 0; + foreach ($unpacked_atomic_type->properties as $key => $property_value) { + if (\is_string($key)) { + if (IssueBuffer::accepts( + new DuplicateArrayKey( + 'String keys are not supported in unpacked arrays', + new CodeLocation($statements_analyzer->getSource(), $item->value) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + continue; + } + + $item_key_atomic_types[] = new Type\Atomic\TLiteralInt($key); + $item_value_atomic_types = array_merge( + $item_value_atomic_types, + array_values($property_value->getAtomicTypes()) + ); + $array_keys[$int_offset + $int_offset_diff + $unpacked_array_offset] = true; + $property_types[$int_offset + $int_offset_diff + $unpacked_array_offset] = $property_value; + + $unpacked_array_offset++; + } + + $int_offset_diff += $unpacked_array_offset - 1; + } else { + $can_create_objectlike = false; + + if ($unpacked_atomic_type instanceof Type\Atomic\TArray) { + if ($unpacked_atomic_type->type_params[0]->hasString()) { + if (IssueBuffer::accepts( + new DuplicateArrayKey( + 'String keys are not supported in unpacked arrays', + new CodeLocation($statements_analyzer->getSource(), $item->value) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($unpacked_atomic_type->type_params[0]->hasInt()) { + $item_key_atomic_types[] = new Type\Atomic\TInt(); + } + + $item_value_atomic_types = array_merge( + $item_value_atomic_types, + array_values($unpacked_atomic_type->type_params[1]->getAtomicTypes()) + ); + } elseif ($unpacked_atomic_type instanceof Type\Atomic\TList) { + $item_key_atomic_types[] = new Type\Atomic\TInt(); + + $item_value_atomic_types = array_merge( + $item_value_atomic_types, + array_values($unpacked_atomic_type->type_param->getAtomicTypes()) + ); + } + } + } + + continue; + } + + $item_key_value = null; + + if ($item->key) { + $all_list = false; + + $was_inside_use = $context->inside_use; + $context->inside_use = true; + if (ExpressionAnalyzer::analyze($statements_analyzer, $item->key, $context) === false) { + return false; + } + $context->inside_use = $was_inside_use; + + if ($item_key_type = $statements_analyzer->node_data->getType($item->key)) { + $key_type = $item_key_type; + + if ($key_type->isNull()) { + $key_type = Type::getString(''); + } + + if ($item->key instanceof PhpParser\Node\Scalar\String_ + && preg_match('/^(0|[1-9][0-9]*)$/', $item->key->value) + ) { + $key_type = Type::getInt(false, (int) $item->key->value); + } + + $item_key_atomic_types = array_merge( + $item_key_atomic_types, + array_values($key_type->getAtomicTypes()) + ); + + if ($key_type->isSingleStringLiteral()) { + $item_key_literal_type = $key_type->getSingleStringLiteral(); + $item_key_value = $item_key_literal_type->value; + + if ($item_key_literal_type instanceof Type\Atomic\TLiteralClassString) { + $class_strings[$item_key_value] = true; + } + } elseif ($key_type->isSingleIntLiteral()) { + $item_key_value = $key_type->getSingleIntLiteral()->value; + + if ($item_key_value > $int_offset + $int_offset_diff) { + $int_offset_diff = $item_key_value - $int_offset; + } + } + } + } else { + $item_key_value = $int_offset + $int_offset_diff; + $item_key_atomic_types[] = new Type\Atomic\TInt(); + } + + if ($item_key_value !== null) { + if (isset($array_keys[$item_key_value])) { + if (IssueBuffer::accepts( + new DuplicateArrayKey( + 'Key \'' . $item_key_value . '\' already exists on array', + new CodeLocation($statements_analyzer->getSource(), $item) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $array_keys[$item_key_value] = true; + } + + if ($statements_analyzer->data_flow_graph + && ($statements_analyzer->data_flow_graph instanceof \Psalm\Internal\Codebase\VariableUseGraph + || !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())) + ) { + if ($item_value_type = $statements_analyzer->node_data->getType($item->value)) { + if ($item_value_type->parent_nodes) { + $var_location = new CodeLocation($statements_analyzer->getSource(), $item); + + $new_parent_node = \Psalm\Internal\DataFlow\DataFlowNode::getForAssignment( + 'array' + . ($item_key_value !== null ? '[\'' . $item_key_value . '\']' : ''), + $var_location + ); + + $statements_analyzer->data_flow_graph->addNode($new_parent_node); + + foreach ($item_value_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + $new_parent_node, + 'array-assignment' + . ($item_key_value !== null ? '-\'' . $item_key_value . '\'' : '') + ); + } + + $parent_taint_nodes += [$new_parent_node->id => $new_parent_node]; + } + } + } + + if ($item->byRef) { + $var_id = ExpressionIdentifier::getArrayVarId( + $item->value, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($var_id) { + $context->removeDescendents( + $var_id, + $context->vars_in_scope[$var_id] ?? null, + null, + $statements_analyzer + ); + + $context->vars_in_scope[$var_id] = Type::getMixed(); + } + } + + if ($item_value_atomic_types && !$can_create_objectlike) { + continue; + } + + if ($item_value_type = $statements_analyzer->node_data->getType($item->value)) { + if ($item_key_value !== null && count($property_types) <= 100) { + $property_types[$item_key_value] = $item_value_type; + } else { + $can_create_objectlike = false; + } + + $item_value_atomic_types = array_merge( + $item_value_atomic_types, + array_values($item_value_type->getAtomicTypes()) + ); + } else { + $item_value_atomic_types[] = new Type\Atomic\TMixed(); + + if ($item_key_value !== null && count($property_types) <= 100) { + $property_types[$item_key_value] = Type::getMixed(); + } else { + $can_create_objectlike = false; + } + } + } + + if ($item_key_atomic_types) { + $item_key_type = TypeCombination::combineTypes( + $item_key_atomic_types, + $codebase, + false, + true, + 30 + ); + } else { + $item_key_type = null; + } + + if ($item_value_atomic_types) { + $item_value_type = TypeCombination::combineTypes( + $item_value_atomic_types, + $codebase, + false, + true, + 30 + ); + } else { + $item_value_type = null; + } + + // if this array looks like an object-like array, let's return that instead + if ($item_value_type + && $item_key_type + && ($item_key_type->hasString() || $item_key_type->hasInt()) + && $can_create_objectlike + && $property_types + ) { + $object_like = new Type\Atomic\TKeyedArray($property_types, $class_strings); + $object_like->sealed = true; + $object_like->is_list = $all_list; + + $stmt_type = new Type\Union([$object_like]); + + if ($parent_taint_nodes) { + $stmt_type->parent_nodes = $parent_taint_nodes; + } + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + return true; + } + + if ($all_list) { + $array_type = new Type\Atomic\TNonEmptyList($item_value_type ?: Type::getMixed()); + $array_type->count = count($stmt->items); + + $stmt_type = new Type\Union([ + $array_type, + ]); + + if ($parent_taint_nodes) { + $stmt_type->parent_nodes = $parent_taint_nodes; + } + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + return true; + } + + $array_type = new Type\Atomic\TNonEmptyArray([ + $item_key_type && !$item_key_type->hasMixed() ? $item_key_type : Type::getArrayKey(), + $item_value_type ?: Type::getMixed(), + ]); + + $array_type->count = count($stmt->items); + + $stmt_type = new Type\Union([ + $array_type, + ]); + + if ($parent_taint_nodes) { + $stmt_type->parent_nodes = $parent_taint_nodes; + } + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php new file mode 100644 index 0000000000000000000000000000000000000000..69b444d226517c9ade74e325cdd25862ea2dafea --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -0,0 +1,3352 @@ +>> + */ + public static function scrapeAssertions( + PhpParser\Node\Expr $conditional, + ?string $this_class_name, + FileSource $source, + ?Codebase $codebase = null, + bool $inside_negation = false, + bool $cache = true, + bool $inside_conditional = true + ): array { + $if_types = []; + + if ($conditional instanceof PhpParser\Node\Expr\Instanceof_) { + $instanceof_types = self::getInstanceOfTypes($conditional, $this_class_name, $source); + + if ($instanceof_types) { + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional->expr, + $this_class_name, + $source + ); + + if ($var_name) { + $if_types[$var_name] = [$instanceof_types]; + + $var_type = $source instanceof StatementsAnalyzer + ? $source->node_data->getType($conditional->expr) + : null; + + foreach ($instanceof_types as $instanceof_type) { + if ($instanceof_type[0] === '=') { + $instanceof_type = substr($instanceof_type, 1); + } + + if ($codebase + && $var_type + && $inside_negation + && $source instanceof StatementsAnalyzer + ) { + if ($codebase->interfaceExists($instanceof_type)) { + continue; + } + + $instanceof_type = Type::parseString( + $instanceof_type, + null, + $source->getTemplateTypeMap() ?: [] + ); + + if (!UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $instanceof_type, + $var_type + )) { + if ($var_type->from_docblock) { + if (IssueBuffer::accepts( + new RedundantConditionGivenDocblockType( + $var_type->getId() . ' does not contain ' + . $instanceof_type->getId(), + new CodeLocation($source, $conditional), + $var_type->getId() . ' ' . $instanceof_type->getId() + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new RedundantCondition( + $var_type->getId() . ' cannot be identical to ' + . $instanceof_type->getId(), + new CodeLocation($source, $conditional), + $var_type->getId() . ' ' . $instanceof_type->getId() + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + } + } + + return $if_types; + } + + if ($conditional instanceof PhpParser\Node\Expr\Assign) { + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional->var, + $this_class_name, + $source + ); + + $candidate_if_types = $inside_conditional + ? self::scrapeAssertions( + $conditional->expr, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache, + $inside_conditional + ) + : []; + + if ($var_name) { + if ($candidate_if_types) { + $if_types[$var_name] = [['>' . \json_encode($candidate_if_types)]]; + } else { + $if_types[$var_name] = [['!falsy']]; + } + } + + return $if_types; + } + + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional, + $this_class_name, + $source + ); + + if ($var_name) { + $if_types[$var_name] = [['!falsy']]; + + if (!$conditional instanceof PhpParser\Node\Expr\MethodCall + && !$conditional instanceof PhpParser\Node\Expr\StaticCall + ) { + return $if_types; + } + } + + if ($conditional instanceof PhpParser\Node\Expr\BooleanNot) { + $expr_assertions = null; + + if ($cache && $source instanceof StatementsAnalyzer) { + $expr_assertions = $source->node_data->getAssertions($conditional->expr); + } + + if ($expr_assertions === null) { + $expr_assertions = self::scrapeAssertions( + $conditional->expr, + $this_class_name, + $source, + $codebase, + !$inside_negation, + $cache, + $inside_conditional + ); + + if ($cache && $source instanceof StatementsAnalyzer) { + $source->node_data->setAssertions($conditional->expr, $expr_assertions); + } + } + + if (count($expr_assertions) !== 1) { + return []; + } + + $if_types = \Psalm\Type\Algebra::negateTypes($expr_assertions); + + return $if_types; + } + + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical || + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal + ) { + $if_types = self::scrapeEqualityAssertions( + $conditional, + $this_class_name, + $source, + $codebase, + false, + $cache, + $inside_conditional + ); + + return $if_types; + } + + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical || + $conditional instanceof PhpParser\Node\Expr\BinaryOp\NotEqual + ) { + $if_types = self::scrapeInequalityAssertions( + $conditional, + $this_class_name, + $source, + $codebase, + false, + $cache, + $inside_conditional + ); + + return $if_types; + } + + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual + ) { + $min_count = null; + $count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count); + $min_comparison = null; + $positive_number_position = self::hasPositiveNumberCheck($conditional, $min_comparison); + $max_count = null; + $count_inequality_position = self::hasLessThanCountEqualityCheck($conditional, $max_count); + + if ($count_equality_position) { + if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) { + $counted_expr = $conditional->left; + } else { + throw new \UnexpectedValueException('$count_equality_position value'); + } + + /** @var PhpParser\Node\Expr\FuncCall $counted_expr */ + $var_name = ExpressionIdentifier::getArrayVarId( + $counted_expr->args[0]->value, + $this_class_name, + $source + ); + + if ($var_name) { + if (self::hasReconcilableNonEmptyCountEqualityCheck($conditional)) { + $if_types[$var_name] = [['non-empty-countable']]; + } else { + if ($min_count) { + $if_types[$var_name] = [['=has-at-least-' . $min_count]]; + } else { + $if_types[$var_name] = [['=non-empty-countable']]; + } + } + } + + return $if_types; + } + + if ($count_inequality_position) { + if ($count_inequality_position === self::ASSIGNMENT_TO_LEFT) { + $count_expr = $conditional->right; + } else { + throw new \UnexpectedValueException('$count_inequality_position value'); + } + + /** @var PhpParser\Node\Expr\FuncCall $count_expr */ + $var_name = ExpressionIdentifier::getArrayVarId( + $count_expr->args[0]->value, + $this_class_name, + $source + ); + + if ($var_name) { + if ($max_count) { + $if_types[$var_name] = [['!has-at-least-' . ($max_count + 1)]]; + } else { + $if_types[$var_name] = [['!non-empty-countable']]; + } + } + + return $if_types; + } + + if ($positive_number_position) { + if ($positive_number_position === self::ASSIGNMENT_TO_RIGHT) { + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional->left, + $this_class_name, + $source + ); + $value_node = $conditional->left; + } else { + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional->right, + $this_class_name, + $source + ); + $value_node = $conditional->right; + } + + if ($codebase + && $source instanceof StatementsAnalyzer + && ($var_type = $source->node_data->getType($value_node)) + && $var_type->isSingle() + && $var_type->hasBool() + && $min_comparison > 1 + ) { + if ($var_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + $var_type . ' cannot be greater than ' . $min_comparison, + new CodeLocation($source, $conditional), + null + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + $var_type . ' cannot be greater than ' . $min_comparison, + new CodeLocation($source, $conditional), + null + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($var_name) { + $if_types[$var_name] = [[($min_comparison === 1 ? '' : '=') . 'positive-numeric']]; + } + + return $if_types; + } + + return []; + } + + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual + ) { + $min_count = null; + $count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count); + $typed_value_position = self::hasTypedValueComparison($conditional, $source); + + $max_count = null; + $count_inequality_position = self::hasLessThanCountEqualityCheck($conditional, $max_count); + + if ($count_equality_position) { + if ($count_equality_position === self::ASSIGNMENT_TO_LEFT) { + $count_expr = $conditional->right; + } else { + throw new \UnexpectedValueException('$count_equality_position value'); + } + + /** @var PhpParser\Node\Expr\FuncCall $count_expr */ + $var_name = ExpressionIdentifier::getArrayVarId( + $count_expr->args[0]->value, + $this_class_name, + $source + ); + + if ($var_name) { + if ($min_count) { + $if_types[$var_name] = [['=has-at-least-' . $min_count]]; + } else { + $if_types[$var_name] = [['=non-empty-countable']]; + } + } + + return $if_types; + } + + if ($count_inequality_position) { + if ($count_inequality_position === self::ASSIGNMENT_TO_RIGHT) { + $count_expr = $conditional->left; + } else { + throw new \UnexpectedValueException('$count_inequality_position value'); + } + + /** @var PhpParser\Node\Expr\FuncCall $count_expr */ + $var_name = ExpressionIdentifier::getArrayVarId( + $count_expr->args[0]->value, + $this_class_name, + $source + ); + + if ($var_name) { + if ($max_count) { + $if_types[$var_name] = [['!has-at-least-' . ($max_count + 1)]]; + } else { + $if_types[$var_name] = [['!non-empty-countable']]; + } + } + + return $if_types; + } + + if ($typed_value_position) { + if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) { + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional->left, + $this_class_name, + $source + ); + + $expr = $conditional->right; + } elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) { + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional->right, + $this_class_name, + $source + ); + + $expr = $conditional->left; + } else { + throw new \UnexpectedValueException('$typed_value_position value'); + } + + $expr_type = $source instanceof StatementsAnalyzer + ? $source->node_data->getType($expr) + : null; + + if ($var_name + && $expr_type + && $expr_type->isSingleIntLiteral() + && ($expr_type->getSingleIntLiteral()->value === 0) + ) { + $if_types[$var_name] = [['=isset']]; + } + + return $if_types; + } + + return []; + } + + if ($conditional instanceof PhpParser\Node\Expr\FuncCall) { + $if_types = self::processFunctionCall($conditional, $this_class_name, $source, $codebase, false); + + return $if_types; + } + + if ($conditional instanceof PhpParser\Node\Expr\MethodCall + || $conditional instanceof PhpParser\Node\Expr\StaticCall + ) { + $custom_assertions = self::processCustomAssertion($conditional, $this_class_name, $source, false); + + if ($custom_assertions) { + return $custom_assertions; + } + + return $if_types; + } + + if ($conditional instanceof PhpParser\Node\Expr\Empty_) { + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional->expr, + $this_class_name, + $source + ); + + if ($var_name) { + $if_types[$var_name] = [['empty']]; + } + + return $if_types; + } + + if ($conditional instanceof PhpParser\Node\Expr\Isset_) { + foreach ($conditional->vars as $isset_var) { + $var_name = ExpressionIdentifier::getArrayVarId( + $isset_var, + $this_class_name, + $source + ); + + if ($var_name) { + $if_types[$var_name] = [['isset']]; + } else { + // look for any variables we *can* use for an isset assertion + $array_root = $isset_var; + + while ($array_root instanceof PhpParser\Node\Expr\ArrayDimFetch && !$var_name) { + $array_root = $array_root->var; + + $var_name = ExpressionIdentifier::getArrayVarId( + $array_root, + $this_class_name, + $source + ); + } + + if ($var_name) { + $if_types[$var_name] = [['=isset']]; + } + } + } + + return $if_types; + } + + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Coalesce) { + return self::scrapeAssertions( + new PhpParser\Node\Expr\Ternary( + new PhpParser\Node\Expr\Isset_( + [$conditional->left] + ), + $conditional->left, + $conditional->right, + $conditional->getAttributes() + ), + $this_class_name, + $source, + $codebase, + $inside_negation, + false, + $inside_conditional + ); + } + + return []; + } + + /** + * @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional + * + * @return array>> + */ + private static function scrapeEqualityAssertions( + PhpParser\Node\Expr\BinaryOp $conditional, + ?string $this_class_name, + FileSource $source, + ?Codebase $codebase = null, + bool $inside_negation = false, + bool $cache = true, + bool $inside_conditional = true + ): array { + $if_types = []; + + $null_position = self::hasNullVariable($conditional, $source); + $false_position = self::hasFalseVariable($conditional); + $true_position = self::hasTrueVariable($conditional); + $empty_array_position = self::hasEmptyArrayVariable($conditional); + $gettype_position = self::hasGetTypeCheck($conditional); + $get_debug_type_position = self::hasGetDebugTypeCheck($conditional); + $min_count = null; + $count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count); + + if ($null_position !== null) { + if ($null_position === self::ASSIGNMENT_TO_RIGHT) { + $base_conditional = $conditional->left; + } elseif ($null_position === self::ASSIGNMENT_TO_LEFT) { + $base_conditional = $conditional->right; + } else { + throw new \UnexpectedValueException('$null_position value'); + } + + $var_name = ExpressionIdentifier::getArrayVarId( + $base_conditional, + $this_class_name, + $source + ); + + if ($var_name && $base_conditional instanceof PhpParser\Node\Expr\Assign) { + $var_name = '=' . $var_name; + } + + if ($var_name) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) { + $if_types[$var_name] = [['null']]; + } else { + $if_types[$var_name] = [['falsy']]; + } + } + + if ($codebase + && $source instanceof StatementsAnalyzer + && ($var_type = $source->node_data->getType($base_conditional)) + && $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + ) { + $null_type = Type::getNull(); + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $var_type, + $null_type + ) && !UnionTypeComparator::isContainedBy( + $codebase, + $null_type, + $var_type + )) { + if ($var_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + $var_type . ' does not contain null', + new CodeLocation($source, $conditional), + $var_type . ' null' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TypeDoesNotContainNull( + $var_type . ' does not contain null', + new CodeLocation($source, $conditional), + $var_type->getId() + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + return $if_types; + } + + if ($true_position) { + if ($true_position === self::ASSIGNMENT_TO_RIGHT) { + $base_conditional = $conditional->left; + } elseif ($true_position === self::ASSIGNMENT_TO_LEFT) { + $base_conditional = $conditional->right; + } else { + throw new \UnexpectedValueException('Unrecognised position'); + } + + if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) { + $if_types = self::processFunctionCall( + $base_conditional, + $this_class_name, + $source, + $codebase, + false + ); + } else { + $var_name = ExpressionIdentifier::getArrayVarId( + $base_conditional, + $this_class_name, + $source + ); + + if ($var_name) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) { + $if_types[$var_name] = [['true']]; + } else { + $if_types[$var_name] = [['!falsy']]; + } + } else { + $base_assertions = null; + + if ($source instanceof StatementsAnalyzer && $cache) { + $base_assertions = $source->node_data->getAssertions($base_conditional); + } + + if ($base_assertions === null) { + $base_assertions = self::scrapeAssertions( + $base_conditional, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + + if ($source instanceof StatementsAnalyzer && $cache) { + $source->node_data->setAssertions($base_conditional, $base_assertions); + } + } + + $if_types = $base_assertions; + } + } + + if ($codebase + && $source instanceof StatementsAnalyzer + && ($var_type = $source->node_data->getType($base_conditional)) + ) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) { + $config = $source->getCodebase()->config; + + if ($config->strict_binary_operands + && $var_type->isSingle() + && $var_type->hasBool() + && !$var_type->from_docblock + ) { + if (IssueBuffer::accepts( + new RedundantIdentityWithTrue( + 'The "=== true" part of this comparison is redundant', + new CodeLocation($source, $conditional) + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + + $true_type = Type::getTrue(); + + if (!UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $true_type, + $var_type + )) { + if ($var_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + $var_type . ' does not contain true', + new CodeLocation($source, $conditional), + $var_type . ' true' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + $var_type . ' does not contain true', + new CodeLocation($source, $conditional), + $var_type . ' true' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + + return $if_types; + } + + if ($false_position) { + if ($false_position === self::ASSIGNMENT_TO_RIGHT) { + $base_conditional = $conditional->left; + } elseif ($false_position === self::ASSIGNMENT_TO_LEFT) { + $base_conditional = $conditional->right; + } else { + throw new \UnexpectedValueException('$false_position value'); + } + + if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) { + $if_types = self::processFunctionCall( + $base_conditional, + $this_class_name, + $source, + $codebase, + true + ); + } else { + $var_name = ExpressionIdentifier::getArrayVarId( + $base_conditional, + $this_class_name, + $source + ); + + if ($var_name) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) { + $if_types[$var_name] = [['false']]; + } else { + $if_types[$var_name] = [['falsy']]; + } + } else { + $base_assertions = null; + + if ($source instanceof StatementsAnalyzer && $cache) { + $base_assertions = $source->node_data->getAssertions($base_conditional); + } + + if ($base_assertions === null) { + $base_assertions = self::scrapeAssertions( + $base_conditional, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache, + $inside_conditional + ); + + if ($source instanceof StatementsAnalyzer && $cache) { + $source->node_data->setAssertions($base_conditional, $base_assertions); + } + } + + $notif_types = $base_assertions; + + if (count($notif_types) === 1) { + $if_types = \Psalm\Type\Algebra::negateTypes($notif_types); + } + } + } + + if ($codebase + && $source instanceof StatementsAnalyzer + && ($var_type = $source->node_data->getType($base_conditional)) + ) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) { + $false_type = Type::getFalse(); + + if (!UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $false_type, + $var_type + )) { + if ($var_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + $var_type . ' does not contain false', + new CodeLocation($source, $conditional), + $var_type . ' false' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + $var_type . ' does not contain false', + new CodeLocation($source, $conditional), + $var_type . ' false' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + + return $if_types; + } + + if ($empty_array_position !== null) { + if ($empty_array_position === self::ASSIGNMENT_TO_RIGHT) { + $base_conditional = $conditional->left; + } elseif ($empty_array_position === self::ASSIGNMENT_TO_LEFT) { + $base_conditional = $conditional->right; + } else { + throw new \UnexpectedValueException('$empty_array_position value'); + } + + $var_name = ExpressionIdentifier::getArrayVarId( + $base_conditional, + $this_class_name, + $source + ); + + if ($var_name) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) { + $if_types[$var_name] = [['!non-empty-countable']]; + } else { + $if_types[$var_name] = [['falsy']]; + } + } + + if ($codebase + && $source instanceof StatementsAnalyzer + && ($var_type = $source->node_data->getType($base_conditional)) + && $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + ) { + $empty_array_type = Type::getEmptyArray(); + + if (!UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $empty_array_type, + $var_type + )) { + if ($var_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + $var_type . ' does not contain an empty array', + new CodeLocation($source, $conditional), + null + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + $var_type . ' does not contain empty array', + new CodeLocation($source, $conditional), + null + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + return $if_types; + } + + if ($gettype_position) { + if ($gettype_position === self::ASSIGNMENT_TO_RIGHT) { + $string_expr = $conditional->left; + $gettype_expr = $conditional->right; + } elseif ($gettype_position === self::ASSIGNMENT_TO_LEFT) { + $string_expr = $conditional->right; + $gettype_expr = $conditional->left; + } else { + throw new \UnexpectedValueException('$gettype_position value'); + } + + /** @var PhpParser\Node\Expr\FuncCall $gettype_expr */ + $var_name = ExpressionIdentifier::getArrayVarId( + $gettype_expr->args[0]->value, + $this_class_name, + $source + ); + + /** @var PhpParser\Node\Scalar\String_ $string_expr */ + $var_type = $string_expr->value; + + if (!isset(ClassLikeAnalyzer::GETTYPE_TYPES[$var_type])) { + if (IssueBuffer::accepts( + new UnevaluatedCode( + 'gettype cannot return this value', + new CodeLocation($source, $string_expr) + ) + )) { + // fall through + } + } else { + if ($var_name && $var_type) { + $if_types[$var_name] = [[$var_type]]; + } + } + + return $if_types; + } + + if ($get_debug_type_position) { + if ($get_debug_type_position === self::ASSIGNMENT_TO_RIGHT) { + $whichclass_expr = $conditional->left; + $get_debug_type_expr = $conditional->right; + } elseif ($get_debug_type_position === self::ASSIGNMENT_TO_LEFT) { + $whichclass_expr = $conditional->right; + $get_debug_type_expr = $conditional->left; + } else { + throw new \UnexpectedValueException('$gettype_position value'); + } + + /** @var PhpParser\Node\Expr\FuncCall $get_debug_type_expr */ + $var_name = ExpressionIdentifier::getArrayVarId( + $get_debug_type_expr->args[0]->value, + $this_class_name, + $source + ); + + if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) { + $var_type = $whichclass_expr->value; + } elseif ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch + && $whichclass_expr->class instanceof PhpParser\Node\Name + ) { + $var_type = ClassLikeAnalyzer::getFQCLNFromNameObject( + $whichclass_expr->class, + $source->getAliases() + ); + } else { + throw new \UnexpectedValueException('Shouldn’t get here'); + } + + if ($var_name && $var_type) { + if ($var_type === 'class@anonymous') { + $if_types[$var_name] = [['=object']]; + } elseif ($var_type === 'resource (closed)') { + $if_types[$var_name] = [['closed-resource']]; + } elseif (substr($var_type, 0, 10) === 'resource (') { + $if_types[$var_name] = [['=resource']]; + } else { + $if_types[$var_name] = [[$var_type]]; + } + } + + return $if_types; + } + + if ($count_equality_position) { + if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) { + $count_expr = $conditional->left; + } elseif ($count_equality_position === self::ASSIGNMENT_TO_LEFT) { + $count_expr = $conditional->right; + } else { + throw new \UnexpectedValueException('$count_equality_position value'); + } + + /** @var PhpParser\Node\Expr\FuncCall $count_expr */ + $var_name = ExpressionIdentifier::getArrayVarId( + $count_expr->args[0]->value, + $this_class_name, + $source + ); + + if ($var_name) { + if ($min_count) { + $if_types[$var_name] = [['=has-at-least-' . $min_count]]; + } else { + $if_types[$var_name] = [['=non-empty-countable']]; + } + } + + return $if_types; + } + + if (!$source instanceof StatementsAnalyzer) { + return []; + } + + $getclass_position = self::hasGetClassCheck($conditional, $source); + + if ($getclass_position) { + if ($getclass_position === self::ASSIGNMENT_TO_RIGHT) { + $whichclass_expr = $conditional->left; + $getclass_expr = $conditional->right; + } elseif ($getclass_position === self::ASSIGNMENT_TO_LEFT) { + $whichclass_expr = $conditional->right; + $getclass_expr = $conditional->left; + } else { + throw new \UnexpectedValueException('$getclass_position value'); + } + + if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall && isset($getclass_expr->args[0])) { + $var_name = ExpressionIdentifier::getArrayVarId( + $getclass_expr->args[0]->value, + $this_class_name, + $source + ); + } else { + $var_name = '$this'; + } + + if ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch + && $whichclass_expr->class instanceof PhpParser\Node\Name + ) { + $var_type = ClassLikeAnalyzer::getFQCLNFromNameObject( + $whichclass_expr->class, + $source->getAliases() + ); + + if ($var_type === 'self' || $var_type === 'static') { + $var_type = $this_class_name; + } elseif ($var_type === 'parent') { + $var_type = null; + } + + if ($var_type) { + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $source, + $var_type, + new CodeLocation($source, $whichclass_expr), + null, + null, + $source->getSuppressedIssues(), + true + ) === false + ) { + return $if_types; + } + } + + if ($var_name && $var_type) { + $if_types[$var_name] = [['=getclass-' . $var_type]]; + } + } else { + $type = $source->node_data->getType($whichclass_expr); + + if ($type && $var_name) { + foreach ($type->getAtomicTypes() as $type_part) { + if ($type_part instanceof Type\Atomic\TTemplateParamClass) { + $if_types[$var_name] = [['=' . $type_part->param_name]]; + } + } + } + } + + return $if_types; + } + + $typed_value_position = self::hasTypedValueComparison($conditional, $source); + + if ($typed_value_position) { + if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) { + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional->left, + $this_class_name, + $source + ); + + $other_type = $source->node_data->getType($conditional->left); + $var_type = $source->node_data->getType($conditional->right); + } elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) { + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional->right, + $this_class_name, + $source + ); + + $var_type = $source->node_data->getType($conditional->left); + $other_type = $source->node_data->getType($conditional->right); + } else { + throw new \UnexpectedValueException('$typed_value_position value'); + } + + if ($var_name && $var_type) { + $identical = $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + || ($other_type + && (($var_type->isString(true) && $other_type->isString(true)) + || ($var_type->isInt(true) && $other_type->isInt(true)) + || ($var_type->isFloat() && $other_type->isFloat()) + ) + ); + + if ($identical) { + $if_types[$var_name] = [['=' . $var_type->getAssertionString()]]; + } else { + $if_types[$var_name] = [['~' . $var_type->getAssertionString()]]; + } + } + + if ($codebase + && $other_type + && $var_type + && ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + || ($other_type->isString() + && $var_type->isString()) + ) + ) { + $parent_source = $source->getSource(); + + if ($parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer + && (($var_type->isSingleStringLiteral() + && $var_type->getSingleStringLiteral()->value === $this_class_name) + || ($other_type->isSingleStringLiteral() + && $other_type->getSingleStringLiteral()->value === $this_class_name)) + ) { + // do nothing + } elseif (!UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $other_type, + $var_type + )) { + if ($var_type->from_docblock || $other_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + $var_type->getId() . ' does not contain ' . $other_type->getId(), + new CodeLocation($source, $conditional), + $var_type->getId() . ' ' . $other_type->getId() + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + $var_type->getId() . ' cannot be identical to ' . $other_type->getId(), + new CodeLocation($source, $conditional), + $var_type->getId() . ' ' . $other_type->getId() + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + return $if_types; + } + + $var_type = $source->node_data->getType($conditional->left); + $other_type = $source->node_data->getType($conditional->right); + + if ($codebase + && $var_type + && $other_type + && $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + ) { + if (!UnionTypeComparator::canExpressionTypesBeIdentical($codebase, $var_type, $other_type)) { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + $var_type->getId() . ' cannot be identical to ' . $other_type->getId(), + new CodeLocation($source, $conditional), + $var_type->getId() . ' ' . $other_type->getId() + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + + return []; + } + + /** + * @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional + * + * @return array>> + */ + private static function scrapeInequalityAssertions( + PhpParser\Node\Expr\BinaryOp $conditional, + ?string $this_class_name, + FileSource $source, + ?Codebase $codebase = null, + bool $inside_negation = false, + bool $cache = true, + bool $inside_conditional = true + ): array { + $if_types = []; + + $null_position = self::hasNullVariable($conditional, $source); + $false_position = self::hasFalseVariable($conditional); + $true_position = self::hasTrueVariable($conditional); + $empty_array_position = self::hasEmptyArrayVariable($conditional); + $gettype_position = self::hasGetTypeCheck($conditional); + $get_debug_type_position = self::hasGetDebugTypeCheck($conditional); + $count = null; + $count_inequality_position = self::hasNotCountEqualityCheck($conditional, $count); + + if ($null_position !== null) { + if ($null_position === self::ASSIGNMENT_TO_RIGHT) { + $base_conditional = $conditional->left; + } elseif ($null_position === self::ASSIGNMENT_TO_LEFT) { + $base_conditional = $conditional->right; + } else { + throw new \UnexpectedValueException('Bad null variable position'); + } + + $var_name = ExpressionIdentifier::getArrayVarId( + $base_conditional, + $this_class_name, + $source + ); + + if ($var_name) { + if ($base_conditional instanceof PhpParser\Node\Expr\Assign) { + $var_name = '=' . $var_name; + } + + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) { + $if_types[$var_name] = [['!null']]; + } else { + $if_types[$var_name] = [['!falsy']]; + } + } + + if ($codebase + && $source instanceof StatementsAnalyzer + && ($var_type = $source->node_data->getType($base_conditional)) + ) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) { + $null_type = Type::getNull(); + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $var_type, + $null_type + ) && !UnionTypeComparator::isContainedBy( + $codebase, + $null_type, + $var_type + )) { + if ($var_type->from_docblock) { + if (IssueBuffer::accepts( + new RedundantConditionGivenDocblockType( + 'Docblock-defined type ' . $var_type . ' can never contain null', + new CodeLocation($source, $conditional), + $var_type->getId() . ' null' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new RedundantCondition( + $var_type . ' can never contain null', + new CodeLocation($source, $conditional), + $var_type->getId() . ' null' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + + return $if_types; + } + + if ($false_position) { + if ($false_position === self::ASSIGNMENT_TO_RIGHT) { + $base_conditional = $conditional->left; + } elseif ($false_position === self::ASSIGNMENT_TO_LEFT) { + $base_conditional = $conditional->right; + } else { + throw new \UnexpectedValueException('Bad false variable position'); + } + + $var_name = ExpressionIdentifier::getArrayVarId( + $base_conditional, + $this_class_name, + $source + ); + + if ($var_name) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) { + $if_types[$var_name] = [['!false']]; + } else { + $if_types[$var_name] = [['!falsy']]; + } + } else { + $base_assertions = null; + + if ($source instanceof StatementsAnalyzer && $cache) { + $base_assertions = $source->node_data->getAssertions($base_conditional); + } + + if ($base_assertions === null) { + $base_assertions = self::scrapeAssertions( + $base_conditional, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache, + $inside_conditional + ); + + if ($source instanceof StatementsAnalyzer && $cache) { + $source->node_data->setAssertions($base_conditional, $base_assertions); + } + } + + $notif_types = $base_assertions; + + if (count($notif_types) === 1) { + $if_types = \Psalm\Type\Algebra::negateTypes($notif_types); + } + } + + if ($codebase + && $source instanceof StatementsAnalyzer + && ($var_type = $source->node_data->getType($base_conditional)) + ) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) { + $false_type = Type::getFalse(); + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $var_type, + $false_type + ) && !UnionTypeComparator::isContainedBy( + $codebase, + $false_type, + $var_type + )) { + if ($var_type->from_docblock) { + if (IssueBuffer::accepts( + new RedundantConditionGivenDocblockType( + 'Docblock-defined type ' . $var_type . ' can never contain false', + new CodeLocation($source, $conditional), + $var_type->getId() . ' false' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new RedundantCondition( + $var_type . ' can never contain false', + new CodeLocation($source, $conditional), + $var_type->getId() . ' false' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + + return $if_types; + } + + if ($true_position) { + if ($true_position === self::ASSIGNMENT_TO_RIGHT) { + $base_conditional = $conditional->left; + } elseif ($true_position === self::ASSIGNMENT_TO_LEFT) { + $base_conditional = $conditional->right; + } else { + throw new \UnexpectedValueException('Bad null variable position'); + } + + if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) { + $if_types = self::processFunctionCall( + $base_conditional, + $this_class_name, + $source, + $codebase, + true + ); + } else { + $var_name = ExpressionIdentifier::getArrayVarId( + $base_conditional, + $this_class_name, + $source + ); + + if ($var_name) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) { + $if_types[$var_name] = [['!true']]; + } else { + $if_types[$var_name] = [['falsy']]; + } + } else { + $base_assertions = null; + + if ($source instanceof StatementsAnalyzer && $cache) { + $base_assertions = $source->node_data->getAssertions($base_conditional); + } + + if ($base_assertions === null) { + $base_assertions = self::scrapeAssertions( + $base_conditional, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache, + $inside_conditional + ); + + if ($source instanceof StatementsAnalyzer && $cache) { + $source->node_data->setAssertions($base_conditional, $base_assertions); + } + } + + $notif_types = $base_assertions; + + if (count($notif_types) === 1) { + $if_types = \Psalm\Type\Algebra::negateTypes($notif_types); + } + } + } + + if ($codebase + && $source instanceof StatementsAnalyzer + && ($var_type = $source->node_data->getType($base_conditional)) + ) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) { + $true_type = Type::getTrue(); + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $var_type, + $true_type + ) && !UnionTypeComparator::isContainedBy( + $codebase, + $true_type, + $var_type + )) { + if ($var_type->from_docblock) { + if (IssueBuffer::accepts( + new RedundantConditionGivenDocblockType( + 'Docblock-defined type ' . $var_type . ' can never contain true', + new CodeLocation($source, $conditional), + $var_type->getId() . ' true' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new RedundantCondition( + $var_type . ' can never contain ' . $true_type, + new CodeLocation($source, $conditional), + $var_type->getId() . ' true' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + + return $if_types; + } + + if ($count_inequality_position) { + if ($count_inequality_position === self::ASSIGNMENT_TO_RIGHT) { + $count_expr = $conditional->left; + } elseif ($count_inequality_position === self::ASSIGNMENT_TO_LEFT) { + $count_expr = $conditional->right; + } else { + throw new \UnexpectedValueException('$count_equality_position value'); + } + + /** @var PhpParser\Node\Expr\FuncCall $count_expr */ + $var_name = ExpressionIdentifier::getArrayVarId( + $count_expr->args[0]->value, + $this_class_name, + $source + ); + + if ($var_name) { + if ($count) { + $if_types[$var_name] = [['!has-exactly-' . $count]]; + } else { + $if_types[$var_name] = [['non-empty-countable']]; + } + } + + return $if_types; + } + + if ($empty_array_position !== null) { + if ($empty_array_position === self::ASSIGNMENT_TO_RIGHT) { + $base_conditional = $conditional->left; + } elseif ($empty_array_position === self::ASSIGNMENT_TO_LEFT) { + $base_conditional = $conditional->right; + } else { + throw new \UnexpectedValueException('Bad empty array variable position'); + } + + $var_name = ExpressionIdentifier::getArrayVarId( + $base_conditional, + $this_class_name, + $source + ); + + if ($var_name) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) { + $if_types[$var_name] = [['non-empty-countable']]; + } else { + $if_types[$var_name] = [['!falsy']]; + } + } + + if ($codebase + && $source instanceof StatementsAnalyzer + && ($var_type = $source->node_data->getType($base_conditional)) + ) { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) { + $empty_array_type = Type::getEmptyArray(); + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $var_type, + $empty_array_type + ) && !UnionTypeComparator::isContainedBy( + $codebase, + $empty_array_type, + $var_type + )) { + if ($var_type->from_docblock) { + if (IssueBuffer::accepts( + new RedundantConditionGivenDocblockType( + 'Docblock-defined type ' . $var_type->getId() . ' can never contain null', + new CodeLocation($source, $conditional), + $var_type->getId() . ' null' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new RedundantCondition( + $var_type->getId() . ' can never contain null', + new CodeLocation($source, $conditional), + $var_type->getId() . ' null' + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + + return $if_types; + } + + if ($gettype_position) { + if ($gettype_position === self::ASSIGNMENT_TO_RIGHT) { + $whichclass_expr = $conditional->left; + $gettype_expr = $conditional->right; + } elseif ($gettype_position === self::ASSIGNMENT_TO_LEFT) { + $whichclass_expr = $conditional->right; + $gettype_expr = $conditional->left; + } else { + throw new \UnexpectedValueException('$gettype_position value'); + } + + /** @var PhpParser\Node\Expr\FuncCall $gettype_expr */ + $var_name = ExpressionIdentifier::getArrayVarId( + $gettype_expr->args[0]->value, + $this_class_name, + $source + ); + + if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) { + $var_type = $whichclass_expr->value; + } elseif ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch + && $whichclass_expr->class instanceof PhpParser\Node\Name + ) { + $var_type = ClassLikeAnalyzer::getFQCLNFromNameObject( + $whichclass_expr->class, + $source->getAliases() + ); + } else { + throw new \UnexpectedValueException('Shouldn’t get here'); + } + + if (!isset(ClassLikeAnalyzer::GETTYPE_TYPES[$var_type])) { + if (IssueBuffer::accepts( + new UnevaluatedCode( + 'gettype cannot return this value', + new CodeLocation($source, $whichclass_expr) + ) + )) { + // fall through + } + } else { + if ($var_name && $var_type) { + $if_types[$var_name] = [['!' . $var_type]]; + } + } + + return $if_types; + } + + if ($get_debug_type_position) { + if ($get_debug_type_position === self::ASSIGNMENT_TO_RIGHT) { + $whichclass_expr = $conditional->left; + $get_debug_type_expr = $conditional->right; + } elseif ($get_debug_type_position === self::ASSIGNMENT_TO_LEFT) { + $whichclass_expr = $conditional->right; + $get_debug_type_expr = $conditional->left; + } else { + throw new \UnexpectedValueException('$gettype_position value'); + } + + /** @var PhpParser\Node\Expr\FuncCall $get_debug_type_expr */ + $var_name = ExpressionIdentifier::getArrayVarId( + $get_debug_type_expr->args[0]->value, + $this_class_name, + $source + ); + + if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) { + $var_type = $whichclass_expr->value; + } elseif ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch + && $whichclass_expr->class instanceof PhpParser\Node\Name + ) { + $var_type = ClassLikeAnalyzer::getFQCLNFromNameObject( + $whichclass_expr->class, + $source->getAliases() + ); + } else { + throw new \UnexpectedValueException('Shouldn’t get here'); + } + + if ($var_name && $var_type) { + if ($var_type === 'class@anonymous') { + $if_types[$var_name] = [['!=object']]; + } elseif ($var_type === 'resource (closed)') { + $if_types[$var_name] = [['!closed-resource']]; + } elseif (substr($var_type, 0, 10) === 'resource (') { + $if_types[$var_name] = [['!=resource']]; + } else { + $if_types[$var_name] = [['!' . $var_type]]; + } + } + + return $if_types; + } + + if (!$source instanceof StatementsAnalyzer) { + return []; + } + + $getclass_position = self::hasGetClassCheck($conditional, $source); + $typed_value_position = self::hasTypedValueComparison($conditional, $source); + + if ($getclass_position) { + if ($getclass_position === self::ASSIGNMENT_TO_RIGHT) { + $whichclass_expr = $conditional->left; + $getclass_expr = $conditional->right; + } elseif ($getclass_position === self::ASSIGNMENT_TO_LEFT) { + $whichclass_expr = $conditional->right; + $getclass_expr = $conditional->left; + } else { + throw new \UnexpectedValueException('$getclass_position value'); + } + + if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall) { + $var_name = ExpressionIdentifier::getArrayVarId( + $getclass_expr->args[0]->value, + $this_class_name, + $source + ); + } else { + $var_name = '$this'; + } + + if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) { + $var_type = $whichclass_expr->value; + } elseif ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch + && $whichclass_expr->class instanceof PhpParser\Node\Name + ) { + $var_type = ClassLikeAnalyzer::getFQCLNFromNameObject( + $whichclass_expr->class, + $source->getAliases() + ); + + if ($var_type === 'self' || $var_type === 'static') { + $var_type = $this_class_name; + } elseif ($var_type === 'parent') { + $var_type = null; + } + } else { + $type = $source->node_data->getType($whichclass_expr); + + if ($type && $var_name) { + foreach ($type->getAtomicTypes() as $type_part) { + if ($type_part instanceof Type\Atomic\TTemplateParamClass) { + $if_types[$var_name] = [['!=' . $type_part->param_name]]; + } + } + } + + return $if_types; + } + + if ($var_type + && ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $source, + $var_type, + new CodeLocation($source, $whichclass_expr), + null, + null, + $source->getSuppressedIssues(), + false + ) === false + ) { + // fall through + } else { + if ($var_name && $var_type) { + $if_types[$var_name] = [['!=getclass-' . $var_type]]; + } + } + + return $if_types; + } + + if ($typed_value_position) { + if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) { + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional->left, + $this_class_name, + $source + ); + + $other_type = $source->node_data->getType($conditional->left); + $var_type = $source->node_data->getType($conditional->right); + } elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) { + $var_name = ExpressionIdentifier::getArrayVarId( + $conditional->right, + $this_class_name, + $source + ); + + $var_type = $source->node_data->getType($conditional->left); + $other_type = $source->node_data->getType($conditional->right); + } else { + throw new \UnexpectedValueException('$typed_value_position value'); + } + + if ($var_type) { + if ($var_name) { + $not_identical = $conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical + || ($other_type + && (($var_type->isString() && $other_type->isString()) + || ($var_type->isInt() && $other_type->isInt()) + || ($var_type->isFloat() && $other_type->isFloat()) + ) + ); + + if ($not_identical) { + $if_types[$var_name] = [['!=' . $var_type->getAssertionString()]]; + } else { + $if_types[$var_name] = [['!~' . $var_type->getAssertionString()]]; + } + } + + if ($codebase + && $other_type + && $conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical + ) { + $parent_source = $source->getSource(); + + if ($parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer + && (($var_type->isSingleStringLiteral() + && $var_type->getSingleStringLiteral()->value === $this_class_name) + || ($other_type->isSingleStringLiteral() + && $other_type->getSingleStringLiteral()->value === $this_class_name)) + ) { + // do nothing + } elseif (!UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $other_type, + $var_type + )) { + if ($var_type->from_docblock || $other_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + $var_type . ' can never contain ' . $other_type->getId(), + new CodeLocation($source, $conditional), + $var_type . ' ' . $other_type + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new RedundantCondition( + $var_type->getId() . ' can never contain ' . $other_type->getId(), + new CodeLocation($source, $conditional), + $var_type->getId() . ' ' . $other_type->getId() + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + + return $if_types; + } + + return []; + } + + /** + * @return array>> + */ + public static function processFunctionCall( + PhpParser\Node\Expr\FuncCall $expr, + ?string $this_class_name, + FileSource $source, + ?Codebase $codebase = null, + bool $negate = false + ): array { + $prefix = $negate ? '!' : ''; + + $first_var_name = isset($expr->args[0]->value) + ? ExpressionIdentifier::getArrayVarId( + $expr->args[0]->value, + $this_class_name, + $source + ) + : null; + + $if_types = []; + + $first_var_type = isset($expr->args[0]->value) + && $source instanceof StatementsAnalyzer + ? $source->node_data->getType($expr->args[0]->value) + : null; + + if (self::hasNullCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'null']]; + } + } elseif ($source instanceof StatementsAnalyzer && self::hasIsACheck($expr, $source)) { + if ($expr->args[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch + && $expr->args[0]->value->name instanceof PhpParser\Node\Identifier + && strtolower($expr->args[0]->value->name->name) === 'class' + && $expr->args[0]->value->class instanceof PhpParser\Node\Name + && count($expr->args[0]->value->class->parts) === 1 + && strtolower($expr->args[0]->value->class->parts[0]) === 'static' + ) { + $first_var_name = '$this'; + } + + if ($first_var_name) { + $first_arg = $expr->args[0]->value; + $second_arg = $expr->args[1]->value; + $third_arg = isset($expr->args[2]->value) ? $expr->args[2]->value : null; + + if ($third_arg instanceof PhpParser\Node\Expr\ConstFetch) { + if (!in_array(strtolower($third_arg->name->parts[0]), ['true', 'false'])) { + return $if_types; + } + + $third_arg_value = strtolower($third_arg->name->parts[0]); + } else { + $third_arg_value = $expr->name instanceof PhpParser\Node\Name + && strtolower($expr->name->parts[0]) === 'is_subclass_of' + ? 'true' + : 'false'; + } + + $is_a_prefix = $third_arg_value === 'true' ? 'isa-string-' : 'isa-'; + + if ($first_arg + && ($first_arg_type = $source->node_data->getType($first_arg)) + && $first_arg_type->isSingleStringLiteral() + && $source->getSource()->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer + && $first_arg_type->getSingleStringLiteral()->value === $this_class_name + ) { + // do nothing + } else { + if ($second_arg instanceof PhpParser\Node\Scalar\String_) { + $fq_class_name = $second_arg->value; + if ($fq_class_name[0] === '\\') { + $fq_class_name = substr($fq_class_name, 1); + } + + $if_types[$first_var_name] = [[$prefix . $is_a_prefix . $fq_class_name]]; + } elseif ($second_arg instanceof PhpParser\Node\Expr\ClassConstFetch + && $second_arg->class instanceof PhpParser\Node\Name + && $second_arg->name instanceof PhpParser\Node\Identifier + && strtolower($second_arg->name->name) === 'class' + ) { + $class_node = $second_arg->class; + + if ($class_node->parts === ['static']) { + if ($this_class_name) { + $if_types[$first_var_name] = [[$prefix . $is_a_prefix . $this_class_name . '&static']]; + } + } elseif ($class_node->parts === ['self']) { + if ($this_class_name) { + $if_types[$first_var_name] = [[$prefix . $is_a_prefix . $this_class_name]]; + } + } elseif ($class_node->parts === ['parent']) { + // do nothing + } else { + $if_types[$first_var_name] = [[ + $prefix . $is_a_prefix + . ClassLikeAnalyzer::getFQCLNFromNameObject( + $class_node, + $source->getAliases() + ) + ]]; + } + } elseif (($second_arg_type = $source->node_data->getType($second_arg)) + && $second_arg_type->hasString() + ) { + $vals = []; + + foreach ($second_arg_type->getAtomicTypes() as $second_arg_atomic_type) { + if ($second_arg_atomic_type instanceof Type\Atomic\TTemplateParamClass) { + $vals[] = [$prefix . $is_a_prefix . $second_arg_atomic_type->param_name]; + } + } + + if ($vals) { + $if_types[$first_var_name] = $vals; + } + } + } + } + } elseif (self::hasArrayCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'array']]; + } elseif ($first_var_type + && $codebase + && $source instanceof StatementsAnalyzer + ) { + self::processIrreconcilableFunctionCall( + $first_var_type, + Type::getArray(), + $expr, + $source, + $codebase, + $negate + ); + } + } elseif (self::hasBoolCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'bool']]; + } elseif ($first_var_type + && $codebase + && $source instanceof StatementsAnalyzer + ) { + self::processIrreconcilableFunctionCall( + $first_var_type, + Type::getBool(), + $expr, + $source, + $codebase, + $negate + ); + } + } elseif (self::hasStringCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'string']]; + } elseif ($first_var_type + && $codebase + && $source instanceof StatementsAnalyzer + ) { + self::processIrreconcilableFunctionCall( + $first_var_type, + Type::getString(), + $expr, + $source, + $codebase, + $negate + ); + } + } elseif (self::hasObjectCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'object']]; + } elseif ($first_var_type + && $codebase + && $source instanceof StatementsAnalyzer + ) { + self::processIrreconcilableFunctionCall( + $first_var_type, + Type::getObject(), + $expr, + $source, + $codebase, + $negate + ); + } + } elseif (self::hasNumericCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'numeric']]; + } elseif ($first_var_type + && $codebase + && $source instanceof StatementsAnalyzer + ) { + self::processIrreconcilableFunctionCall( + $first_var_type, + Type::getNumeric(), + $expr, + $source, + $codebase, + $negate + ); + } + } elseif (self::hasIntCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'int']]; + } elseif ($first_var_type + && $codebase + && $source instanceof StatementsAnalyzer + ) { + self::processIrreconcilableFunctionCall( + $first_var_type, + Type::getInt(), + $expr, + $source, + $codebase, + $negate + ); + } + } elseif (self::hasFloatCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'float']]; + } elseif ($first_var_type + && $codebase + && $source instanceof StatementsAnalyzer + ) { + self::processIrreconcilableFunctionCall( + $first_var_type, + Type::getFloat(), + $expr, + $source, + $codebase, + $negate + ); + } + } elseif (self::hasResourceCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'resource']]; + } elseif ($first_var_type + && $codebase + && $source instanceof StatementsAnalyzer + ) { + self::processIrreconcilableFunctionCall( + $first_var_type, + Type::getResource(), + $expr, + $source, + $codebase, + $negate + ); + } + } elseif (self::hasScalarCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'scalar']]; + } elseif ($first_var_type + && $codebase + && $source instanceof StatementsAnalyzer + ) { + self::processIrreconcilableFunctionCall( + $first_var_type, + Type::getScalar(), + $expr, + $source, + $codebase, + $negate + ); + } + } elseif (self::hasCallableCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'callable']]; + } elseif ($expr->args[0]->value instanceof PhpParser\Node\Expr\Array_ + && isset($expr->args[0]->value->items[0], $expr->args[0]->value->items[1]) + && $expr->args[0]->value->items[1]->value instanceof PhpParser\Node\Scalar\String_ + ) { + $first_var_name_in_array_argument = ExpressionIdentifier::getArrayVarId( + $expr->args[0]->value->items[0]->value, + $this_class_name, + $source + ); + if ($first_var_name_in_array_argument) { + $if_types[$first_var_name_in_array_argument] = [ + [$prefix . 'hasmethod-' . $expr->args[0]->value->items[1]->value->value] + ]; + } + } + } elseif (self::hasIterableCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'iterable']]; + } + } elseif (self::hasCountableCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'countable']]; + } + } elseif ($class_exists_check_type = self::hasClassExistsCheck($expr)) { + if ($first_var_name) { + if ($class_exists_check_type === 2 || $prefix) { + $if_types[$first_var_name] = [[$prefix . 'class-string']]; + } else { + $if_types[$first_var_name] = [['=class-string']]; + } + } + } elseif ($class_exists_check_type = self::hasTraitExistsCheck($expr)) { + if ($first_var_name) { + if ($class_exists_check_type === 2 || $prefix) { + $if_types[$first_var_name] = [[$prefix . 'trait-string']]; + } else { + $if_types[$first_var_name] = [['=trait-string']]; + } + } + } elseif (self::hasInterfaceExistsCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'interface-string']]; + } + } elseif (self::hasFunctionExistsCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'callable-string']]; + } + } elseif ($expr->name instanceof PhpParser\Node\Name + && strtolower($expr->name->parts[0]) === 'method_exists' + && isset($expr->args[1]) + && $expr->args[1]->value instanceof PhpParser\Node\Scalar\String_ + ) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'hasmethod-' . $expr->args[1]->value->value]]; + } + } elseif (self::hasInArrayCheck($expr) && $source instanceof StatementsAnalyzer) { + if ($first_var_name + && ($second_arg_type = $source->node_data->getType($expr->args[1]->value)) + ) { + foreach ($second_arg_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TArray + || $atomic_type instanceof Type\Atomic\TKeyedArray + ) { + if ($atomic_type instanceof Type\Atomic\TKeyedArray) { + $atomic_type = $atomic_type->getGenericArrayType(); + } + + $array_literal_types = array_merge( + $atomic_type->type_params[1]->getLiteralStrings(), + $atomic_type->type_params[1]->getLiteralInts(), + $atomic_type->type_params[1]->getLiteralFloats() + ); + + if ($array_literal_types + && count($atomic_type->type_params[1]->getAtomicTypes()) + ) { + $literal_assertions = []; + + foreach ($array_literal_types as $array_literal_type) { + $literal_assertions[] = '=' . $array_literal_type->getId(); + } + + if ($atomic_type->type_params[1]->isFalsable()) { + $literal_assertions[] = 'false'; + } + + if ($atomic_type->type_params[1]->isNullable()) { + $literal_assertions[] = 'null'; + } + + if ($negate) { + $if_types = \Psalm\Type\Algebra::negateTypes([ + $first_var_name => [$literal_assertions] + ]); + } else { + $if_types[$first_var_name] = [$literal_assertions]; + } + } + } + } + } + } elseif (self::hasArrayKeyExistsCheck($expr)) { + $array_root = isset($expr->args[1]->value) + ? ExpressionIdentifier::getArrayVarId( + $expr->args[1]->value, + $this_class_name, + $source + ) + : null; + + if ($first_var_name === null && isset($expr->args[0])) { + $first_arg = $expr->args[0]; + + if ($first_arg->value instanceof PhpParser\Node\Scalar\String_) { + $first_var_name = '\'' . $first_arg->value->value . '\''; + } elseif ($first_arg->value instanceof PhpParser\Node\Scalar\LNumber) { + $first_var_name = (string) $first_arg->value->value; + } + } + + if ($expr->args[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch + && $expr->args[0]->value->name instanceof PhpParser\Node\Identifier + && $expr->args[0]->value->name->name !== 'class' + ) { + $const_type = null; + + if ($source instanceof StatementsAnalyzer) { + $const_type = $source->node_data->getType($expr->args[0]->value); + } + + if ($const_type) { + if ($const_type->isSingleStringLiteral()) { + $first_var_name = $const_type->getSingleStringLiteral()->value; + } elseif ($const_type->isSingleIntLiteral()) { + $first_var_name = (string) $const_type->getSingleIntLiteral()->value; + } else { + $first_var_name = null; + } + } else { + $first_var_name = null; + } + } + + if ($first_var_name !== null + && $array_root + && !strpos($first_var_name, '->') + && !strpos($first_var_name, '[') + ) { + $if_types[$array_root . '[' . $first_var_name . ']'] = [[$prefix . 'array-key-exists']]; + } + } elseif (self::hasNonEmptyCountCheck($expr)) { + if ($first_var_name) { + $if_types[$first_var_name] = [[$prefix . 'non-empty-countable']]; + } + } else { + $if_types = self::processCustomAssertion($expr, $this_class_name, $source, $negate); + } + + return $if_types; + } + + private static function processIrreconcilableFunctionCall( + Type\Union $first_var_type, + Type\Union $expected_type, + PhpParser\Node\Expr $expr, + StatementsAnalyzer $source, + Codebase $codebase, + bool $negate + ) : void { + if ($first_var_type->hasMixed()) { + return; + } + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $first_var_type, + $expected_type + )) { + return; + } + + if (!$negate) { + if ($first_var_type->from_docblock) { + if (IssueBuffer::accepts( + new RedundantConditionGivenDocblockType( + 'Docblock type ' . $first_var_type . ' always contains ' . $expected_type, + new CodeLocation($source, $expr), + $first_var_type . ' ' . $expected_type + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new RedundantCondition( + $first_var_type . ' always contains ' . $expected_type, + new CodeLocation($source, $expr), + $first_var_type . ' ' . $expected_type + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } else { + if ($first_var_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + $first_var_type . ' does not contain ' . $expected_type, + new CodeLocation($source, $expr), + $first_var_type . ' ' . $expected_type + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + $first_var_type . ' does not contain ' . $expected_type, + new CodeLocation($source, $expr), + $first_var_type . ' ' . $expected_type + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + /** + * @param PhpParser\Node\Expr\FuncCall|PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $expr + * + * @return array>> + */ + protected static function processCustomAssertion( + PhpParser\Node\Expr $expr, + ?string $this_class_name, + FileSource $source, + bool $negate = false + ): array { + if (!$source instanceof StatementsAnalyzer) { + return []; + } + + $if_true_assertions = $source->node_data->getIfTrueAssertions($expr); + $if_false_assertions = $source->node_data->getIfFalseAssertions($expr); + + if ($if_true_assertions === null && $if_false_assertions === null) { + return []; + } + + $prefix = $negate ? '!' : ''; + + $first_var_name = isset($expr->args[0]->value) + ? ExpressionIdentifier::getArrayVarId( + $expr->args[0]->value, + $this_class_name, + $source + ) + : null; + + $if_types = []; + + if ($if_true_assertions) { + foreach ($if_true_assertions as $assertion) { + $assertion = clone $assertion; + + foreach ($assertion->rule as $i => $and_rules) { + foreach ($and_rules as $j => $rule) { + if (strpos($rule, 'scalar-class-constant(') === 0) { + $codebase = $source->getCodebase(); + + $assertion->rule[$i][$j] = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + Type::parseString(substr($rule, 22, -1)), + null, + null, + null + )->getId(); + } + } + } + + if (is_int($assertion->var_id) && isset($expr->args[$assertion->var_id])) { + if ($assertion->var_id === 0) { + $var_name = $first_var_name; + } else { + $var_name = ExpressionIdentifier::getArrayVarId( + $expr->args[$assertion->var_id]->value, + $this_class_name, + $source + ); + } + + if ($var_name) { + if ($prefix === $assertion->rule[0][0][0]) { + $if_types[$var_name] = [[substr($assertion->rule[0][0], 1)]]; + } else { + $if_types[$var_name] = [[$prefix . $assertion->rule[0][0]]]; + } + } + } elseif ($assertion->var_id === '$this' && $expr instanceof PhpParser\Node\Expr\MethodCall) { + $var_id = ExpressionIdentifier::getArrayVarId( + $expr->var, + $this_class_name, + $source + ); + + if ($var_id) { + if ($prefix === $assertion->rule[0][0][0]) { + $if_types[$var_id] = [[substr($assertion->rule[0][0], 1)]]; + } else { + $if_types[$var_id] = [[$prefix . $assertion->rule[0][0]]]; + } + } + } elseif (\is_string($assertion->var_id) + && $expr instanceof PhpParser\Node\Expr\MethodCall + ) { + if ($prefix === $assertion->rule[0][0][0]) { + $if_types[$assertion->var_id] = [[substr($assertion->rule[0][0], 1)]]; + } else { + $if_types[$assertion->var_id] = [[$prefix . $assertion->rule[0][0]]]; + } + } + } + } + + if ($if_false_assertions) { + $negated_prefix = !$negate ? '!' : ''; + + foreach ($if_false_assertions as $assertion) { + $assertion = clone $assertion; + + foreach ($assertion->rule as $i => $and_rules) { + foreach ($and_rules as $j => $rule) { + if (strpos($rule, 'scalar-class-constant(') === 0) { + $codebase = $source->getCodebase(); + + $assertion->rule[$i][$j] = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + Type::parseString(substr($rule, 22, -1)), + null, + null, + null + )->getId(); + } + } + } + + if (is_int($assertion->var_id) && isset($expr->args[$assertion->var_id])) { + if ($assertion->var_id === 0) { + $var_name = $first_var_name; + } else { + $var_name = ExpressionIdentifier::getArrayVarId( + $expr->args[$assertion->var_id]->value, + $this_class_name, + $source + ); + } + + if ($var_name) { + if ($negated_prefix === $assertion->rule[0][0][0]) { + $if_types[$var_name] = [[substr($assertion->rule[0][0], 1)]]; + } else { + $if_types[$var_name] = [[$negated_prefix . $assertion->rule[0][0]]]; + } + } + } elseif ($assertion->var_id === '$this' && $expr instanceof PhpParser\Node\Expr\MethodCall) { + $var_id = ExpressionIdentifier::getArrayVarId( + $expr->var, + $this_class_name, + $source + ); + + if ($var_id) { + if ($negated_prefix === $assertion->rule[0][0][0]) { + $if_types[$var_id] = [[substr($assertion->rule[0][0], 1)]]; + } else { + $if_types[$var_id] = [[$negated_prefix . $assertion->rule[0][0]]]; + } + } + } elseif (\is_string($assertion->var_id) + && $expr instanceof PhpParser\Node\Expr\MethodCall + ) { + if ($prefix === $assertion->rule[0][0][0]) { + $if_types[$assertion->var_id] = [[substr($assertion->rule[0][0], 1)]]; + } else { + $if_types[$assertion->var_id] = [[$negated_prefix . $assertion->rule[0][0]]]; + } + } + } + } + + return $if_types; + } + + /** + * @return list + */ + protected static function getInstanceOfTypes( + PhpParser\Node\Expr\Instanceof_ $stmt, + ?string $this_class_name, + FileSource $source + ): array { + if ($stmt->class instanceof PhpParser\Node\Name) { + if (!in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)) { + $instanceof_class = ClassLikeAnalyzer::getFQCLNFromNameObject( + $stmt->class, + $source->getAliases() + ); + + if ($source instanceof StatementsAnalyzer) { + $codebase = $source->getCodebase(); + $instanceof_class = $codebase->classlikes->getUnAliasedName($instanceof_class); + } + + return [$instanceof_class]; + } elseif ($this_class_name + && (in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true)) + ) { + if ($stmt->class->parts[0] === 'static') { + return ['=' . $this_class_name . '&static']; + } + + return [$this_class_name]; + } + } elseif ($source instanceof StatementsAnalyzer) { + $stmt_class_type = $source->node_data->getType($stmt->class); + + if ($stmt_class_type) { + $literal_class_strings = []; + + foreach ($stmt_class_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TLiteralClassString) { + $literal_class_strings[] = $atomic_type->value; + } elseif ($atomic_type instanceof Type\Atomic\TTemplateParamClass) { + $literal_class_strings[] = $atomic_type->param_name; + } + } + + return $literal_class_strings; + } + } + + return []; + } + + protected static function hasNullVariable( + PhpParser\Node\Expr\BinaryOp $conditional, + FileSource $source + ): ?int { + if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch + && strtolower($conditional->right->name->parts[0]) === 'null' + ) { + return self::ASSIGNMENT_TO_RIGHT; + } + + if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch + && strtolower($conditional->left->name->parts[0]) === 'null' + ) { + return self::ASSIGNMENT_TO_LEFT; + } + + if ($source instanceof StatementsAnalyzer + && ($right_type = $source->node_data->getType($conditional->right)) + && $right_type->isNull() + ) { + return self::ASSIGNMENT_TO_RIGHT; + } + + return null; + } + + public static function hasFalseVariable(PhpParser\Node\Expr\BinaryOp $conditional): ?int + { + if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch + && strtolower($conditional->right->name->parts[0]) === 'false' + ) { + return self::ASSIGNMENT_TO_RIGHT; + } + + if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch + && strtolower($conditional->left->name->parts[0]) === 'false' + ) { + return self::ASSIGNMENT_TO_LEFT; + } + + return null; + } + + public static function hasTrueVariable(PhpParser\Node\Expr\BinaryOp $conditional): ?int + { + if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch + && strtolower($conditional->right->name->parts[0]) === 'true' + ) { + return self::ASSIGNMENT_TO_RIGHT; + } + + if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch + && strtolower($conditional->left->name->parts[0]) === 'true' + ) { + return self::ASSIGNMENT_TO_LEFT; + } + + return null; + } + + protected static function hasEmptyArrayVariable(PhpParser\Node\Expr\BinaryOp $conditional): ?int + { + if ($conditional->right instanceof PhpParser\Node\Expr\Array_ + && !$conditional->right->items + ) { + return self::ASSIGNMENT_TO_RIGHT; + } + + if ($conditional->left instanceof PhpParser\Node\Expr\Array_ + && !$conditional->left->items + ) { + return self::ASSIGNMENT_TO_LEFT; + } + + return null; + } + + /** + * @return false|int + */ + protected static function hasGetTypeCheck(PhpParser\Node\Expr\BinaryOp $conditional) + { + if ($conditional->right instanceof PhpParser\Node\Expr\FuncCall + && $conditional->right->name instanceof PhpParser\Node\Name + && strtolower($conditional->right->name->parts[0]) === 'gettype' + && $conditional->right->args + && $conditional->left instanceof PhpParser\Node\Scalar\String_ + ) { + return self::ASSIGNMENT_TO_RIGHT; + } + + if ($conditional->left instanceof PhpParser\Node\Expr\FuncCall + && $conditional->left->name instanceof PhpParser\Node\Name + && strtolower($conditional->left->name->parts[0]) === 'gettype' + && $conditional->left->args + && $conditional->right instanceof PhpParser\Node\Scalar\String_ + ) { + return self::ASSIGNMENT_TO_LEFT; + } + + return false; + } + + /** + * @return false|int + */ + protected static function hasGetDebugTypeCheck(PhpParser\Node\Expr\BinaryOp $conditional) + { + if ($conditional->right instanceof PhpParser\Node\Expr\FuncCall + && $conditional->right->name instanceof PhpParser\Node\Name + && strtolower($conditional->right->name->parts[0]) === 'get_debug_type' + && $conditional->right->args + && ($conditional->left instanceof PhpParser\Node\Scalar\String_ + || $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch) + ) { + return self::ASSIGNMENT_TO_RIGHT; + } + + if ($conditional->left instanceof PhpParser\Node\Expr\FuncCall + && $conditional->left->name instanceof PhpParser\Node\Name + && strtolower($conditional->left->name->parts[0]) === 'get_debug_type' + && $conditional->left->args + && ($conditional->right instanceof PhpParser\Node\Scalar\String_ + || $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch) + ) { + return self::ASSIGNMENT_TO_LEFT; + } + + return false; + } + + /** + * @return false|int + */ + protected static function hasGetClassCheck( + PhpParser\Node\Expr\BinaryOp $conditional, + FileSource $source + ) { + if (!$source instanceof StatementsAnalyzer) { + return false; + } + + $right_get_class = $conditional->right instanceof PhpParser\Node\Expr\FuncCall + && $conditional->right->name instanceof PhpParser\Node\Name + && strtolower($conditional->right->name->parts[0]) === 'get_class'; + + $right_static_class = $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch + && $conditional->right->class instanceof PhpParser\Node\Name + && $conditional->right->class->parts === ['static'] + && $conditional->right->name instanceof PhpParser\Node\Identifier + && strtolower($conditional->right->name->name) === 'class'; + + $left_class_string = $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch + && $conditional->left->class instanceof PhpParser\Node\Name + && $conditional->left->name instanceof PhpParser\Node\Identifier + && strtolower($conditional->left->name->name) === 'class'; + + $left_type = $source->node_data->getType($conditional->left); + + $left_class_string_t = false; + + if ($left_type && $left_type->isSingle()) { + foreach ($left_type->getAtomicTypes() as $type_part) { + if ($type_part instanceof Type\Atomic\TClassString) { + $left_class_string_t = true; + break; + } + } + } + + if (($right_get_class || $right_static_class) && ($left_class_string || $left_class_string_t)) { + return self::ASSIGNMENT_TO_RIGHT; + } + + $left_get_class = $conditional->left instanceof PhpParser\Node\Expr\FuncCall + && $conditional->left->name instanceof PhpParser\Node\Name + && strtolower($conditional->left->name->parts[0]) === 'get_class'; + + $left_static_class = $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch + && $conditional->left->class instanceof PhpParser\Node\Name + && $conditional->left->class->parts === ['static'] + && $conditional->left->name instanceof PhpParser\Node\Identifier + && strtolower($conditional->left->name->name) === 'class'; + + $right_class_string = $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch + && $conditional->right->class instanceof PhpParser\Node\Name + && $conditional->right->name instanceof PhpParser\Node\Identifier + && strtolower($conditional->right->name->name) === 'class'; + + $right_type = $source->node_data->getType($conditional->right); + + $right_class_string_t = false; + + if ($right_type && $right_type->isSingle()) { + foreach ($right_type->getAtomicTypes() as $type_part) { + if ($type_part instanceof Type\Atomic\TClassString) { + $right_class_string_t = true; + break; + } + } + } + + if (($left_get_class || $left_static_class) && ($right_class_string || $right_class_string_t)) { + return self::ASSIGNMENT_TO_LEFT; + } + + return false; + } + + /** + * @return false|int + */ + protected static function hasNonEmptyCountEqualityCheck( + PhpParser\Node\Expr\BinaryOp $conditional, + ?int &$min_count + ) { + $left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall + && $conditional->left->name instanceof PhpParser\Node\Name + && strtolower($conditional->left->name->parts[0]) === 'count' + && $conditional->left->args; + + $operator_greater_than_or_equal = + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual; + + if ($left_count + && $conditional->right instanceof PhpParser\Node\Scalar\LNumber + && $operator_greater_than_or_equal + && $conditional->right->value >= ( + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater + ? 0 + : 1 + ) + ) { + $min_count = $conditional->right->value + + ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 1 : 0); + + return self::ASSIGNMENT_TO_RIGHT; + } + + $right_count = $conditional->right instanceof PhpParser\Node\Expr\FuncCall + && $conditional->right->name instanceof PhpParser\Node\Name + && strtolower($conditional->right->name->parts[0]) === 'count' + && $conditional->right->args; + + $operator_less_than_or_equal = + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual; + + if ($right_count + && $conditional->left instanceof PhpParser\Node\Scalar\LNumber + && $operator_less_than_or_equal + && $conditional->left->value >= ( + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 0 : 1 + ) + ) { + $min_count = $conditional->left->value + + ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 1 : 0); + + return self::ASSIGNMENT_TO_LEFT; + } + + return false; + } + + /** + * @return false|int + */ + protected static function hasLessThanCountEqualityCheck( + PhpParser\Node\Expr\BinaryOp $conditional, + ?int &$max_count + ) { + $left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall + && $conditional->left->name instanceof PhpParser\Node\Name + && strtolower($conditional->left->name->parts[0]) === 'count' + && $conditional->left->args; + + $operator_less_than_or_equal = + $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller; + + if ($left_count + && $conditional->right instanceof PhpParser\Node\Scalar\LNumber + && $operator_less_than_or_equal + ) { + $max_count = $conditional->right->value - + ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 1 : 0); + + return self::ASSIGNMENT_TO_RIGHT; + } + + $right_count = $conditional->right instanceof PhpParser\Node\Expr\FuncCall + && $conditional->right->name instanceof PhpParser\Node\Name + && strtolower($conditional->right->name->parts[0]) === 'count' + && $conditional->right->args; + + $operator_greater_than_or_equal = + $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater; + + if ($right_count + && $conditional->left instanceof PhpParser\Node\Scalar\LNumber + && $operator_greater_than_or_equal + ) { + $max_count = $conditional->left->value - + ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 1 : 0); + + return self::ASSIGNMENT_TO_LEFT; + } + + return false; + } + + /** + * @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional + * + * @return false|int + */ + protected static function hasNotCountEqualityCheck( + PhpParser\Node\Expr\BinaryOp $conditional, + ?int &$count + ) { + $left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall + && $conditional->left->name instanceof PhpParser\Node\Name + && strtolower($conditional->left->name->parts[0]) === 'count' + && $conditional->left->args; + + if ($left_count && $conditional->right instanceof PhpParser\Node\Scalar\LNumber) { + $count = $conditional->right->value; + + return self::ASSIGNMENT_TO_RIGHT; + } + + $right_count = $conditional->right instanceof PhpParser\Node\Expr\FuncCall + && $conditional->right->name instanceof PhpParser\Node\Name + && strtolower($conditional->right->name->parts[0]) === 'count' + && $conditional->right->args; + + if ($right_count && $conditional->left instanceof PhpParser\Node\Scalar\LNumber) { + $count = $conditional->left->value; + + return self::ASSIGNMENT_TO_LEFT; + } + + return false; + } + + /** + * @return false|int + */ + protected static function hasPositiveNumberCheck( + PhpParser\Node\Expr\BinaryOp $conditional, + ?int &$min_count + ) { + $operator_greater_than_or_equal = + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual; + + if ($conditional->right instanceof PhpParser\Node\Scalar\LNumber + && $operator_greater_than_or_equal + && $conditional->right->value >= ( + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater + ? 0 + : 1 + ) + ) { + $min_count = $conditional->right->value + + ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 1 : 0); + + return self::ASSIGNMENT_TO_RIGHT; + } + + $operator_less_than_or_equal = + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual; + + if ($conditional->left instanceof PhpParser\Node\Scalar\LNumber + && $operator_less_than_or_equal + && $conditional->left->value >= ( + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 0 : 1 + ) + ) { + $min_count = $conditional->left->value + + ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 1 : 0); + + return self::ASSIGNMENT_TO_LEFT; + } + + return false; + } + + /** + * @return false|int + */ + protected static function hasReconcilableNonEmptyCountEqualityCheck(PhpParser\Node\Expr\BinaryOp $conditional) + { + $left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall + && $conditional->left->name instanceof PhpParser\Node\Name + && strtolower($conditional->left->name->parts[0]) === 'count'; + + $right_number = $conditional->right instanceof PhpParser\Node\Scalar\LNumber + && $conditional->right->value === ( + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 0 : 1); + + $operator_greater_than_or_equal = + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual; + + if ($left_count && $right_number && $operator_greater_than_or_equal) { + return self::ASSIGNMENT_TO_RIGHT; + } + + $right_count = $conditional->right instanceof PhpParser\Node\Expr\FuncCall + && $conditional->right->name instanceof PhpParser\Node\Name + && strtolower($conditional->right->name->parts[0]) === 'count'; + + $left_number = $conditional->left instanceof PhpParser\Node\Scalar\LNumber + && $conditional->left->value === ( + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 0 : 1); + + $operator_less_than_or_equal = + $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual; + + if ($right_count && $left_number && $operator_less_than_or_equal) { + return self::ASSIGNMENT_TO_LEFT; + } + + return false; + } + + /** + * @return false|int + */ + protected static function hasTypedValueComparison( + PhpParser\Node\Expr\BinaryOp $conditional, + FileSource $source + ) { + if (!$source instanceof StatementsAnalyzer) { + return false; + } + + if (($right_type = $source->node_data->getType($conditional->right)) + && ((!$conditional->right instanceof PhpParser\Node\Expr\Variable + && !$conditional->right instanceof PhpParser\Node\Expr\PropertyFetch + && !$conditional->right instanceof PhpParser\Node\Expr\StaticPropertyFetch) + || $conditional->left instanceof PhpParser\Node\Expr\Variable + || $conditional->left instanceof PhpParser\Node\Expr\PropertyFetch + || $conditional->left instanceof PhpParser\Node\Expr\StaticPropertyFetch) + && count($right_type->getAtomicTypes()) === 1 + && !$right_type->hasMixed() + ) { + return self::ASSIGNMENT_TO_RIGHT; + } + + if (($left_type = $source->node_data->getType($conditional->left)) + && !$conditional->left instanceof PhpParser\Node\Expr\Variable + && !$conditional->left instanceof PhpParser\Node\Expr\PropertyFetch + && !$conditional->left instanceof PhpParser\Node\Expr\StaticPropertyFetch + && count($left_type->getAtomicTypes()) === 1 + && !$left_type->hasMixed() + ) { + return self::ASSIGNMENT_TO_LEFT; + } + + return false; + } + + protected static function hasNullCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_null') { + return true; + } + + return false; + } + + protected static function hasIsACheck( + PhpParser\Node\Expr\FuncCall $stmt, + StatementsAnalyzer $source + ): bool { + if ($stmt->name instanceof PhpParser\Node\Name + && (strtolower($stmt->name->parts[0]) === 'is_a' + || strtolower($stmt->name->parts[0]) === 'is_subclass_of') + && isset($stmt->args[1]) + ) { + $second_arg = $stmt->args[1]->value; + + if ($second_arg instanceof PhpParser\Node\Scalar\String_ + || ( + $second_arg instanceof PhpParser\Node\Expr\ClassConstFetch + && $second_arg->class instanceof PhpParser\Node\Name + && $second_arg->name instanceof PhpParser\Node\Identifier + && strtolower($second_arg->name->name) === 'class' + ) + || (($second_arg_type = $source->node_data->getType($second_arg)) + && $second_arg_type->hasString()) + ) { + return true; + } + } + + return false; + } + + protected static function hasArrayCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_array') { + return true; + } + + return false; + } + + protected static function hasStringCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_string') { + return true; + } + + return false; + } + + protected static function hasBoolCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_bool') { + return true; + } + + return false; + } + + protected static function hasObjectCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_object']) { + return true; + } + + return false; + } + + protected static function hasNumericCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_numeric']) { + return true; + } + + return false; + } + + protected static function hasIterableCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_iterable') { + return true; + } + + return false; + } + + protected static function hasCountableCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_countable') { + return true; + } + + return false; + } + + /** + * @return 0|1|2 + */ + protected static function hasClassExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): int + { + if ($stmt->name instanceof PhpParser\Node\Name + && strtolower($stmt->name->parts[0]) === 'class_exists' + ) { + if (!isset($stmt->args[1])) { + return 2; + } + + $second_arg = $stmt->args[1]->value; + + if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch + && strtolower($second_arg->name->parts[0]) === 'true' + ) { + return 2; + } + + return 1; + } + + return 0; + } + + /** + * @return 0|1|2 + */ + protected static function hasTraitExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): int + { + if ($stmt->name instanceof PhpParser\Node\Name + && strtolower($stmt->name->parts[0]) === 'trait_exists' + ) { + if (!isset($stmt->args[1])) { + return 2; + } + + $second_arg = $stmt->args[1]->value; + + if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch + && strtolower($second_arg->name->parts[0]) === 'true' + ) { + return 2; + } + + return 1; + } + + return 0; + } + + protected static function hasInterfaceExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name + && strtolower($stmt->name->parts[0]) === 'interface_exists' + ) { + return true; + } + + return false; + } + + protected static function hasFunctionExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'function_exists') { + return true; + } + + return false; + } + + protected static function hasIntCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && + ($stmt->name->parts === ['is_int'] || + $stmt->name->parts === ['is_integer'] || + $stmt->name->parts === ['is_long']) + ) { + return true; + } + + return false; + } + + protected static function hasFloatCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && + ($stmt->name->parts === ['is_float'] || + $stmt->name->parts === ['is_real'] || + $stmt->name->parts === ['is_double']) + ) { + return true; + } + + return false; + } + + protected static function hasResourceCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_resource']) { + return true; + } + + return false; + } + + protected static function hasScalarCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_scalar']) { + return true; + } + + return false; + } + + protected static function hasCallableCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_callable']) { + return true; + } + + return false; + } + + protected static function hasInArrayCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name + && $stmt->name->parts === ['in_array'] + && isset($stmt->args[2]) + ) { + $second_arg = $stmt->args[2]->value; + + if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch + && strtolower($second_arg->name->parts[0]) === 'true' + ) { + return true; + } + } + + return false; + } + + protected static function hasNonEmptyCountCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name + && $stmt->name->parts === ['count'] + ) { + return true; + } + + return false; + } + + protected static function hasArrayKeyExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + { + if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['array_key_exists']) { + return true; + } + + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..b0b4e141332c9adb07b5fa7fe28ce0d22a9487d1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -0,0 +1,829 @@ +var, + $statements_analyzer->getFQCLN(), + $statements_analyzer, + $nesting + ); + + self::updateArrayType( + $statements_analyzer, + $stmt, + $assign_value, + $assignment_value_type, + $context + ); + + if (!$statements_analyzer->node_data->getType($stmt->var) && $var_id) { + $context->vars_in_scope[$var_id] = Type::getMixed(); + } + } + + /** + * @return false|null + */ + public static function updateArrayType( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\ArrayDimFetch $stmt, + ?PhpParser\Node\Expr $assign_value, + Type\Union $assignment_type, + Context $context + ): ?bool { + $root_array_expr = $stmt; + + $child_stmts = []; + + while ($root_array_expr->var instanceof PhpParser\Node\Expr\ArrayDimFetch) { + $child_stmts[] = $root_array_expr; + $root_array_expr = $root_array_expr->var; + } + + $child_stmts[] = $root_array_expr; + $root_array_expr = $root_array_expr->var; + + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $root_array_expr, + $context, + true + ) === false) { + // fall through + } + + $codebase = $statements_analyzer->getCodebase(); + + $root_type = $statements_analyzer->node_data->getType($root_array_expr) ?: Type::getMixed(); + + if ($root_type->hasMixed()) { + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $stmt->var, + $context, + true + ) === false) { + // fall through + } + + if ($stmt->dim) { + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $stmt->dim, + $context + ) === false) { + // fall through + } + } + } + + $child_stmts = array_reverse($child_stmts); + + $current_type = $root_type; + + $current_dim = $stmt->dim; + + $reversed_child_stmts = []; + + // gets a variable id that *may* contain array keys + $root_var_id = ExpressionIdentifier::getArrayVarId( + $root_array_expr, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + $var_id_additions = []; + + $parent_var_id = null; + + $offset_already_existed = false; + $full_var_id = true; + + $child_stmt = null; + + // First go from the root element up, and go as far as we can to figure out what + // array types there are + while ($child_stmts) { + $child_stmt = array_shift($child_stmts); + + if (count($child_stmts)) { + array_unshift($reversed_child_stmts, $child_stmt); + } + + $child_stmt_dim_type = null; + + $dim_value = null; + + if ($child_stmt->dim) { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $child_stmt->dim, + $context + ) === false) { + return false; + } + + $context->inside_use = $was_inside_use; + + if (!($child_stmt_dim_type = $statements_analyzer->node_data->getType($child_stmt->dim))) { + return null; + } + + if ($child_stmt->dim instanceof PhpParser\Node\Scalar\String_ + || (($child_stmt->dim instanceof PhpParser\Node\Expr\ConstFetch + || $child_stmt->dim instanceof PhpParser\Node\Expr\ClassConstFetch) + && $child_stmt_dim_type->isSingleStringLiteral()) + ) { + if ($child_stmt->dim instanceof PhpParser\Node\Scalar\String_) { + $dim_value = new Type\Atomic\TLiteralString($child_stmt->dim->value); + } else { + $dim_value = $child_stmt_dim_type->getSingleStringLiteral(); + } + + if (preg_match('/^(0|[1-9][0-9]*)$/', $dim_value->value)) { + $var_id_additions[] = '[' . $dim_value->value . ']'; + } else { + $var_id_additions[] = '[\'' . $dim_value->value . '\']'; + } + } elseif ($child_stmt->dim instanceof PhpParser\Node\Scalar\LNumber + || (($child_stmt->dim instanceof PhpParser\Node\Expr\ConstFetch + || $child_stmt->dim instanceof PhpParser\Node\Expr\ClassConstFetch) + && $child_stmt_dim_type->isSingleIntLiteral()) + ) { + if ($child_stmt->dim instanceof PhpParser\Node\Scalar\LNumber) { + $dim_value = new Type\Atomic\TLiteralInt($child_stmt->dim->value); + } else { + $dim_value = $child_stmt_dim_type->getSingleIntLiteral(); + } + + $var_id_additions[] = '[' . $dim_value->value . ']'; + } elseif ($child_stmt->dim instanceof PhpParser\Node\Expr\Variable + && is_string($child_stmt->dim->name) + ) { + $var_id_additions[] = '[$' . $child_stmt->dim->name . ']'; + } elseif ($child_stmt->dim instanceof PhpParser\Node\Expr\PropertyFetch + && $child_stmt->dim->name instanceof PhpParser\Node\Identifier + ) { + $object_id = ExpressionIdentifier::getArrayVarId( + $child_stmt->dim->var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($object_id) { + $var_id_additions[] = '[' . $object_id . '->' . $child_stmt->dim->name->name . ']'; + } + } elseif ($child_stmt->dim instanceof PhpParser\Node\Expr\ClassConstFetch + && $child_stmt->dim->name instanceof PhpParser\Node\Identifier + && $child_stmt->dim->class instanceof PhpParser\Node\Name + ) { + $object_name = ClassLikeAnalyzer::getFQCLNFromNameObject( + $child_stmt->dim->class, + $statements_analyzer->getAliases() + ); + $var_id_additions[] = '[' . $object_name . '::' . $child_stmt->dim->name->name . ']'; + } else { + $var_id_additions[] = '[' . $child_stmt_dim_type . ']'; + $full_var_id = false; + } + } else { + $var_id_additions[] = ''; + $full_var_id = false; + } + + if (!($child_stmt_var_type = $statements_analyzer->node_data->getType($child_stmt->var))) { + return null; + } + + if ($child_stmt_var_type->isEmpty()) { + $child_stmt_var_type = Type::getEmptyArray(); + $statements_analyzer->node_data->setType($child_stmt->var, $child_stmt_var_type); + } + + $array_var_id = $root_var_id . implode('', $var_id_additions); + + if ($parent_var_id && isset($context->vars_in_scope[$parent_var_id])) { + $child_stmt_var_type = clone $context->vars_in_scope[$parent_var_id]; + $statements_analyzer->node_data->setType($child_stmt->var, $child_stmt_var_type); + } + + $array_type = clone $child_stmt_var_type; + + $child_stmt_type = ArrayFetchAnalyzer::getArrayAccessTypeGivenOffset( + $statements_analyzer, + $child_stmt, + $array_type, + $child_stmt_dim_type ?: Type::getInt(), + true, + $array_var_id, + $context, + $assign_value, + $child_stmts ? null : $assignment_type + ); + + $statements_analyzer->node_data->setType( + $child_stmt, + $child_stmt_type + ); + + $statements_analyzer->node_data->setType($child_stmt->var, $array_type); + + if ($root_var_id) { + if (!$parent_var_id) { + $rooted_parent_id = $root_var_id; + $root_type = $array_type; + } else { + $rooted_parent_id = $parent_var_id; + } + + $context->vars_in_scope[$rooted_parent_id] = $array_type; + $context->possibly_assigned_var_ids[$rooted_parent_id] = true; + } + + if (!$child_stmts) { + // we need this slight hack as the type we're putting it has to be + // different from the type we're getting out + if ($array_type->isSingle() && $array_type->hasClassStringMap()) { + $assignment_type = $child_stmt_type; + } + + $child_stmt_type = $assignment_type; + $statements_analyzer->node_data->setType($child_stmt, $assignment_type); + + if ($statements_analyzer->data_flow_graph) { + self::taintArrayAssignment( + $statements_analyzer, + $child_stmt, + $array_type, + $assignment_type, + ExpressionIdentifier::getArrayVarId( + $child_stmt->var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ), + $dim_value !== null ? [$dim_value] : [] + ); + } + } + + $current_type = $child_stmt_type; + $current_dim = $child_stmt->dim; + + $parent_var_id = $array_var_id; + } + + if ($root_var_id + && $full_var_id + && $child_stmt + && ($child_stmt_var_type = $statements_analyzer->node_data->getType($child_stmt->var)) + && !$child_stmt_var_type->hasObjectType() + ) { + $array_var_id = $root_var_id . implode('', $var_id_additions); + $parent_var_id = $root_var_id . implode('', \array_slice($var_id_additions, 0, -1)); + + if (isset($context->vars_in_scope[$array_var_id]) + && !$context->vars_in_scope[$array_var_id]->possibly_undefined + ) { + $offset_already_existed = true; + } + + $context->vars_in_scope[$array_var_id] = clone $assignment_type; + $context->possibly_assigned_var_ids[$array_var_id] = true; + } + + // only update as many child stmts are we were able to process above + foreach ($reversed_child_stmts as $child_stmt) { + $child_stmt_type = $statements_analyzer->node_data->getType($child_stmt); + + if (!$child_stmt_type) { + throw new \InvalidArgumentException('Should never get here'); + } + + $key_values = []; + + if ($current_dim instanceof PhpParser\Node\Scalar\String_) { + $key_values[] = new Type\Atomic\TLiteralString($current_dim->value); + } elseif ($current_dim instanceof PhpParser\Node\Scalar\LNumber) { + $key_values[] = new Type\Atomic\TLiteralInt($current_dim->value); + } elseif ($current_dim + && ($current_dim_type = $statements_analyzer->node_data->getType($current_dim)) + ) { + $string_literals = $current_dim_type->getLiteralStrings(); + $int_literals = $current_dim_type->getLiteralInts(); + + $all_atomic_types = $current_dim_type->getAtomicTypes(); + + if (count($string_literals) + count($int_literals) === count($all_atomic_types)) { + foreach ($string_literals as $string_literal) { + $key_values[] = clone $string_literal; + } + + foreach ($int_literals as $int_literal) { + $key_values[] = clone $int_literal; + } + } + } + + if ($key_values) { + $new_child_type = self::updateTypeWithKeyValues( + $codebase, + $child_stmt_type, + $current_type, + $key_values + ); + } else { + if (!$current_dim) { + $array_assignment_type = new Type\Union([ + new TList($current_type), + ]); + } else { + $current_dim_type = $statements_analyzer->node_data->getType($current_dim); + + $array_assignment_type = new Type\Union([ + new TArray([ + $current_dim_type && !$current_dim_type->hasMixed() + ? $current_dim_type + : Type::getArrayKey(), + $current_type, + ]), + ]); + } + + $new_child_type = Type::combineUnionTypes( + $child_stmt_type, + $array_assignment_type, + $codebase, + true, + true + ); + } + + $new_child_type->removeType('null'); + $new_child_type->possibly_undefined = false; + + if (!$child_stmt_type->hasObjectType()) { + $child_stmt_type = $new_child_type; + $statements_analyzer->node_data->setType($child_stmt, $new_child_type); + } + + $current_type = $child_stmt_type; + $current_dim = $child_stmt->dim; + + array_pop($var_id_additions); + + $parent_array_var_id = null; + + if ($root_var_id) { + $array_var_id = $root_var_id . implode('', $var_id_additions); + $parent_array_var_id = $root_var_id . implode('', \array_slice($var_id_additions, 0, -1)); + $context->vars_in_scope[$array_var_id] = clone $child_stmt_type; + $context->possibly_assigned_var_ids[$array_var_id] = true; + } + + if ($statements_analyzer->data_flow_graph) { + self::taintArrayAssignment( + $statements_analyzer, + $child_stmt, + $statements_analyzer->node_data->getType($child_stmt->var) ?: Type::getMixed(), + $new_child_type, + $parent_array_var_id, + $key_values + ); + } + } + + $root_is_string = $root_type->isString(); + $key_values = []; + + if ($current_dim instanceof PhpParser\Node\Scalar\String_) { + $key_values[] = new Type\Atomic\TLiteralString($current_dim->value); + } elseif ($current_dim instanceof PhpParser\Node\Scalar\LNumber && !$root_is_string) { + $key_values[] = new Type\Atomic\TLiteralInt($current_dim->value); + } elseif ($current_dim + && ($current_dim_type = $statements_analyzer->node_data->getType($current_dim)) + && !$root_is_string + ) { + $string_literals = $current_dim_type->getLiteralStrings(); + $int_literals = $current_dim_type->getLiteralInts(); + + $all_atomic_types = $current_dim_type->getAtomicTypes(); + + if (count($string_literals) + count($int_literals) === count($all_atomic_types)) { + foreach ($string_literals as $string_literal) { + $key_values[] = clone $string_literal; + } + + foreach ($int_literals as $int_literal) { + $key_values[] = clone $int_literal; + } + } + } + + if ($key_values) { + $new_child_type = self::updateTypeWithKeyValues( + $codebase, + $root_type, + $current_type, + $key_values + ); + } elseif (!$root_is_string) { + if ($current_dim) { + if ($current_dim_type = $statements_analyzer->node_data->getType($current_dim)) { + if ($current_dim_type->hasMixed()) { + $current_dim_type = Type::getArrayKey(); + } + + $array_atomic_key_type = ArrayFetchAnalyzer::replaceOffsetTypeWithInts( + $current_dim_type + ); + } else { + $array_atomic_key_type = Type::getArrayKey(); + } + + if ($offset_already_existed + && $child_stmt + && $parent_var_id + && ($parent_type = $context->vars_in_scope[$parent_var_id] ?? null) + ) { + if ($parent_type->hasList()) { + $array_atomic_type = new TNonEmptyList( + $current_type + ); + } elseif ($parent_type->hasClassStringMap() + && $current_dim_type + && $current_dim_type->isTemplatedClassString() + ) { + /** + * @var Type\Atomic\TClassStringMap + * @psalm-suppress PossiblyUndefinedStringArrayOffset + */ + $class_string_map = $parent_type->getAtomicTypes()['array']; + /** + * @var Type\Atomic\TTemplateParamClass + */ + $offset_type_part = \array_values($current_dim_type->getAtomicTypes())[0]; + + $template_result = new \Psalm\Internal\Type\TemplateResult( + [], + [ + $offset_type_part->param_name => [ + $offset_type_part->defining_class => [ + new Type\Union([ + new Type\Atomic\TTemplateParam( + $class_string_map->param_name, + $offset_type_part->as_type + ? new Type\Union([$offset_type_part->as_type]) + : Type::getObject(), + 'class-string-map' + ) + ]) + ] + ] + ] + ); + + $current_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + + $array_atomic_type = new Type\Atomic\TClassStringMap( + $class_string_map->param_name, + $class_string_map->as_type, + $current_type + ); + } else { + $array_atomic_type = new TNonEmptyArray([ + $array_atomic_key_type, + $current_type, + ]); + } + } else { + $array_atomic_type = new TNonEmptyArray([ + $array_atomic_key_type, + $current_type, + ]); + } + } else { + $array_atomic_type = new TNonEmptyList($current_type); + } + + $from_countable_object_like = false; + + $new_child_type = null; + + if (!$current_dim && !$context->inside_loop) { + $atomic_root_types = $root_type->getAtomicTypes(); + + if (isset($atomic_root_types['array'])) { + if ($array_atomic_type instanceof Type\Atomic\TClassStringMap) { + $array_atomic_type = new TNonEmptyArray([ + $array_atomic_type->getStandinKeyParam(), + $array_atomic_type->value_param + ]); + } elseif ($atomic_root_types['array'] instanceof TNonEmptyArray + || $atomic_root_types['array'] instanceof TNonEmptyList + ) { + $array_atomic_type->count = $atomic_root_types['array']->count; + } elseif ($atomic_root_types['array'] instanceof TKeyedArray + && $atomic_root_types['array']->sealed + ) { + $array_atomic_type->count = count($atomic_root_types['array']->properties); + $from_countable_object_like = true; + + if ($atomic_root_types['array']->is_list + && $array_atomic_type instanceof TList + ) { + $array_atomic_type = clone $atomic_root_types['array']; + + $new_child_type = new Type\Union([$array_atomic_type]); + + $new_child_type->parent_nodes = $root_type->parent_nodes; + } + } elseif ($array_atomic_type instanceof TList) { + $array_atomic_type = new TNonEmptyList( + $array_atomic_type->type_param + ); + } else { + $array_atomic_type = new TNonEmptyArray( + $array_atomic_type->type_params + ); + } + } + } + + $array_assignment_type = new Type\Union([ + $array_atomic_type, + ]); + + if (!$new_child_type) { + $new_child_type = Type::combineUnionTypes( + $root_type, + $array_assignment_type, + $codebase, + true, + true + ); + } + + if ($from_countable_object_like) { + $atomic_root_types = $new_child_type->getAtomicTypes(); + + if (isset($atomic_root_types['array']) + && ($atomic_root_types['array'] instanceof TNonEmptyArray + || $atomic_root_types['array'] instanceof TNonEmptyList) + && $atomic_root_types['array']->count !== null + ) { + $atomic_root_types['array']->count++; + } + } + } else { + $new_child_type = $root_type; + } + + $new_child_type->removeType('null'); + + if (!$root_type->hasObjectType()) { + $root_type = $new_child_type; + } + + $statements_analyzer->node_data->setType($root_array_expr, $root_type); + + if ($root_array_expr instanceof PhpParser\Node\Expr\PropertyFetch) { + if ($root_array_expr->name instanceof PhpParser\Node\Identifier) { + InstancePropertyAssignmentAnalyzer::analyze( + $statements_analyzer, + $root_array_expr, + $root_array_expr->name->name, + null, + $root_type, + $context, + false + ); + } else { + if (ExpressionAnalyzer::analyze($statements_analyzer, $root_array_expr->name, $context) === false) { + return false; + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $root_array_expr->var, $context) === false) { + return false; + } + } + } elseif ($root_array_expr instanceof PhpParser\Node\Expr\StaticPropertyFetch + && $root_array_expr->name instanceof PhpParser\Node\Identifier + ) { + StaticPropertyAssignmentAnalyzer::analyze( + $statements_analyzer, + $root_array_expr, + null, + $root_type, + $context + ); + } elseif ($root_var_id) { + $context->vars_in_scope[$root_var_id] = $root_type; + } + + if ($root_array_expr instanceof PhpParser\Node\Expr\MethodCall + || $root_array_expr instanceof PhpParser\Node\Expr\StaticCall + || $root_array_expr instanceof PhpParser\Node\Expr\FuncCall + ) { + if ($root_type->hasArray()) { + if (IssueBuffer::accepts( + new InvalidArrayAssignment( + 'Assigning to the output of a function has no effect', + new \Psalm\CodeLocation($statements_analyzer->getSource(), $root_array_expr) + ), + $statements_analyzer->getSuppressedIssues() + ) + ) { + // do nothing + } + } + } + + return null; + } + + /** + * @param non-empty-list $key_values + */ + private static function updateTypeWithKeyValues( + \Psalm\Codebase $codebase, + Type\Union $child_stmt_type, + Type\Union $current_type, + array $key_values + ) : Type\Union { + $has_matching_objectlike_property = false; + $has_matching_string = false; + + foreach ($child_stmt_type->getAtomicTypes() as $type) { + foreach ($key_values as $key_value) { + if ($type instanceof TKeyedArray) { + if (isset($type->properties[$key_value->value])) { + $has_matching_objectlike_property = true; + + $type->properties[$key_value->value] = clone $current_type; + } + } elseif ($type instanceof Type\Atomic\TString + && $key_value instanceof Type\Atomic\TLiteralInt + ) { + $has_matching_string = true; + + if ($type instanceof Type\Atomic\TLiteralString + && $current_type->isSingleStringLiteral() + ) { + $new_char = $current_type->getSingleStringLiteral()->value; + + if (\strlen($new_char) === 1) { + $type->value[0] = $new_char; + } + } + } elseif ($type instanceof TNonEmptyList + && $key_value instanceof Type\Atomic\TLiteralInt + && count($key_values) === 1 + ) { + $has_matching_objectlike_property = true; + + $type->type_param = Type::combineUnionTypes( + clone $current_type, + $type->type_param, + $codebase, + true, + false + ); + } + } + } + + $child_stmt_type->bustCache(); + + if (!$has_matching_objectlike_property && !$has_matching_string) { + if (count($key_values) === 1) { + $key_value = $key_values[0]; + + $object_like = new TKeyedArray( + [$key_value->value => clone $current_type], + $key_value instanceof Type\Atomic\TLiteralClassString + ? [(string) $key_value->value => true] + : null + ); + + $object_like->sealed = true; + + $array_assignment_type = new Type\Union([ + $object_like, + ]); + } else { + $array_assignment_literals = $key_values; + + $array_assignment_type = new Type\Union([ + new Type\Atomic\TNonEmptyArray([ + new Type\Union($array_assignment_literals), + clone $current_type + ]) + ]); + } + + return Type::combineUnionTypes( + $child_stmt_type, + $array_assignment_type, + $codebase, + true, + false + ); + } + + return $child_stmt_type; + } + + /** + * @param list $key_values $key_values + */ + private static function taintArrayAssignment( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\ArrayDimFetch $expr, + Type\Union $stmt_type, + Type\Union $child_stmt_type, + ?string $var_var_id, + array $key_values + ) : void { + if ($statements_analyzer->data_flow_graph + && ($statements_analyzer->data_flow_graph instanceof VariableUseGraph + || !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())) + ) { + if (!$stmt_type->parent_nodes) { + $var_location = new \Psalm\CodeLocation($statements_analyzer->getSource(), $expr->var); + + $parent_node = \Psalm\Internal\DataFlow\DataFlowNode::getForAssignment( + $var_var_id ?: 'assignment', + $var_location + ); + + $statements_analyzer->data_flow_graph->addNode($parent_node); + + $stmt_type->parent_nodes = [$parent_node->id => $parent_node]; + } + + foreach ($stmt_type->parent_nodes as $parent_node) { + foreach ($child_stmt_type->parent_nodes as $child_parent_node) { + if ($key_values) { + foreach ($key_values as $key_value) { + $statements_analyzer->data_flow_graph->addPath( + $child_parent_node, + $parent_node, + 'array-assignment-\'' . $key_value->value . '\'' + ); + } + } else { + $statements_analyzer->data_flow_graph->addPath( + $child_parent_node, + $parent_node, + 'array-assignment' + ); + } + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..8f17de3f09405f5083abd0103affdb8bfccbbf70 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php @@ -0,0 +1,1267 @@ +getCodebase(); + + $property_exists = false; + + $property_ids = []; + + if ($stmt instanceof PropertyProperty) { + if (!$context->self || !$stmt->default) { + return null; + } + + $property_id = $context->self . '::$' . $prop_name; + $property_ids[] = $property_id; + + $property_exists = true; + + try { + $class_property_type = $codebase->properties->getPropertyType( + $property_id, + true, + $statements_analyzer, + $context + ); + } catch (\UnexpectedValueException $e) { + return false; + } + + if ($class_property_type) { + $class_storage = $codebase->classlike_storage_provider->get($context->self); + + $class_property_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + clone $class_property_type, + $class_storage->name, + $class_storage->name, + $class_storage->parent_class + ); + } + + $class_property_types[] = $class_property_type ?: Type::getMixed(); + + $var_id = '$this->' . $prop_name; + } else { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->var, $context) === false) { + return false; + } + + $context->inside_use = $was_inside_use; + + $lhs_type = $statements_analyzer->node_data->getType($stmt->var); + + if ($lhs_type === null) { + return null; + } + + $lhs_var_id = ExpressionIdentifier::getVarId( + $stmt->var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + $var_id = ExpressionIdentifier::getVarId( + $stmt, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($var_id) { + $context->assigned_var_ids[$var_id] = true; + + if ($direct_assignment && isset($context->protected_var_ids[$var_id])) { + if (IssueBuffer::accepts( + new LoopInvalidation( + 'Variable ' . $var_id . ' has already been assigned in a for/foreach loop', + new CodeLocation($statements_analyzer->getSource(), $stmt->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($lhs_type->hasMixed()) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + if ($stmt->name instanceof PhpParser\Node\Identifier) { + $codebase->analyzer->addMixedMemberName( + '$' . $stmt->name->name, + $context->calling_method_id ?: $statements_analyzer->getFileName() + ); + } + + if (IssueBuffer::accepts( + new MixedPropertyAssignment( + $lhs_var_id . ' of type mixed cannot be assigned to', + new CodeLocation($statements_analyzer->getSource(), $stmt->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return null; + } + + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } + + if ($lhs_type->isNull()) { + if (IssueBuffer::accepts( + new NullPropertyAssignment( + $lhs_var_id . ' of type null cannot be assigned to', + new CodeLocation($statements_analyzer->getSource(), $stmt->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return null; + } + + if ($lhs_type->isNullable() && !$lhs_type->ignore_nullable_issues) { + if (IssueBuffer::accepts( + new PossiblyNullPropertyAssignment( + $lhs_var_id . ' with possibly null type \'' . $lhs_type . '\' cannot be assigned to', + new CodeLocation($statements_analyzer->getSource(), $stmt->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + + $has_regular_setter = false; + + $invalid_assignment_types = []; + + $has_valid_assignment_type = false; + + $lhs_atomic_types = $lhs_type->getAtomicTypes(); + + while ($lhs_atomic_types) { + $lhs_type_part = \array_pop($lhs_atomic_types); + + if ($lhs_type_part instanceof Type\Atomic\TTemplateParam) { + $lhs_atomic_types = \array_merge( + $lhs_atomic_types, + $lhs_type_part->as->getAtomicTypes() + ); + + continue; + } + + if ($lhs_type_part instanceof TNull) { + continue; + } + + if ($lhs_type_part instanceof Type\Atomic\TFalse + && $lhs_type->ignore_falsable_issues + && count($lhs_type->getAtomicTypes()) > 1 + ) { + continue; + } + + if (!$lhs_type_part instanceof TObject && !$lhs_type_part instanceof TNamedObject) { + $invalid_assignment_types[] = (string)$lhs_type_part; + + continue; + } + + $has_valid_assignment_type = true; + + // stdClass and SimpleXMLElement are special cases where we cannot infer the return types + // but we don't want to throw an error + // Hack has a similar issue: https://github.com/facebook/hhvm/issues/5164 + if ($lhs_type_part instanceof TObject || + ( + in_array( + strtolower($lhs_type_part->value), + Config::getInstance()->getUniversalObjectCrates() + [ + 'dateinterval', + 'domdocument', + 'domnode' + ], + true + ) + ) + ) { + if ($var_id) { + if ($lhs_type_part instanceof TNamedObject && + strtolower($lhs_type_part->value) === 'stdclass' + ) { + $context->vars_in_scope[$var_id] = $assignment_value_type; + } else { + $context->vars_in_scope[$var_id] = Type::getMixed(); + } + } + + return null; + } + + if (ExpressionAnalyzer::isMock($lhs_type_part->value)) { + if ($var_id) { + $context->vars_in_scope[$var_id] = Type::getMixed(); + } + + return null; + } + + $intersection_types = $lhs_type_part->getIntersectionTypes() ?: []; + + $fq_class_name = $lhs_type_part->value; + + $override_property_visibility = false; + + $class_exists = false; + $interface_exists = false; + + if (!$codebase->classExists($lhs_type_part->value)) { + if ($codebase->interfaceExists($lhs_type_part->value)) { + $interface_exists = true; + $interface_storage = $codebase->classlike_storage_provider->get( + strtolower($lhs_type_part->value) + ); + + $override_property_visibility = $interface_storage->override_property_visibility; + + foreach ($intersection_types as $intersection_type) { + if ($intersection_type instanceof TNamedObject + && $codebase->classExists($intersection_type->value) + ) { + $fq_class_name = $intersection_type->value; + $class_exists = true; + break; + } + } + + if (!$class_exists) { + if (IssueBuffer::accepts( + new NoInterfaceProperties( + 'Interfaces cannot have properties', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $lhs_type_part->value + ), + $statements_analyzer->getSuppressedIssues() + )) { + return null; + } + + if (!$codebase->methods->methodExists( + new \Psalm\Internal\MethodIdentifier( + $fq_class_name, + '__set' + ) + )) { + return null; + } + } + } + + if (!$class_exists && !$interface_exists) { + if (IssueBuffer::accepts( + new UndefinedClass( + 'Cannot set properties of undefined class ' . $lhs_type_part->value, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $lhs_type_part->value + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return null; + } + } else { + $class_exists = true; + } + + $property_id = $fq_class_name . '::$' . $prop_name; + $property_ids[] = $property_id; + + $has_magic_setter = false; + + $set_method_id = new \Psalm\Internal\MethodIdentifier($fq_class_name, '__set'); + + if ((!$codebase->properties->propertyExists($property_id, false, $statements_analyzer, $context) + || ($lhs_var_id !== '$this' + && $fq_class_name !== $context->self + && ClassLikeAnalyzer::checkPropertyVisibility( + $property_id, + $context, + $statements_analyzer, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues(), + false + ) !== true) + ) + && $codebase->methods->methodExists( + $set_method_id, + $context->calling_method_id, + $codebase->collect_locations + ? new CodeLocation($statements_analyzer->getSource(), $stmt) + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $statements_analyzer + : null, + $statements_analyzer->getFilePath() + ) + ) { + $has_magic_setter = true; + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + if ($var_id) { + if (isset($class_storage->pseudo_property_set_types['$' . $prop_name])) { + $class_property_types[] = + clone $class_storage->pseudo_property_set_types['$' . $prop_name]; + + $has_regular_setter = true; + $property_exists = true; + + if (!$context->collect_initializations) { + self::taintProperty( + $statements_analyzer, + $stmt, + $property_id, + $class_storage, + $assignment_value_type, + $context + ); + } + + continue; + } + } + + if ($assignment_value) { + if ($var_id) { + $context->removeVarFromConflictingClauses( + $var_id, + Type::getMixed(), + $statements_analyzer + ); + + unset($context->vars_in_scope[$var_id]); + } + + $old_data_provider = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $fake_method_call = new PhpParser\Node\Expr\MethodCall( + $stmt->var, + new PhpParser\Node\Identifier('__set', $stmt->name->getAttributes()), + [ + new PhpParser\Node\Arg( + new PhpParser\Node\Scalar\String_( + $prop_name, + $stmt->name->getAttributes() + ) + ), + new PhpParser\Node\Arg( + $assignment_value + ) + ] + ); + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('PossiblyNullReference', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['PossiblyNullReference']); + } + + \Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_method_call, + $context, + false + ); + + if (!in_array('PossiblyNullReference', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['PossiblyNullReference']); + } + + $statements_analyzer->node_data = $old_data_provider; + } + + /* + * If we have an explicit list of all allowed magic properties on the class, and we're + * not in that list, fall through + */ + if (!$var_id || !$class_storage->sealed_properties) { + if (!$context->collect_initializations) { + self::taintProperty( + $statements_analyzer, + $stmt, + $property_id, + $class_storage, + $assignment_value_type, + $context + ); + } + + continue; + } + + if (!$class_exists) { + if (IssueBuffer::accepts( + new UndefinedMagicPropertyAssignment( + 'Magic instance property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if (!$class_exists) { + continue; + } + + $has_regular_setter = true; + + if ($stmt->var instanceof PhpParser\Node\Expr\Variable + && $stmt->var->name === 'this' + && $context->self + ) { + $self_property_id = $context->self . '::$' . $prop_name; + + if ($self_property_id !== $property_id + && $codebase->properties->propertyExists( + $self_property_id, + false, + $statements_analyzer, + $context + ) + ) { + $property_id = $self_property_id; + } + } + + if ($statements_analyzer->data_flow_graph && !$context->collect_initializations) { + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + self::taintProperty( + $statements_analyzer, + $stmt, + $property_id, + $class_storage, + $assignment_value_type, + $context + ); + } + + if (!$codebase->properties->propertyExists( + $property_id, + false, + $statements_analyzer, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt) + )) { + if ($stmt->var instanceof PhpParser\Node\Expr\Variable && $stmt->var->name === 'this') { + // if this is a proper error, we'll see it on the first pass + if ($context->collect_mutations) { + continue; + } + + if (IssueBuffer::accepts( + new UndefinedThisPropertyAssignment( + 'Instance property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if ($has_magic_setter) { + if (IssueBuffer::accepts( + new UndefinedMagicPropertyAssignment( + 'Magic instance property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new UndefinedPropertyAssignment( + 'Instance property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + continue; + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->name, + $property_id + ); + } + + $property_exists = true; + + if (!$override_property_visibility) { + if (!$context->collect_mutations) { + if (ClassLikeAnalyzer::checkPropertyVisibility( + $property_id, + $context, + $statements_analyzer, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues() + ) === false) { + return false; + } + } else { + if (ClassLikeAnalyzer::checkPropertyVisibility( + $property_id, + $context, + $statements_analyzer, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues(), + false + ) !== true) { + continue; + } + } + } + + $declaring_property_class = (string) $codebase->properties->getDeclaringClassForProperty( + $property_id, + false + ); + + if ($codebase->properties_to_rename) { + $declaring_property_id = strtolower($declaring_property_class) . '::$' . $prop_name; + + foreach ($codebase->properties_to_rename as $original_property_id => $new_property_name) { + if ($declaring_property_id === $original_property_id) { + $file_manipulations = [ + new \Psalm\FileManipulation( + (int) $stmt->name->getAttribute('startFilePos'), + (int) $stmt->name->getAttribute('endFilePos') + 1, + $new_property_name + ) + ]; + + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $statements_analyzer->getFilePath(), + $file_manipulations + ); + } + } + } + + $declaring_class_storage = $codebase->classlike_storage_provider->get($declaring_property_class); + + if (isset($declaring_class_storage->properties[$prop_name])) { + $property_storage = $declaring_class_storage->properties[$prop_name]; + + if ($property_storage->deprecated) { + if (IssueBuffer::accepts( + new DeprecatedProperty( + $property_id . ' is marked deprecated', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($context->self && ! NamespaceAnalyzer::isWithin($context->self, $property_storage->internal)) { + if (IssueBuffer::accepts( + new InternalProperty( + $property_id . ' is internal to ' . $property_storage->internal + . ' but called from ' . $context->self, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + self::trackPropertyImpurity( + $statements_analyzer, + $stmt, + $property_id, + $property_storage, + $declaring_class_storage, + $context + ); + + if (!$property_storage->readonly + && !$context->collect_mutations + && !$context->collect_initializations + && isset($context->vars_in_scope[$lhs_var_id]) + && !$context->vars_in_scope[$lhs_var_id]->allow_mutations + ) { + if ($context->mutation_free) { + if (IssueBuffer::accepts( + new ImpurePropertyAssignment( + 'Cannot assign to a property from a mutation-free context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_impure = true; + } + } + + if ($property_storage->getter_method) { + $getter_id = $lhs_var_id . '->' . $property_storage->getter_method . '()'; + + unset($context->vars_in_scope[$getter_id]); + } + } + + $class_property_type = $codebase->properties->getPropertyType( + $property_id, + true, + $statements_analyzer, + $context + ); + + if (!$class_property_type + || (isset($declaring_class_storage->properties[$prop_name]) + && !$declaring_class_storage->properties[$prop_name]->type_location) + ) { + if (!$class_property_type) { + $class_property_type = Type::getMixed(); + } + + $source_analyzer = $statements_analyzer->getSource()->getSource(); + + if ($lhs_var_id === '$this' + && $source_analyzer instanceof ClassAnalyzer + ) { + if (isset($source_analyzer->inferred_property_types[$prop_name])) { + $source_analyzer->inferred_property_types[$prop_name] = Type::combineUnionTypes( + $assignment_value_type, + $source_analyzer->inferred_property_types[$prop_name] + ); + } else { + $source_analyzer->inferred_property_types[$prop_name] = $assignment_value_type; + } + } + } + + if (!$class_property_type->isMixed()) { + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + $class_property_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + clone $class_property_type, + $fq_class_name, + $lhs_type_part, + $declaring_class_storage->parent_class, + true, + false, + $class_storage->final + ); + + $class_property_type = \Psalm\Internal\Codebase\Methods::localizeType( + $codebase, + $class_property_type, + $fq_class_name, + $declaring_property_class + ); + + if ($lhs_type_part instanceof Type\Atomic\TGenericObject) { + $class_property_type = AtomicPropertyFetchAnalyzer::localizePropertyType( + $codebase, + $class_property_type, + $lhs_type_part, + $class_storage, + $declaring_class_storage + ); + } + + $assignment_value_type = \Psalm\Internal\Codebase\Methods::localizeType( + $codebase, + $assignment_value_type, + $fq_class_name, + $declaring_property_class + ); + + if (!$class_property_type->hasMixed() && $assignment_value_type->hasMixed()) { + if (IssueBuffer::accepts( + new MixedAssignment( + 'Cannot assign' . ($var_id ? ' ' . $var_id . ' ' : ' ') . 'to a mixed type', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + $class_property_types[] = $class_property_type; + } + + if ($invalid_assignment_types) { + $invalid_assignment_type = $invalid_assignment_types[0]; + + if (!$has_valid_assignment_type) { + if (IssueBuffer::accepts( + new InvalidPropertyAssignment( + $lhs_var_id . ' with non-object type \'' . $invalid_assignment_type . + '\' cannot treated as an object', + new CodeLocation($statements_analyzer->getSource(), $stmt->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } else { + if (IssueBuffer::accepts( + new PossiblyInvalidPropertyAssignment( + $lhs_var_id . ' with possible non-object type \'' . $invalid_assignment_type . + '\' cannot treated as an object', + new CodeLocation($statements_analyzer->getSource(), $stmt->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + } + + if (!$has_regular_setter) { + return null; + } + + if ($var_id) { + if ($context->collect_initializations + && $lhs_var_id === '$this' + ) { + $assignment_value_type->initialized_class = $context->self; + } + + // because we don't want to be assigning for property declarations + $context->vars_in_scope[$var_id] = $assignment_value_type; + } + } + + if (!$property_exists) { + return null; + } + + if ($assignment_value_type->hasMixed()) { + return null; + } + + $invalid_assignment_value_types = []; + + $has_valid_assignment_value_type = false; + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + && count($class_property_types) === 1 + ) { + $codebase->analyzer->addNodeType( + $statements_analyzer->getFilePath(), + $stmt->name, + $class_property_types[0]->getId() + ); + } + + foreach ($class_property_types as $class_property_type) { + if ($class_property_type->hasMixed()) { + continue; + } + + $union_comparison_results = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + $type_match_found = UnionTypeComparator::isContainedBy( + $codebase, + $assignment_value_type, + $class_property_type, + true, + true, + $union_comparison_results + ); + + if ($type_match_found && $union_comparison_results->replacement_union_type) { + if ($var_id) { + $context->vars_in_scope[$var_id] = $union_comparison_results->replacement_union_type; + } + } + + if ($union_comparison_results->type_coerced) { + if ($union_comparison_results->type_coerced_from_mixed) { + if (IssueBuffer::accepts( + new MixedPropertyTypeCoercion( + $var_id . ' expects \'' . $class_property_type->getId() . '\', ' + . ' parent type `' . $assignment_value_type->getId() . '` provided', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt, + $context->include_location + ), + $property_ids[0] + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep soldiering on + } + } else { + if (IssueBuffer::accepts( + new PropertyTypeCoercion( + $var_id . ' expects \'' . $class_property_type->getId() . '\', ' + . ' parent type \'' . $assignment_value_type->getId() . '\' provided', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt, + $context->include_location + ), + $property_ids[0] + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep soldiering on + } + } + } + + if ($union_comparison_results->to_string_cast) { + if (IssueBuffer::accepts( + new ImplicitToStringCast( + $var_id . ' expects \'' . $class_property_type . '\', ' + . '\'' . $assignment_value_type . '\' provided with a __toString method', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt, + $context->include_location + ) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if (!$type_match_found && !$union_comparison_results->type_coerced) { + if (UnionTypeComparator::canBeContainedBy( + $codebase, + $assignment_value_type, + $class_property_type, + true, + true + )) { + $has_valid_assignment_value_type = true; + } + + $invalid_assignment_value_types[] = $class_property_type->getId(); + } else { + $has_valid_assignment_value_type = true; + } + + if ($type_match_found) { + if (!$assignment_value_type->ignore_nullable_issues + && $assignment_value_type->isNullable() + && !$class_property_type->isNullable() + ) { + if (IssueBuffer::accepts( + new PossiblyNullPropertyAssignmentValue( + $var_id . ' with non-nullable declared type \'' . $class_property_type . + '\' cannot be assigned nullable type \'' . $assignment_value_type . '\'', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt, + $context->include_location + ), + $property_ids[0] + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + + if (!$assignment_value_type->ignore_falsable_issues + && $assignment_value_type->isFalsable() + && !$class_property_type->hasBool() + && !$class_property_type->hasScalar() + ) { + if (IssueBuffer::accepts( + new PossiblyFalsePropertyAssignmentValue( + $var_id . ' with non-falsable declared type \'' . $class_property_type . + '\' cannot be assigned possibly false type \'' . $assignment_value_type . '\'', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt, + $context->include_location + ), + $property_ids[0] + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + } + } + + if ($invalid_assignment_value_types) { + $invalid_class_property_type = $invalid_assignment_value_types[0]; + + if (!$has_valid_assignment_value_type) { + if (IssueBuffer::accepts( + new InvalidPropertyAssignmentValue( + $var_id . ' with declared type \'' . $invalid_class_property_type . + '\' cannot be assigned type \'' . $assignment_value_type->getId() . '\'', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt, + $context->include_location + ), + $property_ids[0] + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } else { + if (IssueBuffer::accepts( + new PossiblyInvalidPropertyAssignmentValue( + $var_id . ' with declared type \'' . $invalid_class_property_type . + '\' cannot be assigned possibly different type \'' . + $assignment_value_type->getId() . '\'', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt, + $context->include_location + ), + $property_ids[0] + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + } + + return null; + } + + public static function trackPropertyImpurity( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\PropertyFetch $stmt, + string $property_id, + \Psalm\Storage\PropertyStorage $property_storage, + \Psalm\Storage\ClassLikeStorage $declaring_class_storage, + Context $context + ): void { + $codebase = $statements_analyzer->getCodebase(); + + $stmt_var_type = $statements_analyzer->node_data->getType($stmt->var); + + $property_var_pure_compatible = $stmt_var_type + && $stmt_var_type->reference_free + && $stmt_var_type->allow_mutations; + + $appearing_property_class = $codebase->properties->getAppearingClassForProperty( + $property_id, + true + ); + + $project_analyzer = $statements_analyzer->getProjectAnalyzer(); + + if ($appearing_property_class && ($property_storage->readonly || $codebase->alter_code)) { + $can_set_readonly_property = $context->self + && $context->calling_method_id + && ($appearing_property_class === $context->self + || $codebase->classExtends($context->self, $appearing_property_class)) + && (\strpos($context->calling_method_id, '::__construct') + || \strpos($context->calling_method_id, '::unserialize') + || \strpos($context->calling_method_id, '::__unserialize') + || \strpos($context->calling_method_id, '::__clone') + || $property_storage->allow_private_mutation + || $property_var_pure_compatible); + + if (!$can_set_readonly_property) { + if ($property_storage->readonly) { + if (IssueBuffer::accepts( + new InaccessibleProperty( + $property_id . ' is marked readonly', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif (!$declaring_class_storage->mutation_free + && isset($project_analyzer->getIssuesToFix()['MissingImmutableAnnotation']) + && $statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + ) { + $codebase->analyzer->addMutableClass($declaring_class_storage->name); + } + } + } + } + + public static function analyzeStatement( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Stmt\Property $stmt, + Context $context + ): void { + foreach ($stmt->props as $prop) { + if ($prop->default) { + ExpressionAnalyzer::analyze($statements_analyzer, $prop->default, $context); + + if ($prop_default_type = $statements_analyzer->node_data->getType($prop->default)) { + if (self::analyze( + $statements_analyzer, + $prop, + $prop->name->name, + $prop->default, + $prop_default_type, + $context + ) === false) { + // fall through + } + } + } + } + } + + private static function taintProperty( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\PropertyFetch $stmt, + string $property_id, + \Psalm\Storage\ClassLikeStorage $class_storage, + Type\Union $assignment_value_type, + Context $context + ) : void { + if (!$statements_analyzer->data_flow_graph) { + return; + } + + $data_flow_graph = $statements_analyzer->data_flow_graph; + + $var_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var); + $property_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + + if ($class_storage->specialize_instance) { + $var_id = ExpressionIdentifier::getArrayVarId( + $stmt->var, + null, + $statements_analyzer + ); + + $var_property_id = ExpressionIdentifier::getArrayVarId( + $stmt, + null, + $statements_analyzer + ); + + if ($var_id) { + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph + && \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + $context->vars_in_scope[$var_id]->parent_nodes = []; + return; + } + + $var_node = DataFlowNode::getForAssignment( + $var_id, + $var_location + ); + + $data_flow_graph->addNode($var_node); + + $property_node = DataFlowNode::getForAssignment( + $var_property_id ?: $var_id . '->$property', + $property_location + ); + + $data_flow_graph->addNode($property_node); + + $data_flow_graph->addPath( + $property_node, + $var_node, + 'property-assignment' + . ($stmt->name instanceof PhpParser\Node\Identifier ? '-' . $stmt->name : '') + ); + + if ($assignment_value_type->parent_nodes) { + foreach ($assignment_value_type->parent_nodes as $parent_node) { + $data_flow_graph->addPath($parent_node, $property_node, '='); + } + } + + $stmt_var_type = clone $context->vars_in_scope[$var_id]; + + if ($context->vars_in_scope[$var_id]->parent_nodes) { + foreach ($context->vars_in_scope[$var_id]->parent_nodes as $parent_node) { + $data_flow_graph->addPath($parent_node, $var_node, '='); + } + } + + $stmt_var_type->parent_nodes = [$var_node->id => $var_node]; + + $context->vars_in_scope[$var_id] = $stmt_var_type; + } + } else { + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph + && \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + $assignment_value_type->parent_nodes = []; + return; + } + + $code_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + + $localized_property_node = new DataFlowNode( + $property_id . '-' . $code_location->file_name . ':' . $code_location->raw_file_start, + $property_id, + $code_location, + null + ); + + $data_flow_graph->addNode($localized_property_node); + + $property_node = new DataFlowNode( + $property_id, + $property_id, + null, + null + ); + + $data_flow_graph->addNode($property_node); + + $data_flow_graph->addPath($localized_property_node, $property_node, 'property-assignment'); + + if ($assignment_value_type->parent_nodes) { + foreach ($assignment_value_type->parent_nodes as $parent_node) { + $data_flow_graph->addPath($parent_node, $localized_property_node, '='); + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/StaticPropertyAssignmentAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/StaticPropertyAssignmentAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..4c2acaeba1c6011a16202c207323fce07ce47ef7 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/StaticPropertyAssignmentAnalyzer.php @@ -0,0 +1,327 @@ +self, + $statements_analyzer + ); + + $lhs_type = $statements_analyzer->node_data->getType($stmt->class); + + if (!$lhs_type) { + return null; + } + + $codebase = $statements_analyzer->getCodebase(); + + $prop_name = $stmt->name; + + foreach ($lhs_type->getAtomicTypes() as $lhs_atomic_type) { + if ($lhs_atomic_type instanceof Type\Atomic\TClassString) { + if (!$lhs_atomic_type->as_type) { + continue; + } + + $lhs_atomic_type = $lhs_atomic_type->as_type; + } + + if (!$lhs_atomic_type instanceof Type\Atomic\TNamedObject) { + continue; + } + + $fq_class_name = $lhs_atomic_type->value; + + if (!$prop_name instanceof PhpParser\Node\Identifier) { + $was_inside_use = $context->inside_use; + + $context->inside_use = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $prop_name, $context) === false) { + return false; + } + + $context->inside_use = $was_inside_use; + + if (!$context->ignore_variable_property) { + $codebase->analyzer->addMixedMemberName( + strtolower($fq_class_name) . '::$', + $context->calling_method_id ?: $statements_analyzer->getFileName() + ); + } + + return null; + } + + $property_id = $fq_class_name . '::$' . $prop_name; + + if (!$codebase->properties->propertyExists($property_id, false, $statements_analyzer, $context)) { + if (IssueBuffer::accepts( + new UndefinedPropertyAssignment( + 'Static property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return null; + } + + if (ClassLikeAnalyzer::checkPropertyVisibility( + $property_id, + $context, + $statements_analyzer, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues() + ) === false) { + return false; + } + + $declaring_property_class = (string) $codebase->properties->getDeclaringClassForProperty( + $fq_class_name . '::$' . $prop_name->name, + false + ); + + $declaring_property_id = strtolower($declaring_property_class) . '::$' . $prop_name; + + if ($codebase->alter_code && $stmt->class instanceof PhpParser\Node\Name) { + $moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $statements_analyzer, + $stmt->class, + $fq_class_name, + $context->calling_method_id + ); + + if (!$moved_class) { + foreach ($codebase->property_transforms as $original_pattern => $transformation) { + if ($declaring_property_id === $original_pattern) { + [$old_declaring_fq_class_name] = explode('::$', $declaring_property_id); + [$new_fq_class_name, $new_property_name] = explode('::$', $transformation); + + $file_manipulations = []; + + if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) { + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $stmt->class->getAttribute('startFilePos'), + (int) $stmt->class->getAttribute('endFilePos') + 1, + Type::getStringFromFQCLN( + $new_fq_class_name, + $statements_analyzer->getNamespace(), + $statements_analyzer->getAliasedClassesFlipped(), + null + ) + ); + } + + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $stmt->name->getAttribute('startFilePos'), + (int) $stmt->name->getAttribute('endFilePos') + 1, + '$' . $new_property_name + ); + + FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations); + } + } + } + } + + $class_storage = $codebase->classlike_storage_provider->get($declaring_property_class); + + if ($var_id) { + $context->vars_in_scope[$var_id] = $assignment_value_type; + } + + $class_property_type = $codebase->properties->getPropertyType( + $property_id, + true, + $statements_analyzer, + $context + ); + + if (!$class_property_type) { + $class_property_type = Type::getMixed(); + + $source_analyzer = $statements_analyzer->getSource()->getSource(); + + $prop_name_name = $prop_name->name; + + if ($source_analyzer instanceof ClassAnalyzer + && $fq_class_name === $source_analyzer->getFQCLN() + ) { + if (isset($source_analyzer->inferred_property_types[$prop_name_name])) { + $source_analyzer->inferred_property_types[$prop_name_name] = Type::combineUnionTypes( + $assignment_value_type, + $source_analyzer->inferred_property_types[$prop_name_name] + ); + } else { + $source_analyzer->inferred_property_types[$prop_name_name] = $assignment_value_type; + } + } + } else { + $class_property_type = clone $class_property_type; + } + + if ($assignment_value_type->hasMixed()) { + return null; + } + + if ($class_property_type->hasMixed()) { + return null; + } + + $class_property_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $class_property_type, + $fq_class_name, + $fq_class_name, + $class_storage->parent_class + ); + + $union_comparison_results = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + $type_match_found = UnionTypeComparator::isContainedBy( + $codebase, + $assignment_value_type, + $class_property_type, + true, + true, + $union_comparison_results + ); + + if ($union_comparison_results->type_coerced) { + if ($union_comparison_results->type_coerced_from_mixed) { + if (IssueBuffer::accepts( + new MixedPropertyTypeCoercion( + $var_id . ' expects \'' . $class_property_type->getId() . '\', ' + . ' parent type `' . $assignment_value_type->getId() . '` provided', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt, + $context->include_location + ), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep soldiering on + } + } else { + if (IssueBuffer::accepts( + new PropertyTypeCoercion( + $var_id . ' expects \'' . $class_property_type->getId() . '\', ' + . ' parent type \'' . $assignment_value_type->getId() . '\' provided', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt, + $context->include_location + ), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep soldiering on + } + } + } + + if ($union_comparison_results->to_string_cast) { + if (IssueBuffer::accepts( + new ImplicitToStringCast( + $var_id . ' expects \'' . $class_property_type . '\', ' + . '\'' . $assignment_value_type . '\' provided with a __toString method', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt, + $context->include_location + ) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if (!$type_match_found && !$union_comparison_results->type_coerced) { + if (UnionTypeComparator::canBeContainedBy($codebase, $assignment_value_type, $class_property_type)) { + if (IssueBuffer::accepts( + new PossiblyInvalidPropertyAssignmentValue( + $var_id . ' with declared type \'' + . $class_property_type->getId() . '\' cannot be assigned type \'' + . $assignment_value_type->getId() . '\'', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt + ), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } else { + if (IssueBuffer::accepts( + new InvalidPropertyAssignmentValue( + $var_id . ' with declared type \'' . $class_property_type->getId() + . '\' cannot be assigned type \'' + . $assignment_value_type->getId() . '\'', + new CodeLocation( + $statements_analyzer->getSource(), + $assignment_value ?: $stmt + ), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + } + + if ($var_id) { + $context->vars_in_scope[$var_id] = $assignment_value_type; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..63f82de93b4fb2ff23ec2a82844a4b2751099cf7 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php @@ -0,0 +1,1768 @@ +getFQCLN(), + $statements_analyzer + ); + + // gets a variable id that *may* contain array keys + $array_var_id = ExpressionIdentifier::getArrayVarId( + $assign_var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + $var_comments = []; + $comment_type = null; + $comment_type_location = null; + + $was_in_assignment = $context->inside_assignment; + + $context->inside_assignment = true; + + $codebase = $statements_analyzer->getCodebase(); + + $base_assign_value = $assign_value; + + while ($base_assign_value instanceof PhpParser\Node\Expr\Assign) { + $base_assign_value = $base_assign_value->expr; + } + + if ($base_assign_value !== $assign_value) { + ExpressionAnalyzer::analyze($statements_analyzer, $base_assign_value, $context); + + $assign_value_type = $statements_analyzer->node_data->getType($base_assign_value) ?: $assign_value_type; + } + + $removed_taints = []; + + if ($doc_comment) { + $file_path = $statements_analyzer->getRootFilePath(); + + $file_storage_provider = $codebase->file_storage_provider; + + $file_storage = $file_storage_provider->get($file_path); + + $template_type_map = $statements_analyzer->getTemplateTypeMap(); + + try { + $var_comments = CommentAnalyzer::getTypeFromComment( + $doc_comment, + $statements_analyzer->getSource(), + $statements_analyzer->getAliases(), + $template_type_map, + $file_storage->type_aliases + ); + } catch (IncorrectDocblockException $e) { + if (IssueBuffer::accepts( + new MissingDocblockType( + (string)$e->getMessage(), + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ) + )) { + // fall through + } + } catch (DocblockParseException $e) { + if (IssueBuffer::accepts( + new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ) + )) { + // fall through + } + } + + foreach ($var_comments as $var_comment) { + if ($var_comment->removed_taints) { + $removed_taints = $var_comment->removed_taints; + } + + self::assignTypeFromVarDocblock( + $statements_analyzer, + $assign_var, + $var_comment, + $context, + $var_id, + $comment_type, + $comment_type_location, + $not_ignored_docblock_var_ids + ); + } + } + + if ($array_var_id) { + unset($context->referenced_var_ids[$array_var_id]); + $context->assigned_var_ids[$array_var_id] = true; + $context->possibly_assigned_var_ids[$array_var_id] = true; + } + + if ($assign_value) { + if ($var_id && $assign_value instanceof PhpParser\Node\Expr\Closure) { + foreach ($assign_value->uses as $closure_use) { + if ($closure_use->byRef + && is_string($closure_use->var->name) + && $var_id === '$' . $closure_use->var->name + ) { + $context->vars_in_scope[$var_id] = Type::getClosure(); + $context->vars_possibly_in_scope[$var_id] = true; + } + } + } + + $was_inside_use = $context->inside_use; + + $root_expr = $assign_var; + + while ($root_expr instanceof PhpParser\Node\Expr\ArrayDimFetch) { + $root_expr = $root_expr->var; + } + + // if we don't know where this data is going, treat as a dead-end usage + if (!$root_expr instanceof PhpParser\Node\Expr\Variable + || (\is_string($root_expr->name) + && \in_array('$' . $root_expr->name, VariableFetchAnalyzer::SUPER_GLOBALS, true)) + ) { + $context->inside_use = true; + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $assign_value, $context) === false) { + if ($var_id) { + if ($array_var_id) { + $context->removeDescendents($array_var_id, null, $assign_value_type); + } + + // if we're not exiting immediately, make everything mixed + $context->vars_in_scope[$var_id] = $comment_type ?: Type::getMixed(); + } + + return false; + } + + $context->inside_use = $was_inside_use; + } + + if ($comment_type && $comment_type_location) { + $temp_assign_value_type = $assign_value_type + ? $assign_value_type + : ($assign_value ? $statements_analyzer->node_data->getType($assign_value) : null); + + if ($codebase->find_unused_variables + && $temp_assign_value_type + && $array_var_id + && (!$not_ignored_docblock_var_ids || isset($not_ignored_docblock_var_ids[$array_var_id])) + && $temp_assign_value_type->getId() === $comment_type->getId() + && !$comment_type->isMixed() + ) { + if ($codebase->alter_code + && isset($statements_analyzer->getProjectAnalyzer()->getIssuesToFix()['UnnecessaryVarAnnotation']) + ) { + FileManipulationBuffer::addVarAnnotationToRemove($comment_type_location); + } elseif (IssueBuffer::accepts( + new UnnecessaryVarAnnotation( + 'The @var ' . $comment_type . ' annotation for ' + . $array_var_id . ' is unnecessary', + $comment_type_location + ) + )) { + // fall through + } + } + + $parent_nodes = $temp_assign_value_type->parent_nodes ?? []; + + $assign_value_type = $comment_type; + $assign_value_type->parent_nodes = $parent_nodes; + } elseif (!$assign_value_type) { + if ($assign_value) { + $assign_value_type = $statements_analyzer->node_data->getType($assign_value); + } + + if ($assign_value_type) { + $assign_value_type = clone $assign_value_type; + } else { + $assign_value_type = Type::getMixed(); + } + } + + if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph + && !$assign_value_type->parent_nodes + ) { + if ($array_var_id) { + $assignment_node = DataFlowNode::getForAssignment( + $array_var_id, + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ); + } else { + $assignment_node = new DataFlowNode('unknown-origin', 'unknown origin', null); + } + + $assign_value_type->parent_nodes = [ + $assignment_node->id => $assignment_node + ]; + } + + if ($array_var_id && isset($context->vars_in_scope[$array_var_id])) { + if ($context->vars_in_scope[$array_var_id]->by_ref) { + if ($context->mutation_free) { + if (IssueBuffer::accepts( + new ImpureByReferenceAssignment( + 'Variable ' . $array_var_id . ' cannot be assigned to as it is passed by reference', + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ) + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_impure = true; + $statements_analyzer->getSource()->inferred_has_mutation = true; + } + + $assign_value_type->by_ref = true; + } + + // removes dependent vars from $context + $context->removeDescendents( + $array_var_id, + $context->vars_in_scope[$array_var_id], + $assign_value_type, + $statements_analyzer + ); + } else { + $root_var_id = ExpressionIdentifier::getRootVarId( + $assign_var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($root_var_id && isset($context->vars_in_scope[$root_var_id])) { + $context->removeVarFromConflictingClauses( + $root_var_id, + $context->vars_in_scope[$root_var_id], + $statements_analyzer + ); + } + } + + $codebase = $statements_analyzer->getCodebase(); + + if ($assign_value_type->hasMixed()) { + $root_var_id = ExpressionIdentifier::getRootVarId( + $assign_var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + if (!$assign_var instanceof PhpParser\Node\Expr\PropertyFetch + && !strpos($root_var_id ?? '', '->') + && !$comment_type + && substr($var_id ?? '', 0, 2) !== '$_' + ) { + if (IssueBuffer::accepts( + new MixedAssignment( + $var_id + ? 'Unable to determine the type that ' . $var_id . ' is being assigned to' + : 'Unable to determine the type of this assignment', + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } else { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } + + if ($var_id + && isset($context->byref_constraints[$var_id]) + && ($outer_constraint_type = $context->byref_constraints[$var_id]->type) + ) { + if (!UnionTypeComparator::isContainedBy( + $codebase, + $assign_value_type, + $outer_constraint_type, + $assign_value_type->ignore_nullable_issues, + $assign_value_type->ignore_falsable_issues + ) + ) { + if (IssueBuffer::accepts( + new ReferenceConstraintViolation( + 'Variable ' . $var_id . ' is limited to values of type ' + . $context->byref_constraints[$var_id]->type + . ' because it is passed by reference, ' + . $assign_value_type->getId() . ' type found', + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + if ($var_id === '$this' && IssueBuffer::accepts( + new InvalidScope( + 'Cannot re-assign ' . $var_id, + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + if (isset($context->protected_var_ids[$var_id]) + && $assign_value_type->hasLiteralInt() + ) { + if (IssueBuffer::accepts( + new LoopInvalidation( + 'Variable ' . $var_id . ' has already been assigned in a for/foreach loop', + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($assign_var instanceof PhpParser\Node\Expr\Variable) { + if (is_string($assign_var->name)) { + if ($var_id) { + $context->vars_in_scope[$var_id] = $assign_value_type; + $context->vars_possibly_in_scope[$var_id] = true; + + $location = new CodeLocation($statements_analyzer, $assign_var); + + if (!$statements_analyzer->hasVariable($var_id)) { + $statements_analyzer->registerVariable( + $var_id, + $location, + $context->branch_point + ); + } elseif (!$context->inside_isset) { + $statements_analyzer->registerVariableAssignment( + $var_id, + $location + ); + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $location = new CodeLocation($statements_analyzer, $assign_var); + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $assign_var, + $location->raw_file_start + . '-' . $location->raw_file_end + . ':' . $assign_value_type->getId() + ); + } + + if (isset($context->byref_constraints[$var_id])) { + $assign_value_type->by_ref = true; + } + + if ($assign_value_type->by_ref) { + if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph + && $assign_value_type->parent_nodes + ) { + $location = new CodeLocation($statements_analyzer, $assign_var); + + $byref_node = DataFlowNode::getForAssignment($var_id, $location); + + foreach ($assign_value_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + new DataFlowNode('variable-use', 'variable use', null), + 'variable-use' + ); + + $statements_analyzer->data_flow_graph->addPath( + $byref_node, + $parent_node, + 'byref-assignment' + ); + } + } + } + } + } else { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $assign_var->name, $context) === false) { + return false; + } + + $context->inside_use = $was_inside_use; + + if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph + && $assign_value_type->parent_nodes + ) { + foreach ($assign_value_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + new DataFlowNode('variable-use', 'variable use', null), + 'variable-use' + ); + } + } + } + } elseif ($assign_var instanceof PhpParser\Node\Expr\List_ + || $assign_var instanceof PhpParser\Node\Expr\Array_ + ) { + if (!$assign_value_type->hasArray() + && !$assign_value_type->isMixed() + && !$assign_value_type->hasArrayAccessInterface($codebase) + ) { + if (IssueBuffer::accepts( + new InvalidArrayOffset( + 'Cannot destructure non-array of type ' . $assign_value_type->getId(), + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $can_be_empty = true; + + foreach ($assign_var->items as $offset => $assign_var_item) { + // $assign_var_item can be null e.g. list($a, ) = ['a', 'b'] + if (!$assign_var_item) { + continue; + } + + $var = $assign_var_item->value; + + if ($assign_value instanceof PhpParser\Node\Expr\Array_ + && $statements_analyzer->node_data->getType($assign_var_item->value) + ) { + self::analyze( + $statements_analyzer, + $var, + $assign_var_item->value, + null, + $context, + $doc_comment + ); + + continue; + } + + $offset_value = null; + + if (!$assign_var_item->key) { + $offset_value = $offset; + } elseif ($assign_var_item->key instanceof PhpParser\Node\Scalar\String_) { + $offset_value = $assign_var_item->key->value; + } + + $list_var_id = ExpressionIdentifier::getArrayVarId( + $var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + $new_assign_type = null; + $assigned = false; + $has_null = false; + + foreach ($assign_value_type->getAtomicTypes() as $assign_value_atomic_type) { + if ($assign_value_atomic_type instanceof Type\Atomic\TKeyedArray + && !$assign_var_item->key + ) { + // if object-like has int offsets + if ($offset_value !== null + && isset($assign_value_atomic_type->properties[$offset_value]) + ) { + $value_type = $assign_value_atomic_type->properties[$offset_value]; + + if ($value_type->possibly_undefined) { + if (IssueBuffer::accepts( + new PossiblyUndefinedArrayOffset( + 'Possibly undefined array key', + new CodeLocation($statements_analyzer->getSource(), $var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + $value_type = clone $value_type; + $value_type->possibly_undefined = false; + } + + if ($statements_analyzer->data_flow_graph + && $assign_value + ) { + $assign_value_id = ExpressionIdentifier::getArrayVarId( + $assign_value, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + $keyed_array_var_id = null; + + if ($assign_value_id) { + $keyed_array_var_id = $assign_value_id . '[\'' . $offset_value . '\']'; + } + + ArrayFetchAnalyzer::taintArrayFetch( + $statements_analyzer, + $assign_value, + $keyed_array_var_id, + $value_type, + Type::getString((string) $offset_value) + ); + } + + self::analyze( + $statements_analyzer, + $var, + null, + $value_type, + $context, + $doc_comment + ); + + $assigned = true; + + continue; + } + + if ($assign_value_atomic_type->sealed) { + if (IssueBuffer::accepts( + new InvalidArrayOffset( + 'Cannot access value with offset ' . $offset, + new CodeLocation($statements_analyzer->getSource(), $var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($assign_value_atomic_type instanceof Type\Atomic\TMixed) { + if (IssueBuffer::accepts( + new MixedArrayAccess( + 'Cannot access array value on mixed variable ' . $array_var_id, + new CodeLocation($statements_analyzer->getSource(), $var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($assign_value_atomic_type instanceof Type\Atomic\TNull) { + $has_null = true; + + if (IssueBuffer::accepts( + new PossiblyNullArrayAccess( + 'Cannot access array value on null variable ' . $array_var_id, + new CodeLocation($statements_analyzer->getSource(), $var) + ), + $statements_analyzer->getSuppressedIssues() + ) + ) { + // do nothing + } + } elseif (!$assign_value_atomic_type instanceof Type\Atomic\TArray + && !$assign_value_atomic_type instanceof Type\Atomic\TKeyedArray + && !$assign_value_atomic_type instanceof Type\Atomic\TList + && !$assign_value_type->hasArrayAccessInterface($codebase) + ) { + if ($assign_value_type->hasArray()) { + if (($assign_value_atomic_type instanceof Type\Atomic\TFalse + && $assign_value_type->ignore_falsable_issues) + || ($assign_value_atomic_type instanceof Type\Atomic\TNull + && $assign_value_type->ignore_nullable_issues) + ) { + // do nothing + } elseif (IssueBuffer::accepts( + new PossiblyInvalidArrayAccess( + 'Cannot access array value on non-array variable ' + . $array_var_id . ' of type ' . $assign_value_atomic_type->getId(), + new CodeLocation($statements_analyzer->getSource(), $var) + ), + $statements_analyzer->getSuppressedIssues() + ) + ) { + // do nothing + } + } else { + if (IssueBuffer::accepts( + new InvalidArrayAccess( + 'Cannot access array value on non-array variable ' + . $array_var_id . ' of type ' . $assign_value_atomic_type->getId(), + new CodeLocation($statements_analyzer->getSource(), $var) + ), + $statements_analyzer->getSuppressedIssues() + ) + ) { + // do nothing + } + } + } + + if ($var instanceof PhpParser\Node\Expr\List_ + || $var instanceof PhpParser\Node\Expr\Array_ + ) { + if ($assign_value_atomic_type instanceof Type\Atomic\TKeyedArray) { + $assign_value_atomic_type = $assign_value_atomic_type->getGenericArrayType(); + } + + if ($assign_value_atomic_type instanceof Type\Atomic\TList) { + $assign_value_atomic_type = new Type\Atomic\TArray([ + Type::getInt(), + $assign_value_atomic_type->type_param + ]); + } + + $array_value_type = $assign_value_atomic_type instanceof Type\Atomic\TArray + ? clone $assign_value_atomic_type->type_params[1] + : Type::getMixed(); + + self::analyze( + $statements_analyzer, + $var, + null, + $array_value_type, + $context, + $doc_comment + ); + + continue; + } + + if ($list_var_id) { + $context->vars_possibly_in_scope[$list_var_id] = true; + $context->assigned_var_ids[$list_var_id] = true; + $context->possibly_assigned_var_ids[$list_var_id] = true; + + $already_in_scope = isset($context->vars_in_scope[$list_var_id]); + + if (strpos($list_var_id, '-') === false && strpos($list_var_id, '[') === false) { + $location = new CodeLocation($statements_analyzer, $var); + + if (!$statements_analyzer->hasVariable($list_var_id)) { + $statements_analyzer->registerVariable( + $list_var_id, + $location, + $context->branch_point + ); + } else { + $statements_analyzer->registerVariableAssignment( + $list_var_id, + $location + ); + } + + if (isset($context->byref_constraints[$list_var_id])) { + // something + } + } + + if ($assign_value_atomic_type instanceof Type\Atomic\TArray) { + $new_assign_type = clone $assign_value_atomic_type->type_params[1]; + + if ($statements_analyzer->data_flow_graph + && $assign_value + ) { + ArrayFetchAnalyzer::taintArrayFetch( + $statements_analyzer, + $assign_value, + null, + $new_assign_type, + Type::getArrayKey() + ); + } + + $can_be_empty = !$assign_value_atomic_type instanceof Type\Atomic\TNonEmptyArray; + } elseif ($assign_value_atomic_type instanceof Type\Atomic\TList) { + $new_assign_type = clone $assign_value_atomic_type->type_param; + + if ($statements_analyzer->data_flow_graph && $assign_value) { + ArrayFetchAnalyzer::taintArrayFetch( + $statements_analyzer, + $assign_value, + null, + $new_assign_type, + Type::getArrayKey() + ); + } + + $can_be_empty = !$assign_value_atomic_type instanceof Type\Atomic\TNonEmptyList; + } elseif ($assign_value_atomic_type instanceof Type\Atomic\TKeyedArray) { + if ($assign_var_item->key + && ($assign_var_item->key instanceof PhpParser\Node\Scalar\String_ + || $assign_var_item->key instanceof PhpParser\Node\Scalar\LNumber) + && isset($assign_value_atomic_type->properties[$assign_var_item->key->value]) + ) { + $new_assign_type = + clone $assign_value_atomic_type->properties[$assign_var_item->key->value]; + + if ($new_assign_type->possibly_undefined) { + if (IssueBuffer::accepts( + new PossiblyUndefinedArrayOffset( + 'Possibly undefined array key', + new CodeLocation($statements_analyzer->getSource(), $var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + $new_assign_type->possibly_undefined = false; + } + } + + if ($statements_analyzer->data_flow_graph && $assign_value && $new_assign_type) { + ArrayFetchAnalyzer::taintArrayFetch( + $statements_analyzer, + $assign_value, + null, + $new_assign_type, + Type::getArrayKey() + ); + } + + $can_be_empty = !$assign_value_atomic_type->sealed; + } elseif ($assign_value_atomic_type->hasArrayAccessInterface($codebase)) { + ForeachAnalyzer::getKeyValueParamsForTraversableObject( + $assign_value_atomic_type, + $codebase, + $array_access_key_type, + $array_access_value_type + ); + + $new_assign_type = $array_access_value_type; + } + + if ($already_in_scope) { + // removes dependennt vars from $context + $context->removeDescendents( + $list_var_id, + $context->vars_in_scope[$list_var_id], + $new_assign_type, + $statements_analyzer + ); + } + } + } + + if (!$assigned) { + foreach ($var_comments as $var_comment) { + if (!$var_comment->type) { + continue; + } + + try { + if ($var_comment->var_id === $list_var_id) { + $var_comment_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $var_comment->type, + $context->self, + $context->self, + $statements_analyzer->getParentFQCLN() + ); + + $var_comment_type->setFromDocblock(); + + $new_assign_type = $var_comment_type; + break; + } + } catch (\UnexpectedValueException $e) { + if (IssueBuffer::accepts( + new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ) + )) { + // fall through + } + } + } + + if ($list_var_id) { + $context->vars_in_scope[$list_var_id] = $new_assign_type ?: Type::getMixed(); + + if (($context->error_suppressing && ($offset || $can_be_empty)) + || $has_null + ) { + $context->vars_in_scope[$list_var_id]->addType(new Type\Atomic\TNull); + } + + if ($statements_analyzer->data_flow_graph) { + $data_flow_graph = $statements_analyzer->data_flow_graph; + + $var_location = new CodeLocation($statements_analyzer->getSource(), $var); + + if (!$context->vars_in_scope[$list_var_id]->parent_nodes) { + $assignment_node = DataFlowNode::getForAssignment( + $list_var_id, + $var_location + ); + + $context->vars_in_scope[$list_var_id]->parent_nodes = [ + $assignment_node->id => $assignment_node + ]; + } else { + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph + && \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + $context->vars_in_scope[$list_var_id]->parent_nodes = []; + } else { + $new_parent_node = DataFlowNode::getForAssignment($list_var_id, $var_location); + + $statements_analyzer->data_flow_graph->addNode($new_parent_node); + + foreach ($context->vars_in_scope[$list_var_id]->parent_nodes as $parent_node) { + $data_flow_graph->addPath( + $parent_node, + $new_parent_node, + '=', + [], + $removed_taints + ); + } + + $context->vars_in_scope[$list_var_id]->parent_nodes = [ + $new_parent_node->id => $new_parent_node + ]; + } + } + } + } + } + } + } elseif ($assign_var instanceof PhpParser\Node\Expr\ArrayDimFetch) { + ArrayAssignmentAnalyzer::analyze( + $statements_analyzer, + $assign_var, + $context, + $assign_value, + $assign_value_type + ); + } elseif ($assign_var instanceof PhpParser\Node\Expr\PropertyFetch) { + if (!$assign_var->name instanceof PhpParser\Node\Identifier) { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + + // this can happen when the user actually means to type $this->, but there's + // a variable on the next line + if (ExpressionAnalyzer::analyze($statements_analyzer, $assign_var->var, $context) === false) { + return false; + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $assign_var->name, $context) === false) { + return false; + } + + $context->inside_use = $was_inside_use; + } + + if ($assign_var->name instanceof PhpParser\Node\Identifier) { + $prop_name = $assign_var->name->name; + } elseif (($assign_var_name_type = $statements_analyzer->node_data->getType($assign_var->name)) + && $assign_var_name_type->isSingleStringLiteral() + ) { + $prop_name = $assign_var_name_type->getSingleStringLiteral()->value; + } else { + $prop_name = null; + } + + if ($prop_name) { + InstancePropertyAssignmentAnalyzer::analyze( + $statements_analyzer, + $assign_var, + $prop_name, + $assign_value, + $assign_value_type, + $context + ); + } else { + if (ExpressionAnalyzer::analyze($statements_analyzer, $assign_var->var, $context) === false) { + return false; + } + + if (($assign_var_type = $statements_analyzer->node_data->getType($assign_var->var)) + && !$context->ignore_variable_property + ) { + $stmt_var_type = $assign_var_type; + + if ($stmt_var_type->hasObjectType()) { + foreach ($stmt_var_type->getAtomicTypes() as $type) { + if ($type instanceof Type\Atomic\TNamedObject) { + $codebase->analyzer->addMixedMemberName( + strtolower($type->value) . '::$', + $context->calling_method_id ?: $statements_analyzer->getFileName() + ); + } + } + } + } + } + + if ($var_id) { + $context->vars_possibly_in_scope[$var_id] = true; + } + + $property_var_pure_compatible = $statements_analyzer->node_data->isPureCompatible($assign_var->var); + + // prevents writing to any properties in a mutation-free context + if (!$property_var_pure_compatible + && !$context->collect_mutations + && !$context->collect_initializations + ) { + if ($context->mutation_free || $context->external_mutation_free) { + if (IssueBuffer::accepts( + new ImpurePropertyAssignment( + 'Cannot assign to a property from a mutation-free context', + new CodeLocation($statements_analyzer, $assign_var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + if (!$assign_var->var instanceof PhpParser\Node\Expr\Variable + || $assign_var->var->name !== 'this' + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + } + + $statements_analyzer->getSource()->inferred_impure = true; + } + } + } elseif ($assign_var instanceof PhpParser\Node\Expr\StaticPropertyFetch && + $assign_var->class instanceof PhpParser\Node\Name + ) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $assign_var, $context) === false) { + return false; + } + + if ($context->check_classes) { + StaticPropertyAssignmentAnalyzer::analyze( + $statements_analyzer, + $assign_var, + $assign_value, + $assign_value_type, + $context + ); + } + + if ($var_id) { + $context->vars_possibly_in_scope[$var_id] = true; + } + } + + if ($var_id && isset($context->vars_in_scope[$var_id])) { + if ($context->vars_in_scope[$var_id]->isVoid()) { + if (IssueBuffer::accepts( + new AssignmentToVoid( + 'Cannot assign ' . $var_id . ' to type void', + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + $context->vars_in_scope[$var_id] = Type::getNull(); + + if (!$was_in_assignment) { + $context->inside_assignment = false; + } + + return $context->vars_in_scope[$var_id]; + } + + if ($context->vars_in_scope[$var_id]->isNever()) { + if (IssueBuffer::accepts( + new NoValue( + 'This function or method call never returns output', + new CodeLocation($statements_analyzer->getSource(), $assign_var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + $context->vars_in_scope[$var_id] = Type::getEmpty(); + + if (!$was_in_assignment) { + $context->inside_assignment = false; + } + + return $context->vars_in_scope[$var_id]; + } + + if ($statements_analyzer->data_flow_graph) { + $data_flow_graph = $statements_analyzer->data_flow_graph; + + if ($context->vars_in_scope[$var_id]->parent_nodes) { + if ($data_flow_graph instanceof TaintFlowGraph + && \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + $context->vars_in_scope[$var_id]->parent_nodes = []; + } else { + $var_location = new CodeLocation($statements_analyzer->getSource(), $assign_var); + + $new_parent_node = DataFlowNode::getForAssignment($var_id, $var_location); + + $data_flow_graph->addNode($new_parent_node); + + foreach ($context->vars_in_scope[$var_id]->parent_nodes as $parent_node) { + $data_flow_graph->addPath($parent_node, $new_parent_node, '=', [], $removed_taints); + } + + $context->vars_in_scope[$var_id]->parent_nodes = [ + $new_parent_node->id => $new_parent_node + ]; + } + } + } + } + + if (!$was_in_assignment) { + $context->inside_assignment = false; + } + + return $assign_value_type; + } + + public static function assignTypeFromVarDocblock( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node $stmt, + VarDocblockComment $var_comment, + Context $context, + ?string $var_id = null, + ?Type\Union &$comment_type = null, + ?CodeLocation\DocblockTypeLocation &$comment_type_location = null, + array $not_ignored_docblock_var_ids = [] + ) : void { + if (!$var_comment->type) { + return; + } + + $codebase = $statements_analyzer->getCodebase(); + + try { + $var_comment_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $var_comment->type, + $context->self, + $context->self, + $statements_analyzer->getParentFQCLN() + ); + + $var_comment_type->setFromDocblock(); + + $var_comment_type->check( + $statements_analyzer, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues(), + [], + false, + false, + false, + $context->calling_method_id + ); + + $type_location = null; + + if ($var_comment->type_start + && $var_comment->type_end + && $var_comment->line_number + ) { + $type_location = new CodeLocation\DocblockTypeLocation( + $statements_analyzer, + $var_comment->type_start, + $var_comment->type_end, + $var_comment->line_number + ); + + if ($codebase->alter_code) { + $codebase->classlikes->handleDocblockTypeInMigration( + $codebase, + $statements_analyzer, + $var_comment_type, + $type_location, + $context->calling_method_id + ); + } + } + + if (!$var_comment->var_id || $var_comment->var_id === $var_id) { + $comment_type = $var_comment_type; + $comment_type_location = $type_location; + return; + } + + $project_analyzer = $statements_analyzer->getProjectAnalyzer(); + + if ($codebase->find_unused_variables + && $type_location + && (!$not_ignored_docblock_var_ids || isset($not_ignored_docblock_var_ids[$var_comment->var_id])) + && isset($context->vars_in_scope[$var_comment->var_id]) + && $context->vars_in_scope[$var_comment->var_id]->getId() === $var_comment_type->getId() + && !$var_comment_type->isMixed() + ) { + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['UnnecessaryVarAnnotation']) + ) { + FileManipulationBuffer::addVarAnnotationToRemove($type_location); + } elseif (IssueBuffer::accepts( + new UnnecessaryVarAnnotation( + 'The @var ' . $var_comment_type . ' annotation for ' + . $var_comment->var_id . ' is unnecessary', + $type_location + ), + $statements_analyzer->getSuppressedIssues(), + true + )) { + // fall through + } + } + + $parent_nodes = $context->vars_in_scope[$var_comment->var_id]->parent_nodes ?? []; + $var_comment_type->parent_nodes = $parent_nodes; + + $context->vars_in_scope[$var_comment->var_id] = $var_comment_type; + } catch (\UnexpectedValueException $e) { + if (IssueBuffer::accepts( + new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($statements_analyzer->getSource(), $stmt) + ) + )) { + // fall through + } + } + } + + public static function analyzeAssignmentOperation( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\AssignOp $stmt, + Context $context + ): bool { + $array_var_id = ExpressionIdentifier::getArrayVarId( + $stmt->var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($stmt instanceof PhpParser\Node\Expr\AssignOp\Coalesce) { + $old_data_provider = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $fake_coalesce_expr = new PhpParser\Node\Expr\BinaryOp\Coalesce( + $stmt->var, + $stmt->expr, + $stmt->getAttributes() + ); + + $fake_coalesce_type = AssignmentAnalyzer::analyze( + $statements_analyzer, + $stmt->var, + $fake_coalesce_expr, + null, + $context, + $stmt->getDocComment() + ); + + $statements_analyzer->node_data = $old_data_provider; + + if ($fake_coalesce_type) { + if ($array_var_id) { + $context->vars_in_scope[$array_var_id] = $fake_coalesce_type; + } + + $statements_analyzer->node_data->setType($stmt, $fake_coalesce_type); + } + + return true; + } + + $was_in_assignment = $context->inside_assignment; + + $context->inside_assignment = true; + + $root_expr = $stmt->var; + + while ($root_expr instanceof PhpParser\Node\Expr\ArrayDimFetch) { + $root_expr = $root_expr->var; + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->var, $context) === false) { + return false; + } + + $was_inside_use = $context->inside_use; + + // if we don't know where this data is going, treat as a dead-end usage + if (!$root_expr instanceof PhpParser\Node\Expr\Variable + || (\is_string($root_expr->name) + && \in_array('$' . $root_expr->name, VariableFetchAnalyzer::SUPER_GLOBALS, true)) + ) { + $context->inside_use = true; + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + + $context->inside_use = $was_inside_use; + + if ($array_var_id + && $stmt->var instanceof PhpParser\Node\Expr\PropertyFetch + && ($stmt_var_var_type = $statements_analyzer->node_data->getType($stmt->var->var)) + && !$stmt_var_var_type->reference_free + ) { + if ($context->mutation_free) { + if (IssueBuffer::accepts( + new ImpurePropertyAssignment( + 'Cannot assign to a property from a mutation-free context', + new CodeLocation($statements_analyzer, $stmt->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } + } elseif (!$context->collect_mutations + && !$context->collect_initializations + && $stmt->var instanceof PhpParser\Node\Expr\PropertyFetch + ) { + $lhs_var_id = ExpressionIdentifier::getArrayVarId( + $stmt->var->var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($context->mutation_free) { + if (isset($context->vars_in_scope[$lhs_var_id]) + && !$context->vars_in_scope[$lhs_var_id]->allow_mutations + ) { + if (IssueBuffer::accepts( + new ImpurePropertyAssignment( + 'Cannot assign to a property from a mutation-free context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } elseif ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } + } + + $codebase = $statements_analyzer->getCodebase(); + + if ($array_var_id) { + $context->assigned_var_ids[$array_var_id] = true; + $context->possibly_assigned_var_ids[$array_var_id] = true; + + if ($codebase->find_unused_variables && $stmt->var instanceof PhpParser\Node\Expr\Variable) { + $location = new CodeLocation($statements_analyzer, $stmt->var); + $statements_analyzer->registerVariableAssignment( + $array_var_id, + $location + ); + } + } + + $stmt_var_type = $statements_analyzer->node_data->getType($stmt->var); + $stmt_var_type = $stmt_var_type ? clone $stmt_var_type: null; + + $stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr); + $result_type = null; + + if ($stmt instanceof PhpParser\Node\Expr\AssignOp\Plus + || $stmt instanceof PhpParser\Node\Expr\AssignOp\Minus + || $stmt instanceof PhpParser\Node\Expr\AssignOp\Mod + || $stmt instanceof PhpParser\Node\Expr\AssignOp\Mul + || $stmt instanceof PhpParser\Node\Expr\AssignOp\Pow + ) { + BinaryOp\NonDivArithmeticOpAnalyzer::analyze( + $statements_analyzer, + $statements_analyzer->node_data, + $stmt->var, + $stmt->expr, + $stmt, + $result_type, + $context + ); + + if ($stmt->var instanceof PhpParser\Node\Expr\ArrayDimFetch) { + $result_type = $result_type ?: Type::getMixed($context->inside_loop); + + ArrayAssignmentAnalyzer::analyze( + $statements_analyzer, + $stmt->var, + $context, + $stmt->expr, + $result_type + ); + } else { + $result_type = $result_type + ? clone $result_type + : new Type\Union([new Type\Atomic\TInt(), new Type\Atomic\TFloat()]); + } + + if ($array_var_id) { + $context->vars_in_scope[$array_var_id] = $result_type; + } + + $statements_analyzer->node_data->setType($stmt, $result_type); + + BinaryOpAnalyzer::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->var, + $stmt->expr, + 'nondivop' + ); + } elseif ($stmt instanceof PhpParser\Node\Expr\AssignOp\Div) { + if ($stmt_var_type + && $stmt_expr_type + && $stmt_var_type->hasDefinitelyNumericType() + && $stmt_expr_type->hasDefinitelyNumericType() + && $array_var_id + ) { + $context->vars_in_scope[$array_var_id] = Type::combineUnionTypes(Type::getFloat(), Type::getInt()); + $statements_analyzer->node_data->setType($stmt, clone $context->vars_in_scope[$array_var_id]); + } else { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } + + BinaryOpAnalyzer::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->var, + $stmt->expr, + 'div' + ); + } elseif ($stmt instanceof PhpParser\Node\Expr\AssignOp\Concat) { + BinaryOp\ConcatAnalyzer::analyze( + $statements_analyzer, + $stmt->var, + $stmt->expr, + $context, + $result_type + ); + + if ($result_type && $array_var_id) { + $context->vars_in_scope[$array_var_id] = $result_type; + $statements_analyzer->node_data->setType($stmt, clone $context->vars_in_scope[$array_var_id]); + + BinaryOpAnalyzer::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->var, + $stmt->expr, + 'concatop' + ); + } + } elseif ($stmt instanceof PhpParser\Node\Expr\AssignOp\BitwiseOr + || $stmt instanceof PhpParser\Node\Expr\AssignOp\BitwiseXor + || $stmt instanceof PhpParser\Node\Expr\AssignOp\BitwiseAnd + || $stmt instanceof PhpParser\Node\Expr\AssignOp\ShiftLeft + || $stmt instanceof PhpParser\Node\Expr\AssignOp\ShiftRight + ) { + BinaryOp\NonDivArithmeticOpAnalyzer::analyze( + $statements_analyzer, + $statements_analyzer->node_data, + $stmt->var, + $stmt->expr, + $stmt, + $result_type, + $context + ); + + if ($result_type && $array_var_id) { + $context->vars_in_scope[$array_var_id] = clone $result_type; + $statements_analyzer->node_data->setType($stmt, $context->vars_in_scope[$array_var_id]); + } + + BinaryOpAnalyzer::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->var, + $stmt->expr, + 'bitwiseop' + ); + } + + if ($statements_analyzer->data_flow_graph + && $array_var_id + && isset($context->vars_in_scope[$array_var_id]) + && ($stmt_type = $statements_analyzer->node_data->getType($stmt)) + ) { + $data_flow_graph = $statements_analyzer->data_flow_graph; + + if ($stmt_type->parent_nodes) { + if ($data_flow_graph instanceof TaintFlowGraph + && \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + $stmt_type->parent_nodes = []; + } else { + $var_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var); + + $new_parent_node = DataFlowNode::getForAssignment($array_var_id, $var_location); + + $data_flow_graph->addNode($new_parent_node); + + foreach ($stmt_type->parent_nodes as $parent_node) { + $data_flow_graph->addPath($parent_node, $new_parent_node, '='); + } + + $context->vars_in_scope[$array_var_id]->parent_nodes = [ + $new_parent_node->id => $new_parent_node + ]; + } + } + } + + if ($array_var_id && isset($context->vars_in_scope[$array_var_id])) { + if ($result_type && $context->vars_in_scope[$array_var_id]->by_ref) { + $result_type->by_ref = true; + } + + // removes dependent vars from $context + $context->removeDescendents( + $array_var_id, + $context->vars_in_scope[$array_var_id], + $result_type, + $statements_analyzer + ); + } else { + $root_var_id = ExpressionIdentifier::getRootVarId( + $stmt->var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($root_var_id && isset($context->vars_in_scope[$root_var_id])) { + $context->removeVarFromConflictingClauses( + $root_var_id, + $context->vars_in_scope[$root_var_id], + $statements_analyzer + ); + } + } + + if (!($stmt instanceof PhpParser\Node\Expr\AssignOp\Plus + || $stmt instanceof PhpParser\Node\Expr\AssignOp\Minus + || $stmt instanceof PhpParser\Node\Expr\AssignOp\Mod + || $stmt instanceof PhpParser\Node\Expr\AssignOp\Mul + || $stmt instanceof PhpParser\Node\Expr\AssignOp\Pow) + && $stmt->var instanceof PhpParser\Node\Expr\ArrayDimFetch + ) { + ArrayAssignmentAnalyzer::analyze( + $statements_analyzer, + $stmt->var, + $context, + null, + $result_type ?: Type::getEmpty() + ); + } + + if (!$was_in_assignment) { + $context->inside_assignment = false; + } + + return true; + } + + public static function analyzeAssignmentRef( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\AssignRef $stmt, + Context $context + ) : bool { + $assignment_type = self::analyze( + $statements_analyzer, + $stmt->var, + $stmt->expr, + null, + $context, + $stmt->getDocComment() + ); + + if ($assignment_type === false) { + return false; + } + + $assignment_type->by_ref = true; + + $lhs_var_id = ExpressionIdentifier::getArrayVarId( + $stmt->var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + $rhs_var_id = ExpressionIdentifier::getArrayVarId( + $stmt->expr, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($lhs_var_id) { + $context->vars_in_scope[$lhs_var_id] = $assignment_type; + $context->hasVariable($lhs_var_id); + $context->byref_constraints[$lhs_var_id] = new \Psalm\Internal\ReferenceConstraint(); + + if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph) { + foreach ($context->vars_in_scope[$lhs_var_id]->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + new DataFlowNode('variable-use', 'variable use', null), + 'variable-use' + ); + } + } + } + + if ($rhs_var_id) { + if (!isset($context->vars_in_scope[$rhs_var_id])) { + $context->vars_in_scope[$rhs_var_id] = Type::getMixed(); + } + + $context->byref_constraints[$rhs_var_id] = new \Psalm\Internal\ReferenceConstraint(); + } + + if ($statements_analyzer->data_flow_graph + && $lhs_var_id + && $rhs_var_id + && isset($context->vars_in_scope[$rhs_var_id]) + ) { + $rhs_type = $context->vars_in_scope[$rhs_var_id]; + + $data_flow_graph = $statements_analyzer->data_flow_graph; + + $lhs_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var); + + $lhs_node = DataFlowNode::getForAssignment($lhs_var_id, $lhs_location); + + foreach ($rhs_type->parent_nodes as $byref_destination_node) { + $data_flow_graph->addPath($lhs_node, $byref_destination_node, '='); + } + } + + return true; + } + + public static function assignByRefParam( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr $stmt, + Type\Union $by_ref_type, + Type\Union $by_ref_out_type, + Context $context, + bool $constrain_type = true, + bool $prevent_null = false + ): void { + if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch && $stmt->name instanceof PhpParser\Node\Identifier) { + $prop_name = $stmt->name->name; + + InstancePropertyAssignmentAnalyzer::analyze( + $statements_analyzer, + $stmt, + $prop_name, + null, + $by_ref_out_type, + $context + ); + + return; + } + + $var_id = ExpressionIdentifier::getVarId( + $stmt, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($var_id) { + $var_not_in_scope = false; + + if (!$by_ref_type->hasMixed() && $constrain_type) { + $context->byref_constraints[$var_id] = new \Psalm\Internal\ReferenceConstraint($by_ref_type); + } + + if (!$context->hasVariable($var_id)) { + $context->vars_possibly_in_scope[$var_id] = true; + + if (!$statements_analyzer->hasVariable($var_id)) { + if ($constrain_type + && $prevent_null + && !$by_ref_type->isMixed() + && !$by_ref_type->isNullable() + && !strpos($var_id, '->') + && !strpos($var_id, '::') + ) { + if (IssueBuffer::accepts( + new \Psalm\Issue\NullReference( + 'Not expecting null argument passed by reference', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $context->hasVariable($var_id); + } else { + $var_not_in_scope = true; + } + } elseif ($var_id === '$this') { + // don't allow changing $this + return; + } else { + $existing_type = $context->vars_in_scope[$var_id]; + + // removes dependent vars from $context + $context->removeDescendents( + $var_id, + $existing_type, + $by_ref_type, + $statements_analyzer + ); + + $by_ref_out_type = clone $by_ref_out_type; + + if ($existing_type->parent_nodes) { + $by_ref_out_type->parent_nodes += $existing_type->parent_nodes; + } + + if ($existing_type->getId() !== 'array') { + $context->vars_in_scope[$var_id] = $by_ref_out_type; + + if (!($stmt_type = $statements_analyzer->node_data->getType($stmt)) + || $stmt_type->isEmpty() + ) { + $statements_analyzer->node_data->setType($stmt, clone $by_ref_type); + } + + return; + } + } + + $context->assigned_var_ids[$var_id] = true; + + $context->vars_in_scope[$var_id] = $by_ref_out_type; + + $stmt_type = $statements_analyzer->node_data->getType($stmt); + + if (!$stmt_type || $stmt_type->isEmpty()) { + $statements_analyzer->node_data->setType($stmt, clone $by_ref_type); + } + + if ($var_not_in_scope && $stmt instanceof PhpParser\Node\Expr\Variable) { + $statements_analyzer->registerPossiblyUndefinedVariable($var_id, $stmt); + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..b7a8043a49a81deb57d7fb20469d6aa8b7c887b5 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php @@ -0,0 +1,219 @@ +left, + [ + 'stmts' => [ + new PhpParser\Node\Stmt\Expression( + $stmt->right + ) + ] + ], + $stmt->getAttributes() + ); + + return IfAnalyzer::analyze($statements_analyzer, $fake_if_stmt, $context) !== false; + } + + $pre_referenced_var_ids = $context->referenced_var_ids; + + $pre_assigned_var_ids = $context->assigned_var_ids; + + $left_context = clone $context; + + $left_context->referenced_var_ids = []; + $left_context->assigned_var_ids = []; + + /** @var list */ + $left_context->reconciled_expression_clauses = []; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->left, $left_context) === false) { + return false; + } + + $codebase = $statements_analyzer->getCodebase(); + + $left_cond_id = \spl_object_id($stmt->left); + + $left_clauses = Algebra::getFormula( + $left_cond_id, + $left_cond_id, + $stmt->left, + $context->self, + $statements_analyzer, + $codebase + ); + + foreach ($left_context->vars_in_scope as $var_id => $type) { + if (isset($left_context->assigned_var_ids[$var_id])) { + $context->vars_in_scope[$var_id] = $type; + } + } + + /** @var array */ + $left_referenced_var_ids = $left_context->referenced_var_ids; + $context->referenced_var_ids = array_merge($pre_referenced_var_ids, $left_referenced_var_ids); + + $left_assigned_var_ids = array_diff_key($left_context->assigned_var_ids, $pre_assigned_var_ids); + + $left_referenced_var_ids = array_diff_key($left_referenced_var_ids, $left_assigned_var_ids); + + $context_clauses = array_merge($left_context->clauses, $left_clauses); + + if ($left_context->reconciled_expression_clauses) { + $reconciled_expression_clauses = $left_context->reconciled_expression_clauses; + + $context_clauses = array_values( + array_filter( + $context_clauses, + function ($c) use ($reconciled_expression_clauses): bool { + return !\in_array($c->hash, $reconciled_expression_clauses); + } + ) + ); + + if (\count($context_clauses) === 1 + && $context_clauses[0]->wedge + && !$context_clauses[0]->possibilities + ) { + $context_clauses = []; + } + } + + $simplified_clauses = Algebra::simplifyCNF($context_clauses); + + $active_left_assertions = []; + + $left_type_assertions = Algebra::getTruthsFromFormula( + $simplified_clauses, + $left_cond_id, + $left_referenced_var_ids, + $active_left_assertions + ); + + $changed_var_ids = []; + + $right_context = clone $left_context; + + if ($left_type_assertions) { + // while in an and, we allow scope to boil over to support + // statements of the form if ($x && $x->foo()) + $right_vars_in_scope = Reconciler::reconcileKeyedTypes( + $left_type_assertions, + $active_left_assertions, + $context->vars_in_scope, + $changed_var_ids, + $left_referenced_var_ids, + $statements_analyzer, + [], + $context->inside_loop, + new CodeLocation($statements_analyzer->getSource(), $stmt->left), + $context->inside_negation + ); + + $right_context->vars_in_scope = $right_vars_in_scope; + + if ($context->if_scope) { + $context->if_scope->if_cond_changed_var_ids += $changed_var_ids; + } + } + + $partitioned_clauses = Context::removeReconciledClauses($left_clauses, $changed_var_ids); + + $right_context->clauses = $partitioned_clauses[0]; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->right, $right_context) === false) { + return false; + } + + $context->referenced_var_ids = array_merge( + $right_context->referenced_var_ids, + $left_context->referenced_var_ids + ); + + if ($context->inside_conditional) { + $context->updateChecks($right_context); + + $context->vars_possibly_in_scope = array_merge( + $right_context->vars_possibly_in_scope, + $left_context->vars_possibly_in_scope + ); + + $context->assigned_var_ids = array_merge( + $left_context->assigned_var_ids, + $right_context->assigned_var_ids + ); + } + + if ($context->if_context && !$context->inside_negation) { + $context->vars_in_scope = $right_context->vars_in_scope; + $if_context = $context->if_context; + + foreach ($right_context->vars_in_scope as $var_id => $type) { + if (!isset($if_context->vars_in_scope[$var_id])) { + $if_context->vars_in_scope[$var_id] = $type; + } elseif (isset($context->vars_in_scope[$var_id])) { + $if_context->vars_in_scope[$var_id] = $context->vars_in_scope[$var_id]; + } + } + + $if_context->referenced_var_ids = array_merge( + $context->referenced_var_ids, + $if_context->referenced_var_ids + ); + + $if_context->assigned_var_ids = array_merge( + $context->assigned_var_ids, + $if_context->assigned_var_ids + ); + + $if_context->reconciled_expression_clauses = array_merge( + $if_context->reconciled_expression_clauses, + array_map( + function ($c) { + return $c->hash; + }, + $partitioned_clauses[1] + ) + ); + + $if_context->vars_possibly_in_scope = array_merge( + $context->vars_possibly_in_scope, + $if_context->vars_possibly_in_scope + ); + + $if_context->updateChecks($context); + } else { + $context->vars_in_scope = $left_context->vars_in_scope; + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..f9fe56b5d81a319aa858bdba3aa72fe14ead65ea --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php @@ -0,0 +1,265 @@ +getCodebase(); + + $stmt_id = \spl_object_id($stmt); + + $if_clauses = Algebra::getFormula( + $stmt_id, + $stmt_id, + $stmt, + $context->self, + $statements_analyzer, + $codebase + ); + + $mixed_var_ids = []; + + foreach ($context->vars_in_scope as $var_id => $type) { + if ($type->hasMixed()) { + $mixed_var_ids[] = $var_id; + } + } + + foreach ($context->vars_possibly_in_scope as $var_id => $_) { + if (!isset($context->vars_in_scope[$var_id])) { + $mixed_var_ids[] = $var_id; + } + } + + $if_clauses = array_values( + array_map( + function (\Psalm\Internal\Clause $c) use ($mixed_var_ids, $stmt_id): \Psalm\Internal\Clause { + $keys = array_keys($c->possibilities); + + $mixed_var_ids = \array_diff($mixed_var_ids, $keys); + + foreach ($keys as $key) { + foreach ($mixed_var_ids as $mixed_var_id) { + if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { + return new \Psalm\Internal\Clause([], $stmt_id, $stmt_id, true); + } + } + } + + return $c; + }, + $if_clauses + ) + ); + + $ternary_clauses = Algebra::simplifyCNF(array_merge($context->clauses, $if_clauses)); + + $negated_clauses = Algebra::negateFormula($if_clauses); + + $negated_if_types = Algebra::getTruthsFromFormula($negated_clauses); + + $reconcilable_if_types = Algebra::getTruthsFromFormula($ternary_clauses); + + $changed_var_ids = []; + + if ($reconcilable_if_types) { + $t_if_vars_in_scope_reconciled = Reconciler::reconcileKeyedTypes( + $reconcilable_if_types, + [], + $t_if_context->vars_in_scope, + $changed_var_ids, + [], + $statements_analyzer, + [], + $t_if_context->inside_loop, + new CodeLocation($statements_analyzer->getSource(), $stmt->left) + ); + + foreach ($context->vars_in_scope as $var_id => $_) { + if (isset($t_if_vars_in_scope_reconciled[$var_id])) { + $t_if_context->vars_in_scope[$var_id] = $t_if_vars_in_scope_reconciled[$var_id]; + } + } + } + + if (!self::hasArrayDimFetch($stmt->left)) { + // check first if the variable was good + + IssueBuffer::startRecording(); + + ExpressionAnalyzer::analyze($statements_analyzer, $stmt->left, clone $context); + + IssueBuffer::clearRecordingLevel(); + IssueBuffer::stopRecording(); + + $naive_type = $statements_analyzer->node_data->getType($stmt->left); + + if ($naive_type + && !$naive_type->possibly_undefined + && !$naive_type->hasMixed() + && !$naive_type->isNullable() + ) { + $var_id = ExpressionIdentifier::getVarId($stmt->left, $context->self); + + if (!$var_id + || ($var_id !== '$_SESSION' && $var_id !== '$_SERVER' && !isset($changed_var_ids[$var_id])) + ) { + if ($naive_type->from_docblock) { + if (IssueBuffer::accepts( + new \Psalm\Issue\DocblockTypeContradiction( + $naive_type->getId() . ' does not contain null', + new CodeLocation($statements_analyzer, $stmt->left), + $naive_type->getId() . ' null' + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new \Psalm\Issue\TypeDoesNotContainType( + $naive_type->getId() . ' is always defined and non-null', + new CodeLocation($statements_analyzer, $stmt->left), + null + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + + $t_if_context->inside_isset = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->left, $t_if_context) === false) { + return false; + } + + $t_if_context->inside_isset = false; + + foreach ($t_if_context->vars_in_scope as $var_id => $type) { + if (isset($context->vars_in_scope[$var_id])) { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $context->vars_in_scope[$var_id], + $type, + $codebase + ); + } else { + $context->vars_in_scope[$var_id] = $type; + } + } + + $context->referenced_var_ids = array_merge( + $context->referenced_var_ids, + $t_if_context->referenced_var_ids + ); + + $t_else_context = clone $context; + + if ($negated_if_types) { + $t_else_vars_in_scope_reconciled = Reconciler::reconcileKeyedTypes( + $negated_if_types, + [], + $t_else_context->vars_in_scope, + $changed_var_ids, + [], + $statements_analyzer, + [], + $t_else_context->inside_loop, + new CodeLocation($statements_analyzer->getSource(), $stmt->right) + ); + + $t_else_context->vars_in_scope = $t_else_vars_in_scope_reconciled; + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->right, $t_else_context) === false) { + return false; + } + + $context->referenced_var_ids = array_merge( + $context->referenced_var_ids, + $t_else_context->referenced_var_ids + ); + + $lhs_type = null; + + $stmt_left_type = $statements_analyzer->node_data->getType($stmt->left); + + if ($stmt_left_type) { + $if_return_type_reconciled = AssertionReconciler::reconcile( + 'isset', + clone $stmt_left_type, + '', + $statements_analyzer, + $context->inside_loop, + [], + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues() + ); + + $lhs_type = clone $if_return_type_reconciled; + } + + $stmt_right_type = null; + + if (!$lhs_type || !($stmt_right_type = $statements_analyzer->node_data->getType($stmt->right))) { + $stmt_type = Type::getMixed(); + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + } else { + $stmt_type = Type::combineUnionTypes( + $lhs_type, + $stmt_right_type, + $codebase + ); + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + } + + return true; + } + + private static function hasArrayDimFetch(PhpParser\Node\Expr $expr) : bool + { + if ($expr instanceof PhpParser\Node\Expr\ArrayDimFetch) { + return true; + } + + if ($expr instanceof PhpParser\Node\Expr\PropertyFetch + || $expr instanceof PhpParser\Node\Expr\MethodCall + ) { + return self::hasArrayDimFetch($expr->var); + } + + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..4de054b3c988dc1a0a12e05901d26c08b0a37c7d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php @@ -0,0 +1,501 @@ +getCodebase(); + + $left_type = $statements_analyzer->node_data->getType($left); + $right_type = $statements_analyzer->node_data->getType($right); + $config = Config::getInstance(); + + if ($left_type && $right_type) { + $result_type = Type::getString(); + + if ($left_type->hasMixed() || $right_type->hasMixed()) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + if ($left_type->hasMixed()) { + if (IssueBuffer::accepts( + new MixedOperand( + 'Left operand cannot be mixed', + new CodeLocation($statements_analyzer->getSource(), $left) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new MixedOperand( + 'Right operand cannot be mixed', + new CodeLocation($statements_analyzer->getSource(), $right) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + return; + } + + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } + + if ($left_type->isNull()) { + if (IssueBuffer::accepts( + new NullOperand( + 'Cannot concatenate with a ' . $left_type, + new CodeLocation($statements_analyzer->getSource(), $left) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return; + } + + if ($right_type->isNull()) { + if (IssueBuffer::accepts( + new NullOperand( + 'Cannot concatenate with a ' . $right_type, + new CodeLocation($statements_analyzer->getSource(), $right) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return; + } + + if ($left_type->isFalse()) { + if (IssueBuffer::accepts( + new FalseOperand( + 'Cannot concatenate with a ' . $left_type, + new CodeLocation($statements_analyzer->getSource(), $left) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return; + } + + if ($right_type->isFalse()) { + if (IssueBuffer::accepts( + new FalseOperand( + 'Cannot concatenate with a ' . $right_type, + new CodeLocation($statements_analyzer->getSource(), $right) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return; + } + + if ($left_type->isNullable() && !$left_type->ignore_nullable_issues) { + if (IssueBuffer::accepts( + new PossiblyNullOperand( + 'Cannot concatenate with a possibly null ' . $left_type, + new CodeLocation($statements_analyzer->getSource(), $left) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($right_type->isNullable() && !$right_type->ignore_nullable_issues) { + if (IssueBuffer::accepts( + new PossiblyNullOperand( + 'Cannot concatenate with a possibly null ' . $right_type, + new CodeLocation($statements_analyzer->getSource(), $right) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($left_type->isFalsable() && !$left_type->ignore_falsable_issues) { + if (IssueBuffer::accepts( + new PossiblyFalseOperand( + 'Cannot concatenate with a possibly false ' . $left_type, + new CodeLocation($statements_analyzer->getSource(), $left) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($right_type->isFalsable() && !$right_type->ignore_falsable_issues) { + if (IssueBuffer::accepts( + new PossiblyFalseOperand( + 'Cannot concatenate with a possibly false ' . $right_type, + new CodeLocation($statements_analyzer->getSource(), $right) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $left_type_match = true; + $right_type_match = true; + + $has_valid_left_operand = false; + $has_valid_right_operand = false; + + $left_comparison_result = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + $right_comparison_result = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + foreach ($left_type->getAtomicTypes() as $left_type_part) { + if ($left_type_part instanceof Type\Atomic\TTemplateParam) { + if (IssueBuffer::accepts( + new MixedOperand( + 'Left operand cannot be mixed', + new CodeLocation($statements_analyzer->getSource(), $left) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return; + } + + if ($left_type_part instanceof Type\Atomic\TNull || $left_type_part instanceof Type\Atomic\TFalse) { + continue; + } + + $left_type_part_match = AtomicTypeComparator::isContainedBy( + $codebase, + $left_type_part, + new Type\Atomic\TString, + false, + false, + $left_comparison_result + ); + + $left_type_match = $left_type_match && $left_type_part_match; + + $has_valid_left_operand = $has_valid_left_operand || $left_type_part_match; + + if ($left_comparison_result->to_string_cast && $config->strict_binary_operands) { + if (IssueBuffer::accepts( + new ImplicitToStringCast( + 'Left side of concat op expects string, ' + . '\'' . $left_type . '\' provided with a __toString method', + new CodeLocation($statements_analyzer->getSource(), $left) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + foreach ($left_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof TNamedObject) { + $to_string_method_id = new \Psalm\Internal\MethodIdentifier( + $atomic_type->value, + '__tostring' + ); + + if ($codebase->methods->methodExists( + $to_string_method_id, + $context->calling_method_id, + $codebase->collect_locations + ? new CodeLocation($statements_analyzer->getSource(), $left) + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $statements_analyzer + : null, + $statements_analyzer->getFilePath() + )) { + try { + $storage = $codebase->methods->getStorage($to_string_method_id); + } catch (\UnexpectedValueException $e) { + continue; + } + + if ($context->mutation_free && !$storage->mutation_free) { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call a possibly-mutating method ' + . $atomic_type->value . '::__toString from a pure context', + new CodeLocation($statements_analyzer, $left) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } + } + } + } + } + + foreach ($right_type->getAtomicTypes() as $right_type_part) { + if ($right_type_part instanceof Type\Atomic\TTemplateParam) { + if (IssueBuffer::accepts( + new MixedOperand( + 'Right operand cannot be a template param', + new CodeLocation($statements_analyzer->getSource(), $right) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return; + } + + if ($right_type_part instanceof Type\Atomic\TNull || $right_type_part instanceof Type\Atomic\TFalse) { + continue; + } + + $right_type_part_match = AtomicTypeComparator::isContainedBy( + $codebase, + $right_type_part, + new Type\Atomic\TString, + false, + false, + $right_comparison_result + ); + + $right_type_match = $right_type_match && $right_type_part_match; + + $has_valid_right_operand = $has_valid_right_operand || $right_type_part_match; + + if ($right_comparison_result->to_string_cast && $config->strict_binary_operands) { + if (IssueBuffer::accepts( + new ImplicitToStringCast( + 'Right side of concat op expects string, ' + . '\'' . $right_type . '\' provided with a __toString method', + new CodeLocation($statements_analyzer->getSource(), $right) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + foreach ($right_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof TNamedObject) { + $to_string_method_id = new \Psalm\Internal\MethodIdentifier( + $atomic_type->value, + '__tostring' + ); + + if ($codebase->methods->methodExists( + $to_string_method_id, + $context->calling_method_id, + $codebase->collect_locations + ? new CodeLocation($statements_analyzer->getSource(), $right) + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $statements_analyzer + : null, + $statements_analyzer->getFilePath() + )) { + try { + $storage = $codebase->methods->getStorage($to_string_method_id); + } catch (\UnexpectedValueException $e) { + continue; + } + + if ($context->mutation_free && !$storage->mutation_free) { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call a possibly-mutating method ' + . $atomic_type->value . '::__toString from a pure context', + new CodeLocation($statements_analyzer, $right) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } + } + } + } + } + + if (!$left_type_match + && (!$left_comparison_result->scalar_type_match_found || $config->strict_binary_operands) + ) { + if ($has_valid_left_operand) { + if (IssueBuffer::accepts( + new PossiblyInvalidOperand( + 'Cannot concatenate with a ' . $left_type, + new CodeLocation($statements_analyzer->getSource(), $left) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidOperand( + 'Cannot concatenate with a ' . $left_type, + new CodeLocation($statements_analyzer->getSource(), $left) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if (!$right_type_match + && (!$right_comparison_result->scalar_type_match_found || $config->strict_binary_operands) + ) { + if ($has_valid_right_operand) { + if (IssueBuffer::accepts( + new PossiblyInvalidOperand( + 'Cannot concatenate with a ' . $right_type, + new CodeLocation($statements_analyzer->getSource(), $right) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidOperand( + 'Cannot concatenate with a ' . $right_type, + new CodeLocation($statements_analyzer->getSource(), $right) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + // When concatenating two known string literals (with only one possibility), + // put the concatenated string into $result_type + if ($left_type && $right_type && $left_type->isSingleStringLiteral() && $right_type->isSingleStringLiteral()) { + $literal = $left_type->getSingleStringLiteral()->value . $right_type->getSingleStringLiteral()->value; + if (strlen($literal) <= 1000) { + // Limit these to 10000 bytes to avoid extremely large union types from repeated concatenations, etc + $result_type = Type::getString($literal); + } + } else { + if ($left_type + && $right_type + ) { + $left_type_literal_value = $left_type->isSingleStringLiteral() + ? $left_type->getSingleStringLiteral()->value + : null; + + $right_type_literal_value = $right_type->isSingleStringLiteral() + ? $right_type->getSingleStringLiteral()->value + : null; + + if (($left_type->getId() === 'lowercase-string' + || $left_type->getId() === 'non-empty-lowercase-string' + || $left_type->isInt() + || ($left_type_literal_value !== null + && strtolower($left_type_literal_value) === $left_type_literal_value)) + && ($right_type->getId() === 'lowercase-string' + || $right_type->getId() === 'non-empty-lowercase-string' + || $right_type->isInt() + || ($right_type_literal_value !== null + && strtolower($right_type_literal_value) === $right_type_literal_value)) + ) { + if ($left_type->getId() === 'non-empty-lowercase-string' + || $left_type->isInt() + || ($left_type_literal_value !== null + && strtolower($left_type_literal_value) === $left_type_literal_value) + || $right_type->getId() === 'non-empty-lowercase-string' + || $right_type->isInt() + || ($right_type_literal_value !== null + && strtolower($right_type_literal_value) === $right_type_literal_value) + ) { + $result_type = new Type\Union([new Type\Atomic\TNonEmptyLowercaseString()]); + } else { + $result_type = new Type\Union([new Type\Atomic\TLowercaseString()]); + } + } elseif ($left_type->getId() === 'non-empty-string' + || $right_type->getId() === 'non-empty-string' + || $left_type_literal_value + || $right_type_literal_value + ) { + $result_type = new Type\Union([new Type\Atomic\TNonEmptyString()]); + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonComparisonOpAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonComparisonOpAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..48c1d028aed2b8e90aecc502c9dba0299b8f48b0 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonComparisonOpAnalyzer.php @@ -0,0 +1,164 @@ +node_data->getType($stmt->left); + $stmt_right_type = $statements_analyzer->node_data->getType($stmt->right); + + if (!$stmt_left_type || !$stmt_right_type) { + return; + } + + if (($stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseAnd + ) + && $stmt_left_type->hasString() + && $stmt_right_type->hasString() + ) { + $stmt_type = Type::getString(); + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + return; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Plus + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Minus + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Mod + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Mul + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Pow + || (($stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseAnd + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\ShiftLeft + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\ShiftRight + ) + ) + ) { + NonDivArithmeticOpAnalyzer::analyze( + $statements_analyzer, + $statements_analyzer->node_data, + $stmt->left, + $stmt->right, + $stmt, + $result_type, + $context + ); + + if (!$result_type) { + $result_type = new Type\Union([new Type\Atomic\TInt(), new Type\Atomic\TFloat()]); + } + + $statements_analyzer->node_data->setType($stmt, $result_type); + + BinaryOpAnalyzer::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->left, + $stmt->right, + 'nondivop' + ); + + return; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor) { + if ($stmt_left_type->hasBool() || $stmt_right_type->hasBool()) { + $statements_analyzer->node_data->setType($stmt, Type::getInt()); + } + + BinaryOpAnalyzer::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->left, + $stmt->right, + 'xor' + ); + + return; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalXor) { + if ($stmt_left_type->hasBool() || $stmt_right_type->hasBool()) { + $statements_analyzer->node_data->setType($stmt, Type::getBool()); + } + + BinaryOpAnalyzer::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->left, + $stmt->right, + 'xor' + ); + + return; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Div) { + NonDivArithmeticOpAnalyzer::analyze( + $statements_analyzer, + $statements_analyzer->node_data, + $stmt->left, + $stmt->right, + $stmt, + $result_type, + $context + ); + + if (!$result_type) { + $result_type = new Type\Union([new Type\Atomic\TInt(), new Type\Atomic\TFloat()]); + } elseif ($result_type->hasInt()) { + $result_type->addType(new TFloat); + } + + $statements_analyzer->node_data->setType($stmt, $result_type); + + BinaryOpAnalyzer::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->left, + $stmt->right, + 'div' + ); + + return; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr) { + NonDivArithmeticOpAnalyzer::analyze( + $statements_analyzer, + $statements_analyzer->node_data, + $stmt->left, + $stmt->right, + $stmt, + $result_type, + $context + ); + + BinaryOpAnalyzer::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->left, + $stmt->right, + 'or' + ); + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonDivArithmeticOpAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonDivArithmeticOpAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..7e59c0527e6685b175df10836ac5bdded40f962f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonDivArithmeticOpAnalyzer.php @@ -0,0 +1,757 @@ +getCodebase() : null; + + $left_type = $nodes->getType($left); + $right_type = $nodes->getType($right); + $config = Config::getInstance(); + + if ($left_type && $left_type->isEmpty()) { + $left_type = $right_type; + } elseif ($right_type && $right_type->isEmpty()) { + $right_type = $left_type; + } + + if ($left_type && $right_type) { + if ($left_type->isNull()) { + if ($statements_source && IssueBuffer::accepts( + new NullOperand( + 'Left operand cannot be null', + new CodeLocation($statements_source, $left) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + + return; + } + + if ($left_type->isNullable() && !$left_type->ignore_nullable_issues) { + if ($statements_source && IssueBuffer::accepts( + new PossiblyNullOperand( + 'Left operand cannot be nullable, got ' . $left_type, + new CodeLocation($statements_source, $left) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + + if ($right_type->isNull()) { + if ($statements_source && IssueBuffer::accepts( + new NullOperand( + 'Right operand cannot be null', + new CodeLocation($statements_source, $right) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + + return; + } + + if ($right_type->isNullable() && !$right_type->ignore_nullable_issues) { + if ($statements_source && IssueBuffer::accepts( + new PossiblyNullOperand( + 'Right operand cannot be nullable, got ' . $right_type, + new CodeLocation($statements_source, $right) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + + if ($left_type->isFalse()) { + if ($statements_source && IssueBuffer::accepts( + new FalseOperand( + 'Left operand cannot be false', + new CodeLocation($statements_source, $left) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + + return; + } + + if ($left_type->isFalsable() && !$left_type->ignore_falsable_issues) { + if ($statements_source && IssueBuffer::accepts( + new PossiblyFalseOperand( + 'Left operand cannot be falsable, got ' . $left_type, + new CodeLocation($statements_source, $left) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + + if ($right_type->isFalse()) { + if ($statements_source && IssueBuffer::accepts( + new FalseOperand( + 'Right operand cannot be false', + new CodeLocation($statements_source, $right) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + + return; + } + + if ($right_type->isFalsable() && !$right_type->ignore_falsable_issues) { + if ($statements_source && IssueBuffer::accepts( + new PossiblyFalseOperand( + 'Right operand cannot be falsable, got ' . $right_type, + new CodeLocation($statements_source, $right) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + + $invalid_left_messages = []; + $invalid_right_messages = []; + $has_valid_left_operand = false; + $has_valid_right_operand = false; + $has_string_increment = false; + + foreach ($left_type->getAtomicTypes() as $left_type_part) { + foreach ($right_type->getAtomicTypes() as $right_type_part) { + $candidate_result_type = self::analyzeNonDivOperands( + $statements_source, + $codebase, + $config, + $context, + $left, + $right, + $parent, + $left_type_part, + $right_type_part, + $invalid_left_messages, + $invalid_right_messages, + $has_valid_left_operand, + $has_valid_right_operand, + $has_string_increment, + $result_type + ); + + if ($candidate_result_type) { + $result_type = $candidate_result_type; + return; + } + } + } + + if ($invalid_left_messages && $statements_source) { + $first_left_message = $invalid_left_messages[0]; + + if ($has_valid_left_operand) { + if (IssueBuffer::accepts( + new PossiblyInvalidOperand( + $first_left_message, + new CodeLocation($statements_source, $left) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidOperand( + $first_left_message, + new CodeLocation($statements_source, $left) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($invalid_right_messages && $statements_source) { + $first_right_message = $invalid_right_messages[0]; + + if ($has_valid_right_operand) { + if (IssueBuffer::accepts( + new PossiblyInvalidOperand( + $first_right_message, + new CodeLocation($statements_source, $right) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidOperand( + $first_right_message, + new CodeLocation($statements_source, $right) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($has_string_increment && $statements_source) { + if (IssueBuffer::accepts( + new StringIncrement( + 'Possibly unintended string increment', + new CodeLocation($statements_source, $left) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + /** + * @param string[] &$invalid_left_messages + * @param string[] &$invalid_right_messages + */ + private static function analyzeNonDivOperands( + ?StatementsSource $statements_source, + ?\Psalm\Codebase $codebase, + Config $config, + ?Context $context, + PhpParser\Node\Expr $left, + PhpParser\Node\Expr $right, + PhpParser\Node $parent, + Type\Atomic $left_type_part, + Type\Atomic $right_type_part, + array &$invalid_left_messages, + array &$invalid_right_messages, + bool &$has_valid_left_operand, + bool &$has_valid_right_operand, + bool &$has_string_increment, + Type\Union &$result_type = null + ): ?Type\Union { + if ($left_type_part instanceof TLiteralInt + && $right_type_part instanceof TLiteralInt + && ($left instanceof PhpParser\Node\Scalar || $left instanceof PhpParser\Node\Expr\ConstFetch) + && ($right instanceof PhpParser\Node\Scalar || $right instanceof PhpParser\Node\Expr\ConstFetch) + ) { + // time for some arithmetic! + + $calculated_type = null; + + if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Plus) { + $calculated_type = Type::getInt(false, $left_type_part->value + $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Minus) { + $calculated_type = Type::getInt(false, $left_type_part->value - $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mod) { + $calculated_type = Type::getInt(false, $left_type_part->value % $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mul) { + $calculated_type = Type::getInt(false, $left_type_part->value * $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Pow) { + $calculated_type = Type::getInt(false, $left_type_part->value ^ $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr) { + $calculated_type = Type::getInt(false, $left_type_part->value | $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseAnd) { + $calculated_type = Type::getInt(false, $left_type_part->value & $right_type_part->value); + } + + if ($calculated_type) { + if ($result_type) { + $result_type = Type::combineUnionTypes( + $calculated_type, + $result_type + ); + } else { + $result_type = $calculated_type; + } + + $has_valid_left_operand = true; + $has_valid_right_operand = true; + + return null; + } + } + + if ($left_type_part instanceof TNull || $right_type_part instanceof TNull) { + // null case is handled above + return null; + } + + if ($left_type_part instanceof TFalse || $right_type_part instanceof TFalse) { + // null case is handled above + return null; + } + + if ($left_type_part instanceof Type\Atomic\TString + && $right_type_part instanceof TInt + && $parent instanceof PhpParser\Node\Expr\PostInc + ) { + $has_string_increment = true; + + if (!$result_type) { + $result_type = Type::getString(); + } else { + $result_type = Type::combineUnionTypes(Type::getString(), $result_type); + } + + $has_valid_left_operand = true; + $has_valid_right_operand = true; + + return null; + } + + if ($left_type_part instanceof TTemplateParam + && $right_type_part instanceof TTemplateParam + ) { + $combined_type = Type::combineUnionTypes( + $left_type_part->as, + $right_type_part->as + ); + + $combined_atomic_types = array_values($combined_type->getAtomicTypes()); + + if (\count($combined_atomic_types) <= 2) { + $left_type_part = $combined_atomic_types[0]; + $right_type_part = $combined_atomic_types[1] ?? $combined_atomic_types[0]; + } + } + + if ($left_type_part instanceof TMixed + || $right_type_part instanceof TMixed + || $left_type_part instanceof TTemplateParam + || $right_type_part instanceof TTemplateParam + ) { + if ($statements_source && $codebase && $context) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_source->getFilePath() === $statements_source->getRootFilePath() + && (!(($source = $statements_source->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_source->getFilePath()); + } + } + + if ($left_type_part instanceof TMixed || $left_type_part instanceof TTemplateParam) { + if ($statements_source && IssueBuffer::accepts( + new MixedOperand( + 'Left operand cannot be mixed', + new CodeLocation($statements_source, $left) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } else { + if ($statements_source && IssueBuffer::accepts( + new MixedOperand( + 'Right operand cannot be mixed', + new CodeLocation($statements_source, $right) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + + if ($left_type_part instanceof TMixed + && $left_type_part->from_loop_isset + && $parent instanceof PhpParser\Node\Expr\AssignOp\Plus + && !$right_type_part instanceof TMixed + ) { + $result_type_member = new Type\Union([$right_type_part]); + + if (!$result_type) { + $result_type = $result_type_member; + } else { + $result_type = Type::combineUnionTypes($result_type_member, $result_type); + } + + return null; + } + + $from_loop_isset = (!($left_type_part instanceof TMixed) || $left_type_part->from_loop_isset) + && (!($right_type_part instanceof TMixed) || $right_type_part->from_loop_isset); + + $result_type = Type::getMixed($from_loop_isset); + + return $result_type; + } + + if ($statements_source && $codebase && $context) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_source->getFilePath() === $statements_source->getRootFilePath() + && (!(($parent_source = $statements_source->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_source->getFilePath()); + } + } + + if ($left_type_part instanceof TArray + || $right_type_part instanceof TArray + || $left_type_part instanceof TKeyedArray + || $right_type_part instanceof TKeyedArray + || $left_type_part instanceof TList + || $right_type_part instanceof TList + ) { + if ((!$right_type_part instanceof TArray + && !$right_type_part instanceof TKeyedArray + && !$right_type_part instanceof TList) + || (!$left_type_part instanceof TArray + && !$left_type_part instanceof TKeyedArray + && !$left_type_part instanceof TList) + ) { + if (!$left_type_part instanceof TArray + && !$left_type_part instanceof TKeyedArray + && !$left_type_part instanceof TList + ) { + $invalid_left_messages[] = 'Cannot add an array to a non-array ' . $left_type_part; + } else { + $invalid_right_messages[] = 'Cannot add an array to a non-array ' . $right_type_part; + } + + if ($left_type_part instanceof TArray + || $left_type_part instanceof TKeyedArray + || $left_type_part instanceof TList + ) { + $has_valid_left_operand = true; + } elseif ($right_type_part instanceof TArray + || $right_type_part instanceof TKeyedArray + || $right_type_part instanceof TList + ) { + $has_valid_right_operand = true; + } + + $result_type = Type::getArray(); + + return null; + } + + $has_valid_right_operand = true; + $has_valid_left_operand = true; + + if ($left_type_part instanceof TKeyedArray + && $right_type_part instanceof TKeyedArray + ) { + $definitely_existing_mixed_right_properties = array_diff_key( + $right_type_part->properties, + $left_type_part->properties + ); + + $properties = $left_type_part->properties; + + foreach ($right_type_part->properties as $key => $type) { + if (!isset($properties[$key])) { + $properties[$key] = $type; + } elseif ($properties[$key]->possibly_undefined) { + $properties[$key] = Type::combineUnionTypes( + $properties[$key], + $type, + $codebase + ); + + $properties[$key]->possibly_undefined = $type->possibly_undefined; + } + } + + if (!$left_type_part->sealed) { + foreach ($definitely_existing_mixed_right_properties as $key => $type) { + $properties[$key] = Type::combineUnionTypes(Type::getMixed(), $type); + } + } + + $result_type_member = new Type\Union([new TKeyedArray($properties)]); + } else { + $result_type_member = TypeCombination::combineTypes( + [$left_type_part, $right_type_part], + $codebase, + true + ); + } + + if (!$result_type) { + $result_type = $result_type_member; + } else { + $result_type = Type::combineUnionTypes($result_type_member, $result_type, $codebase, true); + } + + if ($left instanceof PhpParser\Node\Expr\ArrayDimFetch + && $context + && $statements_source instanceof StatementsAnalyzer + ) { + ArrayAssignmentAnalyzer::updateArrayType( + $statements_source, + $left, + $right, + $result_type, + $context + ); + } + + return null; + } + + if (($left_type_part instanceof TNamedObject && strtolower($left_type_part->value) === 'gmp') + || ($right_type_part instanceof TNamedObject && strtolower($right_type_part->value) === 'gmp') + ) { + if ((($left_type_part instanceof TNamedObject + && strtolower($left_type_part->value) === 'gmp') + && (($right_type_part instanceof TNamedObject + && strtolower($right_type_part->value) === 'gmp') + || ($right_type_part->isNumericType() || $right_type_part instanceof TMixed))) + || (($right_type_part instanceof TNamedObject + && strtolower($right_type_part->value) === 'gmp') + && (($left_type_part instanceof TNamedObject + && strtolower($left_type_part->value) === 'gmp') + || ($left_type_part->isNumericType() || $left_type_part instanceof TMixed))) + ) { + if (!$result_type) { + $result_type = new Type\Union([new TNamedObject('GMP')]); + } else { + $result_type = Type::combineUnionTypes( + new Type\Union([new TNamedObject('GMP')]), + $result_type + ); + } + } else { + if ($statements_source && IssueBuffer::accepts( + new InvalidOperand( + 'Cannot add GMP to non-numeric type', + new CodeLocation($statements_source, $parent) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + + return null; + } + + if ($left_type_part instanceof Type\Atomic\TLiteralString) { + if (preg_match('/^\-?\d+$/', $left_type_part->value)) { + $left_type_part = new Type\Atomic\TLiteralInt((int) $left_type_part->value); + } elseif (preg_match('/^\-?\d?\.\d+$/', $left_type_part->value)) { + $left_type_part = new Type\Atomic\TLiteralFloat((float) $left_type_part->value); + } + } + + if ($right_type_part instanceof Type\Atomic\TLiteralString) { + if (preg_match('/^\-?\d+$/', $right_type_part->value)) { + $right_type_part = new Type\Atomic\TLiteralInt((int) $right_type_part->value); + } elseif (preg_match('/^\-?\d?\.\d+$/', $right_type_part->value)) { + $right_type_part = new Type\Atomic\TLiteralFloat((float) $right_type_part->value); + } + } + + if ($left_type_part->isNumericType() || $right_type_part->isNumericType()) { + if (($left_type_part instanceof TNumeric || $right_type_part instanceof TNumeric) + && ($left_type_part->isNumericType() && $right_type_part->isNumericType()) + ) { + if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mod) { + $result_type = Type::getInt(); + } elseif (!$result_type) { + $result_type = Type::getNumeric(); + } else { + $result_type = Type::combineUnionTypes(Type::getNumeric(), $result_type); + } + + $has_valid_right_operand = true; + $has_valid_left_operand = true; + + return null; + } + + if ($left_type_part instanceof TInt && $right_type_part instanceof TInt) { + $left_is_positive = $left_type_part instanceof TPositiveInt + || ($left_type_part instanceof TLiteralInt && $left_type_part->value > 0); + + $right_is_positive = $right_type_part instanceof TPositiveInt + || ($right_type_part instanceof TLiteralInt && $right_type_part->value > 0); + + if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Minus) { + $always_positive = false; + } elseif ($left_is_positive && $right_is_positive) { + $always_positive = true; + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Plus + && ($left_type_part instanceof TLiteralInt && $left_type_part->value === 0) + && $right_is_positive + ) { + $always_positive = true; + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Plus + && ($right_type_part instanceof TLiteralInt && $right_type_part->value === 0) + && $left_is_positive + ) { + $always_positive = true; + } else { + $always_positive = false; + } + + if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mod) { + $result_type = $always_positive + ? new Type\Union([new Type\Atomic\TPositiveInt(), new TLiteralInt(0)]) + : Type::getInt(); + } elseif (!$result_type) { + $result_type = $always_positive ? Type::getPositiveInt(true) : Type::getInt(true); + } else { + $result_type = Type::combineUnionTypes( + $always_positive ? Type::getPositiveInt(true) : Type::getInt(true), + $result_type + ); + } + + $has_valid_right_operand = true; + $has_valid_left_operand = true; + + return null; + } + + if ($left_type_part instanceof TFloat && $right_type_part instanceof TFloat) { + if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mod) { + $result_type = Type::getInt(); + } elseif (!$result_type) { + $result_type = Type::getFloat(); + } else { + $result_type = Type::combineUnionTypes(Type::getFloat(), $result_type); + } + + $has_valid_right_operand = true; + $has_valid_left_operand = true; + + return null; + } + + if (($left_type_part instanceof TFloat && $right_type_part instanceof TInt) + || ($left_type_part instanceof TInt && $right_type_part instanceof TFloat) + ) { + if ($config->strict_binary_operands) { + if ($statements_source && IssueBuffer::accepts( + new InvalidOperand( + 'Cannot add ints to floats', + new CodeLocation($statements_source, $parent) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + + if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mod) { + $result_type = Type::getInt(); + } elseif (!$result_type) { + $result_type = Type::getFloat(); + } else { + $result_type = Type::combineUnionTypes(Type::getFloat(), $result_type); + } + + $has_valid_right_operand = true; + $has_valid_left_operand = true; + + return null; + } + + if ($left_type_part->isNumericType() && $right_type_part->isNumericType()) { + if ($config->strict_binary_operands) { + if ($statements_source && IssueBuffer::accepts( + new InvalidOperand( + 'Cannot add numeric types together, please cast explicitly', + new CodeLocation($statements_source, $parent) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + } + + if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mod) { + $result_type = Type::getInt(); + } else { + $result_type = new Type\Union([new Type\Atomic\TInt, new Type\Atomic\TFloat]); + } + + $has_valid_right_operand = true; + $has_valid_left_operand = true; + + return null; + } + + if (!$left_type_part->isNumericType()) { + $invalid_left_messages[] = 'Cannot perform a numeric operation with a non-numeric type ' + . $left_type_part; + $has_valid_right_operand = true; + } else { + $invalid_right_messages[] = 'Cannot perform a numeric operation with a non-numeric type ' + . $right_type_part; + $has_valid_left_operand = true; + } + } else { + $invalid_left_messages[] = + 'Cannot perform a numeric operation with non-numeric types ' . $left_type_part + . ' and ' . $right_type_part; + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..6722b29d88de6e6fbb990e28c30da2abc0812984 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php @@ -0,0 +1,378 @@ +left, $stmt->left->getAttributes()), + [ + 'stmts' => [ + new PhpParser\Node\Stmt\Expression( + $stmt->right + ) + ] + ], + $stmt->getAttributes() + ); + + return IfAnalyzer::analyze($statements_analyzer, $fake_if_stmt, $context) !== false; + } + + $codebase = $statements_analyzer->getCodebase(); + + $mic_drop_context = null; + + if (!$stmt->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr + || !$stmt->left->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr + || !$stmt->left->left->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr + ) { + $if_scope = new \Psalm\Internal\Scope\IfScope(); + + try { + $if_conditional_scope = IfAnalyzer::analyzeIfConditional( + $statements_analyzer, + $stmt->left, + $context, + $codebase, + $if_scope, + $context->branch_point ?: (int) $stmt->getAttribute('startFilePos') + ); + + $left_context = $if_conditional_scope->if_context; + + $left_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids; + $left_assigned_var_ids = $if_conditional_scope->cond_assigned_var_ids; + + if ($stmt->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) { + $mic_drop_context = clone $context; + } + } catch (\Psalm\Exception\ScopeAnalysisException $e) { + return false; + } + } else { + $pre_referenced_var_ids = $context->referenced_var_ids; + $context->referenced_var_ids = []; + + $pre_assigned_var_ids = $context->assigned_var_ids; + + $mic_drop_context = clone $context; + + $left_context = clone $context; + $left_context->parent_context = $context; + $left_context->if_context = null; + $left_context->assigned_var_ids = []; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->left, $left_context) === false) { + return false; + } + + foreach ($left_context->vars_in_scope as $var_id => $type) { + if (!isset($context->vars_in_scope[$var_id])) { + if (isset($left_context->assigned_var_ids[$var_id])) { + $context->vars_in_scope[$var_id] = clone $type; + } + } else { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $context->vars_in_scope[$var_id], + $type, + $codebase + ); + } + } + + $left_referenced_var_ids = $left_context->referenced_var_ids; + $left_context->referenced_var_ids = array_merge($pre_referenced_var_ids, $left_referenced_var_ids); + + $left_assigned_var_ids = array_diff_key($left_context->assigned_var_ids, $pre_assigned_var_ids); + $left_context->assigned_var_ids = array_merge($pre_assigned_var_ids, $left_context->assigned_var_ids); + + $left_referenced_var_ids = array_diff_key($left_referenced_var_ids, $left_assigned_var_ids); + } + + $left_cond_id = \spl_object_id($stmt->left); + + $left_clauses = Algebra::getFormula( + $left_cond_id, + $left_cond_id, + $stmt->left, + $context->self, + $statements_analyzer, + $codebase + ); + + try { + $negated_left_clauses = Algebra::negateFormula($left_clauses); + } catch (\Psalm\Exception\ComplicatedExpressionException $e) { + try { + $negated_left_clauses = Algebra::getFormula( + $left_cond_id, + $left_cond_id, + new PhpParser\Node\Expr\BooleanNot($stmt->left), + $context->self, + $statements_analyzer, + $codebase, + false + ); + } catch (\Psalm\Exception\ComplicatedExpressionException $e) { + return false; + } + } + + if ($left_context->reconciled_expression_clauses) { + $reconciled_expression_clauses = $left_context->reconciled_expression_clauses; + + $negated_left_clauses = array_values( + array_filter( + $negated_left_clauses, + function ($c) use ($reconciled_expression_clauses): bool { + return !\in_array($c->hash, $reconciled_expression_clauses); + } + ) + ); + + if (\count($negated_left_clauses) === 1 + && $negated_left_clauses[0]->wedge + && !$negated_left_clauses[0]->possibilities + ) { + $negated_left_clauses = []; + } + } + + $clauses_for_right_analysis = Algebra::simplifyCNF( + array_merge( + $context->clauses, + $negated_left_clauses + ) + ); + + $active_negated_type_assertions = []; + + $negated_type_assertions = Algebra::getTruthsFromFormula( + $clauses_for_right_analysis, + $left_cond_id, + $left_referenced_var_ids, + $active_negated_type_assertions + ); + + $changed_var_ids = []; + + $right_context = clone $context; + + if ($stmt->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr + && $left_assigned_var_ids + && $mic_drop_context + ) { + IfAnalyzer::addConditionallyAssignedVarsToContext( + $statements_analyzer, + $stmt->left, + $mic_drop_context, + $right_context, + $left_assigned_var_ids + ); + } + + if ($negated_type_assertions) { + // while in an or, we allow scope to boil over to support + // statements of the form if ($x === null || $x->foo()) + $right_vars_in_scope = Reconciler::reconcileKeyedTypes( + $negated_type_assertions, + $active_negated_type_assertions, + $right_context->vars_in_scope, + $changed_var_ids, + $left_referenced_var_ids, + $statements_analyzer, + [], + $left_context->inside_loop, + new CodeLocation($statements_analyzer->getSource(), $stmt->left), + !$context->inside_negation + ); + $right_context->vars_in_scope = $right_vars_in_scope; + } + + $right_context->clauses = $clauses_for_right_analysis; + + if ($changed_var_ids) { + $partitioned_clauses = Context::removeReconciledClauses($right_context->clauses, $changed_var_ids); + $right_context->clauses = $partitioned_clauses[0]; + $right_context->reconciled_expression_clauses = array_merge( + $context->reconciled_expression_clauses, + array_map( + function ($c) { + return $c->hash; + }, + $partitioned_clauses[1] + ) + ); + + $partitioned_clauses = Context::removeReconciledClauses($context->clauses, $changed_var_ids); + $context->clauses = $partitioned_clauses[0]; + $context->reconciled_expression_clauses = array_merge( + $context->reconciled_expression_clauses, + array_map( + function ($c) { + return $c->hash; + }, + $partitioned_clauses[1] + ) + ); + } + + $right_context->if_context = null; + + $pre_referenced_var_ids = $right_context->referenced_var_ids; + $right_context->referenced_var_ids = []; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->right, $right_context) === false) { + return false; + } + + $right_referenced_var_ids = $right_context->referenced_var_ids; + $right_context->referenced_var_ids = array_merge($pre_referenced_var_ids, $right_referenced_var_ids); + + $right_cond_id = \spl_object_id($stmt->right); + + $right_clauses = Algebra::getFormula( + $right_cond_id, + $right_cond_id, + $stmt->right, + $context->self, + $statements_analyzer, + $codebase + ); + + $combined_right_clauses = Algebra::simplifyCNF( + array_merge($clauses_for_right_analysis, $right_clauses) + ); + + $active_right_type_assertions = []; + + $right_type_assertions = Algebra::getTruthsFromFormula( + $combined_right_clauses, + $right_cond_id, + $right_referenced_var_ids, + $active_right_type_assertions + ); + + if ($right_type_assertions) { + $right_changed_var_ids = []; + + Reconciler::reconcileKeyedTypes( + $right_type_assertions, + $active_right_type_assertions, + $right_context->vars_in_scope, + $right_changed_var_ids, + $right_referenced_var_ids, + $statements_analyzer, + [], + $left_context->inside_loop, + new CodeLocation($statements_analyzer->getSource(), $stmt->right), + $context->inside_negation + ); + } + + if (!($stmt->right instanceof PhpParser\Node\Expr\Exit_)) { + foreach ($right_context->vars_in_scope as $var_id => $type) { + if (isset($context->vars_in_scope[$var_id])) { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $context->vars_in_scope[$var_id], + $type, + $codebase + ); + } + } + } elseif ($stmt->left instanceof PhpParser\Node\Expr\Assign) { + $var_id = ExpressionIdentifier::getVarId($stmt->left->var, $context->self); + + if ($var_id && isset($left_context->vars_in_scope[$var_id])) { + $left_inferred_reconciled = AssertionReconciler::reconcile( + '!falsy', + clone $left_context->vars_in_scope[$var_id], + '', + $statements_analyzer, + $context->inside_loop, + [], + new CodeLocation($statements_analyzer->getSource(), $stmt->left), + $statements_analyzer->getSuppressedIssues() + ); + + $context->vars_in_scope[$var_id] = $left_inferred_reconciled; + } + } + + if ($context->inside_conditional) { + $context->updateChecks($right_context); + } + + $context->referenced_var_ids = array_merge( + $right_context->referenced_var_ids, + $context->referenced_var_ids + ); + + $context->assigned_var_ids = array_merge( + $context->assigned_var_ids, + $right_context->assigned_var_ids + ); + + if ($context->if_context) { + $if_context = $context->if_context; + + foreach ($right_context->vars_in_scope as $var_id => $type) { + if (isset($if_context->vars_in_scope[$var_id])) { + $if_context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $type, + $if_context->vars_in_scope[$var_id], + $codebase + ); + } elseif (isset($left_context->vars_in_scope[$var_id])) { + $if_context->vars_in_scope[$var_id] = $left_context->vars_in_scope[$var_id]; + } + } + + $if_context->referenced_var_ids = array_merge( + $context->referenced_var_ids, + $if_context->referenced_var_ids + ); + + $if_context->assigned_var_ids = array_merge( + $context->assigned_var_ids, + $if_context->assigned_var_ids + ); + + $if_context->updateChecks($context); + } + + $context->vars_possibly_in_scope = array_merge( + $right_context->vars_possibly_in_scope, + $context->vars_possibly_in_scope + ); + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..73470aed3a0b05ba1620c16e136813f263d05297 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php @@ -0,0 +1,485 @@ + 100) { + $statements_analyzer->node_data->setType($stmt, Type::getString()); + + // ignore deeply-nested string concatenation + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd || + $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalAnd + ) { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + + $expr_result = BinaryOp\AndAnalyzer::analyze( + $statements_analyzer, + $stmt, + $context, + $from_stmt + ); + + $context->inside_use = $was_inside_use; + + $statements_analyzer->node_data->setType($stmt, Type::getBool()); + + return $expr_result; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr || + $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalOr + ) { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + + $expr_result = BinaryOp\OrAnalyzer::analyze( + $statements_analyzer, + $stmt, + $context, + $from_stmt + ); + + $context->inside_use = $was_inside_use; + + $statements_analyzer->node_data->setType($stmt, Type::getBool()); + + return $expr_result; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Coalesce) { + $expr_result = BinaryOp\CoalesceAnalyzer::analyze( + $statements_analyzer, + $stmt, + $context + ); + + self::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->left, + $stmt->right, + 'coalesce' + ); + + return $expr_result; + } + + if ($stmt->left instanceof PhpParser\Node\Expr\BinaryOp) { + if (self::analyze($statements_analyzer, $stmt->left, $context, $nesting + 1) === false) { + return false; + } + } else { + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->left, $context) === false) { + return false; + } + } + + if ($stmt->right instanceof PhpParser\Node\Expr\BinaryOp) { + if (self::analyze($statements_analyzer, $stmt->right, $context, $nesting + 1) === false) { + return false; + } + } else { + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->right, $context) === false) { + return false; + } + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) { + $stmt_type = Type::getString(); + + BinaryOp\ConcatAnalyzer::analyze( + $statements_analyzer, + $stmt->left, + $stmt->right, + $context, + $result_type + ); + + if ($result_type) { + $stmt_type = $result_type; + } + + if ($statements_analyzer->data_flow_graph + && ($statements_analyzer->data_flow_graph instanceof VariableUseGraph + || !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())) + ) { + $stmt_left_type = $statements_analyzer->node_data->getType($stmt->left); + $stmt_right_type = $statements_analyzer->node_data->getType($stmt->right); + + $var_location = new CodeLocation($statements_analyzer, $stmt); + + $new_parent_node = DataFlowNode::getForAssignment('concat', $var_location); + $statements_analyzer->data_flow_graph->addNode($new_parent_node); + + $stmt_type->parent_nodes = [ + $new_parent_node->id => $new_parent_node + ]; + + if ($stmt_left_type && $stmt_left_type->parent_nodes) { + foreach ($stmt_left_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath($parent_node, $new_parent_node, 'concat'); + } + } + + if ($stmt_right_type && $stmt_right_type->parent_nodes) { + foreach ($stmt_right_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath($parent_node, $new_parent_node, 'concat'); + } + } + } + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Spaceship) { + $statements_analyzer->node_data->setType($stmt, Type::getInt()); + + self::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->left, + $stmt->right, + '<=>' + ); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotEqual + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Greater + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Smaller + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual + ) { + $statements_analyzer->node_data->setType($stmt, Type::getBool()); + + $stmt_left_type = $statements_analyzer->node_data->getType($stmt->left); + $stmt_right_type = $statements_analyzer->node_data->getType($stmt->right); + + if (($stmt instanceof PhpParser\Node\Expr\BinaryOp\Greater + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Smaller + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual) + && $statements_analyzer->getCodebase()->config->strict_binary_operands + && $stmt_left_type + && $stmt_right_type + && (($stmt_left_type->isSingle() && $stmt_left_type->hasBool()) + || ($stmt_right_type->isSingle() && $stmt_right_type->hasBool())) + ) { + if (IssueBuffer::accepts( + new InvalidOperand( + 'Cannot compare ' . $stmt_left_type->getId() . ' to ' . $stmt_right_type->getId(), + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if (($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotEqual + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) + && $stmt->left instanceof PhpParser\Node\Expr\FuncCall + && $stmt->left->name instanceof PhpParser\Node\Name + && $stmt->left->name->parts === ['substr'] + && isset($stmt->left->args[1]) + && $stmt_right_type + && $stmt_right_type->hasLiteralString() + ) { + $from_type = $statements_analyzer->node_data->getType($stmt->left->args[1]->value); + + $length_type = isset($stmt->left->args[2]) + ? ($statements_analyzer->node_data->getType($stmt->left->args[2]->value) ?: Type::getMixed()) + : null; + + $string_length = null; + + if ($from_type && $from_type->isSingleIntLiteral() && $length_type === null) { + $string_length = -$from_type->getSingleIntLiteral()->value; + } elseif ($length_type && $length_type->isSingleIntLiteral()) { + $string_length = $length_type->getSingleIntLiteral()->value; + } + + if ($string_length > 0) { + foreach ($stmt_right_type->getAtomicTypes() as $atomic_right_type) { + if ($atomic_right_type instanceof Type\Atomic\TLiteralString) { + if (\strlen($atomic_right_type->value) !== $string_length) { + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical + ) { + if ($atomic_right_type->from_docblock) { + if (IssueBuffer::accepts( + new \Psalm\Issue\DocblockTypeContradiction( + $atomic_right_type . ' string length is not ' . $string_length, + new CodeLocation($statements_analyzer, $stmt), + null + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new \Psalm\Issue\TypeDoesNotContainType( + $atomic_right_type . ' string length is not ' . $string_length, + new CodeLocation($statements_analyzer, $stmt), + null + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } else { + if ($atomic_right_type->from_docblock) { + if (IssueBuffer::accepts( + new \Psalm\Issue\RedundantConditionGivenDocblockType( + $atomic_right_type . ' string length is never ' . $string_length, + new CodeLocation($statements_analyzer, $stmt), + null + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new \Psalm\Issue\RedundantCondition( + $atomic_right_type . ' string length is never ' . $string_length, + new CodeLocation($statements_analyzer, $stmt), + null + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + } + } + } + + $codebase = $statements_analyzer->getCodebase(); + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal + && $stmt_left_type + && $stmt_right_type + && ($context->mutation_free || $codebase->alter_code) + ) { + self::checkForImpureEqualityComparison( + $statements_analyzer, + $stmt, + $stmt_left_type, + $stmt_right_type + ); + } + + self::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->left, + $stmt->right, + 'comparison' + ); + + return true; + } + + BinaryOp\NonComparisonOpAnalyzer::analyze( + $statements_analyzer, + $stmt, + $context + ); + + return true; + } + + public static function addDataFlow( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr $stmt, + PhpParser\Node\Expr $left, + PhpParser\Node\Expr $right, + string $type = 'binaryop' + ) : void { + if ($stmt->getLine() === -1) { + throw new \UnexpectedValueException('bad'); + } + $result_type = $statements_analyzer->node_data->getType($stmt); + + if ($statements_analyzer->data_flow_graph + && $result_type + ) { + $stmt_left_type = $statements_analyzer->node_data->getType($left); + $stmt_right_type = $statements_analyzer->node_data->getType($right); + + $var_location = new CodeLocation($statements_analyzer, $stmt); + + $new_parent_node = DataFlowNode::getForAssignment($type, $var_location); + $statements_analyzer->data_flow_graph->addNode($new_parent_node); + + $result_type->parent_nodes = [ + $new_parent_node->id => $new_parent_node + ]; + + if ($stmt_left_type && $stmt_left_type->parent_nodes) { + foreach ($stmt_left_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath($parent_node, $new_parent_node, $type); + } + } + + if ($stmt_right_type && $stmt_right_type->parent_nodes) { + foreach ($stmt_right_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath($parent_node, $new_parent_node, $type); + } + } + + if ($stmt instanceof PhpParser\Node\Expr\AssignOp + && $statements_analyzer->data_flow_graph instanceof VariableUseGraph + ) { + $root_expr = $left; + + while ($root_expr instanceof PhpParser\Node\Expr\ArrayDimFetch) { + $root_expr = $root_expr->var; + } + + if ($left instanceof PhpParser\Node\Expr\PropertyFetch) { + $statements_analyzer->data_flow_graph->addPath( + $new_parent_node, + new DataFlowNode('variable-use', 'variable use', null), + 'used-by-instance-property' + ); + } if ($left instanceof PhpParser\Node\Expr\StaticPropertyFetch) { + $statements_analyzer->data_flow_graph->addPath( + $new_parent_node, + new DataFlowNode('variable-use', 'variable use', null), + 'use-in-static-property' + ); + } elseif (!$left instanceof PhpParser\Node\Expr\Variable) { + $statements_analyzer->data_flow_graph->addPath( + $new_parent_node, + new DataFlowNode('variable-use', 'variable use', null), + 'variable-use' + ); + } + } + } + } + + private static function checkForImpureEqualityComparison( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\BinaryOp\Equal $stmt, + Type\Union $stmt_left_type, + Type\Union $stmt_right_type + ) : void { + $codebase = $statements_analyzer->getCodebase(); + + if ($stmt_left_type->hasString() && $stmt_right_type->hasObjectType()) { + foreach ($stmt_right_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof TNamedObject) { + try { + $storage = $codebase->methods->getStorage( + new \Psalm\Internal\MethodIdentifier( + $atomic_type->value, + '__tostring' + ) + ); + } catch (\UnexpectedValueException $e) { + continue; + } + + if (!$storage->mutation_free) { + if ($statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } else { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call a possibly-mutating method ' + . $atomic_type->value . '::__toString from a pure context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + } elseif ($stmt_right_type->hasString() && $stmt_left_type->hasObjectType()) { + foreach ($stmt_left_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof TNamedObject) { + try { + $storage = $codebase->methods->getStorage( + new \Psalm\Internal\MethodIdentifier( + $atomic_type->value, + '__tostring' + ) + ); + } catch (\UnexpectedValueException $e) { + continue; + } + + if (!$storage->mutation_free) { + if ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } else { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call a possibly-mutating method ' + . $atomic_type->value . '::__toString from a pure context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..b8d9fdb58a5c6a194bb5f0a92c46dd9b2780e350 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php @@ -0,0 +1,94 @@ +expr, $context) === false) { + return false; + } + + if (!($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr))) { + $statements_analyzer->node_data->setType($stmt, new Type\Union([new TInt(), new TString()])); + } elseif ($stmt_expr_type->isMixed()) { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } else { + $acceptable_types = []; + $unacceptable_type = null; + $has_valid_operand = false; + + foreach ($stmt_expr_type->getAtomicTypes() as $type_string => $type_part) { + if ($type_part instanceof TInt || $type_part instanceof TString) { + if ($type_part instanceof Type\Atomic\TLiteralInt) { + $type_part->value = ~$type_part->value; + } elseif ($type_part instanceof Type\Atomic\TLiteralString) { + $type_part->value = ~$type_part->value; + } + + $acceptable_types[] = $type_part; + $has_valid_operand = true; + } elseif ($type_part instanceof TFloat) { + $type_part = ($type_part instanceof Type\Atomic\TLiteralFloat) ? + new Type\Atomic\TLiteralInt(~$type_part->value) : + new TInt; + + $stmt_expr_type->removeType($type_string); + $stmt_expr_type->addType($type_part); + + $acceptable_types[] = $type_part; + $has_valid_operand = true; + } elseif (!$unacceptable_type) { + $unacceptable_type = $type_part; + } + } + + if ($unacceptable_type || !$acceptable_types) { + $message = 'Cannot negate a non-numeric non-string type ' . $unacceptable_type; + if ($has_valid_operand) { + if (IssueBuffer::accepts( + new PossiblyInvalidOperand( + $message, + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidOperand( + $message, + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } else { + $statements_analyzer->node_data->setType($stmt, new Type\Union($acceptable_types)); + } + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..9772b810cd8d1de5f804d44f6e4909586700dc91 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php @@ -0,0 +1,36 @@ +node_data->setType($stmt, $stmt_type); + + $inside_negation = $context->inside_negation; + + $context->inside_negation = !$inside_negation; + + $result = ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context); + + $context->inside_negation = $inside_negation; + + $expr_type = $statements_analyzer->node_data->getType($stmt->expr); + + if ($expr_type) { + $stmt_type->parent_nodes = $expr_type->parent_nodes; + } + + return $result; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..2888c890850a213fd51a00daad0fddd44af2ee6e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -0,0 +1,1358 @@ +> $class_generic_params + * @return false|null + */ + public static function checkArgumentMatches( + StatementsAnalyzer $statements_analyzer, + ?string $cased_method_id, + ?string $self_fq_class_name, + ?string $static_fq_class_name, + CodeLocation $function_call_location, + ?FunctionLikeParameter $function_param, + int $argument_offset, + int $unpacked_argument_offset, + bool $allow_named_args, + PhpParser\Node\Arg $arg, + ?Type\Union $arg_value_type, + Context $context, + array $class_generic_params, + ?TemplateResult $template_result, + bool $specialize_taint, + bool $in_call_map + ): ?bool { + $codebase = $statements_analyzer->getCodebase(); + + if (!$arg_value_type) { + if ($function_param && !$function_param->by_ref) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + $param_type = $function_param->type; + + if ($function_param->is_variadic + && $param_type + && $param_type->hasArray() + ) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var TList|TArray + */ + $array_type = $param_type->getAtomicTypes()['array']; + + if ($array_type instanceof TList) { + $param_type = $array_type->type_param; + } else { + $param_type = $array_type->type_params[1]; + } + } + + if ($param_type && !$param_type->hasMixed()) { + if (IssueBuffer::accepts( + new MixedArgument( + 'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id + . ' cannot be mixed, expecting ' . $param_type, + new CodeLocation($statements_analyzer->getSource(), $arg->value), + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + return null; + } + + if (!$function_param) { + return null; + } + + if ($function_param->expect_variable + && $arg_value_type->isSingleStringLiteral() + && !$arg->value instanceof PhpParser\Node\Scalar\MagicConst + && !$arg->value instanceof PhpParser\Node\Expr\ConstFetch + ) { + $values = \preg_split('//u', $arg_value_type->getSingleStringLiteral()->value, -1, \PREG_SPLIT_NO_EMPTY); + + $prev_ord = 0; + + $gt_count = 0; + + foreach ($values as $value) { + /** + * @var int + * @psalm-suppress UnnecessaryVarAnnotation + */ + $ord = \mb_ord($value); + + if ($ord > $prev_ord) { + $gt_count++; + } + + $prev_ord = $ord; + } + + if (count($values) < 12 || ($gt_count / count($values)) < 0.8) { + if (IssueBuffer::accepts( + new InvalidLiteralArgument( + 'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id + . ' expects a non-literal value, ' . $arg_value_type->getId() . ' provided', + new CodeLocation($statements_analyzer->getSource(), $arg->value), + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if (self::checkFunctionLikeTypeMatches( + $statements_analyzer, + $codebase, + $cased_method_id, + $self_fq_class_name, + $static_fq_class_name, + $function_call_location, + $function_param, + $allow_named_args, + $arg_value_type, + $argument_offset, + $unpacked_argument_offset, + $arg, + $context, + $class_generic_params, + $template_result, + $specialize_taint, + $in_call_map + ) === false) { + return false; + } + + return null; + } + + /** + * @param array> $class_generic_params + * @param array> $generic_params + * @param array> $template_types + * @return false|null + */ + private static function checkFunctionLikeTypeMatches( + StatementsAnalyzer $statements_analyzer, + Codebase $codebase, + ?string $cased_method_id, + ?string $self_fq_class_name, + ?string $static_fq_class_name, + CodeLocation $function_call_location, + FunctionLikeParameter $function_param, + bool $allow_named_args, + Type\Union $arg_type, + int $argument_offset, + int $unpacked_argument_offset, + PhpParser\Node\Arg $arg, + Context $context, + ?array $class_generic_params, + ?TemplateResult $template_result, + bool $specialize_taint, + bool $in_call_map + ): ?bool { + if (!$function_param->type) { + if (!$codebase->infer_types_from_usage && !$statements_analyzer->data_flow_graph) { + return null; + } + + $param_type = Type::getMixed(); + } else { + $param_type = clone $function_param->type; + } + + $bindable_template_params = []; + + if ($template_result) { + $bindable_template_params = $param_type->getTemplateTypes(); + } + + if ($class_generic_params) { + $empty_generic_params = []; + + $empty_template_result = new TemplateResult($class_generic_params, $empty_generic_params); + + $arg_value_type = $statements_analyzer->node_data->getType($arg->value); + + $param_type = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $param_type, + $empty_template_result, + $codebase, + $statements_analyzer, + $arg_value_type, + $argument_offset, + $context->self ?: 'fn-' . $context->calling_function_id + ); + + $arg_type = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $arg_type, + $empty_template_result, + $codebase, + $statements_analyzer, + $arg_value_type, + $argument_offset, + $context->self ?: 'fn-' . $context->calling_function_id + ); + } + + if ($template_result && $template_result->template_types) { + $arg_type_param = $arg_type; + + if ($arg->unpack) { + $arg_type_param = null; + + foreach ($arg_type->getAtomicTypes() as $arg_atomic_type) { + if ($arg_atomic_type instanceof Type\Atomic\TArray + || $arg_atomic_type instanceof Type\Atomic\TList + || $arg_atomic_type instanceof Type\Atomic\TKeyedArray + ) { + if ($arg_atomic_type instanceof Type\Atomic\TKeyedArray) { + $arg_type_param = $arg_atomic_type->getGenericValueType(); + } elseif ($arg_atomic_type instanceof Type\Atomic\TList) { + $arg_type_param = $arg_atomic_type->type_param; + } else { + $arg_type_param = $arg_atomic_type->type_params[1]; + } + } elseif ($arg_atomic_type instanceof Type\Atomic\TIterable) { + $arg_type_param = $arg_atomic_type->type_params[1]; + } elseif ($arg_atomic_type instanceof Type\Atomic\TNamedObject) { + ForeachAnalyzer::getKeyValueParamsForTraversableObject( + $arg_atomic_type, + $codebase, + $key_type, + $arg_type_param + ); + } + } + + if (!$arg_type_param) { + $arg_type_param = Type::getMixed(); + $arg_type_param->parent_nodes = $arg_type->parent_nodes; + } + } + + $param_type = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $param_type, + $template_result, + $codebase, + $statements_analyzer, + $arg_type_param, + $argument_offset, + $context->self, + $context->calling_method_id ?: $context->calling_function_id + ); + + foreach ($bindable_template_params as $template_type) { + if (!isset( + $template_result->upper_bounds + [$template_type->param_name] + [$template_type->defining_class] + ) + && !isset( + $template_result->lower_bounds + [$template_type->param_name] + [$template_type->defining_class] + ) + ) { + $template_result->upper_bounds[$template_type->param_name][$template_type->defining_class] = [ + clone $template_type->as, + 0 + ]; + } + } + } + + $parent_class = null; + + $classlike_storage = null; + $static_classlike_storage = null; + + if ($self_fq_class_name) { + $classlike_storage = $codebase->classlike_storage_provider->get($self_fq_class_name); + $parent_class = $classlike_storage->parent_class; + $static_classlike_storage = $classlike_storage; + + if ($static_fq_class_name && $static_fq_class_name !== $self_fq_class_name) { + $static_classlike_storage = $codebase->classlike_storage_provider->get($static_fq_class_name); + } + } + + $fleshed_out_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $param_type, + $classlike_storage ? $classlike_storage->name : null, + $static_classlike_storage ? $static_classlike_storage->name : null, + $parent_class, + true, + false, + $static_classlike_storage ? $static_classlike_storage->final : false + ); + + $fleshed_out_signature_type = $function_param->signature_type + ? \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $function_param->signature_type, + $classlike_storage ? $classlike_storage->name : null, + $static_classlike_storage ? $static_classlike_storage->name : null, + $parent_class + ) + : null; + + $unpacked_atomic_array = null; + + if ($arg->unpack) { + if ($arg_type->hasMixed()) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + if (IssueBuffer::accepts( + new MixedArgument( + 'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id + . ' cannot be ' . $arg_type->getId() . ', expecting array', + new CodeLocation($statements_analyzer->getSource(), $arg->value), + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + if ($cased_method_id) { + $arg_location = new CodeLocation($statements_analyzer->getSource(), $arg->value); + + self::processTaintedness( + $statements_analyzer, + $cased_method_id, + $argument_offset, + $arg_location, + $function_call_location, + $function_param, + $arg_type, + $arg->value, + $context, + $specialize_taint + ); + } + + return null; + } + + if ($arg_type->hasArray()) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var Type\Atomic\TArray|Type\Atomic\TList|Type\Atomic\TKeyedArray + */ + $unpacked_atomic_array = $arg_type->getAtomicTypes()['array']; + + if ($unpacked_atomic_array instanceof Type\Atomic\TKeyedArray) { + if ($function_param->is_variadic) { + $arg_type = $unpacked_atomic_array->getGenericValueType(); + } elseif ($codebase->php_major_version >= 8 + && $allow_named_args + && isset($unpacked_atomic_array->properties[$function_param->name]) + ) { + $arg_type = clone $unpacked_atomic_array->properties[$function_param->name]; + } elseif ($unpacked_atomic_array->is_list + && isset($unpacked_atomic_array->properties[$unpacked_argument_offset]) + ) { + $arg_type = clone $unpacked_atomic_array->properties[$unpacked_argument_offset]; + } else { + $arg_type = Type::getMixed(); + } + } elseif ($unpacked_atomic_array instanceof Type\Atomic\TList) { + $arg_type = $unpacked_atomic_array->type_param; + } else { + $arg_type = $unpacked_atomic_array->type_params[1]; + } + } else { + foreach ($arg_type->getAtomicTypes() as $atomic_type) { + if (!$atomic_type->isIterable($codebase)) { + if (IssueBuffer::accepts( + new InvalidArgument( + 'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id + . ' expects array, ' . $atomic_type->getId() . ' provided', + new CodeLocation($statements_analyzer->getSource(), $arg->value), + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + continue; + } + } + + return null; + } + } + + if (self::verifyType( + $statements_analyzer, + $arg_type, + $fleshed_out_type, + $fleshed_out_signature_type, + $cased_method_id, + $argument_offset, + new CodeLocation($statements_analyzer->getSource(), $arg->value), + $arg->value, + $context, + $function_param, + $arg->unpack, + $unpacked_atomic_array, + $specialize_taint, + $in_call_map, + $function_call_location + ) === false) { + return false; + } + + return null; + } + + /** + * @param Type\Atomic\TKeyedArray|Type\Atomic\TArray|Type\Atomic\TList $unpacked_atomic_array + * @return null|false + */ + public static function verifyType( + StatementsAnalyzer $statements_analyzer, + Type\Union $input_type, + Type\Union $param_type, + ?Type\Union $signature_param_type, + ?string $cased_method_id, + int $argument_offset, + CodeLocation $arg_location, + PhpParser\Node\Expr $input_expr, + Context $context, + FunctionLikeParameter $function_param, + bool $unpack, + ?Type\Atomic $unpacked_atomic_array, + bool $specialize_taint, + bool $in_call_map, + CodeLocation $function_call_location + ): ?bool { + $codebase = $statements_analyzer->getCodebase(); + + if ($param_type->hasMixed()) { + if ($codebase->infer_types_from_usage + && !$input_type->hasMixed() + && !$param_type->from_docblock + && !$param_type->had_template + && $cased_method_id + && strpos($cased_method_id, '::') + && !strpos($cased_method_id, '__') + ) { + $method_parts = explode('::', $cased_method_id); + + $method_id = new \Psalm\Internal\MethodIdentifier($method_parts[0], strtolower($method_parts[1])); + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + if ($declaring_method_id) { + $id_lc = strtolower((string) $declaring_method_id); + if (!isset($codebase->analyzer->possible_method_param_types[$id_lc][$argument_offset])) { + $codebase->analyzer->possible_method_param_types[$id_lc][$argument_offset] + = clone $input_type; + } else { + $codebase->analyzer->possible_method_param_types[$id_lc][$argument_offset] + = Type::combineUnionTypes( + $codebase->analyzer->possible_method_param_types[$id_lc][$argument_offset], + clone $input_type, + $codebase + ); + } + } + } + + if ($cased_method_id) { + self::processTaintedness( + $statements_analyzer, + $cased_method_id, + $argument_offset, + $arg_location, + $function_call_location, + $function_param, + $input_type, + $input_expr, + $context, + $specialize_taint + ); + } + + return null; + } + + $method_identifier = $cased_method_id ? ' of ' . $cased_method_id : ''; + + if ($input_type->hasMixed()) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + if (IssueBuffer::accepts( + new MixedArgument( + 'Argument ' . ($argument_offset + 1) . $method_identifier + . ' cannot be ' . $input_type->getId() . ', expecting ' . + $param_type, + $arg_location, + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + if ($input_type->isMixed()) { + if (!$function_param->by_ref + && !($function_param->is_variadic xor $unpack) + && $cased_method_id !== 'echo' + && $cased_method_id !== 'print' + && (!$in_call_map || $context->strict_types) + ) { + self::coerceValueAfterGatekeeperArgument( + $statements_analyzer, + $input_type, + false, + $input_expr, + $param_type, + $signature_param_type, + $context, + $unpack, + $unpacked_atomic_array + ); + } + } + + if ($cased_method_id) { + $input_type = self::processTaintedness( + $statements_analyzer, + $cased_method_id, + $argument_offset, + $arg_location, + $function_call_location, + $function_param, + $input_type, + $input_expr, + $context, + $specialize_taint + ); + } + + if ($input_type->isMixed()) { + return null; + } + } + + if ($input_type->isNever()) { + if (IssueBuffer::accepts( + new NoValue( + 'This function or method call never returns output', + $arg_location + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return null; + } + + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } + + if ($function_param->by_ref) { + $param_type->possibly_undefined = true; + } + + if ($param_type->hasCallableType() + && $param_type->isSingle() + && $input_type->isSingleStringLiteral() + && !\Psalm\Internal\Codebase\InternalCallMapHandler::inCallMap($input_type->getSingleStringLiteral()->value) + ) { + foreach ($input_type->getAtomicTypes() as $key => $atomic_type) { + $candidate_callable = CallableTypeComparator::getCallableFromAtomic( + $codebase, + $atomic_type, + null, + $statements_analyzer + ); + + if ($candidate_callable) { + $input_type->removeType($key); + $input_type->addType($candidate_callable); + } + } + } + + $union_comparison_results = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + $type_match_found = UnionTypeComparator::isContainedBy( + $codebase, + $input_type, + $param_type, + true, + true, + $union_comparison_results + ); + + $replace_input_type = false; + + if ($union_comparison_results->replacement_union_type) { + $replace_input_type = true; + $input_type = $union_comparison_results->replacement_union_type; + } + + if ($cased_method_id) { + $old_input_type = $input_type; + + $input_type = self::processTaintedness( + $statements_analyzer, + $cased_method_id, + $argument_offset, + $arg_location, + $function_call_location, + $function_param, + $input_type, + $input_expr, + $context, + $specialize_taint + ); + + if ($old_input_type !== $input_type) { + $replace_input_type = true; + } + } + + if ($type_match_found + && $param_type->hasCallableType() + ) { + $potential_method_ids = []; + + foreach ($input_type->getAtomicTypes() as $input_type_part) { + if ($input_type_part instanceof Type\Atomic\TKeyedArray) { + $potential_method_id = CallableTypeComparator::getCallableMethodIdFromTKeyedArray( + $input_type_part, + $codebase, + $context->calling_method_id, + $statements_analyzer->getFilePath() + ); + + if ($potential_method_id && $potential_method_id !== 'not-callable') { + $potential_method_ids[] = $potential_method_id; + } + } elseif ($input_type_part instanceof Type\Atomic\TLiteralString + && strpos($input_type_part->value, '::') + ) { + $parts = explode('::', $input_type_part->value); + $potential_method_ids[] = new \Psalm\Internal\MethodIdentifier( + $parts[0], + strtolower($parts[1]) + ); + } + } + + foreach ($potential_method_ids as $potential_method_id) { + $codebase->methods->methodExists( + $potential_method_id, + $context->calling_method_id, + null, + $statements_analyzer, + $statements_analyzer->getFilePath() + ); + } + } + + if ($context->strict_types + && !$input_type->hasArray() + && !$param_type->from_docblock + && $cased_method_id !== 'echo' + && $cased_method_id !== 'print' + && $cased_method_id !== 'sprintf' + ) { + $union_comparison_results->scalar_type_match_found = false; + + if ($union_comparison_results->to_string_cast) { + $union_comparison_results->to_string_cast = false; + $type_match_found = false; + } + } + + if ($union_comparison_results->type_coerced && !$input_type->hasMixed()) { + if ($union_comparison_results->type_coerced_from_mixed) { + if (IssueBuffer::accepts( + new MixedArgumentTypeCoercion( + 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . $param_type->getId() . + ', parent type ' . $input_type->getId() . ' provided', + $arg_location, + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep soldiering on + } + } else { + if (IssueBuffer::accepts( + new ArgumentTypeCoercion( + 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . $param_type->getId() . + ', parent type ' . $input_type->getId() . ' provided', + $arg_location, + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep soldiering on + } + } + } + + if ($union_comparison_results->to_string_cast && $cased_method_id !== 'echo' && $cased_method_id !== 'print') { + if (IssueBuffer::accepts( + new ImplicitToStringCast( + 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . + $param_type->getId() . ', ' . $input_type->getId() . ' provided with a __toString method', + $arg_location + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if (!$type_match_found && !$union_comparison_results->type_coerced) { + $types_can_be_identical = UnionTypeComparator::canBeContainedBy( + $codebase, + $input_type, + $param_type, + true, + true + ); + + if ($union_comparison_results->scalar_type_match_found) { + if ($cased_method_id !== 'echo' && $cased_method_id !== 'print') { + if (IssueBuffer::accepts( + new InvalidScalarArgument( + 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . + $param_type->getId() . ', ' . $input_type->getId() . ' provided', + $arg_location, + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } elseif ($types_can_be_identical) { + if (IssueBuffer::accepts( + new PossiblyInvalidArgument( + 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . $param_type->getId() . + ', possibly different type ' . $input_type->getId() . ' provided', + $arg_location, + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidArgument( + 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . $param_type->getId() . + ', ' . $input_type->getId() . ' provided', + $arg_location, + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + return null; + } + + if ($input_expr instanceof PhpParser\Node\Scalar\String_ + || $input_expr instanceof PhpParser\Node\Expr\Array_ + || $input_expr instanceof PhpParser\Node\Expr\BinaryOp\Concat + ) { + foreach ($param_type->getAtomicTypes() as $param_type_part) { + if ($param_type_part instanceof TClassString + && $input_expr instanceof PhpParser\Node\Scalar\String_ + && $param_type->isSingle() + ) { + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $input_expr->value, + $arg_location, + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues() + ) === false + ) { + return null; + } + } elseif ($param_type_part instanceof TArray + && $input_expr instanceof PhpParser\Node\Expr\Array_ + ) { + foreach ($param_type_part->type_params[1]->getAtomicTypes() as $param_array_type_part) { + if ($param_array_type_part instanceof TClassString) { + foreach ($input_expr->items as $item) { + if ($item && $item->value instanceof PhpParser\Node\Scalar\String_) { + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $item->value->value, + $arg_location, + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues() + ) === false + ) { + return null; + } + } + } + } + } + } elseif ($param_type_part instanceof TCallable) { + $can_be_callable_like_array = false; + if ($param_type->hasArray()) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + */ + $param_array_type = $param_type->getAtomicTypes()['array']; + + $row_type = null; + if ($param_array_type instanceof TList) { + $row_type = $param_array_type->type_param; + } elseif ($param_array_type instanceof TArray) { + $row_type = $param_array_type->type_params[1]; + } elseif ($param_array_type instanceof Type\Atomic\TKeyedArray) { + $row_type = $param_array_type->getGenericArrayType()->type_params[1]; + } + + if ($row_type && + ($row_type->hasMixed() || $row_type->hasString()) + ) { + $can_be_callable_like_array = true; + } + } + + if (!$can_be_callable_like_array) { + $function_ids = CallAnalyzer::getFunctionIdsFromCallableArg( + $statements_analyzer, + $input_expr + ); + + foreach ($function_ids as $function_id) { + if (strpos($function_id, '::') !== false) { + if ($function_id[0] === '$') { + $function_id = \substr($function_id, 1); + } + + $function_id_parts = explode('&', $function_id); + + $non_existent_method_ids = []; + $has_valid_method = false; + + foreach ($function_id_parts as $function_id_part) { + [$callable_fq_class_name, $method_name] = explode('::', $function_id_part); + + switch ($callable_fq_class_name) { + case 'self': + case 'static': + case 'parent': + $container_class = $statements_analyzer->getFQCLN(); + + if ($callable_fq_class_name === 'parent') { + $container_class = $statements_analyzer->getParentFQCLN(); + } + + if (!$container_class) { + continue 2; + } + + $callable_fq_class_name = $container_class; + } + + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $callable_fq_class_name, + $arg_location, + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues() + ) === false + ) { + return null; + } + + $function_id_part = new \Psalm\Internal\MethodIdentifier( + $callable_fq_class_name, + strtolower($method_name) + ); + + $call_method_id = new \Psalm\Internal\MethodIdentifier( + $callable_fq_class_name, + '__call' + ); + + if (!$codebase->classOrInterfaceExists($callable_fq_class_name)) { + return null; + } + + if (!$codebase->methods->methodExists($function_id_part) + && !$codebase->methods->methodExists($call_method_id) + ) { + $non_existent_method_ids[] = $function_id_part; + } else { + $has_valid_method = true; + } + } + + if (!$has_valid_method && !$param_type->hasString() && !$param_type->hasArray()) { + if (MethodAnalyzer::checkMethodExists( + $codebase, + $non_existent_method_ids[0], + $arg_location, + $statements_analyzer->getSuppressedIssues() + ) === false + ) { + return null; + } + } + } else { + if (!$param_type->hasString() + && !$param_type->hasArray() + && CallAnalyzer::checkFunctionExists( + $statements_analyzer, + $function_id, + $arg_location, + false + ) === false + ) { + return null; + } + } + } + } + } + } + } + + if (!$param_type->isNullable() && $cased_method_id !== 'echo' && $cased_method_id !== 'print') { + if ($input_type->isNull()) { + if (IssueBuffer::accepts( + new NullArgument( + 'Argument ' . ($argument_offset + 1) . $method_identifier . ' cannot be null, ' . + 'null value provided to parameter with type ' . $param_type->getId(), + $arg_location, + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return null; + } + + if ($input_type->isNullable() && !$input_type->ignore_nullable_issues) { + if (IssueBuffer::accepts( + new PossiblyNullArgument( + 'Argument ' . ($argument_offset + 1) . $method_identifier . ' cannot be null, possibly ' . + 'null value provided', + $arg_location, + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($input_type->isFalsable() + && !$param_type->hasBool() + && !$param_type->hasScalar() + && !$input_type->ignore_falsable_issues + && $cased_method_id !== 'echo' + ) { + if (IssueBuffer::accepts( + new PossiblyFalseArgument( + 'Argument ' . ($argument_offset + 1) . $method_identifier . ' cannot be false, possibly ' . + 'false value provided', + $arg_location, + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if (($type_match_found || $input_type->hasMixed()) + && !$function_param->by_ref + && !($function_param->is_variadic xor $unpack) + && $cased_method_id !== 'echo' + && $cased_method_id !== 'print' + && (!$in_call_map || $context->strict_types) + ) { + self::coerceValueAfterGatekeeperArgument( + $statements_analyzer, + $input_type, + $replace_input_type, + $input_expr, + $param_type, + $signature_param_type, + $context, + $unpack, + $unpacked_atomic_array + ); + } + + return null; + } + + /** + * @param Type\Atomic\TKeyedArray|Type\Atomic\TArray|Type\Atomic\TList $unpacked_atomic_array + */ + private static function coerceValueAfterGatekeeperArgument( + StatementsAnalyzer $statements_analyzer, + Type\Union $input_type, + bool $input_type_changed, + PhpParser\Node\Expr $input_expr, + Type\Union $param_type, + ?Type\Union $signature_param_type, + Context $context, + bool $unpack, + ?Type\Atomic $unpacked_atomic_array + ) : void { + if ($param_type->hasMixed()) { + return; + } + + if (!$input_type_changed && $param_type->from_docblock && !$input_type->hasMixed()) { + $input_type = clone $input_type; + + foreach ($param_type->getAtomicTypes() as $param_atomic_type) { + if ($param_atomic_type instanceof Type\Atomic\TGenericObject) { + foreach ($input_type->getAtomicTypes() as $input_atomic_type) { + if ($input_atomic_type instanceof Type\Atomic\TGenericObject + && $input_atomic_type->value === $param_atomic_type->value + ) { + foreach ($input_atomic_type->type_params as $i => $type_param) { + if ($type_param->isEmpty() && isset($param_atomic_type->type_params[$i])) { + $input_type_changed = true; + + /** @psalm-suppress PropertyTypeCoercion */ + $input_atomic_type->type_params[$i] = clone $param_atomic_type->type_params[$i]; + } + } + } + } + } + } + + if (!$input_type_changed) { + return; + } + } + + $var_id = ExpressionIdentifier::getVarId( + $input_expr, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($var_id) { + $was_cloned = false; + + if ($input_type->isNullable() && !$param_type->isNullable()) { + $input_type = clone $input_type; + $was_cloned = true; + $input_type->removeType('null'); + } + + if ($input_type->getId() === $param_type->getId()) { + if (!$was_cloned) { + $was_cloned = true; + $input_type = clone $input_type; + } + + $input_type->from_docblock = false; + + foreach ($input_type->getAtomicTypes() as $atomic_type) { + $atomic_type->from_docblock = false; + } + } elseif ($input_type->hasMixed() && $signature_param_type) { + $was_cloned = true; + $parent_nodes = $input_type->parent_nodes; + $by_ref = $input_type->by_ref; + $input_type = clone $signature_param_type; + + if ($input_type->isNullable()) { + $input_type->ignore_nullable_issues = true; + } + + $input_type->parent_nodes = $parent_nodes; + $input_type->by_ref = $by_ref; + } + + if ($context->inside_conditional && !isset($context->assigned_var_ids[$var_id])) { + $context->assigned_var_ids[$var_id] = false; + } + + if ($was_cloned) { + $context->removeVarFromConflictingClauses($var_id, null, $statements_analyzer); + } + + if ($unpack) { + if ($unpacked_atomic_array instanceof Type\Atomic\TList) { + $unpacked_atomic_array = clone $unpacked_atomic_array; + $unpacked_atomic_array->type_param = $input_type; + + $context->vars_in_scope[$var_id] = new Type\Union([$unpacked_atomic_array]); + } elseif ($unpacked_atomic_array instanceof Type\Atomic\TArray) { + $unpacked_atomic_array = clone $unpacked_atomic_array; + /** @psalm-suppress PropertyTypeCoercion */ + $unpacked_atomic_array->type_params[1] = $input_type; + + $context->vars_in_scope[$var_id] = new Type\Union([$unpacked_atomic_array]); + } elseif ($unpacked_atomic_array instanceof Type\Atomic\TKeyedArray + && $unpacked_atomic_array->is_list + ) { + $unpacked_atomic_array = $unpacked_atomic_array->getList(); + $unpacked_atomic_array->type_param = $input_type; + + $context->vars_in_scope[$var_id] = new Type\Union([$unpacked_atomic_array]); + } else { + $context->vars_in_scope[$var_id] = new Type\Union([ + new TArray([ + Type::getInt(), + $input_type + ]), + ]); + } + } else { + $context->vars_in_scope[$var_id] = $input_type; + } + } + } + + private static function processTaintedness( + StatementsAnalyzer $statements_analyzer, + string $cased_method_id, + int $argument_offset, + CodeLocation $arg_location, + CodeLocation $function_call_location, + FunctionLikeParameter $function_param, + Type\Union $input_type, + PhpParser\Node\Expr $expr, + Context $context, + bool $specialize_taint + ) : Type\Union { + $codebase = $statements_analyzer->getCodebase(); + + if (!$statements_analyzer->data_flow_graph + || ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph + && \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())) + ) { + return $input_type; + } + + if ($function_param->type && $function_param->type->isString() && !$input_type->isString()) { + $cast_type = CastAnalyzer::castStringAttempt( + $statements_analyzer, + $context, + $input_type, + $expr, + false + ); + + $input_type = clone $input_type; + $input_type->parent_nodes += $cast_type->parent_nodes; + } + + if ($specialize_taint) { + $method_node = DataFlowNode::getForMethodArgument( + $cased_method_id, + $cased_method_id, + $argument_offset, + $function_param->location, + $function_call_location + ); + } else { + $method_node = DataFlowNode::getForMethodArgument( + $cased_method_id, + $cased_method_id, + $argument_offset, + $function_param->location + ); + + if (strpos($cased_method_id, '::')) { + [$fq_classlike_name, $cased_method_name] = explode('::', $cased_method_id); + $method_name = strtolower($cased_method_name); + $class_storage = $codebase->classlike_storage_provider->get($fq_classlike_name); + + foreach ($class_storage->dependent_classlikes as $dependent_classlike_lc => $_) { + $dependent_classlike_storage = $codebase->classlike_storage_provider->get( + $dependent_classlike_lc + ); + $new_sink = DataFlowNode::getForMethodArgument( + $dependent_classlike_lc . '::' . $method_name, + $dependent_classlike_storage->name . '::' . $cased_method_name, + $argument_offset, + $arg_location, + null + ); + + $statements_analyzer->data_flow_graph->addNode($new_sink); + $statements_analyzer->data_flow_graph->addPath($method_node, $new_sink, 'arg'); + } + + if (isset($class_storage->overridden_method_ids[$method_name])) { + foreach ($class_storage->overridden_method_ids[$method_name] as $parent_method_id) { + $new_sink = DataFlowNode::getForMethodArgument( + (string) $parent_method_id, + $codebase->methods->getCasedMethodId($parent_method_id), + $argument_offset, + $arg_location, + null + ); + + $statements_analyzer->data_flow_graph->addNode($new_sink); + $statements_analyzer->data_flow_graph->addPath($method_node, $new_sink, 'arg'); + } + } + } + } + + $statements_analyzer->data_flow_graph->addNode($method_node); + + $argument_value_node = DataFlowNode::getForAssignment( + 'call to ' . $cased_method_id, + $arg_location + ); + + $statements_analyzer->data_flow_graph->addNode($argument_value_node); + + $statements_analyzer->data_flow_graph->addPath($argument_value_node, $method_node, 'arg'); + + if ($function_param->sinks && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + if ($specialize_taint) { + $sink = TaintSink::getForMethodArgument( + $cased_method_id, + $cased_method_id, + $argument_offset, + $function_param->location, + $function_call_location + ); + } else { + $sink = TaintSink::getForMethodArgument( + $cased_method_id, + $cased_method_id, + $argument_offset, + $function_param->location + ); + } + + $sink->taints = $function_param->sinks; + + $statements_analyzer->data_flow_graph->addSink($sink); + } + + foreach ($input_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addNode($method_node); + $statements_analyzer->data_flow_graph->addPath($parent_node, $argument_value_node, 'arg'); + } + + if ($function_param->assert_untainted) { + $input_type = clone $input_type; + $input_type->parent_nodes = []; + } + + return $input_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php new file mode 100644 index 0000000000000000000000000000000000000000..04763199977fda30ee81f8f83b9125b925bb60e7 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php @@ -0,0 +1,141 @@ +file_provider->getContents($statements_analyzer->getFilePath()); + + // Find opening paren + $first_argument = $stmt->args[0] ?? null; + $first_argument_character = $first_argument !== null + ? $first_argument->getStartFilePos() + : $stmt->getEndFilePos(); + $method_name_and_first_paren_source_code_length = $first_argument_character - $stmt->getStartFilePos(); + // FIXME: There are weird ::__construct calls in the AST for `extends` + if ($method_name_and_first_paren_source_code_length <= 0) { + return; + } + $method_name_and_first_paren_source_code = substr( + $file_content, + $stmt->getStartFilePos(), + $method_name_and_first_paren_source_code_length + ); + $method_name_and_first_paren_tokens = token_get_all('getStartFilePos()) { + return; + } + + // Record ranges of the source code that need to be tokenized to find commas + /** @var array{0: int, 1: int}[] $ranges */ + $ranges = []; + + // Add range between opening paren and first argument + $first_argument = $stmt->args[0] ?? null; + $first_argument_starting_position = $first_argument !== null + ? $first_argument->getStartFilePos() + : $stmt->getEndFilePos(); + $first_range_starting_position = $opening_paren_position + 1; + if ($first_range_starting_position !== $first_argument_starting_position) { + $ranges[] = [$first_range_starting_position, $first_argument_starting_position]; + } + + // Add range between arguments + foreach ($stmt->args as $i => $argument) { + $range_start = $argument->getEndFilePos() + 1; + $next_argument = $stmt->args[$i + 1] ?? null; + $range_end = $next_argument !== null + ? $next_argument->getStartFilePos() + : $stmt->getEndFilePos(); + + if ($range_start !== $range_end) { + $ranges[] = [$range_start, $range_end]; + } + } + + $commas = []; + foreach ($ranges as $range) { + $position = $range[0]; + $length = $range[1] - $position; + + if ($length > 0) { + $range_source_code = substr($file_content, $position, $length); + $range_tokens = token_get_all('analyzer->addNodeArgument( + $statements_analyzer->getFilePath(), + $argument_start_position, + $comma, + $function_reference, + $argument_number + ); + + ++$argument_number; + $argument_start_position = $comma + 1; + } + + $codebase->analyzer->addNodeArgument( + $statements_analyzer->getFilePath(), + $argument_start_position, + $stmt->getEndFilePos(), + $function_reference, + $argument_number + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..5efc0a3c91ba8d9bf778b27214c7388d5450d1ea --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -0,0 +1,1246 @@ + $args + * @param array|null $function_params + * @param array>|null $generic_params + * + * @return false|null + */ + public static function analyze( + StatementsAnalyzer $statements_analyzer, + array $args, + ?array $function_params, + ?string $method_id, + bool $allow_named_args, + Context $context, + ?TemplateResult $template_result = null + ): ?bool { + $last_param = $function_params + ? $function_params[count($function_params) - 1] + : null; + + // if this modifies the array type based on further args + if ($method_id + && in_array($method_id, ['array_push', 'array_unshift'], true) + && $function_params + && isset($args[0]) + && isset($args[1]) + ) { + if (ArrayFunctionArgumentsAnalyzer::handleAddition( + $statements_analyzer, + $args, + $context, + $method_id === 'array_push' + ) === false + ) { + return false; + } + + return null; + } + + if ($method_id && $method_id === 'array_splice' && $function_params && count($args) > 1) { + if (ArrayFunctionArgumentsAnalyzer::handleSplice($statements_analyzer, $args, $context) === false) { + return false; + } + + return null; + } + + if ($method_id === 'array_map') { + $args = array_reverse($args, true); + } + + foreach ($args as $argument_offset => $arg) { + if ($function_params === null) { + if (self::evaluateAribitraryParam( + $statements_analyzer, + $arg, + $context + ) === false) { + return false; + } + + continue; + } + + $param = null; + + if ($arg->name && $allow_named_args) { + foreach ($function_params as $candidate_param) { + if ($candidate_param->name === $arg->name->name) { + $param = $candidate_param; + break; + } + } + } elseif ($argument_offset < count($function_params)) { + $param = $function_params[$argument_offset]; + } elseif ($last_param && $last_param->is_variadic) { + $param = $last_param; + } + + $by_ref = $param && $param->by_ref; + + $by_ref_type = null; + + if ($by_ref && $param) { + $by_ref_type = $param->type ? clone $param->type : Type::getMixed(); + } + + if ($by_ref + && $by_ref_type + && !($arg->value instanceof PhpParser\Node\Expr\Closure + || $arg->value instanceof PhpParser\Node\Expr\ConstFetch + || $arg->value instanceof PhpParser\Node\Expr\ClassConstFetch + || $arg->value instanceof PhpParser\Node\Expr\FuncCall + || $arg->value instanceof PhpParser\Node\Expr\MethodCall + || $arg->value instanceof PhpParser\Node\Expr\StaticCall + || $arg->value instanceof PhpParser\Node\Expr\New_ + || $arg->value instanceof PhpParser\Node\Expr\Assign + || $arg->value instanceof PhpParser\Node\Expr\Array_ + || $arg->value instanceof PhpParser\Node\Expr\Ternary + || $arg->value instanceof PhpParser\Node\Expr\BinaryOp + ) + ) { + if (self::handleByRefFunctionArg( + $statements_analyzer, + $method_id, + $argument_offset, + $arg, + $context + ) === false) { + return false; + } + + continue; + } + + $toggled_class_exists = false; + + if ($method_id === 'class_exists' + && $argument_offset === 0 + && !$context->inside_class_exists + ) { + $context->inside_class_exists = true; + $toggled_class_exists = true; + } + + if (($arg->value instanceof PhpParser\Node\Expr\Closure + || $arg->value instanceof PhpParser\Node\Expr\ArrowFunction) + && $template_result + && $template_result->upper_bounds + && $param + && !$arg->value->getDocComment() + ) { + self::handleClosureArg( + $statements_analyzer, + $args, + $method_id, + $context, + $template_result, + $argument_offset, + $arg, + $param + ); + } + + $was_inside_call = $context->inside_call; + + $context->inside_call = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $arg->value, $context) === false) { + return false; + } + + if (!$was_inside_call) { + $context->inside_call = false; + } + + if (($argument_offset === 0 && $method_id === 'array_filter' && count($args) === 2) + || ($argument_offset > 0 && $method_id === 'array_map' && count($args) >= 2) + ) { + self::handleArrayMapFilterArrayArg( + $statements_analyzer, + $method_id, + $argument_offset, + $arg, + $context, + $template_result + ); + } + + if ($toggled_class_exists) { + $context->inside_class_exists = false; + } + } + + return null; + } + + private static function handleArrayMapFilterArrayArg( + StatementsAnalyzer $statements_analyzer, + string $method_id, + int $argument_offset, + PhpParser\Node\Arg $arg, + Context $context, + ?TemplateResult &$template_result + ) : void { + $codebase = $statements_analyzer->getCodebase(); + + $generic_param_type = new Type\Union([ + new Type\Atomic\TArray([ + Type::getArrayKey(), + new Type\Union([ + new Type\Atomic\TTemplateParam( + 'ArrayValue' . $argument_offset, + Type::getMixed(), + $method_id + ) + ]) + ]) + ]); + + $template_types = ['ArrayValue' . $argument_offset => [$method_id => [Type::getMixed()]]]; + + $replace_template_result = new \Psalm\Internal\Type\TemplateResult( + $template_types, + [] + ); + + $existing_type = $statements_analyzer->node_data->getType($arg->value); + + \Psalm\Internal\Type\UnionTemplateHandler::replaceTemplateTypesWithStandins( + $generic_param_type, + $replace_template_result, + $codebase, + $statements_analyzer, + $existing_type, + $argument_offset, + 'fn-' . ($context->calling_method_id ?: $context->calling_function_id) + ); + + if ($replace_template_result->upper_bounds) { + if (!$template_result) { + $template_result = new TemplateResult([], []); + } + + $template_result->upper_bounds += $replace_template_result->upper_bounds; + } + } + + /** + * @param array $args + */ + private static function handleClosureArg( + StatementsAnalyzer $statements_analyzer, + array $args, + ?string $method_id, + Context $context, + TemplateResult $template_result, + int $argument_offset, + PhpParser\Node\Arg $arg, + FunctionLikeParameter $param + ) : void { + if (!$param->type) { + return; + } + + $codebase = $statements_analyzer->getCodebase(); + + if (($argument_offset === 1 && $method_id === 'array_filter' && count($args) === 2) + || ($argument_offset === 0 && $method_id === 'array_map' && count($args) >= 2) + ) { + $function_like_params = []; + + foreach ($template_result->upper_bounds as $template_name => $_) { + $function_like_params[] = new \Psalm\Storage\FunctionLikeParameter( + 'function', + false, + new Type\Union([ + new Type\Atomic\TTemplateParam( + $template_name, + Type::getMixed(), + $method_id + ) + ]) + ); + } + + $replaced_type = new Type\Union([ + new Type\Atomic\TCallable( + 'callable', + array_reverse($function_like_params) + ) + ]); + } else { + $replaced_type = clone $param->type; + } + + $replace_template_result = new \Psalm\Internal\Type\TemplateResult( + $template_result->upper_bounds, + [] + ); + + $replaced_type = \Psalm\Internal\Type\UnionTemplateHandler::replaceTemplateTypesWithStandins( + $replaced_type, + $replace_template_result, + $codebase, + $statements_analyzer, + null, + null, + null, + 'fn-' . ($context->calling_method_id ?: $context->calling_function_id) + ); + + $replaced_type->replaceTemplateTypesWithArgTypes( + $replace_template_result, + $codebase + ); + + $closure_id = strtolower($statements_analyzer->getFilePath()) + . ':' . $arg->value->getLine() + . ':' . (int)$arg->value->getAttribute('startFilePos') + . ':-:closure'; + + try { + $closure_storage = $codebase->getClosureStorage( + $statements_analyzer->getFilePath(), + $closure_id + ); + } catch (\UnexpectedValueException $e) { + return; + } + + foreach ($closure_storage->params as $closure_param_offset => $param_storage) { + $param_type_inferred = $param_storage->type_inferred; + + $newly_inferred_type = null; + $has_different_docblock_type = false; + + if ($param_storage->type && !$param_type_inferred) { + if ($param_storage->type !== $param_storage->signature_type) { + $has_different_docblock_type = true; + } + } + + if (!$has_different_docblock_type) { + foreach ($replaced_type->getAtomicTypes() as $replaced_type_part) { + if ($replaced_type_part instanceof Type\Atomic\TCallable + || $replaced_type_part instanceof Type\Atomic\TClosure + ) { + if (isset($replaced_type_part->params[$closure_param_offset]->type) + && !$replaced_type_part->params[$closure_param_offset]->type->hasTemplate() + ) { + if ($param_storage->type && !$param_type_inferred) { + $type_match_found = UnionTypeComparator::isContainedBy( + $codebase, + $replaced_type_part->params[$closure_param_offset]->type, + $param_storage->type + ); + + if (!$type_match_found) { + continue; + } + } + + if (!$newly_inferred_type) { + $newly_inferred_type = $replaced_type_part->params[$closure_param_offset]->type; + } else { + $newly_inferred_type = Type::combineUnionTypes( + $newly_inferred_type, + $replaced_type_part->params[$closure_param_offset]->type, + $codebase + ); + } + } + } + } + } + + if ($newly_inferred_type) { + $param_storage->type = $newly_inferred_type; + $param_storage->type_inferred = true; + } + + if ($param_storage->type && ($method_id === 'array_map' || $method_id === 'array_filter')) { + ArrayFetchAnalyzer::taintArrayFetch( + $statements_analyzer, + $args[1 - $argument_offset]->value, + null, + $param_storage->type, + Type::getMixed() + ); + } + } + } + + /** + * @param array $args + * @param string|MethodIdentifier|null $method_id + * @param array $function_params + * + * @return false|null + */ + public static function checkArgumentsMatch( + StatementsAnalyzer $statements_analyzer, + array $args, + $method_id, + array $function_params, + ?FunctionLikeStorage $function_storage, + ?ClassLikeStorage $class_storage, + ?TemplateResult $class_template_result, + CodeLocation $code_location, + Context $context + ): ?bool { + $in_call_map = $method_id ? InternalCallMapHandler::inCallMap((string) $method_id) : false; + + $cased_method_id = (string) $method_id; + + $is_variadic = false; + + $fq_class_name = null; + + $codebase = $statements_analyzer->getCodebase(); + + if ($method_id) { + if (!$in_call_map && $method_id instanceof \Psalm\Internal\MethodIdentifier) { + $fq_class_name = $method_id->fq_class_name; + } + + if ($function_storage) { + $is_variadic = $function_storage->variadic; + } elseif (is_string($method_id)) { + $is_variadic = $codebase->functions->isVariadic( + $codebase, + strtolower($method_id), + $statements_analyzer->getRootFilePath() + ); + } else { + $is_variadic = $codebase->methods->isVariadic($method_id); + } + } + + if ($method_id instanceof \Psalm\Internal\MethodIdentifier) { + $cased_method_id = $codebase->methods->getCasedMethodId($method_id); + } elseif ($function_storage) { + $cased_method_id = $function_storage->cased_name; + } + + $calling_class_storage = $class_storage; + + $static_fq_class_name = $fq_class_name; + $self_fq_class_name = $fq_class_name; + + if ($method_id instanceof \Psalm\Internal\MethodIdentifier) { + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + if ($declaring_method_id && (string)$declaring_method_id !== (string)$method_id) { + $self_fq_class_name = $declaring_method_id->fq_class_name; + $class_storage = $codebase->classlike_storage_provider->get($self_fq_class_name); + } + + $appearing_method_id = $codebase->methods->getAppearingMethodId($method_id); + + if ($appearing_method_id && $declaring_method_id !== $appearing_method_id) { + $self_fq_class_name = $appearing_method_id->fq_class_name; + } + } + + if ($function_params) { + foreach ($function_params as $function_param) { + $is_variadic = $is_variadic || $function_param->is_variadic; + } + } + + $has_packed_var = false; + + $packed_var_definite_args = 0; + + foreach ($args as $arg) { + if ($arg->unpack) { + $arg_value_type = $statements_analyzer->node_data->getType($arg->value); + + if (!$arg_value_type + || !$arg_value_type->isSingle() + || !$arg_value_type->hasArray() + ) { + $has_packed_var = true; + break; + } + + foreach ($arg_value_type->getAtomicTypes() as $atomic_arg_type) { + if (!$atomic_arg_type instanceof TKeyedArray) { + $has_packed_var = true; + break 2; + } + + $packed_var_definite_args = 0; + + foreach ($atomic_arg_type->properties as $property_type) { + if ($property_type->possibly_undefined) { + $has_packed_var = true; + } else { + $packed_var_definite_args++; + } + } + } + } + } + + if (!$has_packed_var) { + $packed_var_definite_args = \max(0, $packed_var_definite_args - 1); + } + + $last_param = $function_params + ? $function_params[count($function_params) - 1] + : null; + + $template_result = null; + + $class_generic_params = $class_template_result + ? $class_template_result->upper_bounds + : []; + + if ($function_storage) { + $template_types = CallAnalyzer::getTemplateTypesForCall( + $codebase, + $class_storage, + $self_fq_class_name, + $calling_class_storage, + $function_storage->template_types ?: [] + ); + + if ($template_types) { + $template_result = $class_template_result; + + if (!$template_result) { + $template_result = new TemplateResult($template_types, []); + } elseif (!$template_result->template_types) { + $template_result->template_types = $template_types; + } + + foreach ($args as $argument_offset => $arg) { + $function_param = null; + + if ($arg->name && $function_storage->allow_named_arg_calls) { + foreach ($function_params as $candidate_param) { + if ($candidate_param->name === $arg->name->name) { + $function_param = $candidate_param; + break; + } + } + } elseif ($argument_offset < count($function_params)) { + $function_param = $function_params[$argument_offset]; + } elseif ($last_param && $last_param->is_variadic) { + $function_param = $last_param; + } + + if (!$function_param + || !$function_param->type + ) { + continue; + } + + $arg_value_type = $statements_analyzer->node_data->getType($arg->value); + + if (!$arg_value_type) { + continue; + } + + UnionTemplateHandler::replaceTemplateTypesWithStandins( + $function_param->type, + $template_result, + $codebase, + $statements_analyzer, + $arg_value_type, + $argument_offset, + $context->self, + $context->calling_method_id ?: $context->calling_function_id, + false + ); + + if (!$class_template_result) { + $template_result->upper_bounds = []; + } + } + } + } + + foreach ($class_generic_params as $template_name => $type_map) { + foreach ($type_map as $class => $type) { + $class_generic_params[$template_name][$class][0] = clone $type[0]; + } + } + + $function_param_count = count($function_params); + + if (count($function_params) > count($args) && !$has_packed_var) { + for ($i = count($args), $iMax = count($function_params); $i < $iMax; $i++) { + if ($function_params[$i]->default_type + && $function_params[$i]->type + && $function_params[$i]->type->hasTemplate() + && $function_params[$i]->default_type->hasLiteralValue() + ) { + ArgumentAnalyzer::checkArgumentMatches( + $statements_analyzer, + $cased_method_id, + $self_fq_class_name, + $static_fq_class_name, + $code_location, + $function_params[$i], + $i, + $i, + $function_storage ? $function_storage->allow_named_arg_calls : true, + new PhpParser\Node\Arg( + StubsGenerator::getExpressionFromType($function_params[$i]->default_type) + ), + $function_params[$i]->default_type, + $context, + $class_generic_params, + $template_result, + $function_storage ? $function_storage->specialize_call : true, + $in_call_map + ); + } + } + } + + if ($method_id === 'preg_match_all' && count($args) > 3) { + $args = array_reverse($args, true); + } + + foreach ($args as $argument_offset => $arg) { + $arg_function_params = []; + + if ($arg->unpack && $function_param_count > $argument_offset) { + for ($i = $argument_offset; $i < $function_param_count; $i++) { + $arg_function_params[] = $function_params[$i]; + } + } elseif ($arg->name && $function_storage && $function_storage->allow_named_arg_calls) { + foreach ($function_params as $candidate_param) { + if ($candidate_param->name === $arg->name->name) { + $arg_function_params = [$candidate_param]; + break; + } + } + + if (!$arg_function_params) { + if (IssueBuffer::accepts( + new InvalidNamedArgument( + 'Parameter $' . $arg->name->name . ' does not exist on function ' + . ($cased_method_id ?: $method_id), + new CodeLocation($statements_analyzer, $arg->name), + (string) $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } elseif ($function_param_count > $argument_offset) { + $arg_function_params = [$function_params[$argument_offset]]; + } elseif ($last_param && $last_param->is_variadic) { + $arg_function_params = [$last_param]; + } + + if ($arg_function_params + && $arg_function_params[0]->by_ref + && $method_id !== 'extract' + ) { + if (self::handlePossiblyMatchingByRefParam( + $statements_analyzer, + $codebase, + (string) $method_id, + $cased_method_id, + $last_param, + $function_params, + $argument_offset, + $arg, + $context, + $template_result + ) === false) { + return null; + } + } + + $arg_value_type = $statements_analyzer->node_data->getType($arg->value); + + foreach ($arg_function_params as $i => $function_param) { + if (ArgumentAnalyzer::checkArgumentMatches( + $statements_analyzer, + $cased_method_id, + $self_fq_class_name, + $static_fq_class_name, + $code_location, + $function_param, + $argument_offset + $i, + $i, + $function_storage ? $function_storage->allow_named_arg_calls : true, + $arg, + $arg_value_type, + $context, + $class_generic_params, + $template_result, + $function_storage ? $function_storage->specialize_call : true, + $in_call_map + ) === false) { + return false; + } + } + } + + if ($method_id === 'array_map' || $method_id === 'array_filter') { + if ($method_id === 'array_map' && count($args) < 2) { + if (IssueBuffer::accepts( + new TooFewArguments( + 'Too few arguments for ' . $method_id, + $code_location, + $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($method_id === 'array_filter' && count($args) < 1) { + if (IssueBuffer::accepts( + new TooFewArguments( + 'Too few arguments for ' . $method_id, + $code_location, + $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + ArrayFunctionArgumentsAnalyzer::checkArgumentsMatch( + $statements_analyzer, + $context, + $args, + $method_id, + $context->check_functions + ); + + return null; + } + + if (!$is_variadic + && count($args) > count($function_params) + && (!count($function_params) || $function_params[count($function_params) - 1]->name !== '...=') + && ($in_call_map + || !$function_storage instanceof \Psalm\Storage\MethodStorage + || $function_storage->is_static + || ($method_id instanceof MethodIdentifier + && $method_id->method_name === '__construct')) + ) { + if (IssueBuffer::accepts( + new TooManyArguments( + 'Too many arguments for ' . ($cased_method_id ?: $method_id) + . ' - expecting ' . count($function_params) . ' but saw ' . count($args), + $code_location, + (string) $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return null; + } + + if (!$has_packed_var && count($args) < count($function_params)) { + if ($function_storage) { + $expected_param_count = $function_storage->required_param_count; + } else { + for ($i = 0, $j = count($function_params); $i < $j; ++$i) { + $param = $function_params[$i]; + + if ($param->is_optional || $param->is_variadic) { + break; + } + } + + $expected_param_count = $i; + } + + for ($i = count($args) + $packed_var_definite_args, $j = count($function_params); $i < $j; ++$i) { + $param = $function_params[$i]; + + if (!$param->is_optional + && !$param->is_variadic + && ($in_call_map + || !$function_storage instanceof \Psalm\Storage\MethodStorage + || $function_storage->is_static + || ($method_id instanceof MethodIdentifier + && $method_id->method_name === '__construct')) + ) { + if (IssueBuffer::accepts( + new TooFewArguments( + 'Too few arguments for ' . $cased_method_id + . ' - expecting ' . $expected_param_count + . ' but saw ' . (count($args) + $packed_var_definite_args), + $code_location, + (string) $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + break; + } + + if ($param->is_optional + && $param->type + && $param->default_type + && !$param->is_variadic + && $template_result + ) { + UnionTemplateHandler::replaceTemplateTypesWithStandins( + $param->type, + $template_result, + $codebase, + $statements_analyzer, + clone $param->default_type, + $i, + $context->self, + $context->calling_method_id ?: $context->calling_function_id, + true + ); + } + } + } + + return null; + } + + /** + * @param array $function_params + * @return false|null + */ + private static function handlePossiblyMatchingByRefParam( + StatementsAnalyzer $statements_analyzer, + Codebase $codebase, + ?string $method_id, + ?string $cased_method_id, + ?FunctionLikeParameter $last_param, + array $function_params, + int $argument_offset, + PhpParser\Node\Arg $arg, + Context $context, + ?TemplateResult $template_result + ): ?bool { + if ($arg->value instanceof PhpParser\Node\Scalar + || $arg->value instanceof PhpParser\Node\Expr\Cast + || $arg->value instanceof PhpParser\Node\Expr\Array_ + || $arg->value instanceof PhpParser\Node\Expr\ClassConstFetch + || $arg->value instanceof PhpParser\Node\Expr\BinaryOp + || $arg->value instanceof PhpParser\Node\Expr\Ternary + || ( + ( + $arg->value instanceof PhpParser\Node\Expr\ConstFetch + || $arg->value instanceof PhpParser\Node\Expr\FuncCall + || $arg->value instanceof PhpParser\Node\Expr\MethodCall + || $arg->value instanceof PhpParser\Node\Expr\StaticCall + ) && ( + !($arg_value_type = $statements_analyzer->node_data->getType($arg->value)) + || !$arg_value_type->by_ref + ) + ) + ) { + if (IssueBuffer::accepts( + new InvalidPassByReference( + 'Parameter ' . ($argument_offset + 1) . ' of ' . $cased_method_id . ' expects a variable', + new CodeLocation($statements_analyzer->getSource(), $arg->value) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return false; + } + + if (!in_array( + $method_id, + [ + 'ksort', 'asort', 'krsort', 'arsort', 'natcasesort', 'natsort', + 'reset', 'end', 'next', 'prev', 'array_pop', 'array_shift', + 'array_push', 'array_unshift', 'socket_select', 'array_splice', + ], + true + )) { + $by_ref_type = null; + $by_ref_out_type = null; + + $check_null_ref = true; + + if ($last_param) { + if ($argument_offset < count($function_params)) { + $function_param = $function_params[$argument_offset]; + } else { + $function_param = $last_param; + } + + $by_ref_type = $function_param->type; + $by_ref_out_type = $function_param->out_type; + + if ($by_ref_type && $by_ref_type->isNullable()) { + $check_null_ref = false; + } + + if ($template_result && $by_ref_type) { + $original_by_ref_type = clone $by_ref_type; + + $by_ref_type = UnionTemplateHandler::replaceTemplateTypesWithStandins( + clone $by_ref_type, + $template_result, + $codebase, + $statements_analyzer, + $statements_analyzer->node_data->getType($arg->value), + $argument_offset, + 'fn-' . ($context->calling_method_id ?: $context->calling_function_id) + ); + + if ($template_result->upper_bounds) { + $original_by_ref_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + + $by_ref_type = $original_by_ref_type; + } + } + + if ($template_result && $by_ref_out_type) { + $original_by_ref_out_type = clone $by_ref_out_type; + + $by_ref_out_type = UnionTemplateHandler::replaceTemplateTypesWithStandins( + clone $by_ref_out_type, + $template_result, + $codebase, + $statements_analyzer, + $statements_analyzer->node_data->getType($arg->value), + $argument_offset, + 'fn-' . ($context->calling_method_id ?: $context->calling_function_id) + ); + + if ($template_result->upper_bounds) { + $original_by_ref_out_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + + $by_ref_out_type = $original_by_ref_out_type; + } + } + + if ($by_ref_type && $function_param->is_variadic && $arg->unpack) { + $by_ref_type = new Type\Union([ + new Type\Atomic\TArray([ + Type::getInt(), + $by_ref_type, + ]), + ]); + } + } + + $by_ref_type = $by_ref_type ?: Type::getMixed(); + + AssignmentAnalyzer::assignByRefParam( + $statements_analyzer, + $arg->value, + $by_ref_type, + $by_ref_out_type ?: $by_ref_type, + $context, + $method_id && (strpos($method_id, '::') !== false || !InternalCallMapHandler::inCallMap($method_id)), + $check_null_ref + ); + } + + return null; + } + + /** + * @return false|null + */ + private static function evaluateAribitraryParam( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Arg $arg, + Context $context + ): ?bool { + // there are a bunch of things we want to evaluate even when we don't + // know what function/method is being called + if ($arg->value instanceof PhpParser\Node\Expr\Closure + || $arg->value instanceof PhpParser\Node\Expr\ConstFetch + || $arg->value instanceof PhpParser\Node\Expr\ClassConstFetch + || $arg->value instanceof PhpParser\Node\Expr\FuncCall + || $arg->value instanceof PhpParser\Node\Expr\MethodCall + || $arg->value instanceof PhpParser\Node\Expr\StaticCall + || $arg->value instanceof PhpParser\Node\Expr\New_ + || $arg->value instanceof PhpParser\Node\Expr\Cast + || $arg->value instanceof PhpParser\Node\Expr\Assign + || $arg->value instanceof PhpParser\Node\Expr\ArrayDimFetch + || $arg->value instanceof PhpParser\Node\Expr\PropertyFetch + || $arg->value instanceof PhpParser\Node\Expr\Array_ + || $arg->value instanceof PhpParser\Node\Expr\BinaryOp + || $arg->value instanceof PhpParser\Node\Expr\Ternary + || $arg->value instanceof PhpParser\Node\Scalar\Encapsed + || $arg->value instanceof PhpParser\Node\Expr\PostInc + || $arg->value instanceof PhpParser\Node\Expr\PostDec + || $arg->value instanceof PhpParser\Node\Expr\PreInc + || $arg->value instanceof PhpParser\Node\Expr\PreDec + ) { + $was_inside_call = $context->inside_call; + $context->inside_call = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $arg->value, $context) === false) { + return false; + } + + if (!$was_inside_call) { + $context->inside_call = false; + } + } + + if ($arg->value instanceof PhpParser\Node\Expr\PropertyFetch + && $arg->value->name instanceof PhpParser\Node\Identifier + ) { + $var_id = '$' . $arg->value->name->name; + } else { + $var_id = ExpressionIdentifier::getVarId( + $arg->value, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + } + + if ($var_id) { + if ($arg->value instanceof PhpParser\Node\Expr\Variable) { + $statements_analyzer->registerPossiblyUndefinedVariable($var_id, $arg->value); + } + + if (!$context->hasVariable($var_id) + || $context->vars_in_scope[$var_id]->isNull() + ) { + if (!isset($context->vars_in_scope[$var_id]) + && $arg->value instanceof PhpParser\Node\Expr\Variable + ) { + if (IssueBuffer::accepts( + new PossiblyUndefinedVariable( + 'Variable ' . $var_id + . ' must be defined prior to use within an unknown function or method', + new CodeLocation($statements_analyzer->getSource(), $arg->value) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + // we don't know if it exists, assume it's passed by reference + $context->vars_in_scope[$var_id] = Type::getMixed(); + $context->vars_possibly_in_scope[$var_id] = true; + } else { + $was_inside_call = $context->inside_call; + $context->inside_call = true; + ExpressionAnalyzer::analyze($statements_analyzer, $arg->value, $context); + $context->inside_call = $was_inside_call; + + $context->removeVarFromConflictingClauses( + $var_id, + $context->vars_in_scope[$var_id], + $statements_analyzer + ); + + foreach ($context->vars_in_scope[$var_id]->getAtomicTypes() as $type) { + if ($type instanceof TArray && $type->type_params[1]->isEmpty()) { + $context->vars_in_scope[$var_id]->removeType('array'); + $context->vars_in_scope[$var_id]->addType( + new TArray( + [Type::getArrayKey(), Type::getMixed()] + ) + ); + } + } + } + } + + return null; + } + + /** + * @return false|null + */ + private static function handleByRefFunctionArg( + StatementsAnalyzer $statements_analyzer, + ?string $method_id, + int $argument_offset, + PhpParser\Node\Arg $arg, + Context $context + ): ?bool { + $var_id = ExpressionIdentifier::getVarId( + $arg->value, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + $builtin_array_functions = [ + 'ksort', 'asort', 'krsort', 'arsort', 'natcasesort', 'natsort', + 'reset', 'end', 'next', 'prev', 'array_pop', 'array_shift', + ]; + + if (($var_id && isset($context->vars_in_scope[$var_id])) + || ($method_id + && in_array( + $method_id, + $builtin_array_functions, + true + )) + ) { + $was_inside_assignment = $context->inside_assignment; + $context->inside_assignment = true; + + // if the variable is in scope, get or we're in a special array function, + // figure out its type before proceeding + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $arg->value, + $context + ) === false) { + return false; + } + + $context->inside_assignment = $was_inside_assignment; + } + + // special handling for array sort + if ($argument_offset === 0 + && $method_id + && in_array( + $method_id, + $builtin_array_functions, + true + ) + ) { + if (in_array($method_id, ['array_pop', 'array_shift'], true)) { + ArrayFunctionArgumentsAnalyzer::handleByRefArrayAdjustment( + $statements_analyzer, + $arg, + $context, + $method_id === 'array_shift' + ); + + return null; + } + + // noops + if (in_array($method_id, ['reset', 'end', 'next', 'prev', 'ksort'], true)) { + return null; + } + + if (($arg_value_type = $statements_analyzer->node_data->getType($arg->value)) + && $arg_value_type->hasArray() + ) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var TArray|TList|TKeyedArray + */ + $array_type = $arg_value_type->getAtomicTypes()['array']; + + if ($array_type instanceof TKeyedArray) { + $array_type = $array_type->getGenericArrayType(); + } + + if ($array_type instanceof TList) { + $array_type = new TArray([Type::getInt(), $array_type->type_param]); + } + + $by_ref_type = new Type\Union([clone $array_type]); + + AssignmentAnalyzer::assignByRefParam( + $statements_analyzer, + $arg->value, + $by_ref_type, + $by_ref_type, + $context, + false + ); + + return null; + } + } + + if ($method_id === 'socket_select') { + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $arg->value, + $context + ) === false) { + return false; + } + } + + if (!$arg->value instanceof PhpParser\Node\Expr\Variable) { + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('EmptyArrayAccess', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['EmptyArrayAccess']); + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $arg->value, $context) === false) { + return false; + } + + if (!in_array('EmptyArrayAccess', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['EmptyArrayAccess']); + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..4b603502b21f880231a445b29b8a0bba5672d3e5 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php @@ -0,0 +1,943 @@ + $args + */ + public static function checkArgumentsMatch( + StatementsAnalyzer $statements_analyzer, + Context $context, + array $args, + string $method_id, + bool $check_functions + ): void { + $closure_index = $method_id === 'array_map' ? 0 : 1; + + $array_arg_types = []; + + foreach ($args as $i => $arg) { + if ($i === 0 && $method_id === 'array_map') { + continue; + } + + if ($i === 1 && $method_id === 'array_filter') { + break; + } + + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var TKeyedArray|TArray|TList|null + */ + $array_arg_type = ($arg_value_type = $statements_analyzer->node_data->getType($arg->value)) + && ($types = $arg_value_type->getAtomicTypes()) + && isset($types['array']) + ? $types['array'] + : null; + + if ($array_arg_type instanceof TKeyedArray) { + $array_arg_type = $array_arg_type->getGenericArrayType(); + } + + if ($array_arg_type instanceof TList) { + $array_arg_type = new TArray([Type::getInt(), $array_arg_type->type_param]); + } + + $array_arg_types[] = $array_arg_type; + } + + $closure_arg = isset($args[$closure_index]) ? $args[$closure_index] : null; + + $closure_arg_type = null; + + if ($closure_arg) { + $closure_arg_type = $statements_analyzer->node_data->getType($closure_arg->value); + } + + if ($closure_arg && $closure_arg_type) { + $min_closure_param_count = $max_closure_param_count = count($array_arg_types); + + if ($method_id === 'array_filter') { + $max_closure_param_count = count($args) > 2 ? 2 : 1; + } + + foreach ($closure_arg_type->getAtomicTypes() as $closure_type) { + self::checkClosureType( + $statements_analyzer, + $context, + $method_id, + $closure_type, + $closure_arg, + $min_closure_param_count, + $max_closure_param_count, + $array_arg_types, + $check_functions + ); + } + } + } + + /** + * @param array $args + * + * @return false|null + */ + public static function handleAddition( + StatementsAnalyzer $statements_analyzer, + array $args, + Context $context, + bool $is_push + ): ?bool { + $array_arg = $args[0]->value; + + $unpacked_args = array_filter( + $args, + function ($arg) { + return $arg->unpack; + } + ); + + if ($is_push && !$unpacked_args) { + for ($i = 1, $iMax = count($args); $i < $iMax; $i++) { + $was_inside_assignment = $context->inside_assignment; + + $context->inside_assignment = true; + + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $args[$i]->value, + $context + ) === false) { + return false; + } + + $context->inside_assignment = $was_inside_assignment; + + $old_node_data = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + ArrayAssignmentAnalyzer::analyze( + $statements_analyzer, + new PhpParser\Node\Expr\ArrayDimFetch( + $args[0]->value, + null, + $args[$i]->value->getAttributes() + ), + $context, + $args[$i]->value, + $statements_analyzer->node_data->getType($args[$i]->value) ?: Type::getMixed() + ); + + $statements_analyzer->node_data = $old_node_data; + } + + return null; + } + + $context->inside_call = true; + + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $array_arg, + $context + ) === false) { + return false; + } + + for ($i = 1, $iMax = count($args); $i < $iMax; $i++) { + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $args[$i]->value, + $context + ) === false) { + return false; + } + } + + if (($array_arg_type = $statements_analyzer->node_data->getType($array_arg)) + && $array_arg_type->hasArray() + ) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var TArray|TKeyedArray|TList + */ + $array_type = $array_arg_type->getAtomicTypes()['array']; + + $objectlike_list = null; + + if ($array_type instanceof TKeyedArray) { + if ($array_type->is_list) { + $objectlike_list = clone $array_type; + } + + $array_type = $array_type->getGenericArrayType(); + + if ($objectlike_list) { + if ($array_type instanceof TNonEmptyArray) { + $array_type = new TNonEmptyList($array_type->type_params[1]); + } else { + $array_type = new TList($array_type->type_params[1]); + } + } + } + + $by_ref_type = new Type\Union([clone $array_type]); + + foreach ($args as $argument_offset => $arg) { + if ($argument_offset === 0) { + continue; + } + + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $arg->value, + $context + ) === false) { + return false; + } + + if (!($arg_value_type = $statements_analyzer->node_data->getType($arg->value)) + || $arg_value_type->hasMixed() + ) { + $by_ref_type = Type::combineUnionTypes( + $by_ref_type, + new Type\Union([new TArray([Type::getInt(), Type::getMixed()])]) + ); + } elseif ($arg->unpack) { + $arg_value_type = clone $arg_value_type; + + foreach ($arg_value_type->getAtomicTypes() as $arg_value_atomic_type) { + if ($arg_value_atomic_type instanceof TKeyedArray) { + $was_list = $arg_value_atomic_type->is_list; + + $arg_value_atomic_type = $arg_value_atomic_type->getGenericArrayType(); + + if ($was_list) { + if ($arg_value_atomic_type instanceof TNonEmptyArray) { + $arg_value_atomic_type = new TNonEmptyList($arg_value_atomic_type->type_params[1]); + } else { + $arg_value_atomic_type = new TList($arg_value_atomic_type->type_params[1]); + } + } + + $arg_value_type->addType($arg_value_atomic_type); + } + } + + $by_ref_type = Type::combineUnionTypes( + $by_ref_type, + $arg_value_type + ); + } else { + if ($objectlike_list) { + \array_unshift($objectlike_list->properties, $arg_value_type); + + $by_ref_type = new Type\Union([$objectlike_list]); + } elseif ($array_type instanceof TList) { + $by_ref_type = Type::combineUnionTypes( + $by_ref_type, + new Type\Union( + [ + new TNonEmptyList(clone $arg_value_type), + ] + ) + ); + } else { + $by_ref_type = Type::combineUnionTypes( + $by_ref_type, + new Type\Union( + [ + new TNonEmptyArray( + [ + Type::getInt(), + clone $arg_value_type + ] + ), + ] + ) + ); + } + } + } + + AssignmentAnalyzer::assignByRefParam( + $statements_analyzer, + $array_arg, + $by_ref_type, + $by_ref_type, + $context, + false + ); + } + + $context->inside_call = false; + + return null; + } + + /** + * @param array $args + * + * @return false|null + */ + public static function handleSplice( + StatementsAnalyzer $statements_analyzer, + array $args, + Context $context + ): ?bool { + $context->inside_call = true; + $array_arg = $args[0]->value; + + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $array_arg, + $context + ) === false) { + return false; + } + + $offset_arg = $args[1]->value; + + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $offset_arg, + $context + ) === false) { + return false; + } + + if (!isset($args[2])) { + return null; + } + + $length_arg = $args[2]->value; + + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $length_arg, + $context + ) === false) { + return false; + } + + if (!isset($args[3])) { + return null; + } + + $replacement_arg = $args[3]->value; + + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $replacement_arg, + $context + ) === false) { + return false; + } + + $context->inside_call = false; + + $replacement_arg_type = $statements_analyzer->node_data->getType($replacement_arg); + + if ($replacement_arg_type + && !$replacement_arg_type->hasArray() + && $replacement_arg_type->hasString() + && $replacement_arg_type->isSingle() + ) { + $replacement_arg_type = new Type\Union([ + new Type\Atomic\TArray([Type::getInt(), $replacement_arg_type]) + ]); + + $statements_analyzer->node_data->setType($replacement_arg, $replacement_arg_type); + } + + if (($array_arg_type = $statements_analyzer->node_data->getType($array_arg)) + && $array_arg_type->hasArray() + && $replacement_arg_type + && $replacement_arg_type->hasArray() + ) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var TArray|TKeyedArray|TList + */ + $array_type = $array_arg_type->getAtomicTypes()['array']; + + if ($array_type instanceof TKeyedArray) { + if ($array_type->is_list) { + $array_type = new TNonEmptyList($array_type->getGenericValueType()); + } else { + $array_type = $array_type->getGenericArrayType(); + } + } + + if ($array_type instanceof TArray + && $array_type->type_params[0]->hasInt() + && !$array_type->type_params[0]->hasString() + ) { + if ($array_type instanceof TNonEmptyArray) { + $array_type = new TNonEmptyList($array_type->type_params[1]); + } else { + $array_type = new TList($array_type->type_params[1]); + } + } + + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var TArray|TKeyedArray|TList + */ + $replacement_array_type = $replacement_arg_type->getAtomicTypes()['array']; + + if ($replacement_array_type instanceof TKeyedArray) { + $was_list = $replacement_array_type->is_list; + + $replacement_array_type = $replacement_array_type->getGenericArrayType(); + + if ($was_list) { + if ($replacement_array_type instanceof TNonEmptyArray) { + $replacement_array_type = new TNonEmptyList($replacement_array_type->type_params[1]); + } else { + $replacement_array_type = new TList($replacement_array_type->type_params[1]); + } + } + } + + $by_ref_type = TypeCombination::combineTypes([$array_type, $replacement_array_type]); + + AssignmentAnalyzer::assignByRefParam( + $statements_analyzer, + $array_arg, + $by_ref_type, + $by_ref_type, + $context, + false + ); + + return null; + } + + $array_type = Type::getArray(); + + AssignmentAnalyzer::assignByRefParam( + $statements_analyzer, + $array_arg, + $array_type, + $array_type, + $context, + false + ); + + return null; + } + + public static function handleByRefArrayAdjustment( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Arg $arg, + Context $context, + bool $is_array_shift + ): void { + $var_id = ExpressionIdentifier::getVarId( + $arg->value, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($var_id) { + $context->removeVarFromConflictingClauses($var_id, null, $statements_analyzer); + + if (isset($context->vars_in_scope[$var_id])) { + $array_type = clone $context->vars_in_scope[$var_id]; + + $array_atomic_types = $array_type->getAtomicTypes(); + + foreach ($array_atomic_types as $array_atomic_type) { + if ($array_atomic_type instanceof TKeyedArray) { + if ($is_array_shift && $array_atomic_type->is_list) { + $array_atomic_type = clone $array_atomic_type; + + $array_properties = $array_atomic_type->properties; + + \array_shift($array_properties); + + if (!$array_properties) { + $array_atomic_type = new Type\Atomic\TList( + $array_atomic_type->previous_value_type ?: Type::getMixed() + ); + + $array_type->addType($array_atomic_type); + } else { + $array_atomic_type->properties = $array_properties; + } + } + + if ($array_atomic_type instanceof TKeyedArray) { + $array_atomic_type = $array_atomic_type->getGenericArrayType(); + } + } + + if ($array_atomic_type instanceof TNonEmptyArray) { + if (!$context->inside_loop && $array_atomic_type->count !== null) { + if ($array_atomic_type->count === 0) { + $array_atomic_type = new TArray( + [ + new Type\Union([new TEmpty]), + new Type\Union([new TEmpty]), + ] + ); + } else { + $array_atomic_type->count--; + } + } else { + $array_atomic_type = new TArray($array_atomic_type->type_params); + } + + $array_type->addType($array_atomic_type); + $context->removeDescendents($var_id, $array_type); + } elseif ($array_atomic_type instanceof TNonEmptyList) { + if (!$context->inside_loop && $array_atomic_type->count !== null) { + if ($array_atomic_type->count === 0) { + $array_atomic_type = new TArray( + [ + new Type\Union([new TEmpty]), + new Type\Union([new TEmpty]), + ] + ); + } else { + $array_atomic_type->count--; + } + } else { + $array_atomic_type = new TList($array_atomic_type->type_param); + } + + $array_type->addType($array_atomic_type); + $context->removeDescendents($var_id, $array_type); + } + } + + $context->vars_in_scope[$var_id] = $array_type; + } + } + } + + /** + * @param (TArray|null)[] $array_arg_types + * + */ + private static function checkClosureType( + StatementsAnalyzer $statements_analyzer, + Context $context, + string $method_id, + Type\Atomic $closure_type, + PhpParser\Node\Arg $closure_arg, + int $min_closure_param_count, + int $max_closure_param_count, + array $array_arg_types, + bool $check_functions + ): void { + $codebase = $statements_analyzer->getCodebase(); + + if (!$closure_type instanceof Type\Atomic\TClosure) { + if ($method_id === 'array_map') { + return; + } + + if (!$closure_arg->value instanceof PhpParser\Node\Scalar\String_ + && !$closure_arg->value instanceof PhpParser\Node\Expr\Array_ + && !$closure_arg->value instanceof PhpParser\Node\Expr\BinaryOp\Concat + ) { + return; + } + + $function_ids = CallAnalyzer::getFunctionIdsFromCallableArg( + $statements_analyzer, + $closure_arg->value + ); + + $closure_types = []; + + foreach ($function_ids as $function_id) { + $function_id = strtolower($function_id); + + if (strpos($function_id, '::') !== false) { + if ($function_id[0] === '$') { + $function_id = \substr($function_id, 1); + } + + $function_id_parts = explode('&', $function_id); + + foreach ($function_id_parts as $function_id_part) { + [$callable_fq_class_name, $method_name] = explode('::', $function_id_part); + + switch ($callable_fq_class_name) { + case 'self': + case 'static': + case 'parent': + $container_class = $statements_analyzer->getFQCLN(); + + if ($callable_fq_class_name === 'parent') { + $container_class = $statements_analyzer->getParentFQCLN(); + } + + if (!$container_class) { + continue 2; + } + + $callable_fq_class_name = $container_class; + } + + if (!$codebase->classOrInterfaceExists($callable_fq_class_name)) { + return; + } + + $function_id_part = new \Psalm\Internal\MethodIdentifier( + $callable_fq_class_name, + strtolower($method_name) + ); + + try { + $method_storage = $codebase->methods->getStorage($function_id_part); + } catch (\UnexpectedValueException $e) { + // the method may not exist, but we're suppressing that issue + continue; + } + + $closure_types[] = new Type\Atomic\TClosure( + 'Closure', + $method_storage->params, + $method_storage->return_type ?: Type::getMixed() + ); + } + } else { + if (!$check_functions) { + continue; + } + + if (!$codebase->functions->functionExists($statements_analyzer, $function_id)) { + continue; + } + + $function_storage = $codebase->functions->getStorage( + $statements_analyzer, + $function_id + ); + + if (InternalCallMapHandler::inCallMap($function_id)) { + $callmap_callables = InternalCallMapHandler::getCallablesFromCallMap($function_id); + + if ($callmap_callables === null) { + throw new \UnexpectedValueException('This should not happen'); + } + + $passing_callmap_callables = []; + + foreach ($callmap_callables as $callmap_callable) { + $required_param_count = 0; + + assert($callmap_callable->params !== null); + + foreach ($callmap_callable->params as $i => $param) { + if (!$param->is_optional && !$param->is_variadic) { + $required_param_count = $i + 1; + } + } + + if ($required_param_count <= $max_closure_param_count) { + $passing_callmap_callables[] = $callmap_callable; + } + } + + if ($passing_callmap_callables) { + foreach ($passing_callmap_callables as $passing_callmap_callable) { + $closure_types[] = $passing_callmap_callable; + } + } else { + $closure_types[] = $callmap_callables[0]; + } + } else { + $closure_types[] = new Type\Atomic\TClosure( + 'Closure', + $function_storage->params, + $function_storage->return_type ?: Type::getMixed() + ); + } + } + } + } else { + $closure_types = [$closure_type]; + } + + foreach ($closure_types as $closure_type) { + if ($closure_type->params === null) { + continue; + } + + self::checkClosureTypeArgs( + $statements_analyzer, + $context, + $method_id, + $closure_type, + $closure_arg, + $min_closure_param_count, + $max_closure_param_count, + $array_arg_types + ); + } + } + + /** + * @param Type\Atomic\TClosure|Type\Atomic\TCallable $closure_type + * @param (TArray|null)[] $array_arg_types + */ + private static function checkClosureTypeArgs( + StatementsAnalyzer $statements_analyzer, + Context $context, + string $method_id, + Type\Atomic $closure_type, + PhpParser\Node\Arg $closure_arg, + int $min_closure_param_count, + int $max_closure_param_count, + array $array_arg_types + ): void { + $codebase = $statements_analyzer->getCodebase(); + + $closure_params = $closure_type->params; + + if ($closure_params === null) { + throw new \UnexpectedValueException('Closure params should not be null here'); + } + + $required_param_count = 0; + + foreach ($closure_params as $i => $param) { + if (!$param->is_optional && !$param->is_variadic) { + $required_param_count = $i + 1; + } + } + + if (count($closure_params) < $min_closure_param_count) { + $argument_text = $min_closure_param_count === 1 ? 'one argument' : $min_closure_param_count . ' arguments'; + + if (IssueBuffer::accepts( + new TooManyArguments( + 'The callable passed to ' . $method_id . ' will be called with ' . $argument_text . ', expecting ' + . $required_param_count, + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return; + } elseif ($required_param_count > $max_closure_param_count) { + $argument_text = $max_closure_param_count === 1 ? 'one argument' : $max_closure_param_count . ' arguments'; + + if (IssueBuffer::accepts( + new TooFewArguments( + 'The callable passed to ' . $method_id . ' will be called with ' . $argument_text . ', expecting ' + . $required_param_count, + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return; + } + + // abandon attempt to validate closure params if we have an extra arg for ARRAY_FILTER + if ($method_id === 'array_filter' && $max_closure_param_count > 1) { + return; + } + + foreach ($closure_params as $i => $closure_param) { + if (!isset($array_arg_types[$i])) { + continue; + } + + $array_arg_type = $array_arg_types[$i]; + + $input_type = $array_arg_type->type_params[1]; + + if ($input_type->hasMixed()) { + continue; + } + + $closure_param_type = $closure_param->type; + + if (!$closure_param_type) { + continue; + } + + if ($method_id === 'array_map' + && $i === 0 + && $closure_type->return_type + && $closure_param_type->hasTemplate() + ) { + $closure_param_type = clone $closure_param_type; + $closure_type->return_type = clone $closure_type->return_type; + + $template_result = new \Psalm\Internal\Type\TemplateResult( + [], + [] + ); + + foreach ($closure_param_type->getTemplateTypes() as $template_type) { + $template_result->template_types[$template_type->param_name] = [ + ($template_type->defining_class) => [$template_type->as] + ]; + } + + $closure_param_type = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $closure_param_type, + $template_result, + $codebase, + $statements_analyzer, + $input_type, + $i, + $context->self, + $context->calling_method_id ?: $context->calling_function_id + ); + + $closure_type->return_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + } + + $closure_param_type = TypeExpander::expandUnion( + $codebase, + $closure_param_type, + $context->self, + null, + $statements_analyzer->getParentFQCLN() + ); + + $union_comparison_results = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + $type_match_found = UnionTypeComparator::isContainedBy( + $codebase, + $input_type, + $closure_param_type, + $input_type->ignore_nullable_issues, + $input_type->ignore_falsable_issues, + $union_comparison_results + ); + + if ($union_comparison_results->type_coerced) { + if ($union_comparison_results->type_coerced_from_mixed) { + if (IssueBuffer::accepts( + new MixedArgumentTypeCoercion( + 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . + $closure_param_type->getId() . ', parent type ' . $input_type->getId() . ' provided', + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep soldiering on + } + } else { + if (IssueBuffer::accepts( + new ArgumentTypeCoercion( + 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . + $closure_param_type->getId() . ', parent type ' . $input_type->getId() . ' provided', + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep soldiering on + } + } + } + + if (!$union_comparison_results->type_coerced && !$type_match_found) { + $types_can_be_identical = UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $input_type, + $closure_param_type + ); + + if ($union_comparison_results->scalar_type_match_found) { + if (IssueBuffer::accepts( + new InvalidScalarArgument( + 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . + $closure_param_type->getId() . ', ' . $input_type->getId() . ' provided', + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($types_can_be_identical) { + if (IssueBuffer::accepts( + new PossiblyInvalidArgument( + 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' + . $closure_param_type->getId() . ', possibly different type ' + . $input_type->getId() . ' provided', + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif (IssueBuffer::accepts( + new InvalidArgument( + 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . + $closure_param_type->getId() . ', ' . $input_type->getId() . ' provided', + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php new file mode 100644 index 0000000000000000000000000000000000000000..9a2028288076792974750fbd38024815d1c39bba --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php @@ -0,0 +1,280 @@ +>|null + */ + public static function collect( + Codebase $codebase, + ClassLikeStorage $class_storage, + ClassLikeStorage $static_class_storage, + ?string $method_name = null, + ?Type\Atomic $lhs_type_part = null, + ?string $lhs_var_id = null + ): ?array { + $static_fq_class_name = $static_class_storage->name; + + $non_trait_class_storage = $class_storage->is_trait + ? $static_class_storage + : $class_storage; + + $template_types = $class_storage->template_types; + + $candidate_class_storages = [$class_storage]; + + if ($static_class_storage->template_type_extends + && $method_name + && !empty($non_trait_class_storage->overridden_method_ids[$method_name]) + && isset($class_storage->methods[$method_name]) + && (!isset($non_trait_class_storage->methods[$method_name]->return_type) + || $class_storage->methods[$method_name]->inherited_return_type) + ) { + foreach ($non_trait_class_storage->overridden_method_ids[$method_name] as $overridden_method_id) { + $overridden_storage = $codebase->methods->getStorage($overridden_method_id); + + if (!$overridden_storage->return_type) { + continue; + } + + if ($overridden_storage->return_type->isNull()) { + continue; + } + + $fq_overridden_class = $overridden_method_id->fq_class_name; + + $overridden_class_storage = $codebase->classlike_storage_provider->get($fq_overridden_class); + + $overridden_template_types = $overridden_class_storage->template_types; + + if (!$template_types) { + $template_types = $overridden_template_types; + } elseif ($overridden_template_types) { + foreach ($overridden_template_types as $template_name => $template_map) { + if (isset($template_types[$template_name])) { + $template_types[$template_name] = array_merge( + $template_types[$template_name], + $template_map + ); + } else { + $template_types[$template_name] = $template_map; + } + } + } + + $candidate_class_storages[] = $overridden_class_storage; + } + } + + if (!$template_types) { + return null; + } + + $class_template_params = []; + $e = $static_class_storage->template_type_extends; + + if ($lhs_type_part instanceof TGenericObject) { + if ($class_storage === $static_class_storage && $static_class_storage->template_types) { + $i = 0; + + foreach ($static_class_storage->template_types as $type_name => $_) { + if (isset($lhs_type_part->type_params[$i])) { + if ($lhs_var_id !== '$this' || $static_fq_class_name !== $static_class_storage->name) { + $class_template_params[$type_name][$static_class_storage->name] = [ + $lhs_type_part->type_params[$i] + ]; + } + } + + $i++; + } + } + + foreach ($template_types as $type_name => $_) { + if (isset($class_template_params[$type_name])) { + continue; + } + + if ($class_storage !== $static_class_storage + && isset($e[$class_storage->name][$type_name]) + ) { + $input_type_extends = $e[$class_storage->name][$type_name]; + + $output_type_extends = null; + + foreach ($input_type_extends->getAtomicTypes() as $type_extends_atomic) { + if ($type_extends_atomic instanceof Type\Atomic\TTemplateParam) { + if (isset($static_class_storage->template_types[$type_extends_atomic->param_name])) { + $mapped_offset = array_search( + $type_extends_atomic->param_name, + array_keys($static_class_storage->template_types) + ); + + if (isset($lhs_type_part->type_params[(int) $mapped_offset])) { + $candidate_type = $lhs_type_part->type_params[(int) $mapped_offset]; + + if (!$output_type_extends) { + $output_type_extends = $candidate_type; + } else { + $output_type_extends = Type::combineUnionTypes( + $candidate_type, + $output_type_extends + ); + } + } + } elseif (isset( + $static_class_storage + ->template_type_extends + [$type_extends_atomic->defining_class] + [$type_extends_atomic->param_name] + )) { + $mapped_offset = array_search( + $type_extends_atomic->param_name, + array_keys($static_class_storage + ->template_type_extends + [$type_extends_atomic->defining_class]) + ); + + if (isset($lhs_type_part->type_params[(int) $mapped_offset])) { + $candidate_type = $lhs_type_part->type_params[(int) $mapped_offset]; + + if (!$output_type_extends) { + $output_type_extends = $candidate_type; + } else { + $output_type_extends = Type::combineUnionTypes( + $candidate_type, + $output_type_extends + ); + } + } + } + } else { + if (!$output_type_extends) { + $output_type_extends = new Type\Union([$type_extends_atomic]); + } else { + $output_type_extends = Type::combineUnionTypes( + new Type\Union([$type_extends_atomic]), + $output_type_extends + ); + } + } + } + + if ($lhs_var_id !== '$this' || $static_fq_class_name !== $class_storage->name) { + $class_template_params[$type_name][$class_storage->name] = [ + $output_type_extends ?: Type::getMixed() + ]; + } + } + + if (($lhs_var_id !== '$this' || $static_fq_class_name !== $class_storage->name) + && !isset($class_template_params[$type_name]) + ) { + $class_template_params[$type_name] = [ + $class_storage->name => [Type::getMixed()] + ]; + } + } + } + + foreach ($template_types as $type_name => $type_map) { + foreach ($type_map as [$type]) { + foreach ($candidate_class_storages as $candidate_class_storage) { + if ($candidate_class_storage !== $static_class_storage + && isset($e[$candidate_class_storage->name][$type_name]) + && !isset($class_template_params[$type_name][$candidate_class_storage->name]) + ) { + $class_template_params[$type_name][$candidate_class_storage->name] = [ + new Type\Union( + self::expandType( + $codebase, + $e[$candidate_class_storage->name][$type_name], + $e, + $static_class_storage->name, + $static_class_storage->template_types + ) + ) + ]; + } + } + + if ($lhs_var_id !== '$this') { + if (!isset($class_template_params[$type_name])) { + $class_template_params[$type_name][$class_storage->name] = [$type]; + } + } + } + } + + return $class_template_params; + } + + /** + * @param array> $e + * @return non-empty-list + */ + private static function expandType( + Codebase $codebase, + Type\Union $input_type_extends, + array $e, + string $static_fq_class_name, + ?array $static_template_types + ) : array { + $output_type_extends = []; + + foreach ($input_type_extends->getAtomicTypes() as $type_extends_atomic) { + if ($type_extends_atomic instanceof Type\Atomic\TTemplateParam + && ($static_fq_class_name !== $type_extends_atomic->defining_class + || !isset($static_template_types[$type_extends_atomic->param_name])) + && isset($e[$type_extends_atomic->defining_class][$type_extends_atomic->param_name]) + ) { + $output_type_extends = array_merge( + $output_type_extends, + self::expandType( + $codebase, + $e[$type_extends_atomic->defining_class][$type_extends_atomic->param_name], + $e, + $static_fq_class_name, + $static_template_types + ) + ); + } elseif ($type_extends_atomic instanceof TScalarClassConstant) { + $expanded = TypeExpander::expandAtomic( + $codebase, + $type_extends_atomic, + $type_extends_atomic->fq_classlike_name, + $type_extends_atomic->fq_classlike_name, + null, + true, + true + ); + + if ($expanded instanceof Atomic) { + $output_type_extends[] = $expanded; + } else { + foreach ($expanded as $type) { + $output_type_extends[] = $type; + } + } + } else { + $output_type_extends[] = $type_extends_atomic; + } + } + + return $output_type_extends; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..ab1a1d2460a195c861997224e702d318261550b3 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -0,0 +1,1950 @@ +name; + + $function_id = null; + $function_params = null; + $in_call_map = false; + + $is_stubbed = false; + + $function_storage = null; + + $codebase = $statements_analyzer->getCodebase(); + + $code_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + $codebase_functions = $codebase->functions; + $config = $codebase->config; + $defined_constants = []; + $global_variables = []; + + $function_exists = false; + + $real_stmt = $stmt; + + if ($function_name instanceof PhpParser\Node\Name + && isset($stmt->args[0]) + && !$stmt->args[0]->unpack + ) { + $original_function_id = implode('\\', $function_name->parts); + + if ($original_function_id === 'call_user_func') { + $other_args = \array_slice($stmt->args, 1); + + $function_name = $stmt->args[0]->value; + + $stmt = new PhpParser\Node\Expr\FuncCall( + $function_name, + $other_args, + $stmt->getAttributes() + ); + } + + if ($original_function_id === 'call_user_func_array' && isset($stmt->args[1])) { + $function_name = $stmt->args[0]->value; + + $stmt = new PhpParser\Node\Expr\FuncCall( + $function_name, + [new PhpParser\Node\Arg($stmt->args[1]->value, false, true)], + $stmt->getAttributes() + ); + } + } + + $byref_uses = []; + + $allow_named_args = true; + + if ($function_name instanceof PhpParser\Node\Expr) { + [$expr_function_exists, $expr_function_name, $expr_function_params, $byref_uses] + = self::getAnalyzeNamedExpression( + $statements_analyzer, + $codebase, + $stmt, + $real_stmt, + $function_name, + $context + ); + + if ($expr_function_exists === false) { + return true; + } + + if ($expr_function_exists === true) { + $function_exists = true; + } + + if ($expr_function_name) { + $function_name = $expr_function_name; + } + + if ($expr_function_params) { + $function_params = $expr_function_params; + } + } else { + $original_function_id = implode('\\', $function_name->parts); + + if (!$function_name instanceof PhpParser\Node\Name\FullyQualified) { + $function_id = $codebase_functions->getFullyQualifiedFunctionNameFromString( + $original_function_id, + $statements_analyzer + ); + } else { + $function_id = $original_function_id; + } + + $namespaced_function_exists = $codebase_functions->functionExists( + $statements_analyzer, + strtolower($function_id) + ); + + if (!$namespaced_function_exists + && !$function_name instanceof PhpParser\Node\Name\FullyQualified + ) { + $in_call_map = InternalCallMapHandler::inCallMap($original_function_id); + $is_stubbed = $codebase_functions->hasStubbedFunction($original_function_id); + + if ($is_stubbed || $in_call_map) { + $function_id = $original_function_id; + } + } else { + $in_call_map = InternalCallMapHandler::inCallMap($function_id); + $is_stubbed = $codebase_functions->hasStubbedFunction($function_id); + } + + if ($is_stubbed || $in_call_map || $namespaced_function_exists) { + $function_exists = true; + } + + if ($function_exists + && $codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + ArgumentMapPopulator::recordArgumentPositions( + $statements_analyzer, + $stmt, + $codebase, + $function_id + ); + } + + $is_predefined = true; + + $is_maybe_root_function = !$function_name instanceof PhpParser\Node\Name\FullyQualified + && count($function_name->parts) === 1; + + if (!$in_call_map) { + $predefined_functions = $config->getPredefinedFunctions(); + $is_predefined = isset($predefined_functions[strtolower($original_function_id)]) + || isset($predefined_functions[strtolower($function_id)]); + + if ($context->check_functions) { + if (self::checkFunctionExists( + $statements_analyzer, + $function_id, + $code_location, + $is_maybe_root_function + ) === false + ) { + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ) === false) { + // fall through + } + + return true; + } + } + } else { + $function_exists = true; + } + + if ($function_exists) { + $function_params = null; + + if ($codebase->functions->params_provider->has($function_id)) { + $function_params = $codebase->functions->params_provider->getFunctionParams( + $statements_analyzer, + $function_id, + $stmt->args + ); + } + + if ($function_params === null) { + if (!$in_call_map || $is_stubbed) { + try { + $function_storage = $codebase_functions->getStorage( + $statements_analyzer, + strtolower($function_id) + ); + + $function_params = $function_storage->params; + + if (!$function_storage->allow_named_arg_calls) { + $allow_named_args = false; + } + + if (!$is_predefined) { + $defined_constants = $function_storage->defined_constants; + $global_variables = $function_storage->global_variables; + } + } catch (\UnexpectedValueException $e) { + $function_params = [ + new FunctionLikeParameter('args', false, null, null, null, false, false, true) + ]; + } + } else { + $function_callable = InternalCallMapHandler::getCallableFromCallMapById( + $codebase, + $function_id, + $stmt->args, + $statements_analyzer->node_data + ); + + $function_params = $function_callable->params; + } + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $function_name, + $function_id . '()' + ); + } + } + } + + $set_inside_conditional = false; + + if ($function_name instanceof PhpParser\Node\Name + && $function_name->parts === ['assert'] + && !$context->inside_conditional + ) { + $context->inside_conditional = true; + $set_inside_conditional = true; + } + + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + $function_params, + $function_id, + $allow_named_args, + $context + ) === false) { + // fall through + } + + if ($set_inside_conditional) { + $context->inside_conditional = false; + } + + $template_result = null; + $function_callable = null; + + if ($function_exists) { + if ($function_name instanceof PhpParser\Node\Name && $function_id) { + if (!$is_stubbed && $in_call_map) { + $function_callable = \Psalm\Internal\Codebase\InternalCallMapHandler::getCallableFromCallMapById( + $codebase, + $function_id, + $stmt->args, + $statements_analyzer->node_data + ); + + $function_params = $function_callable->params; + } + } + + $template_result = new TemplateResult([], []); + + // do this here to allow closure param checks + if ($function_params !== null + && ArgumentsAnalyzer::checkArgumentsMatch( + $statements_analyzer, + $stmt->args, + $function_id, + $function_params, + $function_storage, + null, + $template_result, + $code_location, + $context + ) === false + ) { + // fall through + } + + CallAnalyzer::checkTemplateResult( + $statements_analyzer, + $template_result, + $code_location, + $function_id + ); + + if ($function_name instanceof PhpParser\Node\Name && $function_id) { + $stmt_type = self::getFunctionCallReturnType( + $statements_analyzer, + $codebase, + $stmt, + $function_name, + $function_id, + $in_call_map, + $is_stubbed, + $function_storage, + $function_callable, + $template_result, + $context + ); + + $statements_analyzer->node_data->setType($real_stmt, $stmt_type); + + if ($config->after_every_function_checks) { + foreach ($config->after_every_function_checks as $plugin_fq_class_name) { + $plugin_fq_class_name::afterEveryFunctionCallAnalysis( + $stmt, + $function_id, + $context, + $statements_analyzer->getSource(), + $codebase + ); + } + } + } + + foreach ($defined_constants as $const_name => $const_type) { + $context->constants[$const_name] = clone $const_type; + $context->vars_in_scope[$const_name] = clone $const_type; + } + + foreach ($global_variables as $var_id => $_) { + $context->vars_in_scope[$var_id] = Type::getMixed(); + $context->vars_possibly_in_scope[$var_id] = true; + } + + if ($function_name instanceof PhpParser\Node\Name + && $function_name->parts === ['assert'] + && isset($stmt->args[0]) + ) { + self::processAssertFunctionEffects( + $statements_analyzer, + $codebase, + $stmt, + $stmt->args[0], + $context + ); + } + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + && ($stmt_type = $statements_analyzer->node_data->getType($real_stmt)) + ) { + $codebase->analyzer->addNodeType( + $statements_analyzer->getFilePath(), + $stmt, + $stmt_type->getId() + ); + } + + self::checkFunctionCallPurity( + $statements_analyzer, + $codebase, + $stmt, + $function_name, + $function_id, + $in_call_map, + $function_storage, + $context + ); + + if ($function_storage) { + $generic_params = $template_result ? $template_result->upper_bounds : []; + + if ($function_storage->assertions && $function_name instanceof PhpParser\Node\Name) { + self::applyAssertionsToContext( + $function_name, + null, + $function_storage->assertions, + $stmt->args, + $generic_params, + $context, + $statements_analyzer + ); + } + + if ($function_storage->if_true_assertions) { + $statements_analyzer->node_data->setIfTrueAssertions( + $stmt, + array_map( + function (Assertion $assertion) use ($generic_params) : Assertion { + return $assertion->getUntemplatedCopy($generic_params ?: [], null); + }, + $function_storage->if_true_assertions + ) + ); + } + + if ($function_storage->if_false_assertions) { + $statements_analyzer->node_data->setIfFalseAssertions( + $stmt, + array_map( + function (Assertion $assertion) use ($generic_params) : Assertion { + return $assertion->getUntemplatedCopy($generic_params ?: [], null); + }, + $function_storage->if_false_assertions + ) + ); + } + + if ($function_storage->deprecated && $function_id) { + if (IssueBuffer::accepts( + new DeprecatedFunction( + 'The function ' . $function_id . ' has been marked as deprecated', + $code_location, + $function_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // continue + } + } + } + + if ($byref_uses) { + foreach ($byref_uses as $byref_use_var => $_) { + $context->vars_in_scope['$' . $byref_use_var] = Type::getMixed(); + $context->vars_possibly_in_scope['$' . $byref_use_var] = true; + } + } + + if ($function_name instanceof PhpParser\Node\Name) { + self::handleNamedFunction( + $statements_analyzer, + $codebase, + $stmt, + $real_stmt, + $function_name, + $function_id, + $context + ); + } + + if (!$statements_analyzer->node_data->getType($real_stmt)) { + $statements_analyzer->node_data->setType($real_stmt, Type::getMixed()); + } + + return true; + } + + /** + * @return array{ + * ?bool, + * ?PhpParser\Node\Expr|PhpParser\Node\Name, + * array|null, + * ?array + * } + */ + private static function getAnalyzeNamedExpression( + StatementsAnalyzer $statements_analyzer, + \Psalm\Codebase $codebase, + PhpParser\Node\Expr\FuncCall $stmt, + PhpParser\Node\Expr\FuncCall $real_stmt, + PhpParser\Node\Expr $function_name, + Context $context + ): array { + $function_params = null; + + $explicit_function_name = null; + $function_exists = null; + $was_in_call = $context->inside_call; + $context->inside_call = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $function_name, $context) === false) { + $context->inside_call = $was_in_call; + + return [false, null, null, null]; + } + + $context->inside_call = $was_in_call; + + $byref_uses = []; + + if ($stmt_name_type = $statements_analyzer->node_data->getType($function_name)) { + if ($stmt_name_type->isNull()) { + if (IssueBuffer::accepts( + new NullFunctionCall( + 'Cannot call function on null value', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return [false, null, null, null]; + } + + if ($stmt_name_type->isNullable()) { + if (IssueBuffer::accepts( + new PossiblyNullFunctionCall( + 'Cannot call function on possibly null value', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $invalid_function_call_types = []; + $has_valid_function_call_type = false; + + foreach ($stmt_name_type->getAtomicTypes() as $var_type_part) { + if ($var_type_part instanceof Type\Atomic\TClosure || $var_type_part instanceof TCallable) { + if (!$var_type_part->is_pure && $context->pure) { + if (IssueBuffer::accepts( + new ImpureFunctionCall( + 'Cannot call an impure function from a mutation-free context', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $function_params = $var_type_part->params; + + if (($stmt_type = $statements_analyzer->node_data->getType($real_stmt)) + && $var_type_part->return_type + ) { + $statements_analyzer->node_data->setType( + $real_stmt, + Type::combineUnionTypes( + $stmt_type, + $var_type_part->return_type + ) + ); + } else { + $statements_analyzer->node_data->setType( + $real_stmt, + $var_type_part->return_type ?: Type::getMixed() + ); + } + + if ($var_type_part instanceof Type\Atomic\TClosure) { + $byref_uses += $var_type_part->byref_uses; + } + + $function_exists = true; + $has_valid_function_call_type = true; + } elseif ($var_type_part instanceof TTemplateParam && $var_type_part->as->hasCallableType()) { + $has_valid_function_call_type = true; + } elseif ($var_type_part instanceof TMixed || $var_type_part instanceof TTemplateParam) { + $has_valid_function_call_type = true; + + if (IssueBuffer::accepts( + new MixedFunctionCall( + 'Cannot call function on ' . $var_type_part->getId(), + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($var_type_part instanceof TCallableObject + || $var_type_part instanceof TCallableString + ) { + // this is fine + $has_valid_function_call_type = true; + } elseif (($var_type_part instanceof TNamedObject && $var_type_part->value === 'Closure')) { + // this is fine + $has_valid_function_call_type = true; + } elseif ($var_type_part instanceof TString + || $var_type_part instanceof Type\Atomic\TArray + || $var_type_part instanceof Type\Atomic\TList + || ($var_type_part instanceof Type\Atomic\TKeyedArray + && count($var_type_part->properties) === 2) + ) { + $potential_method_id = null; + + if ($var_type_part instanceof Type\Atomic\TKeyedArray) { + $potential_method_id = CallableTypeComparator::getCallableMethodIdFromTKeyedArray( + $var_type_part, + $codebase, + $context->calling_method_id, + $statements_analyzer->getFilePath() + ); + + if ($potential_method_id === 'not-callable') { + $potential_method_id = null; + } + } elseif ($var_type_part instanceof Type\Atomic\TLiteralString) { + if (!$var_type_part->value) { + $invalid_function_call_types[] = '\'\''; + continue; + } + + if (strpos($var_type_part->value, '::')) { + $parts = explode('::', strtolower($var_type_part->value)); + $fq_class_name = $parts[0]; + $fq_class_name = \preg_replace('/^\\\\/', '', $fq_class_name); + $potential_method_id = new \Psalm\Internal\MethodIdentifier($fq_class_name, $parts[1]); + } else { + $explicit_function_name = new PhpParser\Node\Name\FullyQualified( + $var_type_part->value, + $function_name->getAttributes() + ); + } + } + + if ($potential_method_id) { + $codebase->methods->methodExists( + $potential_method_id, + $context->calling_method_id, + null, + $statements_analyzer, + $statements_analyzer->getFilePath() + ); + } + + // this is also kind of fine + $has_valid_function_call_type = true; + } elseif ($var_type_part instanceof TNull) { + // handled above + } elseif (!$var_type_part instanceof TNamedObject + || !$codebase->classlikes->classOrInterfaceExists($var_type_part->value) + || !$codebase->methods->methodExists( + new \Psalm\Internal\MethodIdentifier( + $var_type_part->value, + '__invoke' + ) + ) + ) { + $invalid_function_call_types[] = (string)$var_type_part; + } else { + self::analyzeInvokeCall( + $statements_analyzer, + $stmt, + $real_stmt, + $function_name, + $context, + $var_type_part + ); + } + } + + if ($invalid_function_call_types) { + $var_type_part = reset($invalid_function_call_types); + + if ($has_valid_function_call_type) { + if (IssueBuffer::accepts( + new PossiblyInvalidFunctionCall( + 'Cannot treat type ' . $var_type_part . ' as callable', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidFunctionCall( + 'Cannot treat type ' . $var_type_part . ' as callable', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + return [false, null, null, null]; + } + } + + if (!$statements_analyzer->node_data->getType($real_stmt)) { + $statements_analyzer->node_data->setType($real_stmt, Type::getMixed()); + } + + return [ + $function_exists, + $explicit_function_name ?: $function_name, + $function_params, + $byref_uses + ]; + } + + private static function analyzeInvokeCall( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\FuncCall $stmt, + PhpParser\Node\Expr\FuncCall $real_stmt, + PhpParser\Node\Expr $function_name, + Context $context, + Type\Atomic $atomic_type + ) : void { + $old_data_provider = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $fake_method_call = new PhpParser\Node\Expr\MethodCall( + $function_name, + new PhpParser\Node\Identifier('__invoke', $function_name->getAttributes()), + $stmt->args + ); + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('PossiblyNullReference', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['PossiblyNullReference']); + } + + if (!in_array('InternalMethod', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['InternalMethod']); + } + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + $statements_analyzer->node_data->setType($function_name, new Type\Union([$atomic_type])); + + \Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_method_call, + $context, + false + ); + + if (!in_array('PossiblyNullReference', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['PossiblyNullReference']); + } + + if (!in_array('InternalMethod', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['InternalMethod']); + } + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + $fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call); + + $statements_analyzer->node_data = $old_data_provider; + + if ($stmt_type = $statements_analyzer->node_data->getType($real_stmt)) { + $statements_analyzer->node_data->setType( + $real_stmt, + Type::combineUnionTypes( + $fake_method_call_type ?: Type::getMixed(), + $stmt_type + ) + ); + } else { + $statements_analyzer->node_data->setType( + $real_stmt, + $fake_method_call_type ?: Type::getMixed() + ); + } + } + + private static function processAssertFunctionEffects( + StatementsAnalyzer $statements_analyzer, + \Psalm\Codebase $codebase, + PhpParser\Node\Expr\FuncCall $stmt, + PhpParser\Node\Arg $first_arg, + Context $context + ) : void { + $first_arg_value_id = \spl_object_id($first_arg->value); + + $assert_clauses = \Psalm\Type\Algebra::getFormula( + $first_arg_value_id, + $first_arg_value_id, + $first_arg->value, + $context->self, + $statements_analyzer, + $codebase + ); + + $cond_assigned_var_ids = []; + + \Psalm\Internal\Analyzer\AlgebraAnalyzer::checkForParadox( + $context->clauses, + $assert_clauses, + $statements_analyzer, + $stmt, + $cond_assigned_var_ids + ); + + $simplified_clauses = Algebra::simplifyCNF(array_merge($context->clauses, $assert_clauses)); + + $assert_type_assertions = Algebra::getTruthsFromFormula($simplified_clauses); + + if ($assert_type_assertions) { + $changed_var_ids = []; + + // while in an and, we allow scope to boil over to support + // statements of the form if ($x && $x->foo()) + $op_vars_in_scope = Reconciler::reconcileKeyedTypes( + $assert_type_assertions, + $assert_type_assertions, + $context->vars_in_scope, + $changed_var_ids, + array_map( + function ($_): bool { + return true; + }, + $assert_type_assertions + ), + $statements_analyzer, + $statements_analyzer->getTemplateTypeMap() ?: [], + $context->inside_loop, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ); + + foreach ($changed_var_ids as $var_id => $_) { + $first_appearance = $statements_analyzer->getFirstAppearance($var_id); + + if ($first_appearance + && isset($context->vars_in_scope[$var_id]) + && $context->vars_in_scope[$var_id]->hasMixed() + ) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->decrementMixedCount($statements_analyzer->getFilePath()); + } + + IssueBuffer::remove( + $statements_analyzer->getFilePath(), + 'MixedAssignment', + $first_appearance->raw_file_start + ); + } + + if (isset($op_vars_in_scope[$var_id])) { + $op_vars_in_scope[$var_id]->from_docblock = true; + } + } + + $context->vars_in_scope = $op_vars_in_scope; + } + } + + /** + * @param non-empty-string $function_id + */ + private static function getFunctionCallReturnType( + StatementsAnalyzer $statements_analyzer, + \Psalm\Codebase $codebase, + PhpParser\Node\Expr\FuncCall $stmt, + PhpParser\Node\Name $function_name, + string $function_id, + bool $in_call_map, + bool $is_stubbed, + ?FunctionLikeStorage $function_storage, + ?TCallable $callmap_callable, + TemplateResult $template_result, + Context $context + ) : Type\Union { + $stmt_type = null; + $config = $codebase->config; + + if ($codebase->functions->return_type_provider->has($function_id)) { + $stmt_type = $codebase->functions->return_type_provider->getReturnType( + $statements_analyzer, + $function_id, + $stmt->args, + $context, + new CodeLocation($statements_analyzer->getSource(), $function_name) + ); + } + + if (!$stmt_type) { + if (!$in_call_map || $is_stubbed) { + if ($function_storage && $function_storage->template_types) { + foreach ($function_storage->template_types as $template_name => $_) { + if (!isset($template_result->upper_bounds[$template_name])) { + if ($template_name === 'TFunctionArgCount') { + $template_result->upper_bounds[$template_name] = [ + 'fn-' . $function_id => [Type::getInt(false, count($stmt->args)), 0] + ]; + } else { + $template_result->upper_bounds[$template_name] = [ + 'fn-' . $function_id => [Type::getEmpty(), 0] + ]; + } + } + } + } + + if ($function_storage && !$context->isSuppressingExceptions($statements_analyzer)) { + $context->mergeFunctionExceptions( + $function_storage, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ); + } + + try { + if ($function_storage && $function_storage->return_type) { + $return_type = clone $function_storage->return_type; + + if ($template_result->upper_bounds && $function_storage->template_types) { + $return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $return_type, + null, + null, + null + ); + + $return_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + } + + $return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $return_type, + null, + null, + null + ); + + $return_type_location = $function_storage->return_type_location; + + if ($config->after_function_checks) { + $file_manipulations = []; + + foreach ($config->after_function_checks as $plugin_fq_class_name) { + $plugin_fq_class_name::afterFunctionCallAnalysis( + $stmt, + $function_id, + $context, + $statements_analyzer->getSource(), + $codebase, + $return_type, + $file_manipulations + ); + } + + if ($file_manipulations) { + FileManipulationBuffer::add( + $statements_analyzer->getFilePath(), + $file_manipulations + ); + } + } + + $stmt_type = $return_type; + $return_type->by_ref = $function_storage->returns_by_ref; + + // only check the type locally if it's defined externally + if ($return_type_location && + !$is_stubbed && // makes lookups or array_* functions quicker + !$config->isInProjectDirs($return_type_location->file_path) + ) { + $return_type->check( + $statements_analyzer, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues(), + $context->phantom_classes, + true, + false, + false, + $context->calling_method_id + ); + } + } + } catch (\InvalidArgumentException $e) { + // this can happen when the function was defined in the Config startup script + $stmt_type = Type::getMixed(); + } + } else { + if (!$callmap_callable) { + throw new \UnexpectedValueException('We should have a callmap callable here'); + } + + $stmt_type = self::getReturnTypeFromCallMapWithArgs( + $statements_analyzer, + $function_id, + $stmt->args, + $callmap_callable, + $context + ); + } + } + + if (!$stmt_type) { + $stmt_type = Type::getMixed(); + } + + if ($function_storage) { + self::taintReturnType($statements_analyzer, $stmt, $function_id, $function_storage, $stmt_type); + } + + + return $stmt_type; + } + + /** + * @param array $call_args + */ + private static function getReturnTypeFromCallMapWithArgs( + StatementsAnalyzer $statements_analyzer, + string $function_id, + array $call_args, + TCallable $callmap_callable, + Context $context + ): Type\Union { + $call_map_key = strtolower($function_id); + + $codebase = $statements_analyzer->getCodebase(); + + if (!$call_args) { + switch ($call_map_key) { + case 'hrtime': + return new Type\Union([ + new Type\Atomic\TKeyedArray([ + Type::getInt(), + Type::getInt() + ]) + ]); + + case 'get_called_class': + return new Type\Union([ + new Type\Atomic\TClassString( + $context->self ?: 'object', + $context->self ? new Type\Atomic\TNamedObject($context->self, true) : null + ) + ]); + + case 'get_parent_class': + if ($context->self && $codebase->classExists($context->self)) { + $classlike_storage = $codebase->classlike_storage_provider->get($context->self); + + if ($classlike_storage->parent_classes) { + return new Type\Union([ + new Type\Atomic\TClassString( + \array_values($classlike_storage->parent_classes)[0] + ) + ]); + } + } + } + } else { + switch ($call_map_key) { + case 'count': + if (($first_arg_type = $statements_analyzer->node_data->getType($call_args[0]->value))) { + $atomic_types = $first_arg_type->getAtomicTypes(); + + if (count($atomic_types) === 1) { + if (isset($atomic_types['array'])) { + if ($atomic_types['array'] instanceof Type\Atomic\TCallableArray + || $atomic_types['array'] instanceof Type\Atomic\TCallableList + || $atomic_types['array'] instanceof Type\Atomic\TCallableKeyedArray + ) { + return Type::getInt(false, 2); + } + + if ($atomic_types['array'] instanceof Type\Atomic\TNonEmptyArray) { + return new Type\Union([ + $atomic_types['array']->count !== null + ? new Type\Atomic\TLiteralInt($atomic_types['array']->count) + : new Type\Atomic\TInt + ]); + } + + if ($atomic_types['array'] instanceof Type\Atomic\TNonEmptyList) { + return new Type\Union([ + $atomic_types['array']->count !== null + ? new Type\Atomic\TLiteralInt($atomic_types['array']->count) + : new Type\Atomic\TInt + ]); + } + + if ($atomic_types['array'] instanceof Type\Atomic\TKeyedArray + && $atomic_types['array']->sealed + ) { + return new Type\Union([ + new Type\Atomic\TLiteralInt(count($atomic_types['array']->properties)) + ]); + } + } + } + } + + break; + + case 'hrtime': + if (($first_arg_type = $statements_analyzer->node_data->getType($call_args[0]->value))) { + if ((string) $first_arg_type === 'true') { + $int = Type::getInt(); + $int->from_calculation = true; + return $int; + } + + if ((string) $first_arg_type === 'false') { + return new Type\Union([ + new Type\Atomic\TKeyedArray([ + Type::getInt(), + Type::getInt() + ]) + ]); + } + + return new Type\Union([ + new Type\Atomic\TKeyedArray([ + Type::getInt(), + Type::getInt() + ]), + new Type\Atomic\TInt() + ]); + } + + $int = Type::getInt(); + $int->from_calculation = true; + return $int; + + case 'min': + case 'max': + if (isset($call_args[0])) { + $first_arg = $call_args[0]->value; + + if ($first_arg_type = $statements_analyzer->node_data->getType($first_arg)) { + if ($first_arg_type->hasArray()) { + /** @psalm-suppress PossiblyUndefinedStringArrayOffset */ + $array_type = $first_arg_type->getAtomicTypes()['array']; + if ($array_type instanceof Type\Atomic\TKeyedArray) { + return $array_type->getGenericValueType(); + } + + if ($array_type instanceof Type\Atomic\TArray) { + return clone $array_type->type_params[1]; + } + + if ($array_type instanceof Type\Atomic\TList) { + return clone $array_type->type_param; + } + } elseif ($first_arg_type->hasScalarType() + && ($second_arg = ($call_args[1]->value ?? null)) + && ($second_arg_type = $statements_analyzer->node_data->getType($second_arg)) + && $second_arg_type->hasScalarType() + ) { + return Type::combineUnionTypes($first_arg_type, $second_arg_type); + } + } + } + + break; + + case 'get_parent_class': + // this is unreliable, as it's hard to know exactly what's wanted - attempted this in + // https://github.com/vimeo/psalm/commit/355ed831e1c69c96bbf9bf2654ef64786cbe9fd7 + // but caused problems where it didn’t know exactly what level of child we + // were receiving. + // + // Really this should only work on instances we've created with new Foo(), + // but that requires more work + break; + + case 'fgetcsv': + $string_type = Type::getString(); + $string_type->addType(new Type\Atomic\TNull); + $string_type->ignore_nullable_issues = true; + + $call_map_return_type = new Type\Union([ + new Type\Atomic\TNonEmptyList( + $string_type + ), + new Type\Atomic\TFalse, + new Type\Atomic\TNull + ]); + + if ($codebase->config->ignore_internal_nullable_issues) { + $call_map_return_type->ignore_nullable_issues = true; + } + + if ($codebase->config->ignore_internal_falsable_issues) { + $call_map_return_type->ignore_falsable_issues = true; + } + + return $call_map_return_type; + } + } + + $stmt_type = $callmap_callable->return_type + ? clone $callmap_callable->return_type + : Type::getMixed(); + + switch ($function_id) { + case 'mb_strpos': + case 'mb_strrpos': + case 'mb_stripos': + case 'mb_strripos': + case 'strpos': + case 'strrpos': + case 'stripos': + case 'strripos': + case 'strstr': + case 'stristr': + case 'strrchr': + case 'strpbrk': + case 'array_search': + break; + + default: + if ($stmt_type->isFalsable() + && $codebase->config->ignore_internal_falsable_issues + ) { + $stmt_type->ignore_falsable_issues = true; + } + } + + switch ($call_map_key) { + case 'array_replace': + case 'array_replace_recursive': + if ($codebase->config->ignore_internal_nullable_issues) { + $stmt_type->ignore_nullable_issues = true; + } + break; + } + + return $stmt_type; + } + + private static function taintReturnType( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\FuncCall $stmt, + string $function_id, + FunctionLikeStorage $function_storage, + Type\Union $stmt_type + ) : void { + if (!$statements_analyzer->data_flow_graph instanceof TaintFlowGraph + || \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + return; + } + + $node_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + + $function_call_node = DataFlowNode::getForMethodReturn( + $function_id, + $function_id, + $function_storage->signature_return_type_location ?: $function_storage->location, + $function_storage->specialize_call ? $node_location : null + ); + + $statements_analyzer->data_flow_graph->addNode($function_call_node); + + $stmt_type->parent_nodes[$function_call_node->id] = $function_call_node; + + if ($function_storage->return_source_params) { + $removed_taints = $function_storage->removed_taints; + + if ($function_id === 'preg_replace' && count($stmt->args) > 2) { + $first_stmt_type = $statements_analyzer->node_data->getType($stmt->args[0]->value); + $second_stmt_type = $statements_analyzer->node_data->getType($stmt->args[1]->value); + + if ($first_stmt_type + && $second_stmt_type + && $first_stmt_type->isSingleStringLiteral() + && $second_stmt_type->isSingleStringLiteral() + ) { + $first_arg_value = $first_stmt_type->getSingleStringLiteral()->value; + + $pattern = \substr($first_arg_value, 1, -1); + + if ($pattern[0] === '[' + && $pattern[1] === '^' + && \substr($pattern, -1) === ']' + ) { + $pattern = \substr($pattern, 2, -1); + + if (self::simpleExclusion($pattern, $first_arg_value[0])) { + $removed_taints[] = 'html'; + $removed_taints[] = 'sql'; + } + } + } + } + + foreach ($function_storage->return_source_params as $i => $path_type) { + if (!isset($stmt->args[$i])) { + continue; + } + + $arg_location = new CodeLocation( + $statements_analyzer->getSource(), + $stmt->args[$i]->value + ); + + $function_param_sink = DataFlowNode::getForMethodArgument( + $function_id, + $function_id, + $i, + $arg_location, + $function_storage->specialize_call ? $node_location : null + ); + + $statements_analyzer->data_flow_graph->addNode($function_param_sink); + + $statements_analyzer->data_flow_graph->addPath( + $function_param_sink, + $function_call_node, + $path_type, + $function_storage->added_taints, + $removed_taints + ); + } + } + + if ($function_storage->taint_source_types) { + $method_node = TaintSource::getForMethodReturn( + $function_id, + $function_id, + $node_location + ); + + $method_node->taints = $function_storage->taint_source_types; + + $statements_analyzer->data_flow_graph->addSource($method_node); + } + } + + /** + * @psalm-pure + */ + private static function simpleExclusion(string $pattern, string $escape_char) : bool + { + $str_length = \strlen($pattern); + + for ($i = 0; $i < $str_length; $i++) { + $current = $pattern[$i]; + $next = $pattern[$i + 1] ?? null; + + if ($current === '\\') { + if ($next == null + || $next === 'x' + || $next === 'u' + ) { + return false; + } + + if ($next === '.' + || $next === '(' + || $next === ')' + || $next === '[' + || $next === ']' + || $next === 's' + || $next === 'w' + || $next === $escape_char + ) { + $i++; + continue; + } + + return false; + } + + if ($next !== '-') { + if ($current === '_' + || $current === '-' + || $current === '|' + || $current === ':' + || $current === '#' + || $current === '.' + || $current === ' ' + ) { + continue; + } + + return false; + } + + if ($current === ']') { + return false; + } + + if (!isset($pattern[$i + 2])) { + return false; + } + + if (($current === 'a' && $pattern[$i + 2] === 'z') + || ($current === 'a' && $pattern[$i + 2] === 'Z') + || ($current === 'A' && $pattern[$i + 2] === 'Z') + || ($current === '0' && $pattern[$i + 2] === '9') + ) { + $i += 2; + continue; + } + + return false; + } + + return true; + } + + private static function checkFunctionCallPurity( + StatementsAnalyzer $statements_analyzer, + \Psalm\Codebase $codebase, + PhpParser\Node\Expr\FuncCall $stmt, + PhpParser\Node $function_name, + ?string $function_id, + bool $in_call_map, + ?FunctionLikeStorage $function_storage, + Context $context + ) : void { + $config = $codebase->config; + + if (!$context->collect_initializations + && !$context->collect_mutations + && ($context->mutation_free + || $context->external_mutation_free + || $codebase->find_unused_variables + || !$config->remember_property_assignments_after_call + || ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations)) + ) { + $must_use = true; + + $callmap_function_pure = $function_id && $in_call_map + ? $codebase->functions->isCallMapFunctionPure( + $codebase, + $statements_analyzer->node_data, + $function_id, + $stmt->args, + $must_use + ) + : null; + + if ((!$in_call_map + && $function_storage + && !$function_storage->pure) + || ($callmap_function_pure === false) + ) { + if ($context->mutation_free || $context->external_mutation_free) { + if (IssueBuffer::accepts( + new ImpureFunctionCall( + 'Cannot call an impure function from a mutation-free context', + new CodeLocation($statements_analyzer, $function_name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } + + if (!$config->remember_property_assignments_after_call) { + $context->removeAllObjectVars(); + } + } elseif ($function_id + && (($function_storage + && $function_storage->pure + && !$function_storage->assertions + && $must_use) + || ($callmap_function_pure === true && $must_use)) + && $codebase->find_unused_variables + && !$context->inside_conditional + && !$context->inside_unset + ) { + if (!$context->inside_assignment && !$context->inside_call && !$context->inside_use) { + if (IssueBuffer::accepts( + new UnusedFunctionCall( + 'The call to ' . $function_id . ' is not used', + new CodeLocation($statements_analyzer, $function_name), + $function_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + /** @psalm-suppress UndefinedPropertyAssignment */ + $stmt->pure = true; + } + } + } + } + + private static function handleNamedFunction( + StatementsAnalyzer $statements_analyzer, + \Psalm\Codebase $codebase, + PhpParser\Node\Expr\FuncCall $stmt, + PhpParser\Node\Expr\FuncCall $real_stmt, + PhpParser\Node\Name $function_name, + ?string $function_id, + Context $context + ) : void { + $first_arg = isset($stmt->args[0]) ? $stmt->args[0] : null; + + if ($function_name->parts === ['get_class'] + || $function_name->parts === ['gettype'] + || $function_name->parts === ['get_debug_type'] + ) { + if ($first_arg) { + $var = $first_arg->value; + + if ($var instanceof PhpParser\Node\Expr\Variable + && is_string($var->name) + ) { + $var_id = '$' . $var->name; + + if (isset($context->vars_in_scope[$var_id])) { + if ($function_name->parts === ['get_class']) { + $atomic_type = new Type\Atomic\TDependentGetClass( + $var_id, + $context->vars_in_scope[$var_id]->hasMixed() + ? Type::getObject() + : $context->vars_in_scope[$var_id] + ); + } elseif ($function_name->parts === ['gettype']) { + $atomic_type = new Type\Atomic\TDependentGetType($var_id); + } else { + $atomic_type = new Type\Atomic\TDependentGetDebugType($var_id); + } + + $statements_analyzer->node_data->setType($real_stmt, new Type\Union([$atomic_type])); + } + } elseif (($var_type = $statements_analyzer->node_data->getType($var)) + && ($function_name->parts === ['get_class'] + || $function_name->parts === ['get_debug_type'] + ) + ) { + $class_string_types = []; + + foreach ($var_type->getAtomicTypes() as $class_type) { + if ($class_type instanceof Type\Atomic\TNamedObject) { + $class_string_types[] = new Type\Atomic\TClassString($class_type->value, clone $class_type); + } elseif ($class_type instanceof Type\Atomic\TTemplateParam + && $class_type->as->isSingle() + ) { + $as_atomic_type = \array_values($class_type->as->getAtomicTypes())[0]; + + if ($as_atomic_type instanceof Type\Atomic\TObject) { + $class_string_types[] = new Type\Atomic\TTemplateParamClass( + $class_type->param_name, + 'object', + null, + $class_type->defining_class + ); + } elseif ($as_atomic_type instanceof TNamedObject) { + $class_string_types[] = new Type\Atomic\TTemplateParamClass( + $class_type->param_name, + $as_atomic_type->value, + $as_atomic_type, + $class_type->defining_class + ); + } + } elseif ($function_name->parts === ['get_class']) { + $class_string_types[] = new Type\Atomic\TClassString(); + } elseif ($function_name->parts === ['get_debug_type']) { + if ($class_type instanceof Type\Atomic\TInt) { + $class_string_types[] = new Type\Atomic\TLiteralString('int'); + } elseif ($class_type instanceof Type\Atomic\TString) { + $class_string_types[] = new Type\Atomic\TLiteralString('string'); + } elseif ($class_type instanceof Type\Atomic\TFloat) { + $class_string_types[] = new Type\Atomic\TLiteralString('float'); + } elseif ($class_type instanceof Type\Atomic\TBool) { + $class_string_types[] = new Type\Atomic\TLiteralString('bool'); + } elseif ($class_type instanceof Type\Atomic\TClosedResource) { + $class_string_types[] = new Type\Atomic\TLiteralString('resource (closed)'); + } elseif ($class_type instanceof Type\Atomic\TNull) { + $class_string_types[] = new Type\Atomic\TLiteralString('null'); + } else { + $class_string_types[] = new Type\Atomic\TString(); + } + } + } + + if ($class_string_types) { + $statements_analyzer->node_data->setType($real_stmt, new Type\Union($class_string_types)); + } + } + } elseif ($function_name->parts === ['get_class'] + && ($get_class_name = $statements_analyzer->getFQCLN()) + ) { + $statements_analyzer->node_data->setType( + $real_stmt, + new Type\Union([ + new Type\Atomic\TClassString( + $get_class_name, + new Type\Atomic\TNamedObject($get_class_name) + ) + ]) + ); + } + } + + if ($function_name->parts === ['method_exists']) { + $second_arg = isset($stmt->args[1]) ? $stmt->args[1] : null; + + if ($first_arg + && $first_arg->value instanceof PhpParser\Node\Expr\Variable + && $second_arg + && $second_arg->value instanceof PhpParser\Node\Scalar\String_ + ) { + // do nothing + } else { + $context->check_methods = false; + } + } elseif ($function_name->parts === ['class_exists']) { + if ($first_arg) { + if ($first_arg->value instanceof PhpParser\Node\Scalar\String_) { + if (!$codebase->classlikes->classExists($first_arg->value->value)) { + $context->phantom_classes[strtolower($first_arg->value->value)] = true; + } + } elseif ($first_arg->value instanceof PhpParser\Node\Expr\ClassConstFetch + && $first_arg->value->class instanceof PhpParser\Node\Name + && $first_arg->value->name instanceof PhpParser\Node\Identifier + && $first_arg->value->name->name === 'class' + ) { + $resolved_name = (string) $first_arg->value->class->getAttribute('resolvedName'); + + if (!$codebase->classlikes->classExists($resolved_name)) { + $context->phantom_classes[strtolower($resolved_name)] = true; + } + } + } + } elseif ($function_name->parts === ['interface_exists']) { + if ($first_arg) { + if ($first_arg->value instanceof PhpParser\Node\Scalar\String_) { + $context->phantom_classes[strtolower($first_arg->value->value)] = true; + } elseif ($first_arg->value instanceof PhpParser\Node\Expr\ClassConstFetch + && $first_arg->value->class instanceof PhpParser\Node\Name + && $first_arg->value->name instanceof PhpParser\Node\Identifier + && $first_arg->value->name->name === 'class' + ) { + $resolved_name = (string) $first_arg->value->class->getAttribute('resolvedName'); + + if (!$codebase->classlikes->interfaceExists($resolved_name)) { + $context->phantom_classes[strtolower($resolved_name)] = true; + } + } + } + } elseif ($function_name->parts === ['file_exists'] && $first_arg) { + $var_id = ExpressionIdentifier::getArrayVarId($first_arg->value, null); + + if ($var_id) { + $context->phantom_files[$var_id] = true; + } + } elseif ($function_name->parts === ['extension_loaded']) { + if ($first_arg + && $first_arg->value instanceof PhpParser\Node\Scalar\String_ + ) { + if (@extension_loaded($first_arg->value->value)) { + // do nothing + } else { + $context->check_classes = false; + } + } + } elseif ($function_name->parts === ['function_exists']) { + $context->check_functions = false; + } elseif ($function_name->parts === ['is_callable']) { + $context->check_methods = false; + $context->check_functions = false; + } elseif ($function_name->parts === ['defined']) { + $context->check_consts = false; + } elseif ($function_name->parts === ['extract']) { + $context->check_variables = false; + + foreach ($context->vars_in_scope as $var_id => $_) { + if ($var_id === '$this' || strpos($var_id, '[') || strpos($var_id, '>')) { + continue; + } + + $mixed_type = Type::getMixed(); + $mixed_type->parent_nodes = $context->vars_in_scope[$var_id]->parent_nodes; + + $context->vars_in_scope[$var_id] = $mixed_type; + $context->assigned_var_ids[$var_id] = true; + $context->possibly_assigned_var_ids[$var_id] = true; + } + } elseif ($function_name->parts === ['compact']) { + $all_args_string_literals = true; + $new_items = []; + + foreach ($stmt->args as $arg) { + $arg_type = $statements_analyzer->node_data->getType($arg->value); + + if (!$arg_type || !$arg_type->isSingleStringLiteral()) { + $all_args_string_literals = false; + break; + } + + $var_name = $arg_type->getSingleStringLiteral()->value; + + $new_items[] = new PhpParser\Node\Expr\ArrayItem( + new PhpParser\Node\Expr\Variable($var_name, $arg->value->getAttributes()), + new PhpParser\Node\Scalar\String_($var_name, $arg->value->getAttributes()), + false, + $arg->getAttributes() + ); + } + + if ($all_args_string_literals) { + $arr = new PhpParser\Node\Expr\Array_($new_items, $stmt->getAttributes()); + $old_node_data = $statements_analyzer->node_data; + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + ExpressionAnalyzer::analyze($statements_analyzer, $arr, $context); + + $arr_type = $statements_analyzer->node_data->getType($arr); + + $statements_analyzer->node_data = $old_node_data; + + if ($arr_type) { + $statements_analyzer->node_data->setType($stmt, $arr_type); + } + } + } elseif ($function_name->parts === ['func_get_args']) { + $source = $statements_analyzer->getSource(); + + if ($statements_analyzer->data_flow_graph + && $source instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + ) { + if ($statements_analyzer->data_flow_graph instanceof \Psalm\Internal\Codebase\VariableUseGraph) { + foreach ($source->param_nodes as $param_node) { + $statements_analyzer->data_flow_graph->addPath( + $param_node, + new DataFlowNode('variable-use', 'variable use', null), + 'variable-use' + ); + } + } + } + } elseif (strtolower($function_name->parts[0]) === 'var_dump' + || strtolower($function_name->parts[0]) === 'shell_exec') { + if (IssueBuffer::accepts( + new ForbiddenCode( + 'Unsafe ' . implode('', $function_name->parts), + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // continue + } + } elseif (isset($codebase->config->forbidden_functions[strtolower((string) $function_name)])) { + if (IssueBuffer::accepts( + new ForbiddenCode( + 'You have forbidden the use of ' . $function_name, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // continue + } + } elseif ($function_name->parts === ['define']) { + if ($first_arg) { + $fq_const_name = ConstFetchAnalyzer::getConstName( + $first_arg->value, + $statements_analyzer->node_data, + $codebase, + $statements_analyzer->getAliases() + ); + + if ($fq_const_name !== null && isset($stmt->args[1])) { + $second_arg = $stmt->args[1]; + $was_in_call = $context->inside_call; + $context->inside_call = true; + ExpressionAnalyzer::analyze($statements_analyzer, $second_arg->value, $context); + $context->inside_call = $was_in_call; + + ConstFetchAnalyzer::setConstType( + $statements_analyzer, + $fq_const_name, + $statements_analyzer->node_data->getType($second_arg->value) ?: Type::getMixed(), + $context + ); + } + } else { + $context->check_consts = false; + } + } elseif ($function_name->parts === ['constant']) { + if ($first_arg) { + $fq_const_name = ConstFetchAnalyzer::getConstName( + $first_arg->value, + $statements_analyzer->node_data, + $codebase, + $statements_analyzer->getAliases() + ); + + if ($fq_const_name !== null) { + $const_type = ConstFetchAnalyzer::getConstType( + $statements_analyzer, + $fq_const_name, + true, + $context + ); + + if ($const_type) { + $statements_analyzer->node_data->setType($real_stmt, $const_type); + } + } + } else { + $context->check_consts = false; + } + } elseif ($first_arg + && $function_id + && strpos($function_id, 'is_') === 0 + && $function_id !== 'is_a' + ) { + $stmt_assertions = $statements_analyzer->node_data->getAssertions($stmt); + + if ($stmt_assertions !== null) { + $assertions = $stmt_assertions; + } else { + $assertions = AssertionFinder::processFunctionCall( + $stmt, + $context->self, + $statements_analyzer, + $codebase, + $context->inside_negation + ); + } + + $changed_vars = []; + + $referenced_var_ids = array_map( + function (array $_) : bool { + return true; + }, + $assertions + ); + + if ($assertions) { + Reconciler::reconcileKeyedTypes( + $assertions, + $assertions, + $context->vars_in_scope, + $changed_vars, + $referenced_var_ids, + $statements_analyzer, + [], + $context->inside_loop, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ); + } + } elseif ($first_arg && $function_id === 'strtolower') { + $first_arg_type = $statements_analyzer->node_data->getType($first_arg->value); + + if ($first_arg_type + && UnionTypeComparator::isContainedBy( + $codebase, + $first_arg_type, + new Type\Union([new Type\Atomic\TLowercaseString()]) + ) + ) { + if ($first_arg_type->from_docblock) { + if (IssueBuffer::accepts( + new \Psalm\Issue\RedundantConditionGivenDocblockType( + 'The call to strtolower is unnecessary given the docblock type', + new CodeLocation($statements_analyzer, $function_name), + null + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new \Psalm\Issue\RedundantCondition( + 'The call to strtolower is unnecessary', + new CodeLocation($statements_analyzer, $function_name), + null + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicCallContext.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicCallContext.php new file mode 100644 index 0000000000000000000000000000000000000000..77af4b4fd9a729bade607cd51746410cda49f72d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicCallContext.php @@ -0,0 +1,26 @@ + */ + public $args; + + /** @var NodeDataProvider */ + public $node_data; + + /** @param list $args */ + public function __construct(MethodIdentifier $method_id, array $args, NodeDataProvider $node_data) + { + $this->method_id = $method_id; + $this->args = $args; + $this->node_data = $node_data; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php new file mode 100644 index 0000000000000000000000000000000000000000..76d5969b8f461a3963b00478fb7070e87bc62837 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php @@ -0,0 +1,82 @@ + + */ + public $invalid_method_call_types = []; + + /** + * @var array + */ + public $existent_method_ids = []; + + /** + * @var array + */ + public $non_existent_class_method_ids = []; + + /** + * @var array + */ + public $non_existent_interface_method_ids = []; + + /** + * @var array + */ + public $non_existent_magic_method_ids = []; + + /** + * @var bool + */ + public $check_visibility = true; + + /** + * @var bool + */ + public $too_many_arguments = true; + + /** + * @var list<\Psalm\Internal\MethodIdentifier> + */ + public $too_many_arguments_method_ids = []; + + /** + * @var bool + */ + public $too_few_arguments = false; + + /** + * @var list<\Psalm\Internal\MethodIdentifier> + */ + public $too_few_arguments_method_ids = []; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..8b82589807e8c7a43ef7ed3b0cfaa76cbec7404b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php @@ -0,0 +1,1280 @@ +config; + + if ($lhs_type_part instanceof Type\Atomic\TTemplateParam + && !$lhs_type_part->as->isMixed() + ) { + $extra_types = $lhs_type_part->extra_types; + + $lhs_type_part = array_values( + $lhs_type_part->as->getAtomicTypes() + )[0]; + + $lhs_type_part->from_docblock = true; + + if ($lhs_type_part instanceof TNamedObject) { + $lhs_type_part->extra_types = $extra_types; + } elseif ($lhs_type_part instanceof Type\Atomic\TObject && $extra_types) { + $lhs_type_part = array_shift($extra_types); + if ($extra_types) { + $lhs_type_part->extra_types = $extra_types; + } + } + + $result->has_mixed_method_call = true; + } + + $source = $statements_analyzer->getSource(); + + if (!$lhs_type_part instanceof TNamedObject) { + self::handleInvalidClass( + $statements_analyzer, + $codebase, + $stmt, + $lhs_type_part, + $lhs_var_id, + $context, + $is_intersection, + $result + ); + + return; + } + + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } + + $result->has_valid_method_call_type = true; + + $fq_class_name = $lhs_type_part->value; + + $is_mock = ExpressionAnalyzer::isMock($fq_class_name); + + $result->has_mock = $result->has_mock || $is_mock; + + if ($fq_class_name === 'static') { + $fq_class_name = (string) $context->self; + } + + if ($is_mock || + $context->isPhantomClass($fq_class_name) + ) { + $result->return_type = Type::getMixed(); + + ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ); + + return; + } + + if ($lhs_var_id === '$this') { + $does_class_exist = true; + } else { + $does_class_exist = ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $fq_class_name, + new CodeLocation($source, $stmt->var), + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues(), + true, + false, + true, + $lhs_type_part->from_docblock + ); + } + + if (!$does_class_exist) { + return; + } + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + $result->check_visibility = $result->check_visibility && !$class_storage->override_method_visibility; + + $intersection_types = $lhs_type_part->getIntersectionTypes(); + + $all_intersection_return_type = null; + $all_intersection_existent_method_ids = []; + + if ($intersection_types) { + foreach ($intersection_types as $intersection_type) { + $intersection_result = clone $result; + + /** @var ?Type\Union */ + $intersection_result->return_type = null; + + self::analyze( + $statements_analyzer, + $stmt, + $codebase, + $context, + $intersection_type, + $lhs_type_part, + true, + $lhs_var_id, + $intersection_result + ); + + $result->returns_by_ref = $intersection_result->returns_by_ref; + $result->has_mock = $intersection_result->has_mock; + $result->has_valid_method_call_type = $intersection_result->has_valid_method_call_type; + $result->has_mixed_method_call = $intersection_result->has_mixed_method_call; + $result->invalid_method_call_types = $intersection_result->invalid_method_call_types; + $result->check_visibility = $intersection_result->check_visibility; + $result->too_many_arguments = $intersection_result->too_many_arguments; + + $all_intersection_existent_method_ids = array_merge( + $all_intersection_existent_method_ids, + $intersection_result->existent_method_ids + ); + + if ($intersection_result->return_type) { + if (!$all_intersection_return_type || $all_intersection_return_type->isMixed()) { + $all_intersection_return_type = $intersection_result->return_type; + } else { + $all_intersection_return_type = Type::intersectUnionTypes( + $all_intersection_return_type, + $intersection_result->return_type, + $codebase + ) ?: Type::getMixed(); + } + } + } + } + + if (!$stmt->name instanceof PhpParser\Node\Identifier) { + if (!$context->ignore_variable_method) { + $codebase->analyzer->addMixedMemberName( + strtolower($fq_class_name) . '::', + $context->calling_method_id ?: $statements_analyzer->getFileName() + ); + } + + ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ); + + $result->return_type = Type::getMixed(); + return; + } + + $method_name_lc = strtolower($stmt->name->name); + + $method_id = new MethodIdentifier($fq_class_name, $method_name_lc); + $cased_method_id = $fq_class_name . '::' . $stmt->name->name; + + $intersection_method_id = $intersection_types + ? '(' . $lhs_type_part . ')' . '::' . $stmt->name->name + : null; + + $args = $stmt->args; + + $old_node_data = null; + + $naive_method_id = $method_id; + + $naive_method_exists = $codebase->methods->methodExists( + $method_id, + $context->calling_method_id, + $codebase->collect_locations + ? new CodeLocation($source, $stmt->name) + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $statements_analyzer + : null, + $statements_analyzer->getFilePath(), + false + ); + + if ($naive_method_exists && $fq_class_name === 'Closure' && $method_name_lc === '__invoke') { + $old_node_data = $statements_analyzer->node_data; + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $fake_function_call = new PhpParser\Node\Expr\FuncCall( + $stmt->var, + $stmt->args, + $stmt->getAttributes() + ); + + FunctionCallAnalyzer::analyze( + $statements_analyzer, + $fake_function_call, + $context + ); + + $function_return = $statements_analyzer->node_data->getType($fake_function_call) ?: Type::getMixed(); + $statements_analyzer->node_data = $old_node_data; + + if (!$result->return_type) { + $result->return_type = $function_return; + } else { + $result->return_type = Type::combineUnionTypes($function_return, $result->return_type); + } + + return; + } + + $fake_method_exists = false; + + if (!$naive_method_exists + && $codebase->methods->existence_provider->has($fq_class_name) + ) { + $method_exists = $codebase->methods->existence_provider->doesMethodExist( + $fq_class_name, + $method_id->method_name, + $source, + null + ); + + if ($method_exists) { + $fake_method_exists = true; + } + } + + if (!$naive_method_exists) { + [$lhs_type_part, $class_storage, $naive_method_exists, $method_id, $fq_class_name] + = self::handleMixins( + $class_storage, + $lhs_type_part, + $method_name_lc, + $codebase, + $context, + $method_id, + $source, + $stmt, + $statements_analyzer, + $fq_class_name, + $lhs_var_id + ); + } + + if (($fake_method_exists + && $codebase->methods->methodExists(new MethodIdentifier($fq_class_name, '__call'))) + || !$naive_method_exists + || !MethodAnalyzer::isMethodVisible( + $method_id, + $context, + $statements_analyzer->getSource() + ) + ) { + $interface_has_method = false; + + if ($class_storage->abstract && $class_storage->class_implements) { + foreach ($class_storage->class_implements as $interface_fqcln_lc => $_) { + $interface_storage = $codebase->classlike_storage_provider->get($interface_fqcln_lc); + + if (isset($interface_storage->methods[$method_name_lc])) { + $interface_has_method = true; + $fq_class_name = $interface_storage->name; + $method_id = new MethodIdentifier( + $fq_class_name, + $method_name_lc + ); + break; + } + } + } + + if (!$interface_has_method + && $codebase->methods->methodExists( + new MethodIdentifier($fq_class_name, '__call'), + $context->calling_method_id, + $codebase->collect_locations + ? new CodeLocation($source, $stmt->name) + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $statements_analyzer + : null, + $statements_analyzer->getFilePath() + ) + ) { + $new_call_context = MissingMethodCallHandler::handleMagicMethod( + $statements_analyzer, + $codebase, + $stmt, + $method_id, + $class_storage, + $context, + $config, + $all_intersection_return_type, + $result + ); + + if ($new_call_context) { + if ($method_id === $new_call_context->method_id) { + return; + } + + $method_id = $new_call_context->method_id; + $args = $new_call_context->args; + $old_node_data = $statements_analyzer->node_data; + } else { + return; + } + } + } + + $source_source = $statements_analyzer->getSource(); + + /** + * @var \Psalm\Internal\Analyzer\ClassLikeAnalyzer|null + */ + $classlike_source = $source_source->getSource(); + $classlike_source_fqcln = $classlike_source ? $classlike_source->getFQCLN() : null; + + if ($lhs_var_id === '$this' + && $context->self + && $classlike_source_fqcln + && $fq_class_name !== $context->self + && $codebase->methods->methodExists( + new MethodIdentifier($context->self, $method_name_lc) + ) + ) { + $method_id = new MethodIdentifier($context->self, $method_name_lc); + $cased_method_id = $context->self . '::' . $stmt->name->name; + $fq_class_name = $context->self; + } + + $is_interface = false; + + if ($codebase->interfaceExists($fq_class_name)) { + $is_interface = true; + } + + $source_method_id = $source instanceof FunctionLikeAnalyzer + ? $source->getId() + : null; + + $corrected_method_exists = ($naive_method_exists && $method_id === $naive_method_id) + || ($method_id !== $naive_method_id + && $codebase->methods->methodExists( + $method_id, + $context->calling_method_id, + $codebase->collect_locations && $method_id !== $source_method_id + ? new CodeLocation($source, $stmt->name) + : null + )); + + if (!$corrected_method_exists + || ($config->use_phpdoc_method_without_magic_or_parent + && isset($class_storage->pseudo_methods[$method_name_lc])) + ) { + MissingMethodCallHandler::handleMissingOrMagicMethod( + $statements_analyzer, + $codebase, + $stmt, + $method_id, + $is_interface, + $context, + $config, + $all_intersection_return_type, + $result + ); + + if ($all_intersection_return_type && $all_intersection_existent_method_ids) { + $result->existent_method_ids = array_merge( + $result->existent_method_ids, + $all_intersection_existent_method_ids + ); + + if (!$result->return_type) { + $result->return_type = $all_intersection_return_type; + } else { + $result->return_type = Type::combineUnionTypes($all_intersection_return_type, $result->return_type); + } + + return; + } + + if ((!$is_interface && !$config->use_phpdoc_method_without_magic_or_parent) + || !isset($class_storage->pseudo_methods[$method_name_lc]) + ) { + if ($is_interface) { + $result->non_existent_interface_method_ids[] = $intersection_method_id ?: $cased_method_id; + } else { + $result->non_existent_class_method_ids[] = $intersection_method_id ?: $cased_method_id; + } + } + + return; + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->name, + $method_id . '()' + ); + } + + if ($context->collect_initializations && $context->calling_method_id) { + [$calling_method_class] = explode('::', $context->calling_method_id); + $codebase->file_reference_provider->addMethodReferenceToClassMember( + $calling_method_class . '::__construct', + strtolower((string) $method_id) + ); + } + + $result->existent_method_ids[] = $method_id; + + if ($stmt->var instanceof PhpParser\Node\Expr\Variable + && ($context->collect_initializations || $context->collect_mutations) + && $stmt->var->name === 'this' + && $source instanceof FunctionLikeAnalyzer + ) { + self::collectSpecialInformation($source, $stmt->name->name, $context); + } + + $fq_class_name = $codebase->classlikes->getUnAliasedName($fq_class_name); + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + $parent_source = $statements_analyzer->getSource(); + + $class_template_params = ClassTemplateParamCollector::collect( + $codebase, + $codebase->methods->getClassLikeStorageForMethod($method_id), + $class_storage, + $method_name_lc, + $lhs_type_part, + $lhs_var_id + ); + + if ($lhs_var_id === '$this' && $parent_source instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) { + $grandparent_source = $parent_source->getSource(); + + if ($grandparent_source instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) { + $fq_trait_name = $grandparent_source->getFQCLN(); + + $fq_trait_name_lc = strtolower($fq_trait_name); + + $trait_storage = $codebase->classlike_storage_provider->get($fq_trait_name_lc); + + if (isset($trait_storage->methods[$method_name_lc])) { + $trait_method_id = new MethodIdentifier($trait_storage->name, $method_name_lc); + + $class_template_params = ClassTemplateParamCollector::collect( + $codebase, + $codebase->methods->getClassLikeStorageForMethod($trait_method_id), + $class_storage, + $method_name_lc, + $lhs_type_part, + $lhs_var_id + ); + } + } + } + + $template_result = new \Psalm\Internal\Type\TemplateResult([], $class_template_params ?: []); + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + ArgumentMapPopulator::recordArgumentPositions( + $statements_analyzer, + $stmt, + $codebase, + (string) $method_id + ); + } + + if (self::checkMethodArgs( + $method_id, + $args, + $template_result, + $context, + new CodeLocation($source, $stmt->name), + $statements_analyzer + ) === false) { + return; + } + + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + $can_memoize = false; + + $return_type_candidate = MethodCallReturnTypeFetcher::fetch( + $statements_analyzer, + $codebase, + $stmt, + $context, + $method_id, + $declaring_method_id, + $naive_method_id, + $cased_method_id, + $lhs_type_part, + $static_type, + $args, + $result, + $template_result + ); + + $in_call_map = InternalCallMapHandler::inCallMap((string) ($declaring_method_id ?: $method_id)); + + if (!$in_call_map) { + $name_code_location = new CodeLocation($statements_analyzer, $stmt->name); + + if ($result->check_visibility) { + if (MethodVisibilityAnalyzer::analyze( + $method_id, + $context, + $statements_analyzer->getSource(), + $name_code_location, + $statements_analyzer->getSuppressedIssues() + ) === false) { + self::updateResultReturnType( + $result, + $return_type_candidate, + $all_intersection_return_type, + $method_name_lc, + $codebase + ); + + return; + } + } + + MethodCallProhibitionAnalyzer::analyze( + $codebase, + $context, + $method_id, + $statements_analyzer->getNamespace(), + $name_code_location, + $statements_analyzer->getSuppressedIssues() + ); + + $getter_return_type = self::getMagicGetterOrSetterProperty( + $statements_analyzer, + $stmt, + $context, + $fq_class_name + ); + + if ($getter_return_type) { + $return_type_candidate = $getter_return_type; + } + } + + try { + $method_storage = $codebase->methods->getStorage($declaring_method_id ?: $method_id); + } catch (\UnexpectedValueException $e) { + $method_storage = null; + } + + if ($method_storage) { + if (!$context->collect_mutations && !$context->collect_initializations) { + $can_memoize = MethodCallPurityAnalyzer::analyze( + $statements_analyzer, + $codebase, + $stmt, + $lhs_var_id, + $cased_method_id, + $method_id, + $method_storage, + $class_storage, + $context, + $config + ); + } + + $has_packed_arg = false; + foreach ($args as $arg) { + $has_packed_arg = $has_packed_arg || $arg->unpack; + } + + if (!$has_packed_arg) { + $has_variadic_param = $method_storage->variadic; + + foreach ($method_storage->params as $param) { + $has_variadic_param = $has_variadic_param || $param->is_variadic; + } + + for ($i = count($args), $j = count($method_storage->params); $i < $j; ++$i) { + $param = $method_storage->params[$i]; + + if (!$param->is_optional + && !$param->is_variadic + && !$in_call_map + ) { + $result->too_few_arguments = true; + $result->too_few_arguments_method_ids[] = $declaring_method_id ?: $method_id; + } + } + + if ($has_variadic_param || count($method_storage->params) >= count($args) || $in_call_map) { + $result->too_many_arguments = false; + } else { + $result->too_many_arguments_method_ids[] = $declaring_method_id ?: $method_id; + } + } + + $class_template_params = $template_result->upper_bounds; + + if ($method_storage->assertions) { + self::applyAssertionsToContext( + $stmt->name, + ExpressionIdentifier::getArrayVarId($stmt->var, null, $statements_analyzer), + $method_storage->assertions, + $args, + $class_template_params, + $context, + $statements_analyzer + ); + } + + if ($method_storage->if_true_assertions) { + $statements_analyzer->node_data->setIfTrueAssertions( + $stmt, + array_map( + function (Assertion $assertion) use ( + $class_template_params, + $lhs_var_id + ) : Assertion { + return $assertion->getUntemplatedCopy( + $class_template_params ?: [], + $lhs_var_id + ); + }, + $method_storage->if_true_assertions + ) + ); + } + + if ($method_storage->if_false_assertions) { + $statements_analyzer->node_data->setIfFalseAssertions( + $stmt, + array_map( + function (Assertion $assertion) use ( + $class_template_params, + $lhs_var_id + ) : Assertion { + return $assertion->getUntemplatedCopy( + $class_template_params ?: [], + $lhs_var_id + ); + }, + $method_storage->if_false_assertions + ) + ); + } + } + + if ($old_node_data) { + $statements_analyzer->node_data = $old_node_data; + } + + if (!$args && $lhs_var_id) { + if ($config->memoize_method_calls || $can_memoize) { + $method_var_id = $lhs_var_id . '->' . $method_name_lc . '()'; + + if (isset($context->vars_in_scope[$method_var_id])) { + $return_type_candidate = clone $context->vars_in_scope[$method_var_id]; + + if ($can_memoize) { + /** @psalm-suppress UndefinedPropertyAssignment */ + $stmt->pure = true; + } + } else { + $context->vars_in_scope[$method_var_id] = $return_type_candidate; + } + } + } + + if ($codebase->methods_to_rename) { + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + foreach ($codebase->methods_to_rename as $original_method_id => $new_method_name) { + if ($declaring_method_id && (strtolower((string) $declaring_method_id)) === $original_method_id) { + $file_manipulations = [ + new \Psalm\FileManipulation( + (int) $stmt->name->getAttribute('startFilePos'), + (int) $stmt->name->getAttribute('endFilePos') + 1, + $new_method_name + ) + ]; + + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $statements_analyzer->getFilePath(), + $file_manipulations + ); + } + } + } + + if ($config->after_method_checks) { + $file_manipulations = []; + + $appearing_method_id = $codebase->methods->getAppearingMethodId($method_id); + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + if ($appearing_method_id !== null && $declaring_method_id !== null) { + foreach ($config->after_method_checks as $plugin_fq_class_name) { + $plugin_fq_class_name::afterMethodCallAnalysis( + $stmt, + (string) $method_id, + (string) $appearing_method_id, + (string) $declaring_method_id, + $context, + $statements_analyzer, + $codebase, + $file_manipulations, + $return_type_candidate + ); + } + } + + if ($file_manipulations) { + FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations); + } + } + + self::updateResultReturnType( + $result, + $return_type_candidate, + $all_intersection_return_type, + $method_name_lc, + $codebase + ); + } + + private static function updateResultReturnType( + AtomicMethodCallAnalysisResult $result, + ?Type\Union $return_type_candidate, + ?Type\Union $all_intersection_return_type, + string $method_name, + Codebase $codebase + ) : void { + if ($return_type_candidate) { + if ($all_intersection_return_type) { + $return_type_candidate = Type::intersectUnionTypes( + $all_intersection_return_type, + $return_type_candidate, + $codebase + ) ?: Type::getMixed(); + } + + if (!$result->return_type) { + $result->return_type = $return_type_candidate; + } else { + $result->return_type = Type::combineUnionTypes($return_type_candidate, $result->return_type); + } + } elseif ($all_intersection_return_type) { + if (!$result->return_type) { + $result->return_type = $all_intersection_return_type; + } else { + $result->return_type = Type::combineUnionTypes($all_intersection_return_type, $result->return_type); + } + } elseif ($method_name === '__tostring') { + $result->return_type = Type::getString(); + } else { + $result->return_type = Type::getMixed(); + } + } + + private static function handleInvalidClass( + StatementsAnalyzer $statements_analyzer, + Codebase $codebase, + PhpParser\Node\Expr\MethodCall $stmt, + Type\Atomic $lhs_type_part, + ?string $lhs_var_id, + Context $context, + bool $is_intersection, + AtomicMethodCallAnalysisResult $result + ) : void { + switch (get_class($lhs_type_part)) { + case Type\Atomic\TNull::class: + case Type\Atomic\TFalse::class: + // handled above + return; + + case Type\Atomic\TTemplateParam::class: + case Type\Atomic\TEmptyMixed::class: + case Type\Atomic\TEmpty::class: + case Type\Atomic\TMixed::class: + case Type\Atomic\TNonEmptyMixed::class: + case Type\Atomic\TObject::class: + case Type\Atomic\TObjectWithProperties::class: + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + $result->has_mixed_method_call = true; + + if ($lhs_type_part instanceof Type\Atomic\TObjectWithProperties + && $stmt->name instanceof PhpParser\Node\Identifier + && isset($lhs_type_part->methods[$stmt->name->name]) + ) { + $result->existent_method_ids[] = $lhs_type_part->methods[$stmt->name->name]; + } elseif (!$is_intersection) { + if ($stmt->name instanceof PhpParser\Node\Identifier) { + $codebase->analyzer->addMixedMemberName( + strtolower($stmt->name->name), + $context->calling_method_id ?: $statements_analyzer->getFileName() + ); + } + + if ($context->check_methods) { + $message = 'Cannot determine the type of the object' + . ' on the left hand side of this expression'; + + if ($lhs_var_id) { + $message = 'Cannot determine the type of ' . $lhs_var_id; + + if ($stmt->name instanceof PhpParser\Node\Identifier) { + $message .= ' when calling method ' . $stmt->name->name; + } + } + + if (IssueBuffer::accepts( + new MixedMethodCall( + $message, + new CodeLocation($statements_analyzer, $stmt->name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ) === false) { + return; + } + + $result->return_type = Type::getMixed(); + return; + + default: + $result->invalid_method_call_types[] = (string)$lhs_type_part; + return; + } + } + + /** + * Check properties accessed with magic getters and setters. + * If `@psalm-seal-properties` is set, they must be defined. + * If an `@property` annotation is specified, the setter must set something with the correct + * type. + */ + private static function getMagicGetterOrSetterProperty( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\MethodCall $stmt, + Context $context, + string $fq_class_name + ) : ?Type\Union { + if (!$stmt->name instanceof PhpParser\Node\Identifier) { + return null; + } + + $method_name = strtolower($stmt->name->name); + if (!in_array($method_name, ['__get', '__set'], true)) { + return null; + } + + $codebase = $statements_analyzer->getCodebase(); + + $first_arg_value = $stmt->args[0]->value; + if (!$first_arg_value instanceof PhpParser\Node\Scalar\String_) { + return null; + } + + $prop_name = $first_arg_value->value; + $property_id = $fq_class_name . '::$' . $prop_name; + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + $codebase->properties->propertyExists( + $property_id, + $method_name === '__get', + $statements_analyzer, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ); + + switch ($method_name) { + case '__set': + // If `@psalm-seal-properties` is set, the property must be defined with + // a `@property` annotation + if ($class_storage->sealed_properties + && !isset($class_storage->pseudo_property_set_types['$' . $prop_name]) + && IssueBuffer::accepts( + new UndefinedThisPropertyAssignment( + 'Instance property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + ) + ) { + // fall through + } + + // If a `@property` annotation is set, the type of the value passed to the + // magic setter must match the annotation. + $second_arg_type = $statements_analyzer->node_data->getType($stmt->args[1]->value); + + if (isset($class_storage->pseudo_property_set_types['$' . $prop_name]) && $second_arg_type) { + $pseudo_set_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $class_storage->pseudo_property_set_types['$' . $prop_name], + $fq_class_name, + new Type\Atomic\TNamedObject($fq_class_name), + $class_storage->parent_class + ); + + $union_comparison_results = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + $type_match_found = UnionTypeComparator::isContainedBy( + $codebase, + $second_arg_type, + $pseudo_set_type, + $second_arg_type->ignore_nullable_issues, + $second_arg_type->ignore_falsable_issues, + $union_comparison_results + ); + + if ($union_comparison_results->type_coerced) { + if ($union_comparison_results->type_coerced_from_mixed) { + if (IssueBuffer::accepts( + new MixedPropertyTypeCoercion( + $prop_name . ' expects \'' . $pseudo_set_type->getId() . '\', ' + . ' parent type `' . $second_arg_type . '` provided', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep soldiering on + } + } else { + if (IssueBuffer::accepts( + new PropertyTypeCoercion( + $prop_name . ' expects \'' . $pseudo_set_type->getId() . '\', ' + . ' parent type `' . $second_arg_type . '` provided', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep soldiering on + } + } + } + + if (!$type_match_found && !$union_comparison_results->type_coerced_from_mixed) { + if (UnionTypeComparator::canBeContainedBy( + $codebase, + $second_arg_type, + $pseudo_set_type + )) { + if (IssueBuffer::accepts( + new PossiblyInvalidPropertyAssignmentValue( + $prop_name . ' with declared type \'' + . $pseudo_set_type + . '\' cannot be assigned possibly different type \'' . $second_arg_type . '\'', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidPropertyAssignmentValue( + $prop_name . ' with declared type \'' + . $pseudo_set_type + . '\' cannot be assigned type \'' . $second_arg_type . '\'', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + break; + + case '__get': + // If `@psalm-seal-properties` is set, the property must be defined with + // a `@property` annotation + if ($class_storage->sealed_properties + && !isset($class_storage->pseudo_property_get_types['$' . $prop_name]) + && IssueBuffer::accepts( + new UndefinedThisPropertyFetch( + 'Instance property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + ) + ) { + // fall through + } + + if (isset($class_storage->pseudo_property_get_types['$' . $prop_name])) { + return clone $class_storage->pseudo_property_get_types['$' . $prop_name]; + } + + break; + } + + return null; + } + + /** + * @param lowercase-string $method_name_lc + * @return array{Type\Atomic, \Psalm\Storage\ClassLikeStorage, bool, MethodIdentifier, string} + */ + private static function handleMixins( + \Psalm\Storage\ClassLikeStorage $class_storage, + Type\Atomic $lhs_type_part, + string $method_name_lc, + Codebase $codebase, + Context $context, + MethodIdentifier $method_id, + \Psalm\StatementsSource $source, + PhpParser\Node\Expr\MethodCall $stmt, + StatementsAnalyzer $statements_analyzer, + string $fq_class_name, + ?string $lhs_var_id + ) { + $naive_method_exists = false; + + if ($class_storage->templatedMixins + && $lhs_type_part instanceof Type\Atomic\TGenericObject + && $class_storage->template_types + ) { + $template_type_keys = \array_keys($class_storage->template_types); + + foreach ($class_storage->templatedMixins as $mixin) { + $param_position = \array_search( + $mixin->param_name, + $template_type_keys + ); + + if ($param_position !== false + && isset($lhs_type_part->type_params[$param_position]) + ) { + $current_type_param = $lhs_type_part->type_params[$param_position]; + if ($current_type_param->isSingle()) { + $lhs_type_part_new = array_values( + $current_type_param->getAtomicTypes() + )[0]; + + if ($lhs_type_part_new instanceof Type\Atomic\TNamedObject) { + $new_method_id = new MethodIdentifier( + $lhs_type_part_new->value, + $method_name_lc + ); + + $mixin_class_storage = $codebase->classlike_storage_provider->get( + $lhs_type_part_new->value + ); + + if ($codebase->methods->methodExists( + $new_method_id, + $context->calling_method_id, + $codebase->collect_locations + ? new CodeLocation($source, $stmt->name) + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $statements_analyzer + : null, + $statements_analyzer->getFilePath() + )) { + $lhs_type_part = clone $lhs_type_part_new; + $class_storage = $mixin_class_storage; + + $naive_method_exists = true; + $method_id = $new_method_id; + } elseif (isset($mixin_class_storage->pseudo_methods[$method_name_lc])) { + $lhs_type_part = clone $lhs_type_part_new; + $class_storage = $mixin_class_storage; + $method_id = $new_method_id; + } + } + } + } + } + } elseif ($class_storage->mixin_declaring_fqcln + && $class_storage->namedMixins + ) { + foreach ($class_storage->namedMixins as $mixin) { + if (!$class_storage->mixin_declaring_fqcln) { + continue; + } + + $new_method_id = new MethodIdentifier( + $mixin->value, + $method_name_lc + ); + + if ($codebase->methods->methodExists( + $new_method_id, + $context->calling_method_id, + $codebase->collect_locations + ? new CodeLocation($source, $stmt->name) + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $statements_analyzer + : null, + $statements_analyzer->getFilePath() + )) { + $mixin_declaring_class_storage = $codebase->classlike_storage_provider->get( + $class_storage->mixin_declaring_fqcln + ); + + $mixin_class_template_params = ClassTemplateParamCollector::collect( + $codebase, + $mixin_declaring_class_storage, + $codebase->classlike_storage_provider->get($fq_class_name), + null, + $lhs_type_part, + $lhs_var_id + ); + + $lhs_type_part = clone $mixin; + + $lhs_type_part->replaceTemplateTypesWithArgTypes( + new \Psalm\Internal\Type\TemplateResult([], $mixin_class_template_params ?: []), + $codebase + ); + + $lhs_type_expanded = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + new Type\Union([$lhs_type_part]), + $mixin_declaring_class_storage->name, + $fq_class_name, + $class_storage->parent_class, + true, + false, + $class_storage->final + ); + + $new_lhs_type_part = array_values($lhs_type_expanded->getAtomicTypes())[0]; + + if ($new_lhs_type_part instanceof Type\Atomic\TNamedObject) { + $lhs_type_part = $new_lhs_type_part; + } + + $mixin_class_storage = $codebase->classlike_storage_provider->get($mixin->value); + + $fq_class_name = $mixin_class_storage->name; + $mixin_class_storage->mixin_declaring_fqcln = $class_storage->mixin_declaring_fqcln; + $class_storage = $mixin_class_storage; + $naive_method_exists = true; + $method_id = $new_method_id; + } + } + } + + return [ + $lhs_type_part, + $class_storage, + $naive_method_exists, + $method_id, + $fq_class_name + ]; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallProhibitionAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallProhibitionAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..dadd5295c73650910a45499b09a0b2ceb903dfe1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallProhibitionAnalyzer.php @@ -0,0 +1,71 @@ +methods; + + $method_id = $codebase_methods->getDeclaringMethodId($method_id); + + if ($method_id === null) { + return null; + } + + $storage = $codebase_methods->getStorage($method_id); + + if ($storage->deprecated) { + if (IssueBuffer::accepts( + new DeprecatedMethod( + 'The method ' . $codebase_methods->getCasedMethodId($method_id) . + ' has been marked as deprecated', + $code_location, + (string) $method_id + ), + $suppressed_issues + )) { + // continue + } + } + + if (!$context->collect_initializations + && !$context->collect_mutations + ) { + if (!NamespaceAnalyzer::isWithin($namespace ?: '', $storage->internal)) { + if (IssueBuffer::accepts( + new InternalMethod( + 'The method ' . $codebase_methods->getCasedMethodId($method_id) + . ' is internal to ' . $storage->internal + . ' but called from ' . $context->self, + $code_location, + (string) $method_id + ), + $suppressed_issues + )) { + // fall through + } + } + } + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..fabb08c30e553b2ea7e80cd2b07e2dacbd06c403 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php @@ -0,0 +1,153 @@ +external_mutation_free + && $statements_analyzer->node_data->isPureCompatible($stmt->var); + + if ($context->pure + && !$method_storage->mutation_free + && !$method_pure_compatible + ) { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call a non-mutation-free method ' + . $cased_method_id . ' from a pure context', + new CodeLocation($statements_analyzer, $stmt->name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($context->mutation_free + && !$method_storage->mutation_free + && !$method_pure_compatible + ) { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call an possibly-mutating method ' + . $cased_method_id . ' from a mutation-free context', + new CodeLocation($statements_analyzer, $stmt->name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($context->external_mutation_free + && !$method_storage->mutation_free + && $method_id->fq_class_name !== $context->self + && !$method_pure_compatible + ) { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call an possibly-mutating method ' + . $cased_method_id . ' from a mutation-free context', + new CodeLocation($statements_analyzer, $stmt->name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif (($method_storage->mutation_free + || ($method_storage->external_mutation_free + && (isset($stmt->var->external_mutation_free) || isset($stmt->var->pure)))) + && !$context->inside_unset + ) { + if ($method_storage->mutation_free + && (!$method_storage->mutation_free_inferred + || $method_storage->final) + ) { + if ($context->inside_conditional + && !$method_storage->assertions + && !$method_storage->if_true_assertions + ) { + /** @psalm-suppress UndefinedPropertyAssignment */ + $stmt->pure = true; + } + + $can_memoize = true; + } + + if ($codebase->find_unused_variables + && !$context->inside_conditional + && !$context->inside_use + ) { + if (!$context->inside_assignment && !$context->inside_call) { + if (IssueBuffer::accepts( + new \Psalm\Issue\UnusedMethodCall( + 'The call to ' . $cased_method_id . ' is not used', + new CodeLocation($statements_analyzer, $stmt->name), + (string) $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif (!$method_storage->mutation_free_inferred) { + /** @psalm-suppress UndefinedPropertyAssignment */ + $stmt->pure = true; + } + } + } + + if ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + && !$method_storage->mutation_free + && !$method_pure_compatible + ) { + if (!$method_storage->mutation_free) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + } + + $statements_analyzer->getSource()->inferred_impure = true; + } + + if (!$config->remember_property_assignments_after_call + && !$method_storage->mutation_free + && !$method_pure_compatible + ) { + $context->removeAllObjectVars(); + } elseif ($method_storage->this_property_mutations) { + foreach ($method_storage->this_property_mutations as $name => $_) { + $mutation_var_id = $lhs_var_id . '->' . $name; + + $this_property_didnt_exist = $lhs_var_id === '$this' + && isset($context->vars_in_scope[$mutation_var_id]) + && !isset($class_storage->declaring_property_ids[$name]); + + $context->remove($mutation_var_id); + + if ($this_property_didnt_exist) { + $context->vars_in_scope[$mutation_var_id] = Type::getMixed(); + } + } + } + + return $can_memoize; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php new file mode 100644 index 0000000000000000000000000000000000000000..337848d00a5d5031691a46f6d23ab0add5ff3f4e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php @@ -0,0 +1,353 @@ + $args + */ + public static function fetch( + StatementsAnalyzer $statements_analyzer, + Codebase $codebase, + PhpParser\Node\Expr\MethodCall $stmt, + Context $context, + MethodIdentifier $method_id, + ?MethodIdentifier $declaring_method_id, + MethodIdentifier $premixin_method_id, + string $cased_method_id, + Type\Atomic $lhs_type_part, + ?Type\Atomic $static_type, + array $args, + AtomicMethodCallAnalysisResult $result, + TemplateResult $template_result + ) : Type\Union { + $call_map_id = $declaring_method_id ?: $method_id; + + $fq_class_name = $method_id->fq_class_name; + $method_name = $method_id->method_name; + + if ($codebase->methods->return_type_provider->has($premixin_method_id->fq_class_name)) { + $return_type_candidate = $codebase->methods->return_type_provider->getReturnType( + $statements_analyzer, + $premixin_method_id->fq_class_name, + $premixin_method_id->method_name, + $stmt->args, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt->name), + $lhs_type_part instanceof TGenericObject ? $lhs_type_part->type_params : null + ); + + if ($return_type_candidate) { + return $return_type_candidate; + } + } + + if ($declaring_method_id && $declaring_method_id !== $method_id) { + $declaring_fq_class_name = $declaring_method_id->fq_class_name; + $declaring_method_name = $declaring_method_id->method_name; + + if ($codebase->methods->return_type_provider->has($declaring_fq_class_name)) { + $return_type_candidate = $codebase->methods->return_type_provider->getReturnType( + $statements_analyzer, + $declaring_fq_class_name, + $declaring_method_name, + $stmt->args, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt->name), + $lhs_type_part instanceof TGenericObject ? $lhs_type_part->type_params : null, + $fq_class_name, + $method_name + ); + + if ($return_type_candidate) { + return $return_type_candidate; + } + } + } + + $class_storage = $codebase->methods->getClassLikeStorageForMethod($method_id); + + if (InternalCallMapHandler::inCallMap((string) $call_map_id)) { + if (($template_result->upper_bounds || $class_storage->stubbed) + && ($method_storage = ($class_storage->methods[$method_id->method_name] ?? null)) + && $method_storage->return_type + ) { + $return_type_candidate = clone $method_storage->return_type; + + $return_type_candidate = self::replaceTemplateTypes( + $return_type_candidate, + $template_result, + $method_id, + \count($stmt->args), + $codebase + ); + } else { + $callmap_callables = InternalCallMapHandler::getCallablesFromCallMap((string) $call_map_id); + + if (!$callmap_callables || $callmap_callables[0]->return_type === null) { + throw new \UnexpectedValueException('Shouldn’t get here'); + } + + $return_type_candidate = $callmap_callables[0]->return_type; + } + + if (($call_map_id->fq_class_name === 'Iterator' + || $call_map_id->fq_class_name === 'IteratorIterator' + || $call_map_id->fq_class_name === 'Generator' + || $call_map_id->fq_class_name === 'SplDoublyLinkedList' + || $call_map_id->fq_class_name === 'SplObjectStorage' + ) + && $method_name === 'current' + ) { + if ($stmt->getAttributes()) { + $return_type_candidate->addType(new Type\Atomic\TNull()); + $return_type_candidate->ignore_nullable_issues = true; + } + } + + if ($return_type_candidate->isFalsable()) { + $return_type_candidate->ignore_falsable_issues = true; + } + + $return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $return_type_candidate, + $fq_class_name, + $static_type, + $class_storage->parent_class + ); + } else { + $self_fq_class_name = $fq_class_name; + + $return_type_candidate = $codebase->methods->getMethodReturnType( + $method_id, + $self_fq_class_name, + $statements_analyzer, + $args + ); + + if ($return_type_candidate) { + $return_type_candidate = clone $return_type_candidate; + + $return_type_candidate = self::replaceTemplateTypes( + $return_type_candidate, + $template_result, + $method_id, + \count($stmt->args), + $codebase + ); + + $return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $return_type_candidate, + $self_fq_class_name, + $static_type, + $class_storage->parent_class, + true, + false, + $static_type instanceof Type\Atomic\TNamedObject + && $codebase->classlike_storage_provider->get($static_type->value)->final + ); + + $return_type_location = $codebase->methods->getMethodReturnTypeLocation( + $method_id, + $secondary_return_type_location + ); + + if ($secondary_return_type_location) { + $return_type_location = $secondary_return_type_location; + } + + $config = \Psalm\Config::getInstance(); + + // only check the type locally if it's defined externally + if ($return_type_location && !$config->isInProjectDirs($return_type_location->file_path)) { + $return_type_candidate->check( + $statements_analyzer, + new CodeLocation($statements_analyzer, $stmt), + $statements_analyzer->getSuppressedIssues(), + $context->phantom_classes, + true, + false, + false, + $context->calling_method_id + ); + } + } else { + $result->returns_by_ref = + $result->returns_by_ref + || $codebase->methods->getMethodReturnsByRef($method_id); + } + } + + if (!$return_type_candidate) { + $return_type_candidate = $method_name === '__tostring' ? Type::getString() : Type::getMixed(); + } + + self::taintMethodCallResult( + $statements_analyzer, + $return_type_candidate, + $stmt->name, + $stmt->var, + $method_id, + $declaring_method_id, + $cased_method_id, + $context + ); + + return $return_type_candidate; + } + + public static function taintMethodCallResult( + StatementsAnalyzer $statements_analyzer, + Type\Union $return_type_candidate, + PhpParser\Node $name_expr, + PhpParser\Node\Expr $var_expr, + MethodIdentifier $method_id, + ?MethodIdentifier $declaring_method_id, + string $cased_method_id, + Context $context + ) : void { + $codebase = $statements_analyzer->getCodebase(); + + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph + && $declaring_method_id + && !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + $method_storage = $codebase->methods->getStorage( + $declaring_method_id + ); + + $node_location = new CodeLocation($statements_analyzer, $name_expr); + + $method_call_node = DataFlowNode::getForMethodReturn( + (string) $method_id, + $cased_method_id, + $method_storage->signature_return_type_location ?: $method_storage->location, + $method_storage->specialize_call ? $node_location : null + ); + + $statements_analyzer->data_flow_graph->addNode($method_call_node); + + $return_type_candidate->parent_nodes = [ + $method_call_node->id => $method_call_node + ]; + + if ($method_storage->specialize_call) { + $var_id = ExpressionIdentifier::getArrayVarId( + $var_expr, + null, + $statements_analyzer + ); + + if ($var_id && isset($context->vars_in_scope[$var_id])) { + $var_node = DataFlowNode::getForAssignment( + $var_id, + new CodeLocation($statements_analyzer, $var_expr) + ); + + $statements_analyzer->data_flow_graph->addNode($var_node); + + $statements_analyzer->data_flow_graph->addPath( + $method_call_node, + $var_node, + 'method-call-' . $method_id->method_name + ); + + $stmt_var_type = clone $context->vars_in_scope[$var_id]; + + if ($context->vars_in_scope[$var_id]->parent_nodes) { + foreach ($context->vars_in_scope[$var_id]->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath($parent_node, $var_node, '='); + } + } + + $stmt_var_type->parent_nodes = [$var_node->id => $var_node]; + + $context->vars_in_scope[$var_id] = $stmt_var_type; + } + } + + if ($method_storage->taint_source_types) { + $method_node = TaintSource::getForMethodReturn( + (string) $method_id, + $cased_method_id, + $method_storage->signature_return_type_location ?: $method_storage->location + ); + + $method_node->taints = $method_storage->taint_source_types; + + $statements_analyzer->data_flow_graph->addSource($method_node); + } + } + } + + private static function replaceTemplateTypes( + Type\Union $return_type_candidate, + TemplateResult $template_result, + MethodIdentifier $method_id, + int $arg_count, + Codebase $codebase + ) : Type\Union { + if ($template_result->template_types) { + $bindable_template_types = $return_type_candidate->getTemplateTypes(); + + foreach ($bindable_template_types as $template_type) { + if ($template_type->defining_class !== $method_id->fq_class_name + && !isset( + $template_result->upper_bounds + [$template_type->param_name] + [$template_type->defining_class] + ) + ) { + if ($template_type->param_name === 'TFunctionArgCount') { + $template_result->upper_bounds[$template_type->param_name] = [ + 'fn-' . strtolower((string) $method_id) => [ + Type::getInt(false, $arg_count), + 0 + ] + ]; + } else { + $template_result->upper_bounds[$template_type->param_name] = [ + ($template_type->defining_class) => [Type::getEmpty(), 0] + ]; + } + } + } + } + + if ($template_result->upper_bounds) { + $return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $return_type_candidate, + null, + null, + null + ); + + $return_type_candidate->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + } + + return $return_type_candidate; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodVisibilityAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodVisibilityAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..43079ccd4878c35cce0da62fd222d4dddbe99ee6 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodVisibilityAnalyzer.php @@ -0,0 +1,167 @@ +getCodebase(); + $codebase_methods = $codebase->methods; + $codebase_classlikes = $codebase->classlikes; + + $fq_classlike_name = $method_id->fq_class_name; + $method_name = $method_id->method_name; + + if ($codebase_methods->visibility_provider->has($fq_classlike_name)) { + $method_visible = $codebase_methods->visibility_provider->isMethodVisible( + $source, + $fq_classlike_name, + $method_name, + $context, + $code_location + ); + + if ($method_visible === false) { + if (IssueBuffer::accepts( + new InaccessibleMethod( + 'Cannot access method ' . $codebase_methods->getCasedMethodId($method_id) . + ' from context ' . $context->self, + $code_location + ), + $suppressed_issues + )) { + return false; + } + } elseif ($method_visible === true) { + return false; + } + } + + $declaring_method_id = $codebase_methods->getDeclaringMethodId($method_id); + + if (!$declaring_method_id) { + if ($method_name === '__construct' + || ($method_id->fq_class_name === 'Closure' + && ($method_id->method_name === 'fromcallable' + || $method_id->method_name === '__invoke')) + ) { + return null; + } + + throw new \UnexpectedValueException('$declaring_method_id not expected to be null here'); + } + + $appearing_method_id = $codebase_methods->getAppearingMethodId($method_id); + + $appearing_method_class = null; + $appearing_class_storage = null; + $appearing_method_name = null; + + if ($appearing_method_id) { + $appearing_method_class = $appearing_method_id->fq_class_name; + $appearing_method_name = $appearing_method_id->method_name; + + // if the calling class is the same, we know the method exists, so it must be visible + if ($appearing_method_class === $context->self) { + return null; + } + + $appearing_class_storage = $codebase->classlike_storage_provider->get($appearing_method_class); + } + + $declaring_method_class = $declaring_method_id->fq_class_name; + + if ($source->getSource() instanceof TraitAnalyzer + && strtolower($declaring_method_class) === strtolower((string) $source->getFQCLN()) + ) { + return null; + } + + $storage = $codebase->methods->getStorage($declaring_method_id); + $visibility = $storage->visibility; + + if ($appearing_method_name + && isset($appearing_class_storage->trait_visibility_map[$appearing_method_name]) + ) { + $visibility = $appearing_class_storage->trait_visibility_map[$appearing_method_name]; + } + + switch ($visibility) { + case ClassLikeAnalyzer::VISIBILITY_PUBLIC: + return null; + + case ClassLikeAnalyzer::VISIBILITY_PRIVATE: + if (!$context->self || $appearing_method_class !== $context->self) { + if (IssueBuffer::accepts( + new InaccessibleMethod( + 'Cannot access private method ' . $codebase_methods->getCasedMethodId($method_id) . + ' from context ' . $context->self, + $code_location + ), + $suppressed_issues + )) { + return false; + } + } + + return null; + + case ClassLikeAnalyzer::VISIBILITY_PROTECTED: + if (!$context->self) { + if (IssueBuffer::accepts( + new InaccessibleMethod( + 'Cannot access protected method ' . $method_id, + $code_location + ), + $suppressed_issues + )) { + return false; + } + + return null; + } + + if ($appearing_method_class + && $codebase_classlikes->classExtends($appearing_method_class, $context->self) + ) { + return null; + } + + if ($appearing_method_class + && !$codebase_classlikes->classExtends($context->self, $appearing_method_class) + ) { + if (IssueBuffer::accepts( + new InaccessibleMethod( + 'Cannot access protected method ' . $codebase_methods->getCasedMethodId($method_id) . + ' from context ' . $context->self, + $code_location + ), + $suppressed_issues + )) { + return false; + } + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..fbec6a033044c601db0c099f20b0a500d9395d34 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php @@ -0,0 +1,273 @@ +fq_class_name; + $method_name_lc = $method_id->method_name; + + if ($codebase->methods->return_type_provider->has($fq_class_name)) { + $return_type_candidate = $codebase->methods->return_type_provider->getReturnType( + $statements_analyzer, + $method_id->fq_class_name, + $method_id->method_name, + $stmt->args, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt->name) + ); + + if ($return_type_candidate) { + if ($all_intersection_return_type) { + $return_type_candidate = Type::intersectUnionTypes( + $all_intersection_return_type, + $return_type_candidate, + $codebase + ) ?: Type::getMixed(); + } + + if (!$result->return_type) { + $result->return_type = $return_type_candidate; + } else { + $result->return_type = Type::combineUnionTypes( + $return_type_candidate, + $result->return_type, + $codebase + ); + } + + CallAnalyzer::checkMethodArgs( + $method_id, + $stmt->args, + null, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer + ); + + return null; + } + } + + if (isset($class_storage->pseudo_methods[$method_name_lc])) { + $result->has_valid_method_call_type = true; + $result->existent_method_ids[] = $method_id; + + $pseudo_method_storage = $class_storage->pseudo_methods[$method_name_lc]; + + ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + $pseudo_method_storage->params, + (string) $method_id, + true, + $context + ); + + ArgumentsAnalyzer::checkArgumentsMatch( + $statements_analyzer, + $stmt->args, + null, + $pseudo_method_storage->params, + $pseudo_method_storage, + null, + null, + new CodeLocation($statements_analyzer, $stmt), + $context + ); + + if ($pseudo_method_storage->return_type) { + $return_type_candidate = clone $pseudo_method_storage->return_type; + + $return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $return_type_candidate, + $fq_class_name, + $fq_class_name, + $class_storage->parent_class + ); + + if ($all_intersection_return_type) { + $return_type_candidate = Type::intersectUnionTypes( + $all_intersection_return_type, + $return_type_candidate, + $codebase + ) ?: Type::getMixed(); + } + + if (!$result->return_type) { + $result->return_type = $return_type_candidate; + } else { + $result->return_type = Type::combineUnionTypes( + $return_type_candidate, + $result->return_type, + $codebase + ); + } + + return null; + } + } else { + ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ); + + if ($class_storage->sealed_methods || $config->seal_all_methods) { + $result->non_existent_magic_method_ids[] = $method_id; + + return null; + } + } + + $result->has_valid_method_call_type = true; + $result->existent_method_ids[] = $method_id; + + $array_values = array_map( + /** + * @return PhpParser\Node\Expr\ArrayItem + */ + function (PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem { + return new PhpParser\Node\Expr\ArrayItem($arg->value); + }, + $stmt->args + ); + + $old_node_data = $statements_analyzer->node_data; + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + return new AtomicCallContext( + new MethodIdentifier( + $fq_class_name, + '__call' + ), + [ + new PhpParser\Node\Arg(new PhpParser\Node\Scalar\String_($method_name_lc)), + new PhpParser\Node\Arg(new PhpParser\Node\Expr\Array_($array_values)), + ], + $old_node_data + ); + } + + public static function handleMissingOrMagicMethod( + StatementsAnalyzer $statements_analyzer, + Codebase $codebase, + PhpParser\Node\Expr\MethodCall $stmt, + MethodIdentifier $method_id, + bool $is_interface, + Context $context, + \Psalm\Config $config, + ?Type\Union $all_intersection_return_type, + AtomicMethodCallAnalysisResult $result + ) : void { + $fq_class_name = $method_id->fq_class_name; + $method_name_lc = $method_id->method_name; + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + if (($is_interface || $config->use_phpdoc_method_without_magic_or_parent) + && isset($class_storage->pseudo_methods[$method_name_lc]) + ) { + $result->has_valid_method_call_type = true; + $result->existent_method_ids[] = $method_id; + + $pseudo_method_storage = $class_storage->pseudo_methods[$method_name_lc]; + + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + $pseudo_method_storage->params, + (string) $method_id, + true, + $context + ) === false) { + return; + } + + if (ArgumentsAnalyzer::checkArgumentsMatch( + $statements_analyzer, + $stmt->args, + null, + $pseudo_method_storage->params, + $pseudo_method_storage, + null, + null, + new CodeLocation($statements_analyzer, $stmt->name), + $context + ) === false) { + return; + } + + if ($pseudo_method_storage->return_type) { + $return_type_candidate = clone $pseudo_method_storage->return_type; + + if ($all_intersection_return_type) { + $return_type_candidate = Type::intersectUnionTypes( + $all_intersection_return_type, + $return_type_candidate, + $codebase + ) ?: Type::getMixed(); + } + + $return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $return_type_candidate, + $fq_class_name, + $fq_class_name, + $class_storage->parent_class, + true, + false, + $class_storage->final + ); + + if (!$result->return_type) { + $result->return_type = $return_type_candidate; + } else { + $result->return_type = Type::combineUnionTypes($return_type_candidate, $result->return_type); + } + + return; + } + + $result->return_type = Type::getMixed(); + + return; + } + + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ) === false) { + return; + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..ba6f52566c6c733ae001a88fddefbbe1a0baa891 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php @@ -0,0 +1,436 @@ +inside_call; + + $context->inside_call = true; + + $was_inside_use = $context->inside_use; + $context->inside_use = true; + + $existing_stmt_var_type = null; + + if (!$real_method_call) { + $existing_stmt_var_type = $statements_analyzer->node_data->getType($stmt->var); + } + + if ($existing_stmt_var_type) { + $statements_analyzer->node_data->setType($stmt->var, $existing_stmt_var_type); + } elseif (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->var, $context) === false) { + return false; + } + + $context->inside_call = $was_inside_call; + + if (!$stmt->name instanceof PhpParser\Node\Identifier) { + $context->inside_call = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->name, $context) === false) { + return false; + } + } + + $context->inside_call = $was_inside_call; + $context->inside_use = $was_inside_use; + + if ($stmt->var instanceof PhpParser\Node\Expr\Variable) { + if (is_string($stmt->var->name) && $stmt->var->name === 'this' && !$statements_analyzer->getFQCLN()) { + if (IssueBuffer::accepts( + new InvalidScope( + 'Use of $this in non-class context', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + } + + $lhs_var_id = ExpressionIdentifier::getArrayVarId( + $stmt->var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + $class_type = $lhs_var_id && $context->hasVariable($lhs_var_id) + ? $context->vars_in_scope[$lhs_var_id] + : null; + + if ($stmt_var_type = $statements_analyzer->node_data->getType($stmt->var)) { + $class_type = $stmt_var_type; + } elseif (!$class_type) { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } + + if (!$context->check_classes) { + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ) === false) { + return false; + } + + return true; + } + + if ($class_type + && $stmt->name instanceof PhpParser\Node\Identifier + && ($class_type->isNull() || $class_type->isVoid()) + ) { + if (IssueBuffer::accepts( + new NullReference( + 'Cannot call method ' . $stmt->name->name . ' on null value', + new CodeLocation($statements_analyzer->getSource(), $stmt->name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return true; + } + + if ($class_type + && $stmt->name instanceof PhpParser\Node\Identifier + && $class_type->isNullable() + && !$class_type->ignore_nullable_issues + ) { + if (IssueBuffer::accepts( + new PossiblyNullReference( + 'Cannot call method ' . $stmt->name->name . ' on possibly null value', + new CodeLocation($statements_analyzer->getSource(), $stmt->name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($class_type + && $stmt->name instanceof PhpParser\Node\Identifier + && $class_type->isFalsable() + && !$class_type->ignore_falsable_issues + ) { + if (IssueBuffer::accepts( + new PossiblyFalseReference( + 'Cannot call method ' . $stmt->name->name . ' on possibly false value', + new CodeLocation($statements_analyzer->getSource(), $stmt->name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $codebase = $statements_analyzer->getCodebase(); + + $source = $statements_analyzer->getSource(); + + if (!$class_type) { + $class_type = Type::getMixed(); + } + + $lhs_types = $class_type->getAtomicTypes(); + + $result = new Method\AtomicMethodCallAnalysisResult(); + + $possible_new_class_types = []; + foreach ($lhs_types as $lhs_type_part) { + Method\AtomicMethodCallAnalyzer::analyze( + $statements_analyzer, + $stmt, + $codebase, + $context, + $lhs_type_part, + $lhs_type_part instanceof Type\Atomic\TNamedObject + || $lhs_type_part instanceof Type\Atomic\TTemplateParam + ? $lhs_type_part + : null, + false, + $lhs_var_id, + $result + ); + if (isset($context->vars_in_scope[$lhs_var_id]) + && ($possible_new_class_type = $context->vars_in_scope[$lhs_var_id]) instanceof Type\Union + && !$possible_new_class_type->equals($class_type)) { + $possible_new_class_types[] = $context->vars_in_scope[$lhs_var_id]; + } + } + + if (count($possible_new_class_types) > 0) { + $class_type = array_reduce( + $possible_new_class_types, + function (?Type\Union $type_1, Type\Union $type_2) use ($codebase): Type\Union { + if ($type_1 === null) { + return $type_2; + } + return Type::combineUnionTypes($type_1, $type_2, $codebase); + } + ); + } + + if ($result->invalid_method_call_types) { + $invalid_class_type = $result->invalid_method_call_types[0]; + + if ($result->has_valid_method_call_type || $result->has_mixed_method_call) { + if (IssueBuffer::accepts( + new PossiblyInvalidMethodCall( + 'Cannot call method on possible ' . $invalid_class_type . ' variable ' . $lhs_var_id, + new CodeLocation($source, $stmt->name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep going + } + } else { + if (IssueBuffer::accepts( + new InvalidMethodCall( + 'Cannot call method on ' . $invalid_class_type . ' variable ' . $lhs_var_id, + new CodeLocation($source, $stmt->name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep going + } + } + } + + if ($result->non_existent_magic_method_ids) { + if ($context->check_methods) { + if (IssueBuffer::accepts( + new UndefinedMagicMethod( + 'Magic method ' . $result->non_existent_magic_method_ids[0] . ' does not exist', + new CodeLocation($source, $stmt->name), + $result->non_existent_magic_method_ids[0] + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep going + } + } + } + + if ($result->non_existent_class_method_ids) { + if ($context->check_methods) { + if ($result->existent_method_ids || $result->has_mixed_method_call) { + if (IssueBuffer::accepts( + new PossiblyUndefinedMethod( + 'Method ' . $result->non_existent_class_method_ids[0] . ' does not exist', + new CodeLocation($source, $stmt->name), + $result->non_existent_class_method_ids[0] + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep going + } + } else { + if (IssueBuffer::accepts( + new UndefinedMethod( + 'Method ' . $result->non_existent_class_method_ids[0] . ' does not exist', + new CodeLocation($source, $stmt->name), + $result->non_existent_class_method_ids[0] + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep going + } + } + } + + return true; + } + + if ($result->non_existent_interface_method_ids) { + if ($context->check_methods) { + if ($result->existent_method_ids || $result->has_mixed_method_call) { + if (IssueBuffer::accepts( + new PossiblyUndefinedMethod( + 'Method ' . $result->non_existent_interface_method_ids[0] . ' does not exist', + new CodeLocation($source, $stmt->name), + $result->non_existent_interface_method_ids[0] + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep going + } + } else { + if (IssueBuffer::accepts( + new UndefinedInterfaceMethod( + 'Method ' . $result->non_existent_interface_method_ids[0] . ' does not exist', + new CodeLocation($source, $stmt->name), + $result->non_existent_interface_method_ids[0] + ), + $statements_analyzer->getSuppressedIssues() + )) { + // keep going + } + } + } + + return true; + } + + if ($result->too_many_arguments && $result->too_many_arguments_method_ids) { + $error_method_id = $result->too_many_arguments_method_ids[0]; + + if (IssueBuffer::accepts( + new TooManyArguments( + 'Too many arguments for method ' . $error_method_id . ' - saw ' . count($stmt->args), + new CodeLocation($source, $stmt->name), + (string) $error_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($result->too_few_arguments && $result->too_few_arguments_method_ids) { + $error_method_id = $result->too_few_arguments_method_ids[0]; + + if (IssueBuffer::accepts( + new TooFewArguments( + 'Too few arguments for method ' . $error_method_id . ' saw ' . count($stmt->args), + new CodeLocation($source, $stmt->name), + (string) $error_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $stmt_type = $result->return_type; + + if ($stmt_type) { + $statements_analyzer->node_data->setType($stmt, $stmt_type); + } + + if ($result->returns_by_ref) { + if (!$stmt_type) { + $stmt_type = Type::getMixed(); + $statements_analyzer->node_data->setType($stmt, $stmt_type); + } + + $stmt_type->by_ref = $result->returns_by_ref; + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + && $stmt_type + ) { + $codebase->analyzer->addNodeType( + $statements_analyzer->getFilePath(), + $stmt->name, + $stmt_type->getId(), + $stmt + ); + } + + if (!$result->existent_method_ids) { + return self::checkMethodArgs( + null, + $stmt->args, + null, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer + ); + } + + // if we called a method on this nullable variable, remove the nullable status here + // because any further calls must have worked + if ($lhs_var_id + && !$class_type->isMixed() + && $result->has_valid_method_call_type + && !$result->has_mixed_method_call + && !$result->invalid_method_call_types + && ($class_type->from_docblock || $class_type->isNullable()) + && $real_method_call + ) { + $keys_to_remove = []; + + $class_type = clone $class_type; + + foreach ($class_type->getAtomicTypes() as $key => $type) { + if (!$type instanceof TNamedObject) { + $keys_to_remove[] = $key; + } else { + $type->from_docblock = false; + } + } + + foreach ($keys_to_remove as $key) { + $class_type->removeType($key); + } + + $class_type->from_docblock = false; + + $context->removeVarFromConflictingClauses($lhs_var_id, null, $statements_analyzer); + + $context->vars_in_scope[$lhs_var_id] = $class_type; + } + + if ($lhs_var_id) { + // TODO: Always defined? Always correct? + $method_id = $result->existent_method_ids[0]; + if ($method_id instanceof MethodIdentifier) { + // TODO: When should a method have a storage? + if ($codebase->methods->hasStorage($method_id)) { + $storage = $codebase->methods->getStorage($method_id); + if ($storage->self_out_type) { + $self_out_type = $storage->self_out_type; + $context->vars_in_scope[$lhs_var_id] = $self_out_type; + } + } + } else { + // TODO: When is method_id a string? + } + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..cbe4e15bec986c65c4fffa5f3266cf9433d373b1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php @@ -0,0 +1,754 @@ +getCodebase(); + $config = $codebase->config; + + $can_extend = false; + + $from_static = false; + + if ($stmt->class instanceof PhpParser\Node\Name) { + if (!in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)) { + $aliases = $statements_analyzer->getAliases(); + + if ($context->calling_method_id + && !$stmt->class instanceof PhpParser\Node\Name\FullyQualified + ) { + $codebase->file_reference_provider->addMethodReferenceToClassMember( + $context->calling_method_id, + 'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath()) + ); + } + + $fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject( + $stmt->class, + $aliases + ); + + $fq_class_name = $codebase->classlikes->getUnAliasedName($fq_class_name); + } elseif ($context->self !== null) { + switch ($stmt->class->parts[0]) { + case 'self': + $class_storage = $codebase->classlike_storage_provider->get($context->self); + $fq_class_name = $class_storage->name; + break; + + case 'parent': + $fq_class_name = $context->parent; + break; + + case 'static': + // @todo maybe we can do better here + $class_storage = $codebase->classlike_storage_provider->get($context->self); + $fq_class_name = $class_storage->name; + + if (!$class_storage->final) { + $can_extend = true; + $from_static = true; + } + + break; + } + } + + if ($codebase->store_node_types + && $fq_class_name + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->class, + $codebase->classlikes->classExists($fq_class_name) + ? $fq_class_name + : '*' . implode('\\', $stmt->class->parts) + ); + } + } elseif ($stmt->class instanceof PhpParser\Node\Stmt\Class_) { + $statements_analyzer->analyze([$stmt->class], $context); + $fq_class_name = ClassAnalyzer::getAnonymousClassName($stmt->class, $statements_analyzer->getFilePath()); + } else { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + ExpressionAnalyzer::analyze($statements_analyzer, $stmt->class, $context); + $context->inside_use = $was_inside_use; + + if ($stmt_class_type = $statements_analyzer->node_data->getType($stmt->class)) { + $has_single_class = $stmt_class_type->isSingleStringLiteral(); + + if ($has_single_class) { + $fq_class_name = $stmt_class_type->getSingleStringLiteral()->value; + } else { + if (self::checkMethodArgs( + null, + $stmt->args, + null, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer + ) === false) { + return false; + } + } + + $new_type = null; + + foreach ($stmt_class_type->getAtomicTypes() as $lhs_type_part) { + if ($lhs_type_part instanceof Type\Atomic\TTemplateParamClass) { + if (!$statements_analyzer->node_data->getType($stmt)) { + $new_type_part = new Type\Atomic\TTemplateParam( + $lhs_type_part->param_name, + $lhs_type_part->as_type + ? new Type\Union([$lhs_type_part->as_type]) + : Type::getObject(), + $lhs_type_part->defining_class + ); + + if (!$lhs_type_part->as_type) { + if (IssueBuffer::accepts( + new MixedMethodCall( + 'Cannot call constructor on an unknown class', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($new_type) { + $new_type = Type::combineUnionTypes( + $new_type, + new Type\Union([$new_type_part]) + ); + } else { + $new_type = new Type\Union([$new_type_part]); + } + + if ($lhs_type_part->as_type + && $codebase->classlikes->classExists($lhs_type_part->as_type->value) + ) { + $as_storage = $codebase->classlike_storage_provider->get( + $lhs_type_part->as_type->value + ); + + if (!$as_storage->preserve_constructor_signature) { + if (IssueBuffer::accepts( + new UnsafeInstantiation( + 'Cannot safely instantiate class ' . $lhs_type_part->as_type->value + . ' with "new $class_name" as' + . ' its constructor might change in child classes', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + if ($lhs_type_part->as_type) { + $codebase->methods->methodExists( + new \Psalm\Internal\MethodIdentifier( + $lhs_type_part->as_type->value, + '__construct' + ), + $context->calling_method_id, + $codebase->collect_locations + ? new CodeLocation($statements_analyzer->getSource(), $stmt) : null, + $statements_analyzer, + $statements_analyzer->getFilePath() + ); + } + + continue; + } + + if ($lhs_type_part instanceof Type\Atomic\TLiteralClassString + || $lhs_type_part instanceof Type\Atomic\TClassString + || $lhs_type_part instanceof Type\Atomic\TDependentGetClass + ) { + if (!$statements_analyzer->node_data->getType($stmt)) { + if ($lhs_type_part instanceof Type\Atomic\TClassString) { + $generated_type = $lhs_type_part->as_type + ? clone $lhs_type_part->as_type + : new Type\Atomic\TObject(); + + if ($lhs_type_part->as_type + && $codebase->classlikes->classExists($lhs_type_part->as_type->value) + ) { + $as_storage = $codebase->classlike_storage_provider->get( + $lhs_type_part->as_type->value + ); + + if (!$as_storage->preserve_constructor_signature) { + if (IssueBuffer::accepts( + new UnsafeInstantiation( + 'Cannot safely instantiate class ' . $lhs_type_part->as_type->value + . ' with "new $class_name" as' + . ' its constructor might change in child classes', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } elseif ($lhs_type_part instanceof Type\Atomic\TDependentGetClass) { + $generated_type = new Type\Atomic\TObject(); + + if ($lhs_type_part->as_type->hasObjectType() + && $lhs_type_part->as_type->isSingle() + ) { + foreach ($lhs_type_part->as_type->getAtomicTypes() as $typeof_type_atomic) { + if ($typeof_type_atomic instanceof Type\Atomic\TNamedObject) { + $generated_type = new Type\Atomic\TNamedObject( + $typeof_type_atomic->value + ); + } + } + } + } else { + $generated_type = new Type\Atomic\TNamedObject( + $lhs_type_part->value + ); + } + + if ($lhs_type_part instanceof Type\Atomic\TClassString) { + $can_extend = true; + } + + if ($generated_type instanceof Type\Atomic\TObject) { + if (IssueBuffer::accepts( + new MixedMethodCall( + 'Cannot call constructor on an unknown class', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($new_type) { + $new_type = Type::combineUnionTypes( + $new_type, + new Type\Union([$generated_type]) + ); + } else { + $new_type = new Type\Union([$generated_type]); + } + } + + continue; + } + + if ($lhs_type_part instanceof Type\Atomic\TString) { + if ($config->allow_string_standin_for_class + && !$lhs_type_part instanceof Type\Atomic\TNumericString + ) { + // do nothing + } elseif (IssueBuffer::accepts( + new InvalidStringClass( + 'String cannot be used as a class', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($lhs_type_part instanceof Type\Atomic\TMixed + || $lhs_type_part instanceof Type\Atomic\TTemplateParam + ) { + if (IssueBuffer::accepts( + new MixedMethodCall( + 'Cannot call constructor on an unknown class', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($lhs_type_part instanceof Type\Atomic\TFalse + && $stmt_class_type->ignore_falsable_issues + ) { + // do nothing + } elseif ($lhs_type_part instanceof Type\Atomic\TNull + && $stmt_class_type->ignore_nullable_issues + ) { + // do nothing + } elseif (IssueBuffer::accepts( + new UndefinedClass( + 'Type ' . $lhs_type_part . ' cannot be called as a class', + new CodeLocation($statements_analyzer->getSource(), $stmt), + (string)$lhs_type_part + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + if ($new_type) { + $new_type = Type::combineUnionTypes( + $new_type, + Type::getObject() + ); + } else { + $new_type = Type::getObject(); + } + } + + if (!$has_single_class) { + if ($new_type) { + $statements_analyzer->node_data->setType($stmt, $new_type); + } + + ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ); + + return true; + } + } else { + ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ); + + return true; + } + } + + if ($fq_class_name) { + if ($codebase->alter_code + && $stmt->class instanceof PhpParser\Node\Name + && !in_array($stmt->class->parts[0], ['parent', 'static']) + ) { + $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $statements_analyzer, + $stmt->class, + $fq_class_name, + $context->calling_method_id + ); + } + + if ($context->check_classes) { + if ($context->isPhantomClass($fq_class_name)) { + ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ); + + return true; + } + + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $fq_class_name, + new CodeLocation($statements_analyzer->getSource(), $stmt->class), + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues(), + false + ) === false) { + ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ); + + return true; + } + + if ($codebase->interfaceExists($fq_class_name)) { + if (IssueBuffer::accepts( + new InterfaceInstantiation( + 'Interface ' . $fq_class_name . ' cannot be instantiated', + new CodeLocation($statements_analyzer->getSource(), $stmt->class) + ), + $statements_analyzer->getSuppressedIssues() + )) { + } + + return true; + } + } + + if ($stmt->class instanceof PhpParser\Node\Stmt\Class_) { + $result_atomic_type = new Type\Atomic\TAnonymousClassInstance($fq_class_name); + } else { + $result_atomic_type = new TNamedObject($fq_class_name); + $result_atomic_type->was_static = $from_static; + } + + $statements_analyzer->node_data->setType( + $stmt, + new Type\Union([$result_atomic_type]) + ); + + if (strtolower($fq_class_name) !== 'stdclass' && + $codebase->classlikes->classExists($fq_class_name) + ) { + $storage = $codebase->classlike_storage_provider->get($fq_class_name); + + if ($from_static && !$storage->preserve_constructor_signature) { + if (IssueBuffer::accepts( + new UnsafeInstantiation( + 'Cannot safely instantiate class ' . $fq_class_name . ' with "new static" as' + . ' its constructor might change in child classes', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + // if we're not calling this constructor via new static() + if ($storage->abstract && !$can_extend) { + if (IssueBuffer::accepts( + new AbstractInstantiation( + 'Unable to instantiate a abstract class ' . $fq_class_name, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return true; + } + } + + if ($storage->deprecated && strtolower($fq_class_name) !== strtolower((string) $context->self)) { + if (IssueBuffer::accepts( + new DeprecatedClass( + $fq_class_name . ' is marked deprecated', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $fq_class_name + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + + if ($context->self + && !$context->collect_initializations + && !$context->collect_mutations + && !NamespaceAnalyzer::isWithin($context->self, $storage->internal) + ) { + if (IssueBuffer::accepts( + new InternalClass( + $fq_class_name . ' is internal to ' . $storage->internal + . ' but called from ' . $context->self, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $fq_class_name + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $method_id = new \Psalm\Internal\MethodIdentifier($fq_class_name, '__construct'); + + if ($codebase->methods->methodExists( + $method_id, + $context->calling_method_id, + $codebase->collect_locations ? new CodeLocation($statements_analyzer->getSource(), $stmt) : null, + $statements_analyzer, + $statements_analyzer->getFilePath() + )) { + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + ArgumentMapPopulator::recordArgumentPositions( + $statements_analyzer, + $stmt, + $codebase, + (string) $method_id + ); + } + + $template_result = new \Psalm\Internal\Type\TemplateResult([], []); + + if (self::checkMethodArgs( + $method_id, + $stmt->args, + $template_result, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer + ) === false) { + return false; + } + + if (Method\MethodVisibilityAnalyzer::analyze( + $method_id, + $context, + $statements_analyzer->getSource(), + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues() + ) === false) { + return false; + } + + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + if ($declaring_method_id) { + $method_storage = $codebase->methods->getStorage($declaring_method_id); + + if (!$method_storage->external_mutation_free && !$context->inside_throw) { + if ($context->pure) { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call an impure constructor from a pure context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } + } + } + + $generic_param_types = null; + + if ($storage->template_types) { + $declaring_fq_class_name = $declaring_method_id + ? $declaring_method_id->fq_class_name + : $fq_class_name; + + foreach ($storage->template_types as $template_name => $base_type) { + if (isset($template_result->upper_bounds[$template_name][$fq_class_name])) { + $generic_param_type + = $template_result->upper_bounds[$template_name][$fq_class_name][0]; + } elseif ($storage->template_type_extends && $template_result->upper_bounds) { + $generic_param_type = self::getGenericParamForOffset( + $declaring_fq_class_name, + $template_name, + $storage->template_type_extends, + $template_result->upper_bounds + ); + } else { + if ($fq_class_name === 'SplObjectStorage') { + $generic_param_type = Type::getEmpty(); + } else { + $generic_param_type = array_values($base_type)[0][0]; + } + } + + $generic_param_type->had_template = true; + + $generic_param_types[] = $generic_param_type; + } + } + + if ($generic_param_types) { + $result_atomic_type = new Type\Atomic\TGenericObject( + $fq_class_name, + $generic_param_types + ); + + $result_atomic_type->was_static = $from_static; + + $statements_analyzer->node_data->setType( + $stmt, + new Type\Union([$result_atomic_type]) + ); + } + } elseif ($stmt->args) { + if (IssueBuffer::accepts( + new TooManyArguments( + 'Class ' . $fq_class_name . ' has no __construct, but arguments were passed', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $fq_class_name . '::__construct' + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($storage->external_mutation_free) { + /** @psalm-suppress UndefinedPropertyAssignment */ + $stmt->external_mutation_free = true; + $stmt_type = $statements_analyzer->node_data->getType($stmt); + + if ($stmt_type) { + $stmt_type->reference_free = true; + } + } + + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph + && !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + && ($stmt_type = $statements_analyzer->node_data->getType($stmt)) + ) { + $code_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + + $method_storage = null; + + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + if ($declaring_method_id) { + $method_storage = $codebase->methods->getStorage($declaring_method_id); + } + + if ($storage->external_mutation_free + || ($method_storage && $method_storage->specialize_call) + ) { + $method_source = DataFlowNode::getForMethodReturn( + (string) $method_id, + $fq_class_name . '::__construct', + $storage->location, + $code_location + ); + } else { + $method_source = DataFlowNode::getForMethodReturn( + (string) $method_id, + $fq_class_name . '::__construct', + $storage->location + ); + } + + $statements_analyzer->data_flow_graph->addNode($method_source); + + $stmt_type->parent_nodes = [$method_source->id => $method_source]; + } + } else { + ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ); + } + } + + + + if (!$config->remember_property_assignments_after_call && !$context->collect_initializations) { + $context->removeAllObjectVars(); + } + + return true; + } + + /** + * @param array> $template_type_extends + * @param array> $found_generic_params + */ + private static function getGenericParamForOffset( + string $fq_class_name, + string $template_name, + array $template_type_extends, + array $found_generic_params, + bool $mapped = false + ): Type\Union { + if (isset($found_generic_params[$template_name][$fq_class_name])) { + if (!$mapped && isset($template_type_extends[$fq_class_name][$template_name])) { + foreach ($template_type_extends[$fq_class_name][$template_name]->getAtomicTypes() as $t) { + if ($t instanceof Type\Atomic\TTemplateParam) { + if ($t->param_name !== $template_name) { + return $t->as; + } + } + } + } + + return $found_generic_params[$template_name][$fq_class_name][0]; + } + + foreach ($template_type_extends as $type_map) { + foreach ($type_map as $extended_template_name => $extended_type) { + foreach ($extended_type->getAtomicTypes() as $extended_atomic_type) { + if (is_string($extended_template_name) + && $extended_atomic_type instanceof Type\Atomic\TTemplateParam + && $extended_atomic_type->param_name === $template_name + && $extended_template_name !== $template_name + ) { + return self::getGenericParamForOffset( + $fq_class_name, + $extended_template_name, + $template_type_extends, + $found_generic_params, + true + ); + } + } + } + } + + return Type::getMixed(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..b8530fdb866c003ba18d45bb8b03d2e60069549e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php @@ -0,0 +1,1589 @@ +getFileAnalyzer(); + $codebase = $statements_analyzer->getCodebase(); + $source = $statements_analyzer->getSource(); + + $config = $codebase->config; + + if ($stmt->class instanceof PhpParser\Node\Name) { + $fq_class_name = null; + + if (count($stmt->class->parts) === 1 + && in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true) + ) { + if ($stmt->class->parts[0] === 'parent') { + $child_fq_class_name = $context->self; + + $class_storage = $child_fq_class_name + ? $codebase->classlike_storage_provider->get($child_fq_class_name) + : null; + + if (!$class_storage || !$class_storage->parent_class) { + if (IssueBuffer::accepts( + new ParentNotFound( + 'Cannot call method on parent as this class does not extend another', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return true; + } + + $fq_class_name = $class_storage->parent_class; + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + $fq_class_name = $class_storage->name; + } elseif ($context->self) { + if ($stmt->class->parts[0] === 'static' && isset($context->vars_in_scope['$this'])) { + $fq_class_name = (string) $context->vars_in_scope['$this']; + $lhs_type = clone $context->vars_in_scope['$this']; + } else { + $fq_class_name = $context->self; + } + } else { + if (IssueBuffer::accepts( + new NonStaticSelfCall( + 'Cannot use ' . $stmt->class->parts[0] . ' outside class context', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return true; + } + + if ($context->isPhantomClass($fq_class_name)) { + return true; + } + } elseif ($context->check_classes) { + $aliases = $statements_analyzer->getAliases(); + + if ($context->calling_method_id + && !$stmt->class instanceof PhpParser\Node\Name\FullyQualified + ) { + $codebase->file_reference_provider->addMethodReferenceToClassMember( + $context->calling_method_id, + 'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath()) + ); + } + + $fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject( + $stmt->class, + $aliases + ); + + if ($context->isPhantomClass($fq_class_name)) { + return true; + } + + $does_class_exist = false; + + if ($context->self) { + $self_storage = $codebase->classlike_storage_provider->get($context->self); + + if (isset($self_storage->used_traits[strtolower($fq_class_name)])) { + $fq_class_name = $context->self; + $does_class_exist = true; + } + } + + if (!isset($context->phantom_classes[strtolower($fq_class_name)]) + && !$does_class_exist + ) { + $does_class_exist = ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $fq_class_name, + new CodeLocation($source, $stmt->class), + !$context->collect_initializations + && !$context->collect_mutations + ? $context->self + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $context->calling_method_id + : null, + $statements_analyzer->getSuppressedIssues(), + false, + false, + false + ); + } + + if (!$does_class_exist) { + return $does_class_exist !== false; + } + } + + if ($codebase->store_node_types + && $fq_class_name + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->class, + $fq_class_name + ); + } + + if ($fq_class_name && !$lhs_type) { + $lhs_type = new Type\Union([new TNamedObject($fq_class_name)]); + } + } else { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + ExpressionAnalyzer::analyze($statements_analyzer, $stmt->class, $context); + $context->inside_use = $was_inside_use; + $lhs_type = $statements_analyzer->node_data->getType($stmt->class) ?: Type::getMixed(); + } + + if (!$lhs_type) { + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ) === false) { + return false; + } + + return true; + } + + $has_mock = false; + $moved_call = false; + + foreach ($lhs_type->getAtomicTypes() as $lhs_type_part) { + $intersection_types = []; + + if ($lhs_type_part instanceof TNamedObject) { + $fq_class_name = $lhs_type_part->value; + + if (!ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $fq_class_name, + new CodeLocation($source, $stmt->class), + !$context->collect_initializations + && !$context->collect_mutations + ? $context->self + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $context->calling_method_id + : null, + $statements_analyzer->getSuppressedIssues(), + $stmt->class instanceof PhpParser\Node\Name + && count($stmt->class->parts) === 1 + && in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true) + )) { + return false; + } + + $intersection_types = $lhs_type_part->extra_types; + } elseif ($lhs_type_part instanceof Type\Atomic\TClassString + && $lhs_type_part->as_type + ) { + $fq_class_name = $lhs_type_part->as_type->value; + + if (!ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $fq_class_name, + new CodeLocation($source, $stmt->class), + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues(), + false + )) { + return false; + } + + $intersection_types = $lhs_type_part->as_type->extra_types; + } elseif ($lhs_type_part instanceof Type\Atomic\TDependentGetClass + && !$lhs_type_part->as_type->hasObject() + ) { + $fq_class_name = 'object'; + + if ($lhs_type_part->as_type->hasObjectType() + && $lhs_type_part->as_type->isSingle() + ) { + foreach ($lhs_type_part->as_type->getAtomicTypes() as $typeof_type_atomic) { + if ($typeof_type_atomic instanceof Type\Atomic\TNamedObject) { + $fq_class_name = $typeof_type_atomic->value; + } + } + } + + if ($fq_class_name === 'object') { + continue; + } + } elseif ($lhs_type_part instanceof Type\Atomic\TLiteralClassString) { + $fq_class_name = $lhs_type_part->value; + + if (!ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $fq_class_name, + new CodeLocation($source, $stmt->class), + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues(), + false + )) { + return false; + } + } elseif ($lhs_type_part instanceof Type\Atomic\TTemplateParam + && !$lhs_type_part->as->isMixed() + && !$lhs_type_part->as->hasObject() + ) { + $fq_class_name = null; + + foreach ($lhs_type_part->as->getAtomicTypes() as $generic_param_type) { + if (!$generic_param_type instanceof TNamedObject) { + continue 2; + } + + $fq_class_name = $generic_param_type->value; + break; + } + + if (!$fq_class_name) { + if (IssueBuffer::accepts( + new UndefinedClass( + 'Type ' . $lhs_type_part->as . ' cannot be called as a class', + new CodeLocation($statements_analyzer->getSource(), $stmt), + (string) $lhs_type_part + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + continue; + } + } else { + if ($lhs_type_part instanceof Type\Atomic\TMixed + || $lhs_type_part instanceof Type\Atomic\TTemplateParam + || $lhs_type_part instanceof Type\Atomic\TClassString + ) { + if ($stmt->name instanceof PhpParser\Node\Identifier) { + $codebase->analyzer->addMixedMemberName( + strtolower($stmt->name->name), + $context->calling_method_id ?: $statements_analyzer->getFileName() + ); + } + + if (IssueBuffer::accepts( + new MixedMethodCall( + 'Cannot call method on an unknown class', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + continue; + } + + if ($lhs_type_part instanceof Type\Atomic\TString) { + if ($config->allow_string_standin_for_class + && !$lhs_type_part instanceof Type\Atomic\TNumericString + ) { + continue; + } + + if (IssueBuffer::accepts( + new InvalidStringClass( + 'String cannot be used as a class', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + continue; + } + + if ($lhs_type_part instanceof Type\Atomic\TNull + && $lhs_type->ignore_nullable_issues + ) { + continue; + } + + if (IssueBuffer::accepts( + new UndefinedClass( + 'Type ' . $lhs_type_part . ' cannot be called as a class', + new CodeLocation($statements_analyzer->getSource(), $stmt), + (string) $lhs_type_part + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + continue; + } + + $fq_class_name = $codebase->classlikes->getUnAliasedName($fq_class_name); + + $is_mock = ExpressionAnalyzer::isMock($fq_class_name); + + $has_mock = $has_mock || $is_mock; + + if ($stmt->name instanceof PhpParser\Node\Identifier && !$is_mock) { + $method_name_lc = strtolower($stmt->name->name); + $method_id = new MethodIdentifier($fq_class_name, $method_name_lc); + + $cased_method_id = $fq_class_name . '::' . $stmt->name->name; + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + ArgumentMapPopulator::recordArgumentPositions( + $statements_analyzer, + $stmt, + $codebase, + (string) $method_id + ); + } + + $args = $stmt->args; + + if ($intersection_types + && !$codebase->methods->methodExists($method_id) + ) { + foreach ($intersection_types as $intersection_type) { + if (!$intersection_type instanceof TNamedObject) { + continue; + } + + $intersection_method_id = new MethodIdentifier( + $intersection_type->value, + $method_name_lc + ); + + if ($codebase->methods->methodExists($intersection_method_id)) { + $method_id = $intersection_method_id; + $cased_method_id = $intersection_type->value . '::' . $stmt->name->name; + $fq_class_name = $intersection_type->value; + break; + } + } + } + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + $naive_method_exists = $codebase->methods->methodExists( + $method_id, + !$context->collect_initializations + && !$context->collect_mutations + ? $context->calling_method_id + : null, + $codebase->collect_locations + ? new CodeLocation($source, $stmt->name) + : null, + $statements_analyzer, + $statements_analyzer->getFilePath(), + false + ); + + $fake_method_exists = false; + + if (!$naive_method_exists + && $codebase->methods->existence_provider->has($fq_class_name) + ) { + $method_exists = $codebase->methods->existence_provider->doesMethodExist( + $fq_class_name, + $method_id->method_name, + $source, + null + ); + + if ($method_exists) { + $fake_method_exists = true; + } + } + + if (!$naive_method_exists + && $class_storage->mixin_declaring_fqcln + && $class_storage->namedMixins + ) { + foreach ($class_storage->namedMixins as $mixin) { + $new_method_id = new MethodIdentifier( + $mixin->value, + $method_name_lc + ); + + if ($codebase->methods->methodExists( + $new_method_id, + $context->calling_method_id, + $codebase->collect_locations + ? new CodeLocation($source, $stmt->name) + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $statements_analyzer + : null, + $statements_analyzer->getFilePath() + )) { + $mixin_candidates = []; + foreach ($class_storage->templatedMixins as $mixin_candidate) { + $mixin_candidates[] = clone $mixin_candidate; + } + + foreach ($class_storage->namedMixins as $mixin_candidate) { + $mixin_candidates[] = clone $mixin_candidate; + } + + $mixin_candidates_no_generic = array_filter($mixin_candidates, function ($check): bool { + return !($check instanceof Type\Atomic\TGenericObject); + }); + + // $mixin_candidates_no_generic will only be empty when there are TGenericObject entries. + // In that case, Union will be initialized with an empty array but + // replaced with non-empty types in the following loop. + /** @psalm-suppress ArgumentTypeCoercion */ + $mixin_candidate_type = new Type\Union($mixin_candidates_no_generic); + + foreach ($mixin_candidates as $tGenericMixin) { + if (!($tGenericMixin instanceof Type\Atomic\TGenericObject)) { + continue; + } + + $mixin_declaring_class_storage = $codebase->classlike_storage_provider->get( + $class_storage->mixin_declaring_fqcln + ); + + $new_mixin_candidate_type = AtomicPropertyFetchAnalyzer::localizePropertyType( + $codebase, + new Type\Union([$lhs_type_part]), + $tGenericMixin, + $class_storage, + $mixin_declaring_class_storage + ); + + foreach ($mixin_candidate_type->getAtomicTypes() as $type) { + $new_mixin_candidate_type->addType($type); + } + + $mixin_candidate_type = $new_mixin_candidate_type; + } + + $new_lhs_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $mixin_candidate_type, + $fq_class_name, + $fq_class_name, + $class_storage->parent_class, + true, + false, + $class_storage->final + ); + + $old_data_provider = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $context->vars_in_scope['$tmp_mixin_var'] = $new_lhs_type; + + $fake_method_call_expr = new PhpParser\Node\Expr\MethodCall( + new PhpParser\Node\Expr\Variable( + 'tmp_mixin_var', + $stmt->class->getAttributes() + ), + $stmt->name, + $stmt->args, + $stmt->getAttributes() + ); + + if (MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_method_call_expr, + $context + ) === false) { + return false; + } + + $fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr); + + $statements_analyzer->node_data = $old_data_provider; + + $statements_analyzer->node_data->setType($stmt, $fake_method_call_type ?: Type::getMixed()); + + return true; + } + } + } + + if (!$naive_method_exists + || !MethodAnalyzer::isMethodVisible( + $method_id, + $context, + $statements_analyzer->getSource() + ) + || $fake_method_exists + || (isset($class_storage->pseudo_static_methods[$method_name_lc]) + && ($config->use_phpdoc_method_without_magic_or_parent || $class_storage->parent_class)) + ) { + $callstatic_id = new MethodIdentifier( + $fq_class_name, + '__callstatic' + ); + if ($codebase->methods->methodExists( + $callstatic_id, + $context->calling_method_id, + $codebase->collect_locations + ? new CodeLocation($source, $stmt->name) + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $statements_analyzer + : null, + $statements_analyzer->getFilePath() + )) { + if ($codebase->methods->return_type_provider->has($fq_class_name)) { + $return_type_candidate = $codebase->methods->return_type_provider->getReturnType( + $statements_analyzer, + $method_id->fq_class_name, + $method_id->method_name, + $stmt->args, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt->name), + null, + null, + strtolower($stmt->name->name) + ); + + if ($return_type_candidate) { + CallAnalyzer::checkMethodArgs( + $method_id, + $stmt->args, + null, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer + ); + + $statements_analyzer->node_data->setType($stmt, $return_type_candidate); + + return true; + } + } + + if (isset($class_storage->pseudo_static_methods[$method_name_lc])) { + $pseudo_method_storage = $class_storage->pseudo_static_methods[$method_name_lc]; + + if (self::checkPseudoMethod( + $statements_analyzer, + $stmt, + $method_id, + $fq_class_name, + $args, + $class_storage, + $pseudo_method_storage, + $context + ) === false + ) { + return false; + } + + if ($pseudo_method_storage->return_type) { + return true; + } + } else { + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $args, + null, + null, + true, + $context + ) === false) { + return false; + } + } + + $array_values = array_map( + function (PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem { + return new PhpParser\Node\Expr\ArrayItem($arg->value); + }, + $args + ); + + $args = [ + new PhpParser\Node\Arg(new PhpParser\Node\Scalar\String_((string) $method_id)), + new PhpParser\Node\Arg(new PhpParser\Node\Expr\Array_($array_values)), + ]; + + $method_id = new MethodIdentifier( + $fq_class_name, + '__callstatic' + ); + } elseif (isset($class_storage->pseudo_static_methods[$method_name_lc]) + && ($config->use_phpdoc_method_without_magic_or_parent || $class_storage->parent_class) + ) { + $pseudo_method_storage = $class_storage->pseudo_static_methods[$method_name_lc]; + + if (self::checkPseudoMethod( + $statements_analyzer, + $stmt, + $method_id, + $fq_class_name, + $args, + $class_storage, + $pseudo_method_storage, + $context + ) === false + ) { + return false; + } + + if ($pseudo_method_storage->return_type) { + return true; + } + } + + if (!$context->check_methods) { + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ) === false) { + return false; + } + + return true; + } + } + + $does_method_exist = MethodAnalyzer::checkMethodExists( + $codebase, + $method_id, + new CodeLocation($source, $stmt), + $statements_analyzer->getSuppressedIssues(), + $context->calling_method_id + ); + + if (!$does_method_exist) { + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ) === false) { + return false; + } + + if ($codebase->alter_code && $fq_class_name && !$moved_call) { + $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $statements_analyzer, + $stmt->class, + $fq_class_name, + $context->calling_method_id + ); + } + + return true; + } + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + if ($class_storage->user_defined + && $context->self + && ($context->collect_mutations || $context->collect_initializations) + ) { + $appearing_method_id = $codebase->methods->getAppearingMethodId($method_id); + + if (!$appearing_method_id) { + if (IssueBuffer::accepts( + new UndefinedMethod( + 'Method ' . $method_id . ' does not exist', + new CodeLocation($statements_analyzer->getSource(), $stmt), + (string) $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // + } + + return true; + } + + $appearing_method_class_name = $appearing_method_id->fq_class_name; + + if ($codebase->classExtends($context->self, $appearing_method_class_name)) { + $old_context_include_location = $context->include_location; + $old_self = $context->self; + $context->include_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + $context->self = $appearing_method_class_name; + + if ($context->collect_mutations) { + $file_analyzer->getMethodMutations($method_id, $context); + } else { + // collecting initializations + $local_vars_in_scope = []; + $local_vars_possibly_in_scope = []; + + foreach ($context->vars_in_scope as $var => $_) { + if (strpos($var, '$this->') !== 0 && $var !== '$this') { + $local_vars_in_scope[$var] = $context->vars_in_scope[$var]; + } + } + + foreach ($context->vars_possibly_in_scope as $var => $_) { + if (strpos($var, '$this->') !== 0 && $var !== '$this') { + $local_vars_possibly_in_scope[$var] = $context->vars_possibly_in_scope[$var]; + } + } + + if (!isset($context->initialized_methods[(string) $method_id])) { + if ($context->initialized_methods === null) { + $context->initialized_methods = []; + } + + $context->initialized_methods[(string) $method_id] = true; + + $file_analyzer->getMethodMutations($method_id, $context); + + foreach ($local_vars_in_scope as $var => $type) { + $context->vars_in_scope[$var] = $type; + } + + foreach ($local_vars_possibly_in_scope as $var => $type) { + $context->vars_possibly_in_scope[$var] = $type; + } + } + } + + $context->include_location = $old_context_include_location; + $context->self = $old_self; + } + } + + if ($class_storage->deprecated && $fq_class_name !== $context->self) { + if (IssueBuffer::accepts( + new DeprecatedClass( + $fq_class_name . ' is marked deprecated', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $fq_class_name + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($context->self && ! NamespaceAnalyzer::isWithin($context->self, $class_storage->internal)) { + if (IssueBuffer::accepts( + new InternalClass( + $fq_class_name . ' is internal to ' . $class_storage->internal + . ' but called from ' . $context->self, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $fq_class_name + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if (Method\MethodVisibilityAnalyzer::analyze( + $method_id, + $context, + $statements_analyzer->getSource(), + new CodeLocation($source, $stmt), + $statements_analyzer->getSuppressedIssues() + ) === false) { + return false; + } + + if ((!$stmt->class instanceof PhpParser\Node\Name + || $stmt->class->parts[0] !== 'parent' + || $statements_analyzer->isStatic()) + && ( + !$context->self + || $statements_analyzer->isStatic() + || !$codebase->classExtends($context->self, $fq_class_name) + ) + ) { + if (MethodAnalyzer::checkStatic( + $method_id, + ($stmt->class instanceof PhpParser\Node\Name + && strtolower($stmt->class->parts[0]) === 'self') + || $context->self === $fq_class_name, + !$statements_analyzer->isStatic(), + $codebase, + new CodeLocation($source, $stmt), + $statements_analyzer->getSuppressedIssues(), + $is_dynamic_this_method + ) === false) { + // fall through + } + + if ($is_dynamic_this_method) { + $old_data_provider = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $fake_method_call_expr = new PhpParser\Node\Expr\MethodCall( + new PhpParser\Node\Expr\Variable( + 'this', + $stmt->class->getAttributes() + ), + $stmt->name, + $stmt->args, + $stmt->getAttributes() + ); + + if (MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_method_call_expr, + $context + ) === false) { + return false; + } + + $fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr); + + $statements_analyzer->node_data = $old_data_provider; + + if ($fake_method_call_type) { + $statements_analyzer->node_data->setType($stmt, $fake_method_call_type); + } + + return true; + } + } + + if (Method\MethodCallProhibitionAnalyzer::analyze( + $codebase, + $context, + $method_id, + $statements_analyzer->getNamespace(), + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues() + ) === false) { + // fall through + } + + $found_generic_params = ClassTemplateParamCollector::collect( + $codebase, + $class_storage, + $class_storage, + $method_name_lc, + $lhs_type_part, + null + ); + + if ($found_generic_params + && $stmt->class instanceof PhpParser\Node\Name + && $stmt->class->parts === ['parent'] + && $context->self + && ($self_class_storage = $codebase->classlike_storage_provider->get($context->self)) + && $self_class_storage->template_type_extends + ) { + foreach ($self_class_storage->template_type_extends as $template_fq_class_name => $extended_types) { + foreach ($extended_types as $type_key => $extended_type) { + if (!is_string($type_key)) { + continue; + } + + if (isset($found_generic_params[$type_key][$template_fq_class_name])) { + $found_generic_params[$type_key][$template_fq_class_name][0] = clone $extended_type; + continue; + } + + foreach ($extended_type->getAtomicTypes() as $t) { + if ($t instanceof Type\Atomic\TTemplateParam + && isset($found_generic_params[$t->param_name][$t->defining_class]) + ) { + $found_generic_params[$type_key][$template_fq_class_name] = [ + $found_generic_params[$t->param_name][$t->defining_class][0] + ]; + } else { + $found_generic_params[$type_key][$template_fq_class_name] = [ + clone $extended_type + ]; + break; + } + } + } + } + } + + $template_result = new \Psalm\Internal\Type\TemplateResult([], $found_generic_params ?: []); + + if (self::checkMethodArgs( + $method_id, + $args, + $template_result, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer + ) === false) { + return false; + } + + $fq_class_name = $stmt->class instanceof PhpParser\Node\Name && $stmt->class->parts === ['parent'] + ? (string) $statements_analyzer->getFQCLN() + : $fq_class_name; + + $self_fq_class_name = $fq_class_name; + + $return_type_candidate = null; + + if ($codebase->methods->return_type_provider->has($fq_class_name)) { + $return_type_candidate = $codebase->methods->return_type_provider->getReturnType( + $statements_analyzer, + $fq_class_name, + $stmt->name->name, + $stmt->args, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt->name) + ); + } + + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + if (!$return_type_candidate + && $declaring_method_id + && (string) $declaring_method_id !== (string) $method_id + ) { + $declaring_fq_class_name = $declaring_method_id->fq_class_name; + $declaring_method_name = $declaring_method_id->method_name; + + if ($codebase->methods->return_type_provider->has($declaring_fq_class_name)) { + $return_type_candidate = $codebase->methods->return_type_provider->getReturnType( + $statements_analyzer, + $declaring_fq_class_name, + $declaring_method_name, + $stmt->args, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt->name), + null, + $fq_class_name, + $stmt->name->name + ); + } + } + + if (!$return_type_candidate) { + $return_type_candidate = $codebase->methods->getMethodReturnType( + $method_id, + $self_fq_class_name, + $statements_analyzer, + $args + ); + + if ($return_type_candidate) { + $return_type_candidate = clone $return_type_candidate; + + if ($template_result->template_types) { + $bindable_template_types = $return_type_candidate->getTemplateTypes(); + + foreach ($bindable_template_types as $template_type) { + if (!isset( + $template_result->upper_bounds + [$template_type->param_name] + [$template_type->defining_class] + )) { + if ($template_type->param_name === 'TFunctionArgCount') { + $template_result->upper_bounds[$template_type->param_name] = [ + 'fn-' . strtolower((string) $method_id) => [ + Type::getInt(false, count($stmt->args)), + 0 + ] + ]; + } else { + $template_result->upper_bounds[$template_type->param_name] = [ + ($template_type->defining_class) => [Type::getEmpty(), 0] + ]; + } + } + } + } + + if ($lhs_type_part instanceof Type\Atomic\TTemplateParam) { + $static_type = $lhs_type_part; + } elseif ($lhs_type_part instanceof Type\Atomic\TTemplateParamClass) { + $static_type = new Type\Atomic\TTemplateParam( + $lhs_type_part->param_name, + $lhs_type_part->as_type + ? new Type\Union([$lhs_type_part->as_type]) + : Type::getObject(), + $lhs_type_part->defining_class + ); + } else { + $static_type = $fq_class_name; + } + + if ($template_result->upper_bounds) { + $return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $return_type_candidate, + null, + null, + null + ); + + $return_type_candidate->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + } + + $return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $return_type_candidate, + $self_fq_class_name, + $static_type, + $class_storage->parent_class, + true, + false, + \is_string($static_type) + && $static_type !== $context->self + ); + + $return_type_location = $codebase->methods->getMethodReturnTypeLocation( + $method_id, + $secondary_return_type_location + ); + + if ($secondary_return_type_location) { + $return_type_location = $secondary_return_type_location; + } + + // only check the type locally if it's defined externally + if ($return_type_location && !$config->isInProjectDirs($return_type_location->file_path)) { + $return_type_candidate->check( + $statements_analyzer, + new CodeLocation($source, $stmt), + $statements_analyzer->getSuppressedIssues(), + $context->phantom_classes, + true, + false, + false, + $context->calling_method_id + ); + } + } + } + + $method_storage = $codebase->methods->getUserMethodStorage($method_id); + + if ($method_storage) { + if ($method_storage->abstract + && $stmt->class instanceof PhpParser\Node\Name + && (!$context->self + || !\Psalm\Internal\Type\Comparator\UnionTypeComparator::isContainedBy( + $codebase, + $context->vars_in_scope['$this'] + ?? new Type\Union([ + new Type\Atomic\TNamedObject($context->self) + ]), + new Type\Union([ + new Type\Atomic\TNamedObject($method_id->fq_class_name) + ]) + )) + ) { + if (IssueBuffer::accepts( + new AbstractMethodCall( + 'Cannot call an abstract static method ' . $method_id . ' directly', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return true; + } + + if (!$context->inside_throw) { + if ($context->pure && !$method_storage->pure) { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call an impure method from a pure context', + new CodeLocation($source, $stmt->name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($context->mutation_free && !$method_storage->mutation_free) { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call an possibly-mutating method from a mutation-free context', + new CodeLocation($source, $stmt->name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + && !$method_storage->pure + ) { + if (!$method_storage->mutation_free) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + } + + $statements_analyzer->getSource()->inferred_impure = true; + } + } + + $generic_params = $template_result->upper_bounds; + + if ($method_storage->assertions) { + self::applyAssertionsToContext( + $stmt->name, + null, + $method_storage->assertions, + $stmt->args, + $generic_params, + $context, + $statements_analyzer + ); + } + + if ($method_storage->if_true_assertions) { + $statements_analyzer->node_data->setIfTrueAssertions( + $stmt, + array_map( + function (Assertion $assertion) use ($generic_params) : Assertion { + return $assertion->getUntemplatedCopy($generic_params, null); + }, + $method_storage->if_true_assertions + ) + ); + } + + if ($method_storage->if_false_assertions) { + $statements_analyzer->node_data->setIfFalseAssertions( + $stmt, + array_map( + function (Assertion $assertion) use ($generic_params) : Assertion { + return $assertion->getUntemplatedCopy($generic_params, null); + }, + $method_storage->if_false_assertions + ) + ); + } + } + + if ($codebase->alter_code) { + foreach ($codebase->call_transforms as $original_pattern => $transformation) { + if ($declaring_method_id + && strtolower((string) $declaring_method_id) . '\((.*\))' === $original_pattern + ) { + if (strpos($transformation, '($1)') === strlen($transformation) - 4 + && $stmt->class instanceof PhpParser\Node\Name + ) { + $new_method_id = substr($transformation, 0, -4); + $old_declaring_fq_class_name = $declaring_method_id->fq_class_name; + [$new_fq_class_name, $new_method_name] = explode('::', $new_method_id); + + if ($codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $statements_analyzer, + $stmt->class, + $new_fq_class_name, + $context->calling_method_id, + strtolower($old_declaring_fq_class_name) !== strtolower($new_fq_class_name), + $stmt->class->parts[0] === 'self' + )) { + $moved_call = true; + } + + $file_manipulations = []; + + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $stmt->name->getAttribute('startFilePos'), + (int) $stmt->name->getAttribute('endFilePos') + 1, + $new_method_name + ); + + FileManipulationBuffer::add( + $statements_analyzer->getFilePath(), + $file_manipulations + ); + } + } + } + } + + if ($config->after_method_checks) { + $file_manipulations = []; + + $appearing_method_id = $codebase->methods->getAppearingMethodId($method_id); + + if ($appearing_method_id !== null && $declaring_method_id) { + foreach ($config->after_method_checks as $plugin_fq_class_name) { + $plugin_fq_class_name::afterMethodCallAnalysis( + $stmt, + (string) $method_id, + (string) $appearing_method_id, + (string) $declaring_method_id, + $context, + $source, + $codebase, + $file_manipulations, + $return_type_candidate + ); + } + } + + if ($file_manipulations) { + FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations); + } + } + + $return_type_candidate = $return_type_candidate ?: Type::getMixed(); + + self::taintReturnType( + $statements_analyzer, + $stmt, + $method_id, + $cased_method_id, + $return_type_candidate, + $method_storage + ); + + if ($stmt_type = $statements_analyzer->node_data->getType($stmt)) { + $statements_analyzer->node_data->setType( + $stmt, + Type::combineUnionTypes($stmt_type, $return_type_candidate) + ); + } else { + $statements_analyzer->node_data->setType($stmt, $return_type_candidate); + } + } else { + if ($stmt->name instanceof PhpParser\Node\Expr) { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + + ExpressionAnalyzer::analyze($statements_analyzer, $stmt->name, $context); + + $context->inside_use = $was_inside_use; + } + + if (!$context->ignore_variable_method) { + $codebase->analyzer->addMixedMemberName( + strtolower($fq_class_name) . '::', + $context->calling_method_id ?: $statements_analyzer->getFileName() + ); + } + + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $stmt->args, + null, + null, + true, + $context + ) === false) { + return false; + } + } + + if ($codebase->alter_code + && $fq_class_name + && !$moved_call + && $stmt->class instanceof PhpParser\Node\Name + && !in_array($stmt->class->parts[0], ['parent', 'static']) + ) { + $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $statements_analyzer, + $stmt->class, + $fq_class_name, + $context->calling_method_id, + false, + $stmt->class->parts[0] === 'self' + ); + } + + if ($codebase->store_node_types + && $method_id + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->name, + $method_id . '()' + ); + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + && ($stmt_type = $statements_analyzer->node_data->getType($stmt)) + ) { + $codebase->analyzer->addNodeType( + $statements_analyzer->getFilePath(), + $stmt->name, + $stmt_type->getId(), + $stmt + ); + } + } + + if ($method_id === null) { + return self::checkMethodArgs( + $method_id, + $stmt->args, + null, + $context, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer + ); + } + + if (!$config->remember_property_assignments_after_call && !$context->collect_initializations) { + $context->removeAllObjectVars(); + } + + if (!$statements_analyzer->node_data->getType($stmt)) { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } + + return true; + } + + private static function taintReturnType( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\StaticCall $stmt, + MethodIdentifier $method_id, + string $cased_method_id, + Type\Union $return_type_candidate, + ?\Psalm\Storage\MethodStorage $method_storage + ) : void { + if (!$statements_analyzer->data_flow_graph instanceof TaintFlowGraph + || \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + return; + } + + $code_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + + $method_location = $method_storage + ? ($method_storage->signature_return_type_location ?: $method_storage->location) + : null; + + if ($method_storage && $method_storage->specialize_call) { + $method_source = DataFlowNode::getForMethodReturn( + (string) $method_id, + $cased_method_id, + $method_location, + $code_location + ); + } else { + $method_source = DataFlowNode::getForMethodReturn( + (string) $method_id, + $cased_method_id, + $method_location + ); + } + + $statements_analyzer->data_flow_graph->addNode($method_source); + + $return_type_candidate->parent_nodes = [$method_source->id => $method_source]; + + if ($method_storage && $method_storage->taint_source_types) { + $method_node = TaintSource::getForMethodReturn( + (string) $method_id, + $cased_method_id, + $method_storage->signature_return_type_location ?: $method_storage->location + ); + + $method_node->taints = $method_storage->taint_source_types; + + $statements_analyzer->data_flow_graph->addSource($method_node); + } + } + + /** + * @param array $args + * @return false|null + */ + private static function checkPseudoMethod( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\StaticCall $stmt, + MethodIdentifier $method_id, + string $fq_class_name, + array $args, + \Psalm\Storage\ClassLikeStorage $class_storage, + \Psalm\Storage\MethodStorage $pseudo_method_storage, + Context $context + ): ?bool { + if (ArgumentsAnalyzer::analyze( + $statements_analyzer, + $args, + $pseudo_method_storage->params, + (string) $method_id, + true, + $context + ) === false) { + return false; + } + + $codebase = $statements_analyzer->getCodebase(); + + if (ArgumentsAnalyzer::checkArgumentsMatch( + $statements_analyzer, + $args, + $method_id, + $pseudo_method_storage->params, + $pseudo_method_storage, + null, + null, + new CodeLocation($statements_analyzer, $stmt), + $context + ) === false) { + return false; + } + + $method_storage = null; + + if ($statements_analyzer->data_flow_graph) { + try { + $method_storage = $codebase->methods->getStorage($method_id); + + ArgumentsAnalyzer::analyze( + $statements_analyzer, + $args, + $method_storage->params, + (string) $method_id, + true, + $context + ); + + ArgumentsAnalyzer::checkArgumentsMatch( + $statements_analyzer, + $args, + $method_id, + $method_storage->params, + $method_storage, + null, + null, + new CodeLocation($statements_analyzer, $stmt), + $context + ); + } catch (\Exception $e) { + // do nothing + } + } + + if ($pseudo_method_storage->return_type) { + $return_type_candidate = clone $pseudo_method_storage->return_type; + + $return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion( + $statements_analyzer->getCodebase(), + $return_type_candidate, + $fq_class_name, + $fq_class_name, + $class_storage->parent_class + ); + + if ($method_storage) { + self::taintReturnType( + $statements_analyzer, + $stmt, + $method_id, + (string) $method_id, + $return_type_candidate, + $method_storage + ); + } + + $stmt_type = $statements_analyzer->node_data->getType($stmt); + + if (!$stmt_type) { + $statements_analyzer->node_data->setType($stmt, $return_type_candidate); + } else { + $statements_analyzer->node_data->setType( + $stmt, + Type::combineUnionTypes( + $return_type_candidate, + $stmt_type + ) + ); + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..b2b8a4921a5dbdb4e150c275a1efd8c1deae254f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php @@ -0,0 +1,913 @@ +getFQCLN(); + + $project_analyzer = $source->getFileAnalyzer()->project_analyzer; + $codebase = $source->getCodebase(); + + if ($context->collect_mutations && + $context->self && + ( + $context->self === $fq_class_name || + $codebase->classExtends( + $context->self, + $fq_class_name + ) + ) + ) { + $method_id = new \Psalm\Internal\MethodIdentifier( + $fq_class_name, + strtolower($method_name) + ); + + if ((string) $method_id !== $source->getId()) { + if ($context->collect_initializations) { + if (isset($context->initialized_methods[(string) $method_id])) { + return; + } + + if ($context->initialized_methods === null) { + $context->initialized_methods = []; + } + + $context->initialized_methods[(string) $method_id] = true; + } + + $project_analyzer->getMethodMutations( + $method_id, + $context, + $source->getRootFilePath(), + $source->getRootFileName() + ); + } + } elseif ($context->collect_initializations && + $context->self && + ( + $context->self === $fq_class_name + || $codebase->classlikes->classExtends( + $context->self, + $fq_class_name + ) + ) && + $source->getMethodName() !== $method_name + ) { + $method_id = new \Psalm\Internal\MethodIdentifier($fq_class_name, strtolower($method_name)); + + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + if (isset($context->vars_in_scope['$this'])) { + foreach ($context->vars_in_scope['$this']->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof TNamedObject) { + if ($fq_class_name === $atomic_type->value) { + $alt_declaring_method_id = $declaring_method_id; + } else { + $fq_class_name = $atomic_type->value; + + $method_id = new \Psalm\Internal\MethodIdentifier( + $fq_class_name, + strtolower($method_name) + ); + + $alt_declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + } + + if ($alt_declaring_method_id) { + $declaring_method_id = $alt_declaring_method_id; + break; + } + + if (!$atomic_type->extra_types) { + continue; + } + + foreach ($atomic_type->extra_types as $intersection_type) { + if ($intersection_type instanceof TNamedObject) { + $fq_class_name = $intersection_type->value; + $method_id = new \Psalm\Internal\MethodIdentifier( + $fq_class_name, + strtolower($method_name) + ); + + $alt_declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + + if ($alt_declaring_method_id) { + $declaring_method_id = $alt_declaring_method_id; + break 2; + } + } + } + } + } + } + + if (!$declaring_method_id) { + // can happen for __call + return; + } + + if (isset($context->initialized_methods[(string) $declaring_method_id])) { + return; + } + + if ($context->initialized_methods === null) { + $context->initialized_methods = []; + } + + $context->initialized_methods[(string) $declaring_method_id] = true; + + $method_storage = $codebase->methods->getStorage($declaring_method_id); + + $class_analyzer = $source->getSource(); + + $is_final = $method_storage->final; + + if ($method_name !== $declaring_method_id->method_name) { + $appearing_method_id = $codebase->methods->getAppearingMethodId($method_id); + + if ($appearing_method_id) { + $appearing_class_storage = $codebase->classlike_storage_provider->get( + $appearing_method_id->fq_class_name + ); + + if (isset($appearing_class_storage->trait_final_map[strtolower($method_name)])) { + $is_final = true; + } + } + } + + if ($class_analyzer instanceof ClassLikeAnalyzer + && !$method_storage->is_static + && ($context->collect_nonprivate_initializations + || $method_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE + || $is_final) + ) { + $local_vars_in_scope = []; + $local_vars_possibly_in_scope = []; + + foreach ($context->vars_in_scope as $var => $_) { + if (strpos($var, '$this->') !== 0 && $var !== '$this') { + $local_vars_in_scope[$var] = $context->vars_in_scope[$var]; + } + } + + foreach ($context->vars_possibly_in_scope as $var => $_) { + if (strpos($var, '$this->') !== 0 && $var !== '$this') { + $local_vars_possibly_in_scope[$var] = $context->vars_possibly_in_scope[$var]; + } + } + + $old_calling_method_id = $context->calling_method_id; + + if ($fq_class_name === $source->getFQCLN()) { + $class_analyzer->getMethodMutations($declaring_method_id->method_name, $context); + } else { + $declaring_fq_class_name = $declaring_method_id->fq_class_name; + + $old_self = $context->self; + $context->self = $declaring_fq_class_name; + $project_analyzer->getMethodMutations( + $declaring_method_id, + $context, + $source->getRootFilePath(), + $source->getRootFileName() + ); + $context->self = $old_self; + } + + $context->calling_method_id = $old_calling_method_id; + + foreach ($local_vars_in_scope as $var => $type) { + $context->vars_in_scope[$var] = $type; + } + + foreach ($local_vars_possibly_in_scope as $var => $_) { + $context->vars_possibly_in_scope[$var] = true; + } + } + } + } + + /** + * @param array $args + */ + public static function checkMethodArgs( + ?\Psalm\Internal\MethodIdentifier $method_id, + array $args, + ?TemplateResult $class_template_result, + Context $context, + CodeLocation $code_location, + StatementsAnalyzer $statements_analyzer + ) : bool { + $codebase = $statements_analyzer->getCodebase(); + + if (!$method_id) { + return Call\ArgumentsAnalyzer::analyze( + $statements_analyzer, + $args, + null, + null, + true, + $context, + $class_template_result + ) !== false; + } + + $method_params = $codebase->methods->getMethodParams($method_id, $statements_analyzer, $args, $context); + + $fq_class_name = $method_id->fq_class_name; + $method_name = $method_id->method_name; + + $fq_class_name = strtolower($codebase->classlikes->getUnAliasedName($fq_class_name)); + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + $method_storage = null; + + if (isset($class_storage->declaring_method_ids[$method_name])) { + $declaring_method_id = $class_storage->declaring_method_ids[$method_name]; + + $declaring_fq_class_name = $declaring_method_id->fq_class_name; + $declaring_method_name = $declaring_method_id->method_name; + + if ($declaring_fq_class_name !== $fq_class_name) { + $declaring_class_storage = $codebase->classlike_storage_provider->get($declaring_fq_class_name); + } else { + $declaring_class_storage = $class_storage; + } + + if (!isset($declaring_class_storage->methods[$declaring_method_name])) { + throw new \UnexpectedValueException('Storage should not be empty here'); + } + + $method_storage = $declaring_class_storage->methods[$declaring_method_name]; + + if ($declaring_class_storage->user_defined + && !$method_storage->has_docblock_param_types + && isset($declaring_class_storage->documenting_method_ids[$method_name]) + ) { + $documenting_method_id = $declaring_class_storage->documenting_method_ids[$method_name]; + + $documenting_method_storage = $codebase->methods->getStorage($documenting_method_id); + + if ($documenting_method_storage->template_types) { + $method_storage = $documenting_method_storage; + } + } + + if (!$context->isSuppressingExceptions($statements_analyzer)) { + $context->mergeFunctionExceptions($method_storage, $code_location); + } + } + + if (Call\ArgumentsAnalyzer::analyze( + $statements_analyzer, + $args, + $method_params, + (string) $method_id, + $method_storage ? $method_storage->allow_named_arg_calls : true, + $context, + $class_template_result + ) === false) { + return false; + } + + if (Call\ArgumentsAnalyzer::checkArgumentsMatch( + $statements_analyzer, + $args, + $method_id, + $method_params, + $method_storage, + $class_storage, + $class_template_result, + $code_location, + $context + ) === false) { + return false; + } + + if ($class_template_result) { + self::checkTemplateResult( + $statements_analyzer, + $class_template_result, + $code_location, + strtolower((string) $method_id) + ); + } + + return true; + } + + /** + * @return array> + * @param array> $existing_template_types + */ + public static function getTemplateTypesForCall( + \Psalm\Codebase $codebase, + ?ClassLikeStorage $declaring_class_storage, + ?string $appearing_class_name, + ?ClassLikeStorage $calling_class_storage, + array $existing_template_types = [] + ) : array { + $template_types = $existing_template_types; + + if ($declaring_class_storage) { + if ($calling_class_storage + && $declaring_class_storage !== $calling_class_storage + && $calling_class_storage->template_type_extends + ) { + foreach ($calling_class_storage->template_type_extends as $class_name => $type_map) { + foreach ($type_map as $template_name => $type) { + if (is_string($template_name) && $class_name === $declaring_class_storage->name) { + $output_type = null; + + foreach ($type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TTemplateParam + && isset( + $calling_class_storage + ->template_type_extends + [$atomic_type->defining_class] + [$atomic_type->param_name] + ) + ) { + $output_type_candidate = $calling_class_storage + ->template_type_extends + [$atomic_type->defining_class] + [$atomic_type->param_name]; + } elseif ($atomic_type instanceof Type\Atomic\TTemplateParam) { + $output_type_candidate = $atomic_type->as; + } else { + $output_type_candidate = new Type\Union([$atomic_type]); + } + + if (!$output_type) { + $output_type = $output_type_candidate; + } else { + $output_type = Type::combineUnionTypes( + $output_type_candidate, + $output_type + ); + } + } + + $template_types[$template_name][$declaring_class_storage->name] = [$output_type]; + } + } + } + } elseif ($declaring_class_storage->template_types) { + foreach ($declaring_class_storage->template_types as $template_name => $type_map) { + foreach ($type_map as $key => [$type]) { + $template_types[$template_name][$key] = [$type]; + } + } + } + } + + foreach ($template_types as $key => $type_map) { + foreach ($type_map as $class => $type) { + $template_types[$key][$class][0] = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $type[0], + $appearing_class_name, + $calling_class_storage ? $calling_class_storage->name : null, + null, + true, + false, + $calling_class_storage ? $calling_class_storage->final : false + ); + } + } + + return $template_types; + } + + /** + * @param PhpParser\Node\Scalar\String_|PhpParser\Node\Expr\Array_|PhpParser\Node\Expr\BinaryOp\Concat + * $callable_arg + * + * @return list + * + * @psalm-suppress LessSpecificReturnStatement + * @psalm-suppress MoreSpecificReturnType + */ + public static function getFunctionIdsFromCallableArg( + \Psalm\FileSource $file_source, + PhpParser\Node\Expr $callable_arg + ): array { + if ($callable_arg instanceof PhpParser\Node\Expr\BinaryOp\Concat) { + if ($callable_arg->left instanceof PhpParser\Node\Expr\ClassConstFetch + && $callable_arg->left->class instanceof PhpParser\Node\Name + && $callable_arg->left->name instanceof PhpParser\Node\Identifier + && strtolower($callable_arg->left->name->name) === 'class' + && !in_array(strtolower($callable_arg->left->class->parts[0]), ['self', 'static', 'parent']) + && $callable_arg->right instanceof PhpParser\Node\Scalar\String_ + && preg_match('/^::[A-Za-z0-9]+$/', $callable_arg->right->value) + ) { + return [ + (string) $callable_arg->left->class->getAttribute('resolvedName') . $callable_arg->right->value + ]; + } + + return []; + } + + if ($callable_arg instanceof PhpParser\Node\Scalar\String_) { + $potential_id = preg_replace('/^\\\/', '', $callable_arg->value); + + if (preg_match('/^[A-Za-z0-9_]+(\\\[A-Za-z0-9_]+)*(::[A-Za-z0-9_]+)?$/', $potential_id)) { + return [$potential_id]; + } + + return []; + } + + if (count($callable_arg->items) !== 2) { + return []; + } + + /** @psalm-suppress PossiblyNullPropertyFetch */ + if ($callable_arg->items[0]->key || $callable_arg->items[1]->key) { + return []; + } + + if (!isset($callable_arg->items[0]) || !isset($callable_arg->items[1])) { + throw new \UnexpectedValueException('These should never be unset'); + } + + $class_arg = $callable_arg->items[0]->value; + $method_name_arg = $callable_arg->items[1]->value; + + if (!$method_name_arg instanceof PhpParser\Node\Scalar\String_) { + return []; + } + + if ($class_arg instanceof PhpParser\Node\Scalar\String_) { + return [preg_replace('/^\\\/', '', $class_arg->value) . '::' . $method_name_arg->value]; + } + + if ($class_arg instanceof PhpParser\Node\Expr\ClassConstFetch + && $class_arg->name instanceof PhpParser\Node\Identifier + && strtolower($class_arg->name->name) === 'class' + && $class_arg->class instanceof PhpParser\Node\Name + ) { + $fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject( + $class_arg->class, + $file_source->getAliases() + ); + + return [$fq_class_name . '::' . $method_name_arg->value]; + } + + if (!$file_source instanceof StatementsAnalyzer + || !($class_arg_type = $file_source->node_data->getType($class_arg)) + ) { + return []; + } + + $method_ids = []; + + foreach ($class_arg_type->getAtomicTypes() as $type_part) { + if ($type_part instanceof TNamedObject) { + $method_id = $type_part->value . '::' . $method_name_arg->value; + + if ($type_part->extra_types) { + foreach ($type_part->extra_types as $extra_type) { + if ($extra_type instanceof Type\Atomic\TTemplateParam + || $extra_type instanceof Type\Atomic\TObjectWithProperties + ) { + throw new \UnexpectedValueException('Shouldn’t get a generic param here'); + } + + $method_id .= '&' . $extra_type->value . '::' . $method_name_arg->value; + } + } + + $method_ids[] = '$' . $method_id; + } + } + + return $method_ids; + } + + /** + * @param non-empty-string $function_id + * @param bool $can_be_in_root_scope if true, the function can be shortened to the root version + * + */ + public static function checkFunctionExists( + StatementsAnalyzer $statements_analyzer, + string &$function_id, + CodeLocation $code_location, + bool $can_be_in_root_scope + ): bool { + $cased_function_id = $function_id; + $function_id = strtolower($function_id); + + $codebase = $statements_analyzer->getCodebase(); + + if (!$codebase->functions->functionExists($statements_analyzer, $function_id)) { + /** @var non-empty-lowercase-string */ + $root_function_id = preg_replace('/.*\\\/', '', $function_id); + + if ($can_be_in_root_scope + && $function_id !== $root_function_id + && $codebase->functions->functionExists($statements_analyzer, $root_function_id) + ) { + $function_id = $root_function_id; + } else { + if (IssueBuffer::accepts( + new UndefinedFunction( + 'Function ' . $cased_function_id . ' does not exist', + $code_location, + $function_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return false; + } + } + + return true; + } + + /** + * @param PhpParser\Node\Identifier|PhpParser\Node\Name $expr + * @param \Psalm\Storage\Assertion[] $assertions + * @param string $thisName + * @param array $args + * @param array> $template_type_map, + * + */ + protected static function applyAssertionsToContext( + PhpParser\NodeAbstract $expr, + ?string $thisName, + array $assertions, + array $args, + array $template_type_map, + Context $context, + StatementsAnalyzer $statements_analyzer + ): void { + $type_assertions = []; + + $asserted_keys = []; + + foreach ($assertions as $assertion) { + $assertion_var_id = null; + + $arg_value = null; + + if (is_int($assertion->var_id)) { + if (!isset($args[$assertion->var_id])) { + continue; + } + + $arg_value = $args[$assertion->var_id]->value; + + $arg_var_id = ExpressionIdentifier::getArrayVarId($arg_value, null, $statements_analyzer); + + if ($arg_var_id) { + $assertion_var_id = $arg_var_id; + } + } elseif ($assertion->var_id === '$this' && $thisName !== null) { + $assertion_var_id = $thisName; + } elseif (strpos($assertion->var_id, '$this->') === 0 && $thisName !== null) { + $assertion_var_id = $thisName . str_replace('$this->', '->', $assertion->var_id); + } elseif (isset($context->vars_in_scope[$assertion->var_id])) { + $assertion_var_id = $assertion->var_id; + } + + if ($assertion_var_id) { + $rule = $assertion->rule[0][0]; + + $prefix = ''; + if ($rule[0] === '!') { + $prefix .= '!'; + $rule = substr($rule, 1); + } + if ($rule[0] === '=') { + $prefix .= '='; + $rule = substr($rule, 1); + } + if ($rule[0] === '~') { + $prefix .= '~'; + $rule = substr($rule, 1); + } + + if (isset($template_type_map[$rule])) { + foreach ($template_type_map[$rule] as $template_map) { + if ($template_map[0]->hasMixed()) { + continue 2; + } + + $replacement_atomic_types = $template_map[0]->getAtomicTypes(); + + if (count($replacement_atomic_types) > 1) { + continue 2; + } + + $ored_type_assertions = []; + + foreach ($replacement_atomic_types as $replacement_atomic_type) { + if ($replacement_atomic_type instanceof Type\Atomic\TMixed) { + continue 3; + } + + if ($replacement_atomic_type instanceof Type\Atomic\TArray + || $replacement_atomic_type instanceof Type\Atomic\TKeyedArray + ) { + $ored_type_assertions[] = $prefix . 'array'; + } elseif ($replacement_atomic_type instanceof Type\Atomic\TNamedObject) { + $ored_type_assertions[] = $prefix . $replacement_atomic_type->value; + } elseif ($replacement_atomic_type instanceof Type\Atomic\Scalar) { + $ored_type_assertions[] = $prefix . $replacement_atomic_type->getId(); + } elseif ($replacement_atomic_type instanceof Type\Atomic\TNull) { + $ored_type_assertions[] = $prefix . 'null'; + } elseif ($replacement_atomic_type instanceof Type\Atomic\TTemplateParam) { + $ored_type_assertions[] = $prefix . $replacement_atomic_type->param_name; + } + } + + if ($ored_type_assertions) { + $type_assertions[$assertion_var_id] = [$ored_type_assertions]; + } + } + } else { + if (isset($type_assertions[$assertion_var_id])) { + $type_assertions[$assertion_var_id] = array_merge( + $type_assertions[$assertion_var_id], + $assertion->rule + ); + } else { + $type_assertions[$assertion_var_id] = $assertion->rule; + } + } + } elseif ($arg_value && ($assertion->rule === [['!falsy']] || $assertion->rule === [['true']])) { + if ($assertion->rule === [['true']]) { + $conditional = new PhpParser\Node\Expr\BinaryOp\Identical( + $arg_value, + new PhpParser\Node\Expr\ConstFetch(new PhpParser\Node\Name('true')) + ); + + $assert_clauses = \Psalm\Type\Algebra::getFormula( + \mt_rand(0, 1000000), + \mt_rand(0, 1000000), + $conditional, + $context->self, + $statements_analyzer, + $statements_analyzer->getCodebase() + ); + } else { + $assert_clauses = \Psalm\Type\Algebra::getFormula( + \spl_object_id($arg_value), + \spl_object_id($arg_value), + $arg_value, + $context->self, + $statements_analyzer, + $statements_analyzer->getCodebase() + ); + } + + $simplified_clauses = \Psalm\Type\Algebra::simplifyCNF( + array_merge($context->clauses, $assert_clauses) + ); + + $assert_type_assertions = \Psalm\Type\Algebra::getTruthsFromFormula( + $simplified_clauses + ); + + $type_assertions = array_merge($type_assertions, $assert_type_assertions); + } elseif ($arg_value && $assertion->rule === [['falsy']]) { + $assert_clauses = \Psalm\Type\Algebra::negateFormula( + \Psalm\Type\Algebra::getFormula( + \spl_object_id($arg_value), + \spl_object_id($arg_value), + $arg_value, + $context->self, + $statements_analyzer, + $statements_analyzer->getCodebase() + ) + ); + + $simplified_clauses = \Psalm\Type\Algebra::simplifyCNF( + array_merge($context->clauses, $assert_clauses) + ); + + $assert_type_assertions = \Psalm\Type\Algebra::getTruthsFromFormula( + $simplified_clauses + ); + + $type_assertions = array_merge($type_assertions, $assert_type_assertions); + } + } + + $changed_var_ids = []; + + foreach ($type_assertions as $var_id => $_) { + $asserted_keys[$var_id] = true; + } + + if ($type_assertions) { + foreach (($statements_analyzer->getTemplateTypeMap() ?: []) as $template_name => $map) { + foreach ($map as $ref => [$type]) { + $template_type_map[$template_name][$ref] = [ + new Type\Union([ + new Type\Atomic\TTemplateParam( + $template_name, + $type, + $ref + ) + ]) + ]; + } + } + + // while in an and, we allow scope to boil over to support + // statements of the form if ($x && $x->foo()) + $op_vars_in_scope = \Psalm\Type\Reconciler::reconcileKeyedTypes( + $type_assertions, + $type_assertions, + $context->vars_in_scope, + $changed_var_ids, + $asserted_keys, + $statements_analyzer, + $template_type_map, + $context->inside_loop, + new CodeLocation($statements_analyzer->getSource(), $expr) + ); + + foreach ($changed_var_ids as $var_id => $_) { + if (isset($op_vars_in_scope[$var_id])) { + $first_appearance = $statements_analyzer->getFirstAppearance($var_id); + + $codebase = $statements_analyzer->getCodebase(); + + if ($first_appearance + && isset($context->vars_in_scope[$var_id]) + && $context->vars_in_scope[$var_id]->hasMixed() + ) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->decrementMixedCount($statements_analyzer->getFilePath()); + } + + IssueBuffer::remove( + $statements_analyzer->getFilePath(), + 'MixedAssignment', + $first_appearance->raw_file_start + ); + } + + $op_vars_in_scope[$var_id]->from_docblock = true; + + foreach ($op_vars_in_scope[$var_id]->getAtomicTypes() as $changed_atomic_type) { + $changed_atomic_type->from_docblock = true; + + if ($changed_atomic_type instanceof Type\Atomic\TNamedObject + && $changed_atomic_type->extra_types + ) { + foreach ($changed_atomic_type->extra_types as $extra_type) { + $extra_type->from_docblock = true; + } + } + } + } + } + + $context->vars_in_scope = $op_vars_in_scope; + } + } + + public static function checkTemplateResult( + StatementsAnalyzer $statements_analyzer, + TemplateResult $template_result, + CodeLocation $code_location, + ?string $function_id + ) : void { + if ($template_result->upper_bounds && $template_result->lower_bounds) { + foreach ($template_result->lower_bounds as $template_name => $defining_map) { + foreach ($defining_map as $defining_id => [$lower_bound_type]) { + if (isset($template_result->upper_bounds[$template_name][$defining_id])) { + $upper_bound_type = $template_result->upper_bounds[$template_name][$defining_id][0]; + + $union_comparison_result = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + if (count($template_result->lower_bounds_unintersectable_types) > 1) { + [$upper_bound_type, $lower_bound_type] + = $template_result->lower_bounds_unintersectable_types; + } + + if (!UnionTypeComparator::isContainedBy( + $statements_analyzer->getCodebase(), + $upper_bound_type, + $lower_bound_type, + false, + false, + $union_comparison_result + )) { + if ($union_comparison_result->type_coerced) { + if ($union_comparison_result->type_coerced_from_mixed) { + if (IssueBuffer::accepts( + new MixedArgumentTypeCoercion( + 'Type ' . $upper_bound_type->getId() . ' should be a subtype of ' + . $lower_bound_type->getId(), + $code_location, + $function_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // continue + } + } else { + if (IssueBuffer::accepts( + new ArgumentTypeCoercion( + 'Type ' . $upper_bound_type->getId() . ' should be a subtype of ' + . $lower_bound_type->getId(), + $code_location, + $function_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // continue + } + } + } elseif ($union_comparison_result->scalar_type_match_found) { + if (IssueBuffer::accepts( + new InvalidScalarArgument( + 'Type ' . $upper_bound_type->getId() . ' should be a subtype of ' + . $lower_bound_type->getId(), + $code_location, + $function_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // continue + } + } else { + if (IssueBuffer::accepts( + new InvalidArgument( + 'Type ' . $upper_bound_type->getId() . ' should be a subtype of ' + . $lower_bound_type->getId(), + $code_location, + $function_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // continue + } + } + } + } else { + $template_result->upper_bounds[$template_name][$defining_id][0] = clone $lower_bound_type; + } + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..bba6e2c1232ef6625fd19a6692bac0ff0ad71c2e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -0,0 +1,367 @@ +expr, $context) === false) { + return false; + } + + $as_int = true; + $maybe_type = $statements_analyzer->node_data->getType($stmt->expr); + + if ($maybe_type) { + $maybe = $maybe_type->getAtomicTypes(); + + if (count($maybe) === 1 && current($maybe) instanceof Type\Atomic\TBool) { + $as_int = false; + $statements_analyzer->node_data->setType($stmt, new Type\Union([ + new Type\Atomic\TLiteralInt(0), + new Type\Atomic\TLiteralInt(1), + ])); + } + } + + if ($as_int) { + $type = Type::getInt(); + + if ($statements_analyzer->data_flow_graph + && $statements_analyzer->data_flow_graph instanceof \Psalm\Internal\Codebase\VariableUseGraph + ) { + $type->parent_nodes = $maybe_type ? $maybe_type->parent_nodes : []; + } + + $statements_analyzer->node_data->setType($stmt, $type); + } + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\Double) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + + $statements_analyzer->node_data->setType($stmt, Type::getFloat()); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\Bool_) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + + $statements_analyzer->node_data->setType($stmt, Type::getBool()); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\String_) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + + $stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr); + + if ($stmt_expr_type) { + $stmt_type = self::castStringAttempt( + $statements_analyzer, + $context, + $stmt_expr_type, + $stmt->expr, + true + ); + } else { + $stmt_type = Type::getString(); + } + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\Object_) { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + $context->inside_use = $was_inside_use; + + $statements_analyzer->node_data->setType($stmt, new Type\Union([new TNamedObject('stdClass')])); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\Array_) { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + $context->inside_use = $was_inside_use; + + $permissible_atomic_types = []; + $all_permissible = false; + + if ($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr)) { + $all_permissible = true; + + foreach ($stmt_expr_type->getAtomicTypes() as $type) { + if ($type instanceof Scalar) { + $permissible_atomic_types[] = new TKeyedArray([new Type\Union([$type])]); + } elseif ($type instanceof TNull) { + $permissible_atomic_types[] = new TArray([Type::getEmpty(), Type::getEmpty()]); + } elseif ($type instanceof TArray + || $type instanceof TList + || $type instanceof TKeyedArray + ) { + $permissible_atomic_types[] = clone $type; + } else { + $all_permissible = false; + break; + } + } + } + + if ($permissible_atomic_types && $all_permissible) { + $statements_analyzer->node_data->setType( + $stmt, + TypeCombination::combineTypes($permissible_atomic_types) + ); + } else { + $statements_analyzer->node_data->setType($stmt, Type::getArray()); + } + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\Unset_ + && $statements_analyzer->getCodebase()->php_major_version < 8 + ) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + + $statements_analyzer->node_data->setType($stmt, Type::getNull()); + + return true; + } + + if (IssueBuffer::accepts( + new UnrecognizedExpression( + 'Psalm does not understand the cast ' . get_class($stmt), + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return false; + } + + public static function castStringAttempt( + StatementsAnalyzer $statements_analyzer, + Context $context, + Type\Union $stmt_type, + PhpParser\Node\Expr $stmt, + bool $explicit_cast = false + ) : Type\Union { + $codebase = $statements_analyzer->getCodebase(); + + $invalid_casts = []; + $valid_strings = []; + $castable_types = []; + + $atomic_types = $stmt_type->getAtomicTypes(); + + $parent_nodes = []; + + if ($statements_analyzer->data_flow_graph) { + $parent_nodes = $stmt_type->parent_nodes; + } + + while ($atomic_types) { + $atomic_type = \array_pop($atomic_types); + + if ($atomic_type instanceof TFloat + || $atomic_type instanceof TInt + || $atomic_type instanceof Type\Atomic\TNumeric + ) { + $castable_types[] = new Type\Atomic\TNumericString(); + continue; + } + + if ($atomic_type instanceof TString) { + $valid_strings[] = $atomic_type; + + continue; + } + + if ($atomic_type instanceof TNull + || $atomic_type instanceof Type\Atomic\TFalse + ) { + $valid_strings[] = new Type\Atomic\TLiteralString(''); + continue; + } + + if ($atomic_type instanceof TMixed + || $atomic_type instanceof Type\Atomic\TResource + || $atomic_type instanceof Type\Atomic\Scalar + ) { + $castable_types[] = new TString(); + + continue; + } + + if ($atomic_type instanceof TNamedObject + || $atomic_type instanceof Type\Atomic\TObjectWithProperties + ) { + $intersection_types = [$atomic_type]; + + if ($atomic_type->extra_types) { + $intersection_types = array_merge($intersection_types, $atomic_type->extra_types); + } + + foreach ($intersection_types as $intersection_type) { + if ($intersection_type instanceof TNamedObject) { + $intersection_method_id = new \Psalm\Internal\MethodIdentifier( + $intersection_type->value, + '__tostring' + ); + + if ($codebase->methods->methodExists( + $intersection_method_id, + $context->calling_method_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + )) { + $return_type = $codebase->methods->getMethodReturnType( + $intersection_method_id, + $self_class + ) ?: Type::getString(); + + $declaring_method_id = $codebase->methods->getDeclaringMethodId($intersection_method_id); + + MethodCallReturnTypeFetcher::taintMethodCallResult( + $statements_analyzer, + $return_type, + $stmt, + $stmt, + $intersection_method_id, + $declaring_method_id, + $intersection_type->value . '::__toString', + $context + ); + + if ($statements_analyzer->data_flow_graph) { + $parent_nodes = array_merge($return_type->parent_nodes, $parent_nodes); + } + + $castable_types = array_merge( + $castable_types, + array_values($return_type->getAtomicTypes()) + ); + + continue 2; + } + } + + if ($intersection_type instanceof Type\Atomic\TObjectWithProperties + && isset($intersection_type->methods['__toString']) + ) { + $castable_types[] = new TString(); + + continue 2; + } + } + } + + if ($atomic_type instanceof Type\Atomic\TTemplateParam) { + $atomic_types = array_merge($atomic_types, $atomic_type->as->getAtomicTypes()); + + continue; + } + + $invalid_casts[] = $atomic_type->getId(); + } + + if ($invalid_casts) { + if ($valid_strings || $castable_types) { + if (IssueBuffer::accepts( + new PossiblyInvalidCast( + $invalid_casts[0] . ' cannot be cast to string', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidCast( + $invalid_casts[0] . ' cannot be cast to string', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } elseif ($explicit_cast && !$castable_types) { + // todo: emit error here + } + + $valid_types = array_merge($valid_strings, $castable_types); + + if (!$valid_types) { + $str_type = Type::getString(); + } else { + $str_type = \Psalm\Internal\Type\TypeCombination::combineTypes( + $valid_types, + $codebase + ); + } + + if ($statements_analyzer->data_flow_graph) { + $str_type->parent_nodes = $parent_nodes; + } + + return $str_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..b895ef70855e4690c2b953a04a7acb1f0f722cd1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php @@ -0,0 +1,148 @@ +getCodebase(); + $codebase_methods = $codebase->methods; + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + + $stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr); + + if ($stmt_expr_type) { + $clone_type = $stmt_expr_type; + + $immutable_cloned = false; + + $invalid_clones = []; + $mixed_clone = false; + + $possibly_valid = false; + $atomic_types = $clone_type->getAtomicTypes(); + + while ($atomic_types) { + $clone_type_part = \array_pop($atomic_types); + + if ($clone_type_part instanceof TMixed) { + $mixed_clone = true; + } elseif ($clone_type_part instanceof TObject) { + $possibly_valid = true; + } elseif ($clone_type_part instanceof TNamedObject) { + if (!$codebase->classlikes->classOrInterfaceExists($clone_type_part->value)) { + $invalid_clones[] = $clone_type_part->getId(); + } else { + $clone_method_id = new \Psalm\Internal\MethodIdentifier( + $clone_type_part->value, + '__clone' + ); + + $does_method_exist = $codebase_methods->methodExists( + $clone_method_id, + $context->calling_method_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ); + $is_method_visible = MethodAnalyzer::isMethodVisible( + $clone_method_id, + $context, + $statements_analyzer->getSource() + ); + if ($does_method_exist && !$is_method_visible) { + $invalid_clones[] = $clone_type_part->getId(); + } else { + $possibly_valid = true; + $immutable_cloned = true; + } + } + } elseif ($clone_type_part instanceof TTemplateParam) { + $atomic_types = \array_merge($atomic_types, $clone_type_part->as->getAtomicTypes()); + } else { + if ($clone_type_part instanceof Type\Atomic\TFalse + && $clone_type->ignore_falsable_issues + ) { + continue; + } + + if ($clone_type_part instanceof Type\Atomic\TNull + && $clone_type->ignore_nullable_issues + ) { + continue; + } + + $invalid_clones[] = $clone_type_part->getId(); + } + } + + if ($mixed_clone) { + if (IssueBuffer::accepts( + new MixedClone( + 'Cannot clone mixed', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($invalid_clones) { + if ($possibly_valid) { + if (IssueBuffer::accepts( + new PossiblyInvalidClone( + 'Cannot clone ' . $invalid_clones[0], + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidClone( + 'Cannot clone ' . $invalid_clones[0], + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + return true; + } + + $statements_analyzer->node_data->setType($stmt, $stmt_expr_type); + + if ($immutable_cloned) { + $stmt_expr_type = clone $stmt_expr_type; + $statements_analyzer->node_data->setType($stmt, $stmt_expr_type); + $stmt_expr_type->reference_free = true; + $stmt_expr_type->allow_mutations = true; + } + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..cac351b7184ae2b421f59e7e6617b86f1bfad189 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php @@ -0,0 +1,39 @@ +expr, $context); + + if (($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr)) + && $stmt_expr_type->hasBool() + && $stmt_expr_type->isSingle() + && !$stmt_expr_type->from_docblock + ) { + if (IssueBuffer::accepts( + new \Psalm\Issue\InvalidArgument( + 'Calling empty on a boolean value is almost certainly unintended', + new CodeLocation($statements_analyzer->getSource(), $stmt->expr), + 'empty' + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $statements_analyzer->node_data->setType($stmt, Type::getBool()); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..d0ebbc05329e4b12d83c7e038518d7aa99331e44 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php @@ -0,0 +1,59 @@ +parts as $part) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $part, $context) === false) { + return false; + } + + $part_type = $statements_analyzer->node_data->getType($part); + + if ($part_type) { + $casted_part_type = CastAnalyzer::castStringAttempt( + $statements_analyzer, + $context, + $part_type, + $part + ); + + if ($statements_analyzer->data_flow_graph + && !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + $var_location = new CodeLocation($statements_analyzer, $part); + + $new_parent_node = DataFlowNode::getForAssignment('concat', $var_location); + $statements_analyzer->data_flow_graph->addNode($new_parent_node); + + $stmt_type->parent_nodes[$new_parent_node->id] = $new_parent_node; + + if ($casted_part_type->parent_nodes) { + foreach ($casted_part_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath($parent_node, $new_parent_node, 'concat'); + } + } + } + } + } + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..3c71c5018dfdc00f7f6a50bdb74bea7bbd4007ec --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php @@ -0,0 +1,54 @@ +expr, $context); + + $expr_type = $statements_analyzer->node_data->getType($stmt->expr); + + if ($expr_type) { + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph + && $expr_type->parent_nodes + && !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + $arg_location = new CodeLocation($statements_analyzer->getSource(), $stmt->expr); + + $eval_param_sink = TaintSink::getForMethodArgument( + 'eval', + 'eval', + 0, + $arg_location, + $arg_location + ); + + $eval_param_sink->taints = [\Psalm\Type\TaintKind::INPUT_TEXT]; + + $statements_analyzer->data_flow_graph->addSink($eval_param_sink); + + foreach ($expr_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath($parent_node, $eval_param_sink, 'arg'); + } + } + } + + $context->check_classes = false; + $context->check_variables = false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..e96c833df7fc440b39d804e3d10c13ebdba864eb --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php @@ -0,0 +1,83 @@ +expr) { + $context->inside_call = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $call_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + + $echo_param_sink = TaintSink::getForMethodArgument( + 'exit', + 'exit', + 0, + null, + $call_location + ); + + $echo_param_sink->taints = [ + Type\TaintKind::INPUT_HTML, + Type\TaintKind::USER_SECRET, + Type\TaintKind::SYSTEM_SECRET + ]; + + $statements_analyzer->data_flow_graph->addSink($echo_param_sink); + } + + if ($expr_type = $statements_analyzer->node_data->getType($stmt->expr)) { + $exit_param = new FunctionLikeParameter( + 'var', + false + ); + + if (ArgumentAnalyzer::verifyType( + $statements_analyzer, + $expr_type, + new Type\Union([new TInt(), new TString()]), + null, + 'exit', + 0, + new CodeLocation($statements_analyzer->getSource(), $stmt->expr), + $stmt->expr, + $context, + $exit_param, + false, + null, + true, + true, + new CodeLocation($statements_analyzer, $stmt) + ) === false) { + return false; + } + } + + $context->inside_call = false; + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ExpressionIdentifier.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ExpressionIdentifier.php new file mode 100644 index 0000000000000000000000000000000000000000..9d141a34eff2490db0d14d1be13d09f9c4e3b7dc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/ExpressionIdentifier.php @@ -0,0 +1,219 @@ +name)) { + return '$' . $stmt->name; + } + + if ($stmt instanceof PhpParser\Node\Expr\StaticPropertyFetch + && $stmt->name instanceof PhpParser\Node\Identifier + && $stmt->class instanceof PhpParser\Node\Name + ) { + if (count($stmt->class->parts) === 1 + && in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true) + ) { + if (!$this_class_name) { + $fq_class_name = $stmt->class->parts[0]; + } else { + $fq_class_name = $this_class_name; + } + } else { + $fq_class_name = $source + ? ClassLikeAnalyzer::getFQCLNFromNameObject( + $stmt->class, + $source->getAliases() + ) + : implode('\\', $stmt->class->parts); + } + + return $fq_class_name . '::$' . $stmt->name->name; + } + + if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch && $stmt->name instanceof PhpParser\Node\Identifier) { + $object_id = self::getVarId($stmt->var, $this_class_name, $source); + + if (!$object_id) { + return null; + } + + return $object_id . '->' . $stmt->name->name; + } + + if ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch && $nesting !== null) { + ++$nesting; + + return self::getVarId($stmt->var, $this_class_name, $source, $nesting); + } + + return null; + } + + public static function getRootVarId( + PhpParser\Node\Expr $stmt, + ?string $this_class_name, + ?FileSource $source = null + ): ?string { + if ($stmt instanceof PhpParser\Node\Expr\Variable + || $stmt instanceof PhpParser\Node\Expr\StaticPropertyFetch + ) { + return self::getVarId($stmt, $this_class_name, $source); + } + + if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch && $stmt->name instanceof PhpParser\Node\Identifier) { + $property_root = self::getRootVarId($stmt->var, $this_class_name, $source); + + if ($property_root) { + return $property_root . '->' . $stmt->name->name; + } + } + + if ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch) { + return self::getRootVarId($stmt->var, $this_class_name, $source); + } + + return null; + } + + public static function getArrayVarId( + PhpParser\Node\Expr $stmt, + ?string $this_class_name, + ?FileSource $source = null + ): ?string { + if ($stmt instanceof PhpParser\Node\Expr\Assign) { + return self::getArrayVarId($stmt->var, $this_class_name, $source); + } + + if ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch) { + $root_var_id = self::getArrayVarId($stmt->var, $this_class_name, $source); + + $offset = null; + + if ($root_var_id) { + if ($stmt->dim instanceof PhpParser\Node\Scalar\String_ + || $stmt->dim instanceof PhpParser\Node\Scalar\LNumber + ) { + $offset = $stmt->dim instanceof PhpParser\Node\Scalar\String_ + ? '\'' . $stmt->dim->value . '\'' + : $stmt->dim->value; + } elseif ($stmt->dim instanceof PhpParser\Node\Expr\Variable + && is_string($stmt->dim->name) + ) { + $offset = '$' . $stmt->dim->name; + } elseif ($stmt->dim instanceof PhpParser\Node\Expr\ConstFetch) { + $offset = implode('\\', $stmt->dim->name->parts); + } elseif ($stmt->dim instanceof PhpParser\Node\Expr\PropertyFetch) { + $object_id = self::getArrayVarId($stmt->dim->var, $this_class_name, $source); + + if ($object_id && $stmt->dim->name instanceof PhpParser\Node\Identifier) { + $offset = $object_id . '->' . $stmt->dim->name; + } + } elseif ($stmt->dim instanceof PhpParser\Node\Expr\ClassConstFetch + && $stmt->dim->name instanceof PhpParser\Node\Identifier + && $stmt->dim->class instanceof PhpParser\Node\Name + && $stmt->dim->class->parts[0] === 'static' + ) { + $offset = 'static::' . $stmt->dim->name; + } elseif ($stmt->dim + && $source instanceof StatementsAnalyzer + && ($stmt_dim_type = $source->node_data->getType($stmt->dim)) + && (!$stmt->dim instanceof PhpParser\Node\Expr\ClassConstFetch + || !$stmt->dim->name instanceof PhpParser\Node\Identifier + || $stmt->dim->name->name !== 'class' + ) + ) { + if ($stmt_dim_type->isSingleStringLiteral()) { + $offset = '\'' . $stmt_dim_type->getSingleStringLiteral()->value . '\''; + } elseif ($stmt_dim_type->isSingleIntLiteral()) { + $offset = $stmt_dim_type->getSingleIntLiteral()->value; + } + } elseif ($stmt->dim instanceof PhpParser\Node\Expr\ClassConstFetch + && $stmt->dim->name instanceof PhpParser\Node\Identifier + ) { + /** @var string|null */ + $resolved_name = $stmt->dim->class->getAttribute('resolvedName'); + + if ($resolved_name) { + $offset = $resolved_name . '::' . $stmt->dim->name; + } + } + + return $offset !== null ? $root_var_id . '[' . $offset . ']' : null; + } + } + + if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch) { + $object_id = self::getArrayVarId($stmt->var, $this_class_name, $source); + + if (!$object_id) { + return null; + } + + if ($stmt->name instanceof PhpParser\Node\Identifier) { + return $object_id . '->' . $stmt->name; + } elseif ($source instanceof StatementsAnalyzer + && ($stmt_name_type = $source->node_data->getType($stmt->name)) + && $stmt_name_type->isSingleStringLiteral() + ) { + return $object_id . '->' . $stmt_name_type->getSingleStringLiteral()->value; + } else { + return null; + } + } + + if ($stmt instanceof PhpParser\Node\Expr\ClassConstFetch + && $stmt->name instanceof PhpParser\Node\Identifier + ) { + /** @var string|null */ + $resolved_name = $stmt->class->getAttribute('resolvedName'); + + if ($resolved_name) { + if (($resolved_name === 'self' || $resolved_name === 'static') && $this_class_name) { + $resolved_name = $this_class_name; + } + + return $resolved_name . '::' . $stmt->name; + } + } + + if ($stmt instanceof PhpParser\Node\Expr\MethodCall + && $stmt->name instanceof PhpParser\Node\Identifier + && !$stmt->args + ) { + $config = \Psalm\Config::getInstance(); + + if ($config->memoize_method_calls || isset($stmt->pure)) { + $lhs_var_name = self::getArrayVarId( + $stmt->var, + $this_class_name, + $source + ); + + if (!$lhs_var_name) { + return null; + } + + return $lhs_var_name . '->' . strtolower($stmt->name->name) . '()'; + } + } + + return self::getVarId($stmt, $this_class_name, $source); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..ade6a31d8dd62cd3e57b9c8fa973d9c00bdcbab6 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -0,0 +1,1640 @@ +var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($stmt->dim) { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->dim, $context) === false) { + return false; + } + $context->inside_use = $was_inside_use; + } + + $keyed_array_var_id = ExpressionIdentifier::getArrayVarId( + $stmt, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + $dim_var_id = null; + $new_offset_type = null; + + if ($stmt->dim) { + $used_key_type = $statements_analyzer->node_data->getType($stmt->dim) ?: Type::getMixed(); + + $dim_var_id = ExpressionIdentifier::getArrayVarId( + $stmt->dim, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + } else { + $used_key_type = Type::getInt(); + } + + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $stmt->var, + $context + ) === false) { + return false; + } + + $stmt_var_type = $statements_analyzer->node_data->getType($stmt->var); + + $codebase = $statements_analyzer->getCodebase(); + + if ($keyed_array_var_id + && $context->hasVariable($keyed_array_var_id) + && !$context->vars_in_scope[$keyed_array_var_id]->possibly_undefined + && $stmt_var_type + && !$stmt_var_type->hasClassStringMap() + ) { + $stmt_type = clone $context->vars_in_scope[$keyed_array_var_id]; + + $statements_analyzer->node_data->setType( + $stmt, + $stmt_type + ); + + self::taintArrayFetch( + $statements_analyzer, + $stmt->var, + $keyed_array_var_id, + $stmt_type, + $used_key_type + ); + + return true; + } + + $can_store_result = false; + + if ($stmt_var_type) { + if ($stmt_var_type->isNull()) { + if (!$context->inside_isset) { + if (IssueBuffer::accepts( + new NullArrayAccess( + 'Cannot access array value on null variable ' . $array_var_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($stmt_type = $statements_analyzer->node_data->getType($stmt)) { + $statements_analyzer->node_data->setType( + $stmt, + Type::combineUnionTypes($stmt_type, Type::getNull()) + ); + } else { + $statements_analyzer->node_data->setType($stmt, Type::getNull()); + } + + return true; + } + + $stmt_type = self::getArrayAccessTypeGivenOffset( + $statements_analyzer, + $stmt, + $stmt_var_type, + $used_key_type, + false, + $array_var_id, + $context, + null + ); + + if ($stmt->dim && $stmt_var_type->hasArray()) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var TArray|TKeyedArray|TList|Type\Atomic\TClassStringMap + */ + $array_type = $stmt_var_type->getAtomicTypes()['array']; + + if ($array_type instanceof Type\Atomic\TClassStringMap) { + $array_value_type = Type::getMixed(); + } elseif ($array_type instanceof TArray) { + $array_value_type = $array_type->type_params[1]; + } elseif ($array_type instanceof TList) { + $array_value_type = $array_type->type_param; + } else { + $array_value_type = $array_type->getGenericValueType(); + } + + if ($context->inside_assignment || !$array_value_type->isMixed()) { + $can_store_result = true; + } + } + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + if ($context->inside_isset + && $stmt->dim + && ($stmt_dim_type = $statements_analyzer->node_data->getType($stmt->dim)) + && $stmt_var_type->hasArray() + && ($stmt->var instanceof PhpParser\Node\Expr\ClassConstFetch + || $stmt->var instanceof PhpParser\Node\Expr\ConstFetch) + ) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var TArray|TKeyedArray|TList + */ + $array_type = $stmt_var_type->getAtomicTypes()['array']; + + if ($array_type instanceof TArray) { + $const_array_key_type = $array_type->type_params[0]; + } elseif ($array_type instanceof TList) { + $const_array_key_type = Type::getInt(); + } else { + $const_array_key_type = $array_type->getGenericKeyType(); + } + + if ($dim_var_id + && !$const_array_key_type->hasMixed() + && !$stmt_dim_type->hasMixed() + ) { + $new_offset_type = clone $stmt_dim_type; + $const_array_key_atomic_types = $const_array_key_type->getAtomicTypes(); + + foreach ($new_offset_type->getAtomicTypes() as $offset_key => $offset_atomic_type) { + if ($offset_atomic_type instanceof TString + || $offset_atomic_type instanceof TInt + ) { + if (!isset($const_array_key_atomic_types[$offset_key]) + && !UnionTypeComparator::isContainedBy( + $codebase, + new Type\Union([$offset_atomic_type]), + $const_array_key_type + ) + ) { + $new_offset_type->removeType($offset_key); + } + } elseif (!UnionTypeComparator::isContainedBy( + $codebase, + $const_array_key_type, + new Type\Union([$offset_atomic_type]) + )) { + $new_offset_type->removeType($offset_key); + } + } + } + } + } + + if ($keyed_array_var_id + && $context->hasVariable($keyed_array_var_id) + && (!($stmt_type = $statements_analyzer->node_data->getType($stmt)) || $stmt_type->isVanillaMixed()) + ) { + $statements_analyzer->node_data->setType($stmt, $context->vars_in_scope[$keyed_array_var_id]); + } + + if (!($stmt_type = $statements_analyzer->node_data->getType($stmt))) { + $stmt_type = Type::getMixed(); + $statements_analyzer->node_data->setType($stmt, $stmt_type); + } else { + if ($stmt_type->possibly_undefined + && !$context->inside_isset + && !$context->inside_unset + && ($stmt_var_type && !$stmt_var_type->hasMixed()) + ) { + if (IssueBuffer::accepts( + new PossiblyUndefinedArrayOffset( + 'Possibly undefined array key ' . $keyed_array_var_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $stmt_type->possibly_undefined = false; + } + + if ($context->inside_isset && $dim_var_id && $new_offset_type && $new_offset_type->getAtomicTypes()) { + $context->vars_in_scope[$dim_var_id] = $new_offset_type; + } + + if ($keyed_array_var_id && !$context->inside_isset && $can_store_result) { + $context->vars_in_scope[$keyed_array_var_id] = $stmt_type; + $context->vars_possibly_in_scope[$keyed_array_var_id] = true; + + // reference the variable too + $context->hasVariable($keyed_array_var_id); + } + + self::taintArrayFetch( + $statements_analyzer, + $stmt->var, + $keyed_array_var_id, + $stmt_type, + $used_key_type + ); + + return true; + } + + /** + * Used to create a path between a variable $foo and $foo["a"] + */ + public static function taintArrayFetch( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr $var, + ?string $keyed_array_var_id, + Type\Union $stmt_type, + Type\Union $offset_type + ) : void { + if ($statements_analyzer->data_flow_graph + && ($stmt_var_type = $statements_analyzer->node_data->getType($var)) + && $stmt_var_type->parent_nodes + ) { + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph + && \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + $stmt_var_type->parent_nodes = []; + return; + } + + $var_location = new CodeLocation($statements_analyzer->getSource(), $var); + + $new_parent_node = \Psalm\Internal\DataFlow\DataFlowNode::getForAssignment( + $keyed_array_var_id ?: 'array-fetch', + $var_location + ); + + $statements_analyzer->data_flow_graph->addNode($new_parent_node); + + $dim_value = $offset_type->isSingleStringLiteral() + ? $offset_type->getSingleStringLiteral()->value + : ($offset_type->isSingleIntLiteral() + ? $offset_type->getSingleIntLiteral()->value + : null); + + foreach ($stmt_var_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + $new_parent_node, + 'array-fetch' . ($dim_value !== null ? '-\'' . $dim_value . '\'' : '') + ); + + if ($stmt_type->by_ref) { + $statements_analyzer->data_flow_graph->addPath( + $new_parent_node, + $parent_node, + 'array-assignment' . ($dim_value !== null ? '-\'' . $dim_value . '\'' : '') + ); + } + } + + $stmt_type->parent_nodes = [$new_parent_node->id => $new_parent_node]; + } + } + + public static function getArrayAccessTypeGivenOffset( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\ArrayDimFetch $stmt, + Type\Union $array_type, + Type\Union $offset_type, + bool $in_assignment, + ?string $array_var_id, + Context $context, + PhpParser\Node\Expr $assign_value = null, + Type\Union $replacement_type = null + ): Type\Union { + $codebase = $statements_analyzer->getCodebase(); + + $has_array_access = false; + $non_array_types = []; + + $has_valid_offset = false; + $expected_offset_types = []; + + $key_values = []; + + if ($stmt->dim instanceof PhpParser\Node\Scalar\String_ + || $stmt->dim instanceof PhpParser\Node\Scalar\LNumber + ) { + $key_values[] = $stmt->dim->value; + } elseif ($stmt->dim && ($stmt_dim_type = $statements_analyzer->node_data->getType($stmt->dim))) { + $string_literals = $stmt_dim_type->getLiteralStrings(); + $int_literals = $stmt_dim_type->getLiteralInts(); + + $all_atomic_types = $stmt_dim_type->getAtomicTypes(); + + if (count($string_literals) + count($int_literals) === count($all_atomic_types)) { + foreach ($string_literals as $string_literal) { + $key_values[] = $string_literal->value; + } + + foreach ($int_literals as $int_literal) { + $key_values[] = $int_literal->value; + } + } + } + + $array_access_type = null; + + if ($offset_type->isNull()) { + if (IssueBuffer::accepts( + new NullArrayOffset( + 'Cannot access value on variable ' . $array_var_id . ' using null offset', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + if ($in_assignment) { + $offset_type->removeType('null'); + $offset_type->addType(new TLiteralInt(0)); + } + } + + if ($offset_type->isNullable() && !$context->inside_isset) { + if (!$offset_type->ignore_nullable_issues) { + if (IssueBuffer::accepts( + new PossiblyNullArrayOffset( + 'Cannot access value on variable ' . $array_var_id + . ' using possibly null offset ' . $offset_type, + new CodeLocation($statements_analyzer->getSource(), $stmt->var) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($in_assignment) { + $offset_type->removeType('null'); + + if (!$offset_type->ignore_nullable_issues) { + $offset_type->addType(new TLiteralInt(0)); + } + } + } + + foreach ($array_type->getAtomicTypes() as $type_string => $type) { + $original_type = $type; + + if ($type instanceof TMixed || $type instanceof TTemplateParam || $type instanceof TEmpty) { + if (!$type instanceof TTemplateParam || $type->as->isMixed() || !$type->as->isSingle()) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + if (!$context->inside_isset) { + if ($in_assignment) { + if (IssueBuffer::accepts( + new MixedArrayAssignment( + 'Cannot access array value on mixed variable ' . $array_var_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new MixedArrayAccess( + 'Cannot access array value on mixed variable ' . $array_var_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + $has_valid_offset = true; + if (!$array_access_type) { + $array_access_type = Type::getMixed( + $type instanceof TEmpty + ); + } else { + $array_access_type = Type::combineUnionTypes( + $array_access_type, + Type::getMixed($type instanceof TEmpty) + ); + } + + continue; + } + + $type = clone array_values($type->as->getAtomicTypes())[0]; + } + + if ($type instanceof TNull) { + if ($array_type->ignore_nullable_issues) { + continue; + } + + if ($in_assignment) { + if ($replacement_type) { + if ($array_access_type) { + $array_access_type = Type::combineUnionTypes($array_access_type, $replacement_type); + } else { + $array_access_type = clone $replacement_type; + } + } else { + if (IssueBuffer::accepts( + new PossiblyNullArrayAssignment( + 'Cannot access array value on possibly null variable ' . $array_var_id . + ' of type ' . $array_type, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + $array_access_type = new Type\Union([new TEmpty]); + } + } else { + if (!$context->inside_isset) { + if (IssueBuffer::accepts( + new PossiblyNullArrayAccess( + 'Cannot access array value on possibly null variable ' . $array_var_id . + ' of type ' . $array_type, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($array_access_type) { + $array_access_type = Type::combineUnionTypes($array_access_type, Type::getNull()); + } else { + $array_access_type = Type::getNull(); + } + } + + continue; + } + + if ($type instanceof TArray + || $type instanceof TKeyedArray + || $type instanceof TList + || $type instanceof TClassStringMap + ) { + $has_array_access = true; + + if ($in_assignment + && $type instanceof TArray + && (($type->type_params[0]->isEmpty() && $type->type_params[1]->isEmpty()) + || ($type->type_params[1]->hasMixed() + && count($key_values) === 1 + && \is_string($key_values[0]))) + ) { + $from_empty_array = $type->type_params[0]->isEmpty() && $type->type_params[1]->isEmpty(); + + if (count($key_values) === 1) { + $from_mixed_array = $type->type_params[1]->isMixed(); + + [$previous_key_type, $previous_value_type] = $type->type_params; + + // ok, type becomes an TKeyedArray + $array_type->removeType($type_string); + $type = new TKeyedArray([ + $key_values[0] => $from_mixed_array ? Type::getMixed() : Type::getEmpty() + ]); + + $type->sealed = $from_empty_array; + + if (!$from_empty_array) { + $type->previous_value_type = clone $previous_value_type; + $type->previous_key_type = clone $previous_key_type; + } + + $array_type->addType($type); + } elseif (!$stmt->dim && $from_empty_array && $replacement_type) { + $array_type->removeType($type_string); + $array_type->addType(new Type\Atomic\TNonEmptyList($replacement_type)); + continue; + } + } elseif ($in_assignment + && $type instanceof TKeyedArray + && $type->previous_value_type + && $type->previous_value_type->isMixed() + && count($key_values) === 1 + ) { + $type->properties[$key_values[0]] = Type::getMixed(); + } + + $offset_type = self::replaceOffsetTypeWithInts($offset_type); + + if ($type instanceof TList + && (($in_assignment && $stmt->dim) + || $original_type instanceof TTemplateParam + || !$offset_type->isInt()) + ) { + $type = new TArray([Type::getInt(), $type->type_param]); + } + + if ($type instanceof TArray) { + // if we're assigning to an empty array with a key offset, refashion that array + if ($in_assignment) { + if ($type->type_params[0]->isEmpty()) { + $type->type_params[0] = $offset_type->isMixed() + ? Type::getArrayKey() + : $offset_type; + } + } elseif (!$type->type_params[0]->isEmpty()) { + $expected_offset_type = $type->type_params[0]->hasMixed() + ? new Type\Union([ new TArrayKey ]) + : $type->type_params[0]; + + $templated_offset_type = null; + + foreach ($offset_type->getAtomicTypes() as $offset_atomic_type) { + if ($offset_atomic_type instanceof TTemplateParam) { + $templated_offset_type = $offset_atomic_type; + } + } + + $union_comparison_results = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + if ($original_type instanceof TTemplateParam && $templated_offset_type) { + foreach ($templated_offset_type->as->getAtomicTypes() as $offset_as) { + if ($offset_as instanceof Type\Atomic\TTemplateKeyOf + && $offset_as->param_name === $original_type->param_name + && $offset_as->defining_class === $original_type->defining_class + ) { + /** @psalm-suppress PropertyTypeCoercion */ + $type->type_params[1] = new Type\Union([ + new Type\Atomic\TTemplateIndexedAccess( + $offset_as->param_name, + $templated_offset_type->param_name, + $offset_as->defining_class + ) + ]); + + $has_valid_offset = true; + } + } + } else { + $offset_type_contained_by_expected = UnionTypeComparator::isContainedBy( + $codebase, + $offset_type, + $expected_offset_type, + true, + $offset_type->ignore_falsable_issues, + $union_comparison_results + ); + + if ($codebase->config->ensure_array_string_offsets_exist + && $offset_type_contained_by_expected + ) { + self::checkLiteralStringArrayOffset( + $offset_type, + $expected_offset_type, + $array_var_id, + $stmt, + $context, + $statements_analyzer + ); + } + + if ($codebase->config->ensure_array_int_offsets_exist + && $offset_type_contained_by_expected + ) { + self::checkLiteralIntArrayOffset( + $offset_type, + $expected_offset_type, + $array_var_id, + $stmt, + $context, + $statements_analyzer + ); + } + + if ((!$offset_type_contained_by_expected + && !$union_comparison_results->type_coerced_from_scalar) + || $union_comparison_results->to_string_cast + ) { + if ($union_comparison_results->type_coerced_from_mixed + && !$offset_type->isMixed() + ) { + if (IssueBuffer::accepts( + new MixedArrayTypeCoercion( + 'Coercion from array offset type \'' . $offset_type->getId() . '\' ' + . 'to the expected type \'' . $expected_offset_type->getId() . '\'', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + $expected_offset_types[] = $expected_offset_type->getId(); + } + + if (UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $offset_type, + $expected_offset_type + )) { + $has_valid_offset = true; + } + } else { + $has_valid_offset = true; + } + } + } + + if (!$stmt->dim && $type instanceof TNonEmptyArray && $type->count !== null) { + $type->count++; + } + + if ($in_assignment && $replacement_type) { + /** @psalm-suppress PropertyTypeCoercion */ + $type->type_params[1] = Type::combineUnionTypes( + $type->type_params[1], + $replacement_type, + $codebase + ); + } + + if (!$array_access_type) { + $array_access_type = $type->type_params[1]; + } else { + $array_access_type = Type::combineUnionTypes( + $array_access_type, + $type->type_params[1] + ); + } + + if ($array_access_type->isEmpty() + && !$array_type->hasMixed() + && !$in_assignment + && !$context->inside_isset + ) { + if (IssueBuffer::accepts( + new EmptyArrayAccess( + 'Cannot access value on empty array variable ' . $array_var_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return Type::getMixed(true); + } + + if (!IssueBuffer::isRecording()) { + $array_access_type = Type::getMixed(true); + } + } + } elseif ($type instanceof TList) { + // if we're assigning to an empty array with a key offset, refashion that array + if (!$in_assignment) { + if (!$type instanceof TNonEmptyList + || (count($key_values) === 1 + && is_int($key_values[0]) + && $key_values[0] > 0 + && $key_values[0] > ($type->count - 1)) + ) { + $expected_offset_type = Type::getInt(); + + if ($codebase->config->ensure_array_int_offsets_exist) { + self::checkLiteralIntArrayOffset( + $offset_type, + $expected_offset_type, + $array_var_id, + $stmt, + $context, + $statements_analyzer + ); + } + } + + $has_valid_offset = true; + } + + if ($in_assignment && $type instanceof Type\Atomic\TNonEmptyList && $type->count !== null) { + $type->count++; + } + + if ($in_assignment && $replacement_type) { + $type->type_param = Type::combineUnionTypes( + $type->type_param, + $replacement_type, + $codebase + ); + } + + if (!$array_access_type) { + $array_access_type = $type->type_param; + } else { + $array_access_type = Type::combineUnionTypes( + $array_access_type, + $type->type_param + ); + } + } elseif ($type instanceof TClassStringMap) { + $offset_type_parts = array_values($offset_type->getAtomicTypes()); + + foreach ($offset_type_parts as $offset_type_part) { + if ($offset_type_part instanceof Type\Atomic\TClassString) { + if ($offset_type_part instanceof Type\Atomic\TTemplateParamClass) { + $template_result_get = new TemplateResult( + [], + [ + $type->param_name => [ + 'class-string-map' => [ + new Type\Union([ + new TTemplateParam( + $offset_type_part->param_name, + $offset_type_part->as_type + ? new Type\Union([$offset_type_part->as_type]) + : Type::getObject(), + $offset_type_part->defining_class + ) + ]) + ] + ] + ] + ); + + $template_result_set = new TemplateResult( + [], + [ + $offset_type_part->param_name => [ + $offset_type_part->defining_class => [ + new Type\Union([ + new TTemplateParam( + $type->param_name, + $type->as_type + ? new Type\Union([$type->as_type]) + : Type::getObject(), + 'class-string-map' + ) + ]) + ] + ] + ] + ); + } else { + $template_result_get = new TemplateResult( + [], + [ + $type->param_name => [ + 'class-string-map' => [ + new Type\Union([ + $offset_type_part->as_type + ?: new Type\Atomic\TObject() + ]) + ] + ] + ] + ); + $template_result_set = new TemplateResult( + [], + [] + ); + } + + $expected_value_param_get = clone $type->value_param; + + $expected_value_param_get->replaceTemplateTypesWithArgTypes( + $template_result_get, + $codebase + ); + + if ($replacement_type) { + $expected_value_param_set = clone $type->value_param; + + $replacement_type->replaceTemplateTypesWithArgTypes( + $template_result_set, + $codebase + ); + + $type->value_param = Type::combineUnionTypes( + $replacement_type, + $expected_value_param_set, + $codebase + ); + } + + if (!$array_access_type) { + $array_access_type = $expected_value_param_get; + } else { + $array_access_type = Type::combineUnionTypes( + $array_access_type, + $expected_value_param_get, + $codebase + ); + } + } + } + } else { + $generic_key_type = $type->getGenericKeyType(); + + if (!$stmt->dim && $type->sealed && $type->is_list) { + $key_values[] = count($type->properties); + } + + if ($key_values) { + foreach ($key_values as $key_value) { + if (isset($type->properties[$key_value]) || $replacement_type) { + $has_valid_offset = true; + + if ($replacement_type) { + if (isset($type->properties[$key_value])) { + $type->properties[$key_value] = Type::combineUnionTypes( + $type->properties[$key_value], + $replacement_type + ); + } else { + $type->properties[$key_value] = $replacement_type; + } + } + + if (!$array_access_type) { + $array_access_type = clone $type->properties[$key_value]; + } else { + $array_access_type = Type::combineUnionTypes( + $array_access_type, + $type->properties[$key_value] + ); + } + } elseif ($in_assignment) { + $type->properties[$key_value] = new Type\Union([new TEmpty]); + + if (!$array_access_type) { + $array_access_type = clone $type->properties[$key_value]; + } else { + $array_access_type = Type::combineUnionTypes( + $array_access_type, + $type->properties[$key_value] + ); + } + } elseif ($type->previous_value_type) { + if ($codebase->config->ensure_array_string_offsets_exist) { + self::checkLiteralStringArrayOffset( + $offset_type, + $type->getGenericKeyType(), + $array_var_id, + $stmt, + $context, + $statements_analyzer + ); + } + + if ($codebase->config->ensure_array_int_offsets_exist) { + self::checkLiteralIntArrayOffset( + $offset_type, + $type->getGenericKeyType(), + $array_var_id, + $stmt, + $context, + $statements_analyzer + ); + } + + $type->properties[$key_value] = clone $type->previous_value_type; + + $array_access_type = clone $type->previous_value_type; + } elseif ($array_type->hasMixed()) { + $has_valid_offset = true; + + $array_access_type = Type::getMixed(); + } else { + if ($type->sealed || !$context->inside_isset) { + $object_like_keys = array_keys($type->properties); + + if (count($object_like_keys) === 1) { + $expected_keys_string = '\'' . $object_like_keys[0] . '\''; + } else { + $last_key = array_pop($object_like_keys); + $expected_keys_string = '\'' . implode('\', \'', $object_like_keys) . + '\' or \'' . $last_key . '\''; + } + + $expected_offset_types[] = $expected_keys_string; + } + + $array_access_type = Type::getMixed(); + } + } + } else { + $key_type = $generic_key_type->hasMixed() + ? Type::getArrayKey() + : $generic_key_type; + + $union_comparison_results = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + $is_contained = UnionTypeComparator::isContainedBy( + $codebase, + $offset_type, + $key_type, + true, + $offset_type->ignore_falsable_issues, + $union_comparison_results + ); + + if ($context->inside_isset && !$is_contained) { + $is_contained = UnionTypeComparator::isContainedBy( + $codebase, + $key_type, + $offset_type, + true, + $offset_type->ignore_falsable_issues + ) + || UnionTypeComparator::canBeContainedBy( + $codebase, + $offset_type, + $key_type, + true, + $offset_type->ignore_falsable_issues + ); + } + + if (($is_contained + || $union_comparison_results->type_coerced_from_scalar + || $union_comparison_results->type_coerced_from_mixed + || $in_assignment) + && !$union_comparison_results->to_string_cast + ) { + if ($replacement_type) { + $generic_params = Type::combineUnionTypes( + $type->getGenericValueType(), + $replacement_type + ); + + $new_key_type = Type::combineUnionTypes( + $generic_key_type, + $offset_type->isMixed() ? Type::getArrayKey() : $offset_type + ); + + $property_count = $type->sealed ? count($type->properties) : null; + + if (!$stmt->dim && $property_count) { + ++$property_count; + $array_type->removeType($type_string); + $type = new TNonEmptyArray([ + $new_key_type, + $generic_params, + ]); + $array_type->addType($type); + $type->count = $property_count; + } else { + $array_type->removeType($type_string); + + if (!$stmt->dim && $type->is_list) { + $type = new TList($generic_params); + } else { + $type = new TArray([ + $new_key_type, + $generic_params, + ]); + } + + $array_type->addType($type); + } + + if (!$array_access_type) { + $array_access_type = clone $generic_params; + } else { + $array_access_type = Type::combineUnionTypes( + $array_access_type, + $generic_params + ); + } + } else { + if (!$array_access_type) { + $array_access_type = $type->getGenericValueType(); + } else { + $array_access_type = Type::combineUnionTypes( + $array_access_type, + $type->getGenericValueType() + ); + } + } + + $has_valid_offset = true; + } else { + if (!$context->inside_isset + || ($type->sealed && !$union_comparison_results->type_coerced) + ) { + $expected_offset_types[] = $generic_key_type->getId(); + } + + $array_access_type = Type::getMixed(); + } + } + } + continue; + } + + if ($type instanceof TString) { + if ($in_assignment && $replacement_type) { + if ($replacement_type->hasMixed()) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + if (IssueBuffer::accepts( + new MixedStringOffsetAssignment( + 'Right-hand-side of string offset assignment cannot be mixed', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } + } + } + + if ($type instanceof TSingleLetter) { + $valid_offset_type = Type::getInt(false, 0); + } elseif ($type instanceof TLiteralString) { + if (!strlen($type->value)) { + $valid_offset_type = Type::getEmpty(); + } elseif (strlen($type->value) < 10) { + $valid_offsets = []; + + for ($i = -strlen($type->value), $l = strlen($type->value); $i < $l; $i++) { + $valid_offsets[] = new TLiteralInt($i); + } + + if (!$valid_offsets) { + throw new \UnexpectedValueException('This is weird'); + } + + $valid_offset_type = new Type\Union($valid_offsets); + } else { + $valid_offset_type = Type::getInt(); + } + } else { + $valid_offset_type = Type::getInt(); + } + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $offset_type, + $valid_offset_type, + true + )) { + $expected_offset_types[] = $valid_offset_type->getId(); + + $array_access_type = Type::getMixed(); + } else { + $has_valid_offset = true; + + if (!$array_access_type) { + $array_access_type = Type::getSingleLetter(); + } else { + $array_access_type = Type::combineUnionTypes( + $array_access_type, + Type::getSingleLetter() + ); + } + } + + continue; + } + + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } + + if ($type instanceof Type\Atomic\TFalse && $array_type->ignore_falsable_issues) { + continue; + } + + if ($type instanceof TNamedObject) { + if (strtolower($type->value) === 'simplexmlelement') { + $call_array_access_type = new Type\Union([new TNamedObject('SimpleXMLElement')]); + } elseif (strtolower($type->value) === 'domnodelist' && $stmt->dim) { + $old_data_provider = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $fake_method_call = new PhpParser\Node\Expr\MethodCall( + $stmt->var, + new PhpParser\Node\Identifier('item', $stmt->var->getAttributes()), + [ + new PhpParser\Node\Arg($stmt->dim) + ] + ); + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + if (!in_array('MixedMethodCall', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['MixedMethodCall']); + } + + \Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_method_call, + $context + ); + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + if (!in_array('MixedMethodCall', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['MixedMethodCall']); + } + + $call_array_access_type = $statements_analyzer->node_data->getType( + $fake_method_call + ) ?: Type::getMixed(); + + $statements_analyzer->node_data = $old_data_provider; + } else { + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + if (!in_array('MixedMethodCall', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['MixedMethodCall']); + } + + if ($in_assignment) { + $old_node_data = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $fake_set_method_call = new PhpParser\Node\Expr\MethodCall( + $stmt->var, + new PhpParser\Node\Identifier('offsetSet', $stmt->var->getAttributes()), + [ + new PhpParser\Node\Arg( + $stmt->dim + ? $stmt->dim + : new PhpParser\Node\Expr\ConstFetch( + new PhpParser\Node\Name('null'), + $stmt->var->getAttributes() + ) + ), + new PhpParser\Node\Arg( + $assign_value + ?: new PhpParser\Node\Expr\ConstFetch( + new PhpParser\Node\Name('null'), + $stmt->var->getAttributes() + ) + ), + ] + ); + + \Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_set_method_call, + $context + ); + + $statements_analyzer->node_data = $old_node_data; + } + + if ($stmt->dim) { + $old_node_data = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $fake_get_method_call = new PhpParser\Node\Expr\MethodCall( + $stmt->var, + new PhpParser\Node\Identifier('offsetGet', $stmt->var->getAttributes()), + [ + new PhpParser\Node\Arg( + $stmt->dim + ) + ] + ); + + \Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_get_method_call, + $context + ); + + $call_array_access_type = $statements_analyzer->node_data->getType($fake_get_method_call) + ?: Type::getMixed(); + + $statements_analyzer->node_data = $old_node_data; + } else { + $call_array_access_type = Type::getVoid(); + } + + $has_array_access = true; + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + if (!in_array('MixedMethodCall', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['MixedMethodCall']); + } + } + + if (!$array_access_type) { + $array_access_type = $call_array_access_type; + } else { + $array_access_type = Type::combineUnionTypes( + $array_access_type, + $call_array_access_type + ); + } + } elseif (!$array_type->hasMixed()) { + $non_array_types[] = (string)$type; + } + } + + if ($non_array_types) { + if ($has_array_access) { + if ($in_assignment) { + if (IssueBuffer::accepts( + new PossiblyInvalidArrayAssignment( + 'Cannot access array value on non-array variable ' . + $array_var_id . ' of type ' . $non_array_types[0], + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + ) + ) { + // do nothing + } + } elseif (!$context->inside_isset) { + if (IssueBuffer::accepts( + new PossiblyInvalidArrayAccess( + 'Cannot access array value on non-array variable ' . + $array_var_id . ' of type ' . $non_array_types[0], + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + ) + ) { + // do nothing + } + } + } else { + if ($in_assignment) { + if (IssueBuffer::accepts( + new InvalidArrayAssignment( + 'Cannot access array value on non-array variable ' . + $array_var_id . ' of type ' . $non_array_types[0], + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidArrayAccess( + 'Cannot access array value on non-array variable ' . + $array_var_id . ' of type ' . $non_array_types[0], + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $array_access_type = Type::getMixed(); + } + } + + if ($offset_type->hasMixed()) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + if (IssueBuffer::accepts( + new MixedArrayOffset( + 'Cannot access value on variable ' . $array_var_id . ' using mixed offset', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } + + if ($expected_offset_types) { + $invalid_offset_type = $expected_offset_types[0]; + + $used_offset = 'using a ' . $offset_type->getId() . ' offset'; + + if ($key_values) { + $used_offset = 'using offset value of ' + . (is_int($key_values[0]) ? $key_values[0] : '\'' . $key_values[0] . '\''); + } + + if ($has_valid_offset && $context->inside_isset) { + // do nothing + } elseif ($has_valid_offset) { + if (!$context->inside_unset) { + if (IssueBuffer::accepts( + new PossiblyInvalidArrayOffset( + 'Cannot access value on variable ' . $array_var_id . ' ' . $used_offset + . ', expecting ' . $invalid_offset_type, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } else { + if (IssueBuffer::accepts( + new InvalidArrayOffset( + 'Cannot access value on variable ' . $array_var_id . ' ' . $used_offset + . ', expecting ' . $invalid_offset_type, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + if ($array_access_type === null) { + // shouldn’t happen, but don’t crash + return Type::getMixed(); + } + + if ($array_type->by_ref) { + $array_access_type->by_ref = true; + } + + if ($in_assignment) { + $array_type->bustCache(); + } + + return $array_access_type; + } + + private static function checkLiteralIntArrayOffset( + Type\Union $offset_type, + Type\Union $expected_offset_type, + ?string $array_var_id, + PhpParser\Node\Expr\ArrayDimFetch $stmt, + Context $context, + StatementsAnalyzer $statements_analyzer + ) : void { + if ($context->inside_isset || $context->inside_unset) { + return; + } + + if ($offset_type->hasLiteralInt()) { + $found_match = false; + + foreach ($offset_type->getAtomicTypes() as $offset_type_part) { + if ($array_var_id + && $offset_type_part instanceof TLiteralInt + && isset( + $context->vars_in_scope[ + $array_var_id . '[' . $offset_type_part->value . ']' + ] + ) + && !$context->vars_in_scope[ + $array_var_id . '[' . $offset_type_part->value . ']' + ]->possibly_undefined + ) { + $found_match = true; + break; + } + } + + if (!$found_match) { + if (IssueBuffer::accepts( + new PossiblyUndefinedIntArrayOffset( + 'Possibly undefined array offset \'' + . $offset_type->getId() . '\' ' + . 'is risky given expected type \'' + . $expected_offset_type->getId() . '\'.' + . ' Consider using isset beforehand.', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + private static function checkLiteralStringArrayOffset( + Type\Union $offset_type, + Type\Union $expected_offset_type, + ?string $array_var_id, + PhpParser\Node\Expr\ArrayDimFetch $stmt, + Context $context, + StatementsAnalyzer $statements_analyzer + ) : void { + if ($context->inside_isset || $context->inside_unset) { + return; + } + + if ($offset_type->hasLiteralString() && !$expected_offset_type->hasLiteralClassString()) { + $found_match = false; + + foreach ($offset_type->getAtomicTypes() as $offset_type_part) { + if ($array_var_id + && $offset_type_part instanceof TLiteralString + && isset( + $context->vars_in_scope[ + $array_var_id . '[\'' . $offset_type_part->value . '\']' + ] + ) + && !$context->vars_in_scope[ + $array_var_id . '[\'' . $offset_type_part->value . '\']' + ]->possibly_undefined + ) { + $found_match = true; + break; + } + } + + if (!$found_match) { + if (IssueBuffer::accepts( + new PossiblyUndefinedStringArrayOffset( + 'Possibly undefined array offset \'' + . $offset_type->getId() . '\' ' + . 'is risky given expected type \'' + . $expected_offset_type->getId() . '\'.' + . ' Consider using isset beforehand.', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + public static function replaceOffsetTypeWithInts(Type\Union $offset_type): Type\Union + { + $offset_types = $offset_type->getAtomicTypes(); + + $cloned = false; + + foreach ($offset_types as $key => $offset_type_part) { + if ($offset_type_part instanceof Type\Atomic\TLiteralString) { + if (preg_match('/^(0|[1-9][0-9]*)$/', $offset_type_part->value)) { + if (!$cloned) { + $offset_type = clone $offset_type; + $cloned = true; + } + $offset_type->addType(new Type\Atomic\TLiteralInt((int) $offset_type_part->value)); + $offset_type->removeType($key); + } + } elseif ($offset_type_part instanceof Type\Atomic\TBool) { + if (!$cloned) { + $offset_type = clone $offset_type; + $cloned = true; + } + + if ($offset_type_part instanceof Type\Atomic\TFalse) { + if (!$offset_type->ignore_falsable_issues) { + $offset_type->addType(new Type\Atomic\TLiteralInt(0)); + $offset_type->removeType($key); + } + } elseif ($offset_type_part instanceof Type\Atomic\TTrue) { + $offset_type->addType(new Type\Atomic\TLiteralInt(1)); + $offset_type->removeType($key); + } else { + $offset_type->addType(new Type\Atomic\TLiteralInt(0)); + $offset_type->addType(new Type\Atomic\TLiteralInt(1)); + $offset_type->removeType($key); + } + } + } + + return $offset_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..46a6104e3bd3b5e70a2b27a5addc1cd9a0b7006b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php @@ -0,0 +1,956 @@ + $invalid_fetch_types $invalid_fetch_types + */ + public static function analyze( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\PropertyFetch $stmt, + Context $context, + bool $in_assignment, + ?string $var_id, + ?string $stmt_var_id, + Type\Union $stmt_var_type, + Type\Atomic $lhs_type_part, + string $prop_name, + bool &$has_valid_fetch_type, + array &$invalid_fetch_types + ) : void { + if ($lhs_type_part instanceof TNull) { + return; + } + + if ($lhs_type_part instanceof Type\Atomic\TTemplateParam) { + $extra_types = $lhs_type_part->extra_types; + + $lhs_type_part = array_values( + $lhs_type_part->as->getAtomicTypes() + )[0]; + + $lhs_type_part->from_docblock = true; + + if ($lhs_type_part instanceof TNamedObject) { + $lhs_type_part->extra_types = $extra_types; + } + } + + if ($lhs_type_part instanceof Type\Atomic\TMixed) { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + return; + } + + if ($lhs_type_part instanceof Type\Atomic\TFalse && $stmt_var_type->ignore_falsable_issues) { + return; + } + + if (!$lhs_type_part instanceof TNamedObject && !$lhs_type_part instanceof TObject) { + $invalid_fetch_types[] = (string)$lhs_type_part; + + return; + } + + $has_valid_fetch_type = true; + + if ($lhs_type_part instanceof TObjectWithProperties + && isset($lhs_type_part->properties[$prop_name]) + ) { + if ($stmt_type = $statements_analyzer->node_data->getType($stmt)) { + $statements_analyzer->node_data->setType( + $stmt, + Type::combineUnionTypes( + $lhs_type_part->properties[$prop_name], + $stmt_type + ) + ); + } else { + $statements_analyzer->node_data->setType($stmt, $lhs_type_part->properties[$prop_name]); + } + + return; + } + + // stdClass and SimpleXMLElement are special cases where we cannot infer the return types + // but we don't want to throw an error + // Hack has a similar issue: https://github.com/facebook/hhvm/issues/5164 + if ($lhs_type_part instanceof TObject + || in_array(strtolower($lhs_type_part->value), Config::getInstance()->getUniversalObjectCrates(), true) + ) { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + + return; + } + + if (ExpressionAnalyzer::isMock($lhs_type_part->value)) { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + return; + } + + $intersection_types = $lhs_type_part->getIntersectionTypes() ?: []; + + $fq_class_name = $lhs_type_part->value; + + $override_property_visibility = false; + + $has_magic_getter = false; + + $class_exists = false; + $interface_exists = false; + + $codebase = $statements_analyzer->getCodebase(); + + if (!$codebase->classExists($lhs_type_part->value)) { + if ($codebase->interfaceExists($lhs_type_part->value)) { + $interface_exists = true; + $interface_storage = $codebase->classlike_storage_provider->get($lhs_type_part->value); + + $override_property_visibility = $interface_storage->override_property_visibility; + + foreach ($intersection_types as $intersection_type) { + if ($intersection_type instanceof TNamedObject + && $codebase->classExists($intersection_type->value) + ) { + $fq_class_name = $intersection_type->value; + $class_exists = true; + break; + } + } + + if (!$class_exists) { + if (IssueBuffer::accepts( + new NoInterfaceProperties( + 'Interfaces cannot have properties', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $lhs_type_part->value + ), + $statements_analyzer->getSuppressedIssues() + )) { + return; + } + + if (!$codebase->methodExists($fq_class_name . '::__set')) { + return; + } + } + } + + if (!$class_exists && !$interface_exists) { + if ($lhs_type_part->from_docblock) { + if (IssueBuffer::accepts( + new UndefinedDocblockClass( + 'Cannot set properties of undefined docblock class ' . $lhs_type_part->value, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $lhs_type_part->value + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new UndefinedClass( + 'Cannot set properties of undefined class ' . $lhs_type_part->value, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $lhs_type_part->value + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + return; + } + } else { + $class_exists = true; + } + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + $property_id = $fq_class_name . '::$' . $prop_name; + + $naive_property_exists = $codebase->properties->propertyExists( + $property_id, + true, + $statements_analyzer, + $context, + $codebase->collect_locations ? new CodeLocation($statements_analyzer->getSource(), $stmt) : null + ); + + // add method before changing fq_class_name + $get_method_id = new \Psalm\Internal\MethodIdentifier($fq_class_name, '__get'); + + if (!$naive_property_exists + && $class_storage->namedMixins + ) { + foreach ($class_storage->namedMixins as $mixin) { + $new_property_id = $mixin->value . '::$' . $prop_name; + + try { + $new_class_storage = $codebase->classlike_storage_provider->get($mixin->value); + } catch (\InvalidArgumentException $e) { + $new_class_storage = null; + } + + if ($new_class_storage + && ($codebase->properties->propertyExists( + $new_property_id, + true, + $statements_analyzer, + $context, + $codebase->collect_locations + ? new CodeLocation($statements_analyzer->getSource(), $stmt) + : null + ) + || isset($new_class_storage->pseudo_property_get_types['$' . $prop_name])) + ) { + $fq_class_name = $mixin->value; + $lhs_type_part = clone $mixin; + $class_storage = $new_class_storage; + + if (!isset($new_class_storage->pseudo_property_get_types['$' . $prop_name])) { + $naive_property_exists = true; + } + + $property_id = $new_property_id; + } + } + } + + $declaring_property_class = $codebase->properties->getDeclaringClassForProperty( + $property_id, + true, + $statements_analyzer + ); + + if ((!$naive_property_exists + || ($stmt_var_id !== '$this' + && $fq_class_name !== $context->self + && ClassLikeAnalyzer::checkPropertyVisibility( + $property_id, + $context, + $statements_analyzer, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues(), + false + ) !== true) + ) + && $codebase->methods->methodExists( + $get_method_id, + $context->calling_method_id, + $codebase->collect_locations + ? new CodeLocation($statements_analyzer->getSource(), $stmt) + : null, + !$context->collect_initializations + && !$context->collect_mutations + ? $statements_analyzer + : null, + $statements_analyzer->getFilePath() + ) + ) { + $has_magic_getter = true; + + if (isset($class_storage->pseudo_property_get_types['$' . $prop_name])) { + $stmt_type = clone $class_storage->pseudo_property_get_types['$' . $prop_name]; + + if ($class_storage->template_types) { + if (!$lhs_type_part instanceof TGenericObject) { + $type_params = []; + + foreach ($class_storage->template_types as $type_map) { + $type_params[] = clone array_values($type_map)[0][0]; + } + + $lhs_type_part = new TGenericObject($lhs_type_part->value, $type_params); + } + + $stmt_type = self::localizePropertyType( + $codebase, + $stmt_type, + $lhs_type_part, + $class_storage, + $declaring_property_class + ? $codebase->classlike_storage_provider->get( + $declaring_property_class + ) : $class_storage + ); + } + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + self::processTaints( + $statements_analyzer, + $stmt, + $stmt_type, + $property_id, + $class_storage, + $in_assignment + ); + return; + } + + $old_data_provider = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $fake_method_call = new PhpParser\Node\Expr\MethodCall( + $stmt->var, + new PhpParser\Node\Identifier('__get', $stmt->name->getAttributes()), + [ + new PhpParser\Node\Arg( + new PhpParser\Node\Scalar\String_( + $prop_name, + $stmt->name->getAttributes() + ) + ) + ] + ); + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('PossiblyNullReference', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['PossiblyNullReference']); + } + + if (!in_array('InternalMethod', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['InternalMethod']); + } + + \Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_method_call, + $context, + false + ); + + if (!in_array('PossiblyNullReference', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['PossiblyNullReference']); + } + + if (!in_array('InternalMethod', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['InternalMethod']); + } + + $fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call); + + $statements_analyzer->node_data = $old_data_provider; + + if ($fake_method_call_type) { + $statements_analyzer->node_data->setType($stmt, $fake_method_call_type); + } else { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } + + $property_id = $lhs_type_part->value . '::$' . $prop_name; + + /* + * If we have an explicit list of all allowed magic properties on the class, and we're + * not in that list, fall through + */ + if (!$class_storage->sealed_properties && !$override_property_visibility) { + return; + } + + if (!$class_exists) { + $property_id = $lhs_type_part->value . '::$' . $prop_name; + + if (IssueBuffer::accepts( + new UndefinedMagicPropertyFetch( + 'Magic instance property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return; + } + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->name, + $property_id + ); + } + + $config = $statements_analyzer->getProjectAnalyzer()->getConfig(); + + if (!$naive_property_exists) { + if ($config->use_phpdoc_property_without_magic_or_parent + && isset($class_storage->pseudo_property_get_types['$' . $prop_name]) + ) { + $stmt_type = clone $class_storage->pseudo_property_get_types['$' . $prop_name]; + + if ($class_storage->template_types) { + if (!$lhs_type_part instanceof TGenericObject) { + $type_params = []; + + foreach ($class_storage->template_types as $type_map) { + $type_params[] = clone array_values($type_map)[0][0]; + } + + $lhs_type_part = new TGenericObject($lhs_type_part->value, $type_params); + } + + $stmt_type = self::localizePropertyType( + $codebase, + $stmt_type, + $lhs_type_part, + $class_storage, + $declaring_property_class + ? $codebase->classlike_storage_provider->get( + $declaring_property_class + ) : $class_storage + ); + } + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + self::processTaints( + $statements_analyzer, + $stmt, + $stmt_type, + $property_id, + $class_storage, + $in_assignment + ); + + return; + } + + if ($fq_class_name !== $context->self + && $context->self + && $codebase->classlikes->classExtends($fq_class_name, $context->self) + && $codebase->properties->propertyExists( + $context->self . '::$' . $prop_name, + true, + $statements_analyzer, + $context, + $codebase->collect_locations + ? new CodeLocation($statements_analyzer->getSource(), $stmt) + : null + ) + ) { + $property_id = $context->self . '::$' . $prop_name; + } else { + self::handleUndefinedProperty( + $context, + $statements_analyzer, + $stmt, + $stmt_var_id, + $property_id, + $has_magic_getter, + $var_id + ); + + return; + } + } + + if (!$override_property_visibility) { + if (ClassLikeAnalyzer::checkPropertyVisibility( + $property_id, + $context, + $statements_analyzer, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues() + ) === false) { + return; + } + } + + $declaring_property_class = $codebase->properties->getDeclaringClassForProperty( + $property_id, + true, + $statements_analyzer + ); + + if ($declaring_property_class === null) { + return; + } + + if ($codebase->properties_to_rename) { + $declaring_property_id = strtolower($declaring_property_class) . '::$' . $prop_name; + + foreach ($codebase->properties_to_rename as $original_property_id => $new_property_name) { + if ($declaring_property_id === $original_property_id) { + $file_manipulations = [ + new \Psalm\FileManipulation( + (int) $stmt->name->getAttribute('startFilePos'), + (int) $stmt->name->getAttribute('endFilePos') + 1, + $new_property_name + ) + ]; + + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $statements_analyzer->getFilePath(), + $file_manipulations + ); + } + } + } + + $declaring_class_storage = $codebase->classlike_storage_provider->get( + $declaring_property_class + ); + + if (isset($declaring_class_storage->properties[$prop_name])) { + $property_storage = $declaring_class_storage->properties[$prop_name]; + + if ($property_storage->deprecated) { + if (IssueBuffer::accepts( + new DeprecatedProperty( + $property_id . ' is marked deprecated', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($context->self && !NamespaceAnalyzer::isWithin($context->self, $property_storage->internal)) { + if (IssueBuffer::accepts( + new InternalProperty( + $property_id . ' is internal to ' . $property_storage->internal + . ' but called from ' . $context->self, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($context->inside_unset) { + InstancePropertyAssignmentAnalyzer::trackPropertyImpurity( + $statements_analyzer, + $stmt, + $property_id, + $property_storage, + $declaring_class_storage, + $context + ); + } + } + + $class_property_type = $codebase->properties->getPropertyType( + $property_id, + false, + $statements_analyzer, + $context + ); + + if (!$class_property_type) { + if ($declaring_class_storage->location + && $config->isInProjectDirs( + $declaring_class_storage->location->file_path + ) + ) { + if (IssueBuffer::accepts( + new MissingPropertyType( + 'Property ' . $fq_class_name . '::$' . $prop_name + . ' does not have a declared type', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $class_property_type = Type::getMixed(); + } else { + $class_property_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + clone $class_property_type, + $declaring_class_storage->name, + $declaring_class_storage->name, + $declaring_class_storage->parent_class + ); + + if ($declaring_class_storage->template_types) { + if (!$lhs_type_part instanceof TGenericObject) { + $type_params = []; + + foreach ($declaring_class_storage->template_types as $type_map) { + $type_params[] = clone array_values($type_map)[0][0]; + } + + $lhs_type_part = new TGenericObject($lhs_type_part->value, $type_params); + } + + $class_property_type = self::localizePropertyType( + $codebase, + $class_property_type, + $lhs_type_part, + $class_storage, + $declaring_class_storage + ); + } elseif ($lhs_type_part instanceof TGenericObject) { + $class_property_type = self::localizePropertyType( + $codebase, + $class_property_type, + $lhs_type_part, + $class_storage, + $declaring_class_storage + ); + } + } + + if (!$context->collect_mutations + && !$context->collect_initializations + && !($class_storage->external_mutation_free + && $class_property_type->allow_mutations) + ) { + if ($context->pure) { + if (IssueBuffer::accepts( + new ImpurePropertyFetch( + 'Cannot access a property on a mutable object from a pure context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_impure = true; + } + } + + self::processTaints( + $statements_analyzer, + $stmt, + $class_property_type, + $property_id, + $class_storage, + $in_assignment + ); + + if ($stmt_type = $statements_analyzer->node_data->getType($stmt)) { + $statements_analyzer->node_data->setType( + $stmt, + Type::combineUnionTypes($class_property_type, $stmt_type) + ); + } else { + $statements_analyzer->node_data->setType($stmt, $class_property_type); + } + } + + public static function localizePropertyType( + \Psalm\Codebase $codebase, + Type\Union $class_property_type, + TGenericObject $lhs_type_part, + ClassLikeStorage $calling_class_storage, + ClassLikeStorage $declaring_class_storage + ) : Type\Union { + $template_types = CallAnalyzer::getTemplateTypesForCall( + $codebase, + $declaring_class_storage, + $declaring_class_storage->name, + $calling_class_storage, + $calling_class_storage->template_types ?: [] + ); + + $extended_types = $calling_class_storage->template_type_extends; + + if ($template_types) { + if ($calling_class_storage->template_types) { + foreach ($lhs_type_part->type_params as $param_offset => $lhs_param_type) { + $i = -1; + + foreach ($calling_class_storage->template_types as $calling_param_name => $_) { + $i++; + + if ($i === $param_offset) { + $template_types[$calling_param_name][$calling_class_storage->name] = [ + $lhs_param_type, + 0 + ]; + break; + } + } + } + } + + foreach ($template_types as $type_name => $_) { + if (isset($extended_types[$declaring_class_storage->name][$type_name])) { + $mapped_type = $extended_types[$declaring_class_storage->name][$type_name]; + + foreach ($mapped_type->getAtomicTypes() as $mapped_type_atomic) { + if (!$mapped_type_atomic instanceof Type\Atomic\TTemplateParam) { + continue; + } + + $param_name = $mapped_type_atomic->param_name; + + $position = false; + + if (isset($calling_class_storage->template_types[$param_name])) { + $position = \array_search( + $param_name, + array_keys($calling_class_storage->template_types) + ); + } + + if ($position !== false && isset($lhs_type_part->type_params[$position])) { + $template_types[$type_name][$declaring_class_storage->name] = [ + $lhs_type_part->type_params[$position], + 0 + ]; + } + } + } + } + + $class_property_type->replaceTemplateTypesWithArgTypes( + new TemplateResult([], $template_types), + $codebase + ); + } + + return $class_property_type; + } + + public static function processTaints( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\PropertyFetch $stmt, + Type\Union $type, + string $property_id, + \Psalm\Storage\ClassLikeStorage $class_storage, + bool $in_assignment + ) : void { + if (!$statements_analyzer->data_flow_graph) { + return; + } + + $data_flow_graph = $statements_analyzer->data_flow_graph; + + $var_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var); + $property_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + + if ($class_storage->specialize_instance) { + $var_id = ExpressionIdentifier::getArrayVarId( + $stmt->var, + null, + $statements_analyzer + ); + + $var_property_id = ExpressionIdentifier::getArrayVarId( + $stmt, + null, + $statements_analyzer + ); + + if ($var_id) { + $var_type = $statements_analyzer->node_data->getType($stmt->var); + + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph + && $var_type + && \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + $var_type->parent_nodes = []; + return; + } + + $var_node = DataFlowNode::getForAssignment( + $var_id, + $var_location + ); + + $data_flow_graph->addNode($var_node); + + $property_node = DataFlowNode::getForAssignment( + $var_property_id ?: $var_id . '->$property', + $property_location + ); + + $data_flow_graph->addNode($property_node); + + $data_flow_graph->addPath( + $var_node, + $property_node, + 'property-fetch' + . ($stmt->name instanceof PhpParser\Node\Identifier ? '-' . $stmt->name : '') + ); + + if ($var_type && $var_type->parent_nodes) { + foreach ($var_type->parent_nodes as $parent_node) { + $data_flow_graph->addPath( + $parent_node, + $var_node, + '=' + ); + } + } + + $type->parent_nodes = [$property_node->id => $property_node]; + } + } else { + $code_location = new CodeLocation($statements_analyzer, $stmt->name); + + $localized_property_node = new DataFlowNode( + $property_id . '-' . $code_location->file_name . ':' . $code_location->raw_file_start, + $property_id, + $code_location, + null + ); + + $data_flow_graph->addNode($localized_property_node); + + $property_node = new DataFlowNode( + $property_id, + $property_id, + null, + null + ); + + $data_flow_graph->addNode($property_node); + + if ($in_assignment) { + $data_flow_graph->addPath($localized_property_node, $property_node, 'property-assignment'); + } else { + $data_flow_graph->addPath($property_node, $localized_property_node, 'property-fetch'); + } + + $type->parent_nodes[$localized_property_node->id] = $localized_property_node; + } + } + + private static function handleUndefinedProperty( + Context $context, + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\PropertyFetch $stmt, + ?string $stmt_var_id, + string $property_id, + bool $has_magic_getter, + ?string $var_id + ): void { + if ($context->inside_isset || $context->collect_initializations) { + if ($context->pure) { + if (IssueBuffer::accepts( + new ImpurePropertyFetch( + 'Cannot access a property on a mutable object from a pure context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($context->inside_isset + && $statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_impure = true; + } + + return; + } + + if ($stmt_var_id === '$this') { + if (IssueBuffer::accepts( + new UndefinedThisPropertyFetch( + 'Instance property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if ($has_magic_getter) { + if (IssueBuffer::accepts( + new UndefinedMagicPropertyFetch( + 'Magic instance property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new UndefinedPropertyFetch( + 'Instance property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + $stmt_type = Type::getMixed(); + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + if ($var_id) { + $context->vars_in_scope[$var_id] = $stmt_type; + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..7e9138ed17c9c58ca60c1ecd23edafef1aac08a6 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php @@ -0,0 +1,429 @@ +getCodebase(); + + if ($stmt->class instanceof PhpParser\Node\Name) { + $first_part_lc = strtolower($stmt->class->parts[0]); + + if ($first_part_lc === 'self' || $first_part_lc === 'static') { + if (!$context->self) { + if (IssueBuffer::accepts( + new NonStaticSelfCall( + 'Cannot use ' . $first_part_lc . ' outside class context', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return true; + } + + $fq_class_name = $context->self; + } elseif ($first_part_lc === 'parent') { + $fq_class_name = $statements_analyzer->getParentFQCLN(); + + if ($fq_class_name === null) { + if (IssueBuffer::accepts( + new ParentNotFound( + 'Cannot check property fetch on parent as this class does not extend another', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return true; + } + } else { + $fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject( + $stmt->class, + $statements_analyzer->getAliases() + ); + + if ($stmt->name instanceof PhpParser\Node\Identifier) { + if ((!$context->inside_class_exists || $stmt->name->name !== 'class') + && !isset($context->phantom_classes[strtolower($fq_class_name)]) + ) { + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $fq_class_name, + new CodeLocation($statements_analyzer->getSource(), $stmt->class), + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues(), + false, + true + ) === false) { + return true; + } + } + } + } + + $moved_class = false; + + if ($codebase->alter_code + && !\in_array($stmt->class->parts[0], ['parent', 'static']) + ) { + $moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $statements_analyzer, + $stmt->class, + $fq_class_name, + $context->calling_method_id, + false, + $stmt->class->parts[0] === 'self' + ); + } + + if ($stmt->name instanceof PhpParser\Node\Identifier && $stmt->name->name === 'class') { + if ($codebase->classlikes->classExists($fq_class_name)) { + $fq_class_name = $codebase->classlikes->getUnAliasedName($fq_class_name); + $const_class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + $fq_class_name = $const_class_storage->name; + + if ($const_class_storage->deprecated && $fq_class_name !== $context->self) { + if (IssueBuffer::accepts( + new DeprecatedClass( + 'Class ' . $fq_class_name . ' is deprecated', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $fq_class_name + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($first_part_lc === 'static') { + $static_named_object = new Type\Atomic\TNamedObject($fq_class_name); + $static_named_object->was_static = true; + + $statements_analyzer->node_data->setType( + $stmt, + new Type\Union([ + new Type\Atomic\TClassString($fq_class_name, $static_named_object) + ]) + ); + } else { + $statements_analyzer->node_data->setType($stmt, Type::getLiteralClassString($fq_class_name)); + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->class, + $fq_class_name + ); + } + + return true; + } + + // if we're ignoring that the class doesn't exist, exit anyway + if (!$codebase->classlikes->classOrInterfaceExists($fq_class_name)) { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + + return true; + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->class, + $fq_class_name + ); + } + + if (!$stmt->name instanceof PhpParser\Node\Identifier) { + return true; + } + + $const_id = $fq_class_name . '::' . $stmt->name; + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->name, + $const_id + ); + } + + if ($fq_class_name === $context->self + || ( + $statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer && + $fq_class_name === $statements_analyzer->getSource()->getFQCLN() + ) + ) { + $class_visibility = \ReflectionProperty::IS_PRIVATE; + } elseif ($context->self && + ($codebase->classlikes->classExtends($context->self, $fq_class_name) + || $codebase->classlikes->classExtends($fq_class_name, $context->self)) + ) { + $class_visibility = \ReflectionProperty::IS_PROTECTED; + } else { + $class_visibility = \ReflectionProperty::IS_PUBLIC; + } + + try { + $class_constant_type = $codebase->classlikes->getClassConstantType( + $fq_class_name, + $stmt->name->name, + $class_visibility, + $statements_analyzer + ); + } catch (\InvalidArgumentException $_) { + return true; + } catch (\Psalm\Exception\CircularReferenceException $e) { + if (IssueBuffer::accepts( + new CircularReference( + 'Constant ' . $const_id . ' contains a circular reference', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return true; + } + + if (!$class_constant_type) { + if ($fq_class_name !== $context->self) { + $class_constant_type = $codebase->classlikes->getClassConstantType( + $fq_class_name, + $stmt->name->name, + \ReflectionProperty::IS_PRIVATE, + $statements_analyzer + ); + } + + if ($class_constant_type) { + if (IssueBuffer::accepts( + new InaccessibleClassConstant( + 'Constant ' . $const_id . ' is not visible in this context', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($context->check_consts) { + if (IssueBuffer::accepts( + new UndefinedConstant( + 'Constant ' . $const_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + return true; + } + + if ($context->calling_method_id) { + $codebase->file_reference_provider->addMethodReferenceToClassMember( + $context->calling_method_id, + strtolower($fq_class_name) . '::' . $stmt->name->name + ); + } + + $declaring_const_id = strtolower($fq_class_name) . '::' . $stmt->name->name; + + if ($codebase->alter_code && !$moved_class) { + foreach ($codebase->class_constant_transforms as $original_pattern => $transformation) { + if ($declaring_const_id === $original_pattern) { + [$new_fq_class_name, $new_const_name] = explode('::', $transformation); + + $file_manipulations = []; + + if (strtolower($new_fq_class_name) !== strtolower($fq_class_name)) { + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $stmt->class->getAttribute('startFilePos'), + (int) $stmt->class->getAttribute('endFilePos') + 1, + Type::getStringFromFQCLN( + $new_fq_class_name, + $statements_analyzer->getNamespace(), + $statements_analyzer->getAliasedClassesFlipped(), + null + ) + ); + } + + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $stmt->name->getAttribute('startFilePos'), + (int) $stmt->name->getAttribute('endFilePos') + 1, + $new_const_name + ); + + FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations); + } + } + } + + $const_class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + if ($context->self + && !$context->collect_initializations + && !$context->collect_mutations + && $const_class_storage->internal + && !NamespaceAnalyzer::isWithin($context->self, $const_class_storage->internal) + ) { + if (IssueBuffer::accepts( + new InternalClass( + $fq_class_name . ' is internal to ' . $const_class_storage->internal + . ' but called from ' . $context->self, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $fq_class_name + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($const_class_storage->deprecated && $fq_class_name !== $context->self) { + if (IssueBuffer::accepts( + new DeprecatedClass( + 'Class ' . $fq_class_name . ' is deprecated', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $fq_class_name + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($const_class_storage->constants[$stmt->name->name]->deprecated) { + if (IssueBuffer::accepts( + new DeprecatedConstant( + 'Constant ' . $const_id . ' is deprecated', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($first_part_lc !== 'static' || $const_class_storage->final) { + $stmt_type = clone $class_constant_type; + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + $context->vars_in_scope[$const_id] = $stmt_type; + } else { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } + + return true; + } + + if ($stmt->name instanceof PhpParser\Node\Identifier && $stmt->name->name === 'class') { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + ExpressionAnalyzer::analyze($statements_analyzer, $stmt->class, $context); + $context->inside_use = $was_inside_use; + + $lhs_type = $statements_analyzer->node_data->getType($stmt->class); + + $class_string_types = []; + + $has_mixed_or_object = false; + + if ($lhs_type) { + foreach ($lhs_type->getAtomicTypes() as $lhs_atomic_type) { + if ($lhs_atomic_type instanceof Type\Atomic\TNamedObject) { + $class_string_types[] = new Type\Atomic\TClassString( + $lhs_atomic_type->value, + clone $lhs_atomic_type + ); + } elseif ($lhs_atomic_type instanceof Type\Atomic\TObject + || $lhs_atomic_type instanceof Type\Atomic\TMixed + ) { + $has_mixed_or_object = true; + } + } + } + + if ($has_mixed_or_object) { + $statements_analyzer->node_data->setType($stmt, new Type\Union([new Type\Atomic\TClassString()])); + } elseif ($class_string_types) { + $statements_analyzer->node_data->setType($stmt, new Type\Union($class_string_types)); + } else { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } + + return true; + } + + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + + $was_inside_use = $context->inside_use; + $context->inside_use = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->class, $context) === false) { + return false; + } + + $context->inside_use = $was_inside_use; + + return true; + } + + public static function analyzeClassConstAssignment( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Stmt\ClassConst $stmt, + Context $context + ): void { + foreach ($stmt->consts as $const) { + ExpressionAnalyzer::analyze($statements_analyzer, $const->value, $context); + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..9f729d048005b1142b8a6beb2a4e34758ab04481 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php @@ -0,0 +1,279 @@ +name->parts); + + switch (strtolower($const_name)) { + case 'null': + $statements_analyzer->node_data->setType($stmt, Type::getNull()); + break; + + case 'false': + // false is a subtype of bool + $statements_analyzer->node_data->setType($stmt, Type::getFalse()); + break; + + case 'true': + $statements_analyzer->node_data->setType($stmt, Type::getTrue()); + break; + + case 'stdin': + $statements_analyzer->node_data->setType($stmt, Type::getResource()); + break; + + default: + $const_type = self::getConstType( + $statements_analyzer, + $const_name, + $stmt->name instanceof PhpParser\Node\Name\FullyQualified, + $context + ); + + if ($const_type) { + $statements_analyzer->node_data->setType($stmt, clone $const_type); + } elseif ($context->check_consts) { + if (IssueBuffer::accepts( + new UndefinedConstant( + 'Const ' . $const_name . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + public static function getGlobalConstType( + Codebase $codebase, + ?string $fq_const_name, + string $const_name + ): ?Type\Union { + if ($const_name === 'STDERR' + || $const_name === 'STDOUT' + || $const_name === 'STDIN' + ) { + return Type::getResource(); + } + + if ($fq_const_name) { + $stubbed_const_type = $codebase->getStubbedConstantType( + $fq_const_name + ); + + if ($stubbed_const_type) { + return $stubbed_const_type; + } + } + + $stubbed_const_type = $codebase->getStubbedConstantType( + $const_name + ); + + if ($stubbed_const_type) { + return $stubbed_const_type; + } + + $predefined_constants = $codebase->config->getPredefinedConstants(); + + if (($fq_const_name && array_key_exists($fq_const_name, $predefined_constants)) + || array_key_exists($const_name, $predefined_constants) + ) { + switch ($const_name) { + case 'PHP_VERSION': + case 'DIRECTORY_SEPARATOR': + case 'PATH_SEPARATOR': + case 'PEAR_EXTENSION_DIR': + case 'PEAR_INSTALL_DIR': + case 'PHP_BINARY': + case 'PHP_BINDIR': + case 'PHP_CONFIG_FILE_PATH': + case 'PHP_CONFIG_FILE_SCAN_DIR': + case 'PHP_DATADIR': + case 'PHP_EOL': + case 'PHP_EXTENSION_DIR': + case 'PHP_EXTRA_VERSION': + case 'PHP_LIBDIR': + case 'PHP_LOCALSTATEDIR': + case 'PHP_MANDIR': + case 'PHP_OS': + case 'PHP_OS_FAMILY': + case 'PHP_PREFIX': + case 'PHP_SAPI': + case 'PHP_SYSCONFDIR': + return Type::getString(); + + case 'PHP_MAJOR_VERSION': + case 'PHP_MINOR_VERSION': + case 'PHP_RELEASE_VERSION': + case 'PHP_DEBUG': + case 'PHP_FLOAT_DIG': + case 'PHP_INT_MAX': + case 'PHP_INT_MIN': + case 'PHP_INT_SIZE': + case 'PHP_MAXPATHLEN': + case 'PHP_VERSION_ID': + case 'PHP_ZTS': + return Type::getInt(); + + case 'PHP_FLOAT_EPSILON': + case 'PHP_FLOAT_MAX': + case 'PHP_FLOAT_MIN': + return Type::getFloat(); + } + + if ($fq_const_name && array_key_exists($fq_const_name, $predefined_constants)) { + return ClassLikeAnalyzer::getTypeFromValue($predefined_constants[$fq_const_name]); + } + + return ClassLikeAnalyzer::getTypeFromValue($predefined_constants[$const_name]); + } + + return null; + } + + public static function getConstType( + StatementsAnalyzer $statements_analyzer, + string $const_name, + bool $is_fully_qualified, + ?Context $context + ): ?Type\Union { + $aliased_constants = $statements_analyzer->getAliases()->constants; + + if (isset($aliased_constants[$const_name])) { + $fq_const_name = $aliased_constants[$const_name]; + } elseif ($is_fully_qualified) { + $fq_const_name = $const_name; + } else { + $fq_const_name = Type::getFQCLNFromString($const_name, $statements_analyzer->getAliases()); + } + + if ($fq_const_name) { + $const_name_parts = explode('\\', $fq_const_name); + $const_name = array_pop($const_name_parts); + $namespace_name = implode('\\', $const_name_parts); + $namespace_constants = NamespaceAnalyzer::getConstantsForNamespace( + $namespace_name, + \ReflectionProperty::IS_PUBLIC + ); + + if (isset($namespace_constants[$const_name])) { + return $namespace_constants[$const_name]; + } + } + + if ($context && $context->hasVariable($fq_const_name)) { + return $context->vars_in_scope[$fq_const_name]; + } + + $file_path = $statements_analyzer->getRootFilePath(); + $codebase = $statements_analyzer->getCodebase(); + + $file_storage_provider = $codebase->file_storage_provider; + + $file_storage = $file_storage_provider->get($file_path); + + if (isset($file_storage->declaring_constants[$const_name])) { + $constant_file_path = $file_storage->declaring_constants[$const_name]; + + return $file_storage_provider->get($constant_file_path)->constants[$const_name]; + } + + if (isset($file_storage->declaring_constants[$fq_const_name])) { + $constant_file_path = $file_storage->declaring_constants[$fq_const_name]; + + return $file_storage_provider->get($constant_file_path)->constants[$fq_const_name]; + } + + return ConstFetchAnalyzer::getGlobalConstType($codebase, $fq_const_name, $const_name) + ?? ConstFetchAnalyzer::getGlobalConstType($codebase, $const_name, $const_name); + } + + public static function setConstType( + StatementsAnalyzer $statements_analyzer, + string $const_name, + Type\Union $const_type, + Context $context + ): void { + $context->vars_in_scope[$const_name] = $const_type; + $context->constants[$const_name] = $const_type; + + $source = $statements_analyzer->getSource(); + + if ($source instanceof NamespaceAnalyzer) { + $source->setConstType($const_name, $const_type); + } + } + + public static function getConstName( + PhpParser\Node\Expr $first_arg_value, + \Psalm\Internal\Provider\NodeDataProvider $type_provider, + Codebase $codebase, + Aliases $aliases + ) : ?string { + $const_name = null; + + if ($first_arg_value instanceof PhpParser\Node\Scalar\String_) { + $const_name = $first_arg_value->value; + } elseif ($first_arg_type = $type_provider->getType($first_arg_value)) { + if ($first_arg_type->isSingleStringLiteral()) { + $const_name = $first_arg_type->getSingleStringLiteral()->value; + } + } else { + $simple_type = SimpleTypeInferer::infer($codebase, $type_provider, $first_arg_value, $aliases); + + if ($simple_type && $simple_type->isSingleStringLiteral()) { + $const_name = $simple_type->getSingleStringLiteral()->value; + } + } + + return $const_name; + } + + public static function analyzeConstAssignment( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Stmt\Const_ $stmt, + Context $context + ): void { + foreach ($stmt->consts as $const) { + ExpressionAnalyzer::analyze($statements_analyzer, $const->value, $context); + + self::setConstType( + $statements_analyzer, + $const->name->name, + $statements_analyzer->node_data->getType($const->value) ?: Type::getMixed(), + $context + ); + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..981b48644ac5d9b21f669cb2d604f3d82ab574c6 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php @@ -0,0 +1,463 @@ +inside_use; + $context->inside_use = true; + + if (!$stmt->name instanceof PhpParser\Node\Identifier) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->name, $context) === false) { + return false; + } + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->var, $context) === false) { + return false; + } + + $context->inside_use = $was_inside_use; + + if ($stmt->name instanceof PhpParser\Node\Identifier) { + $prop_name = $stmt->name->name; + } elseif (($stmt_name_type = $statements_analyzer->node_data->getType($stmt->name)) + && $stmt_name_type->isSingleStringLiteral() + ) { + $prop_name = $stmt_name_type->getSingleStringLiteral()->value; + } else { + $prop_name = null; + } + + $codebase = $statements_analyzer->getCodebase(); + + $stmt_var_id = ExpressionIdentifier::getArrayVarId( + $stmt->var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + $var_id = ExpressionIdentifier::getArrayVarId( + $stmt, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($var_id && $context->hasVariable($var_id)) { + self::handleScopedProperty( + $context, + $var_id, + $statements_analyzer, + $stmt, + $codebase, + $stmt_var_id, + $in_assignment + ); + + return true; + } + + if ($stmt_var_id && $context->hasVariable($stmt_var_id)) { + $stmt_var_type = $context->vars_in_scope[$stmt_var_id]; + } else { + $stmt_var_type = $statements_analyzer->node_data->getType($stmt->var); + } + + if (!$stmt_var_type) { + return true; + } + + if ($stmt_var_type->isNull()) { + if (IssueBuffer::accepts( + new NullPropertyFetch( + 'Cannot get property on null variable ' . $stmt_var_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return true; + } + + if ($stmt_var_type->isEmpty()) { + if (IssueBuffer::accepts( + new MixedPropertyFetch( + 'Cannot fetch property on empty var ' . $stmt_var_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return true; + } + + if ($stmt_var_type->hasMixed()) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + if ($stmt->name instanceof PhpParser\Node\Identifier) { + $codebase->analyzer->addMixedMemberName( + '$' . $stmt->name->name, + $context->calling_method_id ?: $statements_analyzer->getFileName() + ); + } + + if (IssueBuffer::accepts( + new MixedPropertyFetch( + 'Cannot fetch property on mixed var ' . $stmt_var_id, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeType( + $statements_analyzer->getFilePath(), + $stmt->name, + $stmt_var_type->getId() + ); + } + } + + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getRootFilePath()); + } + + if ($stmt_var_type->isNullable() && !$stmt_var_type->ignore_nullable_issues) { + if (!$context->inside_isset) { + if (IssueBuffer::accepts( + new PossiblyNullPropertyFetch( + 'Cannot get property on possibly null variable ' . $stmt_var_id . ' of type ' . $stmt_var_type, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + $statements_analyzer->node_data->setType($stmt, Type::getNull()); + } + } + + if (!$prop_name) { + if ($stmt_var_type->hasObjectType() && !$context->ignore_variable_property) { + foreach ($stmt_var_type->getAtomicTypes() as $type) { + if ($type instanceof Type\Atomic\TNamedObject) { + $codebase->analyzer->addMixedMemberName( + strtolower($type->value) . '::$', + $context->calling_method_id ?: $statements_analyzer->getFileName() + ); + } + } + } + + return true; + } + + $invalid_fetch_types = []; + $has_valid_fetch_type = false; + + foreach ($stmt_var_type->getAtomicTypes() as $lhs_type_part) { + AtomicPropertyFetchAnalyzer::analyze( + $statements_analyzer, + $stmt, + $context, + $in_assignment, + $var_id, + $stmt_var_id, + $stmt_var_type, + $lhs_type_part, + $prop_name, + $has_valid_fetch_type, + $invalid_fetch_types + ); + } + + $stmt_type = $statements_analyzer->node_data->getType($stmt); + + if ($stmt_var_type->isNullable() && !$context->inside_isset && $stmt_type) { + $stmt_type->addType(new TNull); + + if ($stmt_var_type->ignore_nullable_issues) { + $stmt_type->ignore_nullable_issues = true; + } + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + && ($stmt_type = $statements_analyzer->node_data->getType($stmt)) + ) { + $codebase->analyzer->addNodeType( + $statements_analyzer->getFilePath(), + $stmt->name, + $stmt_type->getId() + ); + } + + if ($invalid_fetch_types) { + $lhs_type_part = $invalid_fetch_types[0]; + + if ($has_valid_fetch_type) { + if (IssueBuffer::accepts( + new PossiblyInvalidPropertyFetch( + 'Cannot fetch property on possible non-object ' . $stmt_var_id . ' of type ' . $lhs_type_part, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidPropertyFetch( + 'Cannot fetch property on non-object ' . $stmt_var_id . ' of type ' . $lhs_type_part, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($var_id) { + $context->vars_in_scope[$var_id] = $statements_analyzer->node_data->getType($stmt) ?: Type::getMixed(); + } + + return true; + } + + private static function handleScopedProperty( + Context $context, + string $var_id, + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\PropertyFetch $stmt, + \Psalm\Codebase $codebase, + ?string $stmt_var_id, + bool $in_assignment + ): void { + $stmt_type = $context->vars_in_scope[$var_id]; + + // we don't need to check anything + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeType( + $statements_analyzer->getFilePath(), + $stmt->name, + $stmt_type->getId() + ); + } + + if ($stmt_var_id === '$this' + && !$stmt_type->initialized + && $context->collect_initializations + && ($stmt_var_type = $statements_analyzer->node_data->getType($stmt->var)) + && $stmt_var_type->hasObjectType() + && $stmt->name instanceof PhpParser\Node\Identifier + ) { + $source = $statements_analyzer->getSource(); + + $property_id = null; + + foreach ($stmt_var_type->getAtomicTypes() as $lhs_type_part) { + if ($lhs_type_part instanceof TNamedObject) { + if (!$codebase->classExists($lhs_type_part->value)) { + continue; + } + + $property_id = $lhs_type_part->value . '::$' . $stmt->name->name; + } + } + + if ($property_id + && $source instanceof FunctionLikeAnalyzer + && $source->getMethodName() === '__construct' + && !$context->inside_unset + ) { + if ($context->inside_isset + || ($context->inside_assignment + && isset($context->vars_in_scope[$var_id]) + && $context->vars_in_scope[$var_id]->isNullable() + ) + ) { + $stmt_type->initialized = true; + } else { + if (IssueBuffer::accepts( + new UninitializedProperty( + 'Cannot use uninitialized property ' . $var_id, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $var_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + $stmt_type->addType(new Type\Atomic\TNull); + } + } + } + + if (($stmt_var_type = $statements_analyzer->node_data->getType($stmt->var)) + && $stmt_var_type->hasObjectType() + && $stmt->name instanceof PhpParser\Node\Identifier + ) { + // log the appearance + foreach ($stmt_var_type->getAtomicTypes() as $lhs_type_part) { + if ($lhs_type_part instanceof TNamedObject) { + if (!$codebase->classExists($lhs_type_part->value)) { + continue; + } + + $property_id = $lhs_type_part->value . '::$' . $stmt->name->name; + + $class_storage = $codebase->classlike_storage_provider->get($lhs_type_part->value); + + AtomicPropertyFetchAnalyzer::processTaints( + $statements_analyzer, + $stmt, + $stmt_type, + $property_id, + $class_storage, + $in_assignment + ); + + $codebase->properties->propertyExists( + $property_id, + true, + $statements_analyzer, + $context, + $codebase->collect_locations + ? new CodeLocation($statements_analyzer->getSource(), $stmt) + : null + ); + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->name, + $property_id + ); + } + + if (!$context->collect_mutations + && !$context->collect_initializations + && !($class_storage->external_mutation_free + && $stmt_type->allow_mutations) + ) { + if ($context->pure) { + if (IssueBuffer::accepts( + new ImpurePropertyFetch( + 'Cannot access a property on a mutable object from a pure context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_impure = true; + } + } + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/StaticPropertyFetchAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/StaticPropertyFetchAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..b7faa44475e1b0ffa1e24d6e47d0f5a4cc0d3751 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/StaticPropertyFetchAnalyzer.php @@ -0,0 +1,440 @@ +class instanceof PhpParser\Node\Name) { + self::analyzeVariableStaticPropertyFetch($statements_analyzer, $stmt->class, $stmt, $context); + return true; + } + + $codebase = $statements_analyzer->getCodebase(); + + if (count($stmt->class->parts) === 1 + && in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true) + ) { + if ($stmt->class->parts[0] === 'parent') { + $fq_class_name = $statements_analyzer->getParentFQCLN(); + + if ($fq_class_name === null) { + if (IssueBuffer::accepts( + new ParentNotFound( + 'Cannot check property fetch on parent as this class does not extend another', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return true; + } + } else { + $fq_class_name = (string)$context->self; + } + + if ($context->isPhantomClass($fq_class_name)) { + return true; + } + } else { + $aliases = $statements_analyzer->getAliases(); + + if ($context->calling_method_id + && !$stmt->class instanceof PhpParser\Node\Name\FullyQualified + ) { + $codebase->file_reference_provider->addMethodReferenceToClassMember( + $context->calling_method_id, + 'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath()) + ); + } + + $fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject( + $stmt->class, + $aliases + ); + + if ($context->isPhantomClass($fq_class_name)) { + return true; + } + + if ($context->check_classes) { + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $fq_class_name, + new CodeLocation($statements_analyzer->getSource(), $stmt->class), + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues(), + false + ) !== true) { + return false; + } + } + } + + if ($fq_class_name + && $codebase->methods_to_move + && $context->calling_method_id + && isset($codebase->methods_to_move[$context->calling_method_id]) + ) { + $destination_method_id = $codebase->methods_to_move[$context->calling_method_id]; + + $codebase->classlikes->airliftClassLikeReference( + $fq_class_name, + explode('::', $destination_method_id)[0], + $statements_analyzer->getFilePath(), + (int) $stmt->class->getAttribute('startFilePos'), + (int) $stmt->class->getAttribute('endFilePos') + 1 + ); + } + + if ($fq_class_name) { + $statements_analyzer->node_data->setType( + $stmt->class, + new Type\Union([new TNamedObject($fq_class_name)]) + ); + } + + if ($stmt->name instanceof PhpParser\Node\VarLikeIdentifier) { + $prop_name = $stmt->name->name; + } elseif (($stmt_name_type = $statements_analyzer->node_data->getType($stmt->name)) + && $stmt_name_type->isSingleStringLiteral() + ) { + $prop_name = $stmt_name_type->getSingleStringLiteral()->value; + } else { + $prop_name = null; + } + + if (!$prop_name) { + if ($fq_class_name) { + $codebase->analyzer->addMixedMemberName( + strtolower($fq_class_name) . '::$', + $context->calling_method_id ?: $statements_analyzer->getFileName() + ); + } + + return true; + } + + if (!$fq_class_name + || !$context->check_classes + || !$context->check_variables + || ExpressionAnalyzer::isMock($fq_class_name) + ) { + return true; + } + + $var_id = ExpressionIdentifier::getVarId( + $stmt, + $context->self ?: $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + $property_id = $fq_class_name . '::$' . $prop_name; + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->name, + $property_id + ); + } + + if ($context->mutation_free) { + if (IssueBuffer::accepts( + new \Psalm\Issue\ImpureStaticProperty( + 'Cannot use a static property in a mutation-free context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_has_mutation = true; + $statements_analyzer->getSource()->inferred_impure = true; + } + + if ($var_id && $context->hasVariable($var_id)) { + $stmt_type = $context->vars_in_scope[$var_id]; + + // we don't need to check anything + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + if ($codebase->collect_references) { + // log the appearance + $codebase->properties->propertyExists( + $property_id, + true, + $statements_analyzer, + $context, + $codebase->collect_locations + ? new CodeLocation($statements_analyzer->getSource(), $stmt) + : null + ); + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + && ($stmt_type = $statements_analyzer->node_data->getType($stmt)) + ) { + $codebase->analyzer->addNodeType( + $statements_analyzer->getFilePath(), + $stmt->name, + $stmt_type->getId() + ); + } + + return true; + } + + if (!$codebase->properties->propertyExists( + $property_id, + true, + $statements_analyzer, + $context, + $codebase->collect_locations + ? new CodeLocation($statements_analyzer->getSource(), $stmt) + : null + ) + ) { + if ($context->inside_isset) { + return true; + } + + if (IssueBuffer::accepts( + new UndefinedPropertyFetch( + 'Static property ' . $property_id . ' is not defined', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $property_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return true; + } + + if (ClassLikeAnalyzer::checkPropertyVisibility( + $property_id, + $context, + $statements_analyzer, + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues() + ) === false) { + return false; + } + + $declaring_property_class = $codebase->properties->getDeclaringClassForProperty( + $fq_class_name . '::$' . $prop_name, + true, + $statements_analyzer + ); + + if ($declaring_property_class === null) { + return false; + } + + $declaring_property_id = strtolower($declaring_property_class) . '::$' . $prop_name; + + if ($codebase->alter_code) { + $moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $statements_analyzer, + $stmt->class, + $fq_class_name, + $context->calling_method_id + ); + + if (!$moved_class) { + foreach ($codebase->property_transforms as $original_pattern => $transformation) { + if ($declaring_property_id === $original_pattern) { + [$old_declaring_fq_class_name] = explode('::$', $declaring_property_id); + [$new_fq_class_name, $new_property_name] = explode('::$', $transformation); + + $file_manipulations = []; + + if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) { + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $stmt->class->getAttribute('startFilePos'), + (int) $stmt->class->getAttribute('endFilePos') + 1, + Type::getStringFromFQCLN( + $new_fq_class_name, + $statements_analyzer->getNamespace(), + $statements_analyzer->getAliasedClassesFlipped(), + null + ) + ); + } + + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $stmt->name->getAttribute('startFilePos'), + (int) $stmt->name->getAttribute('endFilePos') + 1, + '$' . $new_property_name + ); + + FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations); + } + } + } + } + + $class_storage = $codebase->classlike_storage_provider->get($declaring_property_class); + $property = $class_storage->properties[$prop_name]; + + if ($var_id) { + if ($property->type) { + $context->vars_in_scope[$var_id] = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + clone $property->type, + $class_storage->name, + $class_storage->name, + $class_storage->parent_class + ); + } else { + $context->vars_in_scope[$var_id] = Type::getMixed(); + } + + $stmt_type = clone $context->vars_in_scope[$var_id]; + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeType( + $statements_analyzer->getFilePath(), + $stmt->name, + $stmt_type->getId() + ); + } + } else { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } + + return true; + } + + private static function analyzeVariableStaticPropertyFetch( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr $stmt_class, + PhpParser\Node\Expr\StaticPropertyFetch $stmt, + Context $context + ) : void { + $was_inside_use = $context->inside_use; + + $context->inside_use = true; + + ExpressionAnalyzer::analyze( + $statements_analyzer, + $stmt_class, + $context + ); + + $context->inside_use = $was_inside_use; + + $stmt_class_type = $statements_analyzer->node_data->getType($stmt_class) ?: Type::getMixed(); + + $old_data_provider = $statements_analyzer->node_data; + + $stmt_type = null; + + $codebase = $statements_analyzer->getCodebase(); + + foreach ($stmt_class_type->getAtomicTypes() as $class_atomic_type) { + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $string_type = ($class_atomic_type instanceof Type\Atomic\TClassString + && $class_atomic_type->as_type !== null) + ? $class_atomic_type->as_type->value + : ($class_atomic_type instanceof Type\Atomic\TLiteralString + ? $class_atomic_type->value + : null); + + if ($string_type) { + $new_stmt_name = new PhpParser\Node\Name\FullyQualified( + $string_type, + $stmt_class->getAttributes() + ); + + $fake_static_property = new PhpParser\Node\Expr\StaticPropertyFetch( + $new_stmt_name, + $stmt->name, + $stmt->getAttributes() + ); + + self::analyze($statements_analyzer, $fake_static_property, $context); + + $fake_stmt_type = $statements_analyzer->node_data->getType($fake_static_property) + ?: Type::getMixed(); + } else { + $fake_var_name = '__fake_var_' . (string) $stmt->getAttribute('startFilePos'); + + $fake_var = new PhpParser\Node\Expr\Variable( + $fake_var_name, + $stmt_class->getAttributes() + ); + + $context->vars_in_scope['$' . $fake_var_name] = new Type\Union([$class_atomic_type]); + + $fake_instance_property = new PhpParser\Node\Expr\PropertyFetch( + $fake_var, + $stmt->name, + $stmt->getAttributes() + ); + + InstancePropertyFetchAnalyzer::analyze( + $statements_analyzer, + $fake_instance_property, + $context + ); + + $fake_stmt_type = $statements_analyzer->node_data->getType($fake_instance_property) + ?: Type::getMixed(); + } + + $stmt_type = $stmt_type + ? Type::combineUnionTypes($stmt_type, $fake_stmt_type, $codebase) + : $fake_stmt_type; + + $statements_analyzer->node_data = $old_data_provider; + } + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..e55c71394c6f7b24d017e1223bc11e2c471c2c24 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -0,0 +1,552 @@ +getFileAnalyzer()->project_analyzer; + $codebase = $statements_analyzer->getCodebase(); + + if ($stmt->name === 'this') { + if ($statements_analyzer->isStatic()) { + if (IssueBuffer::accepts( + new InvalidScope( + 'Invalid reference to $this in a static context', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return true; + } + + if (!isset($context->vars_in_scope['$this'])) { + if (IssueBuffer::accepts( + new InvalidScope( + 'Invalid reference to $this in a non-class context', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + $context->vars_in_scope['$this'] = Type::getMixed(); + $context->vars_possibly_in_scope['$this'] = true; + + return true; + } + + $statements_analyzer->node_data->setType($stmt, clone $context->vars_in_scope['$this']); + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + && ($stmt_type = $statements_analyzer->node_data->getType($stmt)) + ) { + $codebase->analyzer->addNodeType( + $statements_analyzer->getFilePath(), + $stmt, + $stmt_type->getId() + ); + } + + if (!$context->collect_mutations && !$context->collect_initializations) { + if ($context->pure) { + if (IssueBuffer::accepts( + new ImpureVariable( + 'Cannot reference $this in a pure context', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_impure = true; + } + } + + return true; + } + + if (!$context->check_variables) { + if (is_string($stmt->name)) { + $var_name = '$' . $stmt->name; + + if (!$context->hasVariable($var_name)) { + $context->vars_in_scope[$var_name] = Type::getMixed(); + $context->vars_possibly_in_scope[$var_name] = true; + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } else { + $stmt_type = clone $context->vars_in_scope[$var_name]; + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + self::addDataFlowToVariable($statements_analyzer, $stmt, $var_name, $stmt_type, $context); + } + } else { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } + + return true; + } + + if (is_string($stmt->name) && self::isSuperGlobal('$' . $stmt->name)) { + $var_name = '$' . $stmt->name; + + if (isset($context->vars_in_scope[$var_name])) { + $type = clone $context->vars_in_scope[$var_name]; + + self::taintVariable($statements_analyzer, $var_name, $type, $stmt); + + $statements_analyzer->node_data->setType($stmt, $type); + + return true; + } + + $type = self::getGlobalType($var_name); + + self::taintVariable($statements_analyzer, $var_name, $type, $stmt); + + $statements_analyzer->node_data->setType($stmt, $type); + $context->vars_in_scope[$var_name] = clone $type; + $context->vars_possibly_in_scope[$var_name] = true; + + return true; + } + + if (!is_string($stmt->name)) { + if ($context->pure) { + if (IssueBuffer::accepts( + new ImpureVariable( + 'Cannot reference an unknown variable in a pure context', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } elseif ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer + && $statements_analyzer->getSource()->track_mutations + ) { + $statements_analyzer->getSource()->inferred_impure = true; + } + + $was_inside_use = $context->inside_use; + $context->inside_use = true; + $expr_result = ExpressionAnalyzer::analyze($statements_analyzer, $stmt->name, $context); + $context->inside_use = $was_inside_use; + + return $expr_result; + } + + if ($passed_by_reference && $by_ref_type) { + AssignmentAnalyzer::assignByRefParam( + $statements_analyzer, + $stmt, + $by_ref_type, + $by_ref_type, + $context + ); + + return true; + } + + $var_name = '$' . $stmt->name; + + if (!$context->hasVariable($var_name)) { + if (!isset($context->vars_possibly_in_scope[$var_name]) + || !$statements_analyzer->getFirstAppearance($var_name) + ) { + if ($array_assignment) { + // if we're in an array assignment, let's assign the variable + // because PHP allows it + + $context->vars_in_scope[$var_name] = Type::getArray(); + $context->vars_possibly_in_scope[$var_name] = true; + + // it might have been defined first in another if/else branch + if (!$statements_analyzer->hasVariable($var_name)) { + $statements_analyzer->registerVariable( + $var_name, + new CodeLocation($statements_analyzer, $stmt), + $context->branch_point + ); + } + } elseif (!$context->inside_isset + || $statements_analyzer->getSource() instanceof FunctionLikeAnalyzer + ) { + if ($context->is_global || $from_global) { + if (IssueBuffer::accepts( + new UndefinedGlobalVariable( + 'Cannot find referenced variable ' . $var_name . ' in global scope', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $var_name + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + + return true; + } + + if (IssueBuffer::accepts( + new UndefinedVariable( + 'Cannot find referenced variable ' . $var_name, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + + return true; + } + } + + $first_appearance = $statements_analyzer->getFirstAppearance($var_name); + + if ($first_appearance && !$context->inside_isset && !$context->inside_unset) { + if ($context->is_global) { + if ($codebase->alter_code) { + if (!isset($project_analyzer->getIssuesToFix()['PossiblyUndefinedGlobalVariable'])) { + return true; + } + + $branch_point = $statements_analyzer->getBranchPoint($var_name); + + if ($branch_point) { + $statements_analyzer->addVariableInitialization($var_name, $branch_point); + } + + return true; + } + + if (IssueBuffer::accepts( + new PossiblyUndefinedGlobalVariable( + 'Possibly undefined global variable ' . $var_name . ', first seen on line ' . + $first_appearance->getLineNumber(), + new CodeLocation($statements_analyzer->getSource(), $stmt), + $var_name + ), + $statements_analyzer->getSuppressedIssues(), + (bool) $statements_analyzer->getBranchPoint($var_name) + )) { + // fall through + } + } else { + if ($codebase->alter_code) { + if (!isset($project_analyzer->getIssuesToFix()['PossiblyUndefinedVariable'])) { + return true; + } + + $branch_point = $statements_analyzer->getBranchPoint($var_name); + + if ($branch_point) { + $statements_analyzer->addVariableInitialization($var_name, $branch_point); + } + + return true; + } + + if (IssueBuffer::accepts( + new PossiblyUndefinedVariable( + 'Possibly undefined variable ' . $var_name . ', first seen on line ' . + $first_appearance->getLineNumber(), + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues(), + (bool) $statements_analyzer->getBranchPoint($var_name) + )) { + // fall through + } + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt, + $first_appearance->raw_file_start . '-' . $first_appearance->raw_file_end . ':mixed' + ); + } + + $stmt_type = Type::getMixed(); + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + self::addDataFlowToVariable($statements_analyzer, $stmt, $var_name, $stmt_type, $context); + + $statements_analyzer->registerPossiblyUndefinedVariable($var_name, $stmt); + + return true; + } + } else { + $stmt_type = clone $context->vars_in_scope[$var_name]; + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + self::addDataFlowToVariable($statements_analyzer, $stmt, $var_name, $stmt_type, $context); + + if ($stmt_type->possibly_undefined_from_try && !$context->inside_isset) { + if ($context->is_global) { + if (IssueBuffer::accepts( + new PossiblyUndefinedGlobalVariable( + 'Possibly undefined global variable ' . $var_name . ' defined in try block', + new CodeLocation($statements_analyzer->getSource(), $stmt), + $var_name + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new PossiblyUndefinedVariable( + 'Possibly undefined variable ' . $var_name . ' defined in try block', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeType( + $statements_analyzer->getFilePath(), + $stmt, + $stmt_type->getId() + ); + } + + if ($codebase->store_node_types + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $first_appearance = $statements_analyzer->getFirstAppearance($var_name); + + if ($first_appearance) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt, + $first_appearance->raw_file_start + . '-' . $first_appearance->raw_file_end + . ':' . $stmt_type->getId() + ); + } + } + } + + return true; + } + + private static function addDataFlowToVariable( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr\Variable $stmt, + string $var_name, + Type\Union $stmt_type, + Context $context + ) : void { + $codebase = $statements_analyzer->getCodebase(); + + if ($statements_analyzer->data_flow_graph + && $codebase->find_unused_variables + && ($context->inside_call + || $context->inside_conditional + || $context->inside_use + || $context->inside_isset) + ) { + if (!$stmt_type->parent_nodes) { + $assignment_node = DataFlowNode::getForAssignment( + $var_name, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ); + + $stmt_type->parent_nodes = [ + $assignment_node->id => $assignment_node + ]; + } + + foreach ($stmt_type->parent_nodes as $parent_node) { + if ($context->inside_call) { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + new DataFlowNode( + 'variable-use', + 'variable use', + null + ), + 'use-inside-call' + ); + } elseif ($context->inside_conditional) { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + new DataFlowNode( + 'variable-use', + 'variable use', + null + ), + 'use-inside-conditional' + ); + } elseif ($context->inside_isset) { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + new DataFlowNode( + 'variable-use', + 'variable use', + null + ), + 'use-inside-isset' + ); + } else { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + new DataFlowNode( + 'variable-use', + 'variable use', + null + ), + 'variable-use' + ); + } + } + } + } + + private static function taintVariable( + StatementsAnalyzer $statements_analyzer, + string $var_name, + Type\Union $type, + PhpParser\Node\Expr\Variable $stmt + ) : void { + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph + && !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + if ($var_name === '$_GET' + || $var_name === '$_POST' + || $var_name === '$_COOKIE' + || $var_name === '$_REQUEST' + ) { + $taint_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + + $server_taint_source = new TaintSource( + $var_name . ':' . $taint_location->file_name . ':' . $taint_location->raw_file_start, + $var_name, + null, + null, + Type\TaintKindGroup::ALL_INPUT + ); + + $statements_analyzer->data_flow_graph->addSource($server_taint_source); + + $type->parent_nodes = [ + $server_taint_source->id => $server_taint_source + ]; + } + } + } + + /** + * @psalm-pure + */ + public static function isSuperGlobal(string $var_id) : bool + { + return in_array( + $var_id, + self::SUPER_GLOBALS, + true + ); + } + + public static function getGlobalType(string $var_id) : Type\Union + { + $config = \Psalm\Config::getInstance(); + + if (isset($config->globals[$var_id])) { + return Type::parseString($config->globals[$var_id]); + } + + if ($var_id === '$argv') { + return new Type\Union([ + new Type\Atomic\TArray([Type::getInt(), Type::getString()]), + ]); + } + + if ($var_id === '$argc') { + return Type::getInt(); + } + + if (self::isSuperGlobal($var_id)) { + $type = Type::getArray(); + + return $type; + } + + return Type::getMixed(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..4ed852993f3f47948a39d7989b92ef1270e9c1ce --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php @@ -0,0 +1,136 @@ +inside_assignment; + $context->inside_assignment = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->var, $context) === false) { + if (!$was_inside_assignment) { + $context->inside_assignment = false; + } + return false; + } + + if (!$was_inside_assignment) { + $context->inside_assignment = false; + } + + $stmt_var_type = $statements_analyzer->node_data->getType($stmt->var); + + if ($stmt instanceof PostInc || $stmt instanceof PostDec) { + $statements_analyzer->node_data->setType($stmt, $stmt_var_type ?: Type::getMixed()); + } + + if (($stmt_var_type = $statements_analyzer->node_data->getType($stmt->var)) + && $stmt_var_type->hasString() + && ($stmt instanceof PostInc || $stmt instanceof PreInc) + ) { + $return_type = null; + + $fake_right_expr = new PhpParser\Node\Scalar\LNumber(1, $stmt->getAttributes()); + $statements_analyzer->node_data->setType($fake_right_expr, Type::getInt()); + + BinaryOp\NonDivArithmeticOpAnalyzer::analyze( + $statements_analyzer, + $statements_analyzer->node_data, + $stmt->var, + $fake_right_expr, + $stmt, + $return_type, + $context + ); + + $stmt_type = clone $stmt_var_type; + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + BinaryOpAnalyzer::addDataFlow( + $statements_analyzer, + $stmt, + $stmt->var, + $fake_right_expr, + 'inc' + ); + + $var_id = ExpressionIdentifier::getArrayVarId($stmt->var, null); + + $codebase = $statements_analyzer->getCodebase(); + + if ($var_id && isset($context->vars_in_scope[$var_id])) { + $context->vars_in_scope[$var_id] = $stmt_type; + + if ($codebase->find_unused_variables && $stmt->var instanceof PhpParser\Node\Expr\Variable) { + $context->assigned_var_ids[$var_id] = true; + $context->possibly_assigned_var_ids[$var_id] = true; + } + + // removes dependent vars from $context + $context->removeDescendents( + $var_id, + $context->vars_in_scope[$var_id], + $return_type, + $statements_analyzer + ); + } + } else { + $fake_right_expr = new PhpParser\Node\Scalar\LNumber(1, $stmt->getAttributes()); + + $operation = $stmt instanceof PostInc || $stmt instanceof PreInc + ? new PhpParser\Node\Expr\BinaryOp\Plus( + $stmt->var, + $fake_right_expr, + $stmt->var->getAttributes() + ) + : new PhpParser\Node\Expr\BinaryOp\Minus( + $stmt->var, + $fake_right_expr, + $stmt->var->getAttributes() + ); + + $fake_assignment = new PhpParser\Node\Expr\Assign( + $stmt->var, + $operation, + $stmt->getAttributes() + ); + + $old_node_data = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $fake_assignment, $context) === false) { + return false; + } + + if ($stmt instanceof PreInc || $stmt instanceof PreDec) { + $old_node_data->setType( + $stmt, + $statements_analyzer->node_data->getType($operation) ?: Type::getMixed() + ); + } + + $statements_analyzer->node_data = $old_node_data; + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..8b4b699034defa45b5186f0401aa65d76801c3cf --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php @@ -0,0 +1,404 @@ +getCodebase(); + $config = $codebase->config; + + if (!$config->allow_includes) { + throw new FileIncludeException( + 'File includes are not allowed per your Psalm config - check the allowFileIncludes flag.' + ); + } + + $was_inside_call = $context->inside_call; + + $context->inside_call = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + + if (!$was_inside_call) { + $context->inside_call = false; + } + + $stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr); + + if ($stmt->expr instanceof PhpParser\Node\Scalar\String_ + || ($stmt_expr_type && $stmt_expr_type->isSingleStringLiteral()) + ) { + if ($stmt->expr instanceof PhpParser\Node\Scalar\String_) { + $path_to_file = $stmt->expr->value; + } else { + $path_to_file = $stmt_expr_type->getSingleStringLiteral()->value; + } + + $path_to_file = str_replace('/', DIRECTORY_SEPARATOR, $path_to_file); + + // attempts to resolve using get_include_path dirs + $include_path = self::resolveIncludePath($path_to_file, dirname($statements_analyzer->getFilePath())); + $path_to_file = $include_path ? $include_path : $path_to_file; + + if (DIRECTORY_SEPARATOR === '/') { + $is_path_relative = $path_to_file[0] !== DIRECTORY_SEPARATOR; + } else { + $is_path_relative = !preg_match('~^[A-Z]:\\\\~i', $path_to_file); + } + + if ($is_path_relative) { + $path_to_file = $config->base_dir . DIRECTORY_SEPARATOR . $path_to_file; + } + } else { + $path_to_file = self::getPathTo( + $stmt->expr, + $statements_analyzer->node_data, + $statements_analyzer, + $statements_analyzer->getFileName(), + $config + ); + } + + if ($stmt_expr_type + && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph + && $stmt_expr_type->parent_nodes + && !\in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) + ) { + $arg_location = new CodeLocation($statements_analyzer->getSource(), $stmt->expr); + + $include_param_sink = TaintSink::getForMethodArgument( + 'include', + 'include', + 0, + $arg_location + ); + + $include_param_sink->taints = [\Psalm\Type\TaintKind::INPUT_TEXT]; + + $statements_analyzer->data_flow_graph->addSink($include_param_sink); + + foreach ($stmt_expr_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath($parent_node, $include_param_sink, 'arg'); + } + } + + if ($path_to_file) { + $path_to_file = self::normalizeFilePath($path_to_file); + + // if the file is already included, we can't check much more + if (in_array(realpath($path_to_file), get_included_files(), true)) { + return true; + } + + $current_file_analyzer = $statements_analyzer->getFileAnalyzer(); + + if ($current_file_analyzer->project_analyzer->fileExists($path_to_file)) { + if ($statements_analyzer->hasParentFilePath($path_to_file) + || !$codebase->file_storage_provider->has($path_to_file) + || ($statements_analyzer->hasAlreadyRequiredFilePath($path_to_file) + && !$codebase->file_storage_provider->get($path_to_file)->has_extra_statements) + ) { + return true; + } + + $current_file_analyzer->addRequiredFilePath($path_to_file); + + $file_name = $config->shortenFileName($path_to_file); + + $nesting = $statements_analyzer->getRequireNesting() + 1; + $current_file_analyzer->project_analyzer->progress->debug( + str_repeat(' ', $nesting) . 'checking ' . $file_name . PHP_EOL + ); + + $include_file_analyzer = new \Psalm\Internal\Analyzer\FileAnalyzer( + $current_file_analyzer->project_analyzer, + $path_to_file, + $file_name + ); + + $include_file_analyzer->setRootFilePath( + $current_file_analyzer->getRootFilePath(), + $current_file_analyzer->getRootFileName() + ); + + $include_file_analyzer->addParentFilePath($current_file_analyzer->getFilePath()); + $include_file_analyzer->addRequiredFilePath($current_file_analyzer->getFilePath()); + + foreach ($current_file_analyzer->getRequiredFilePaths() as $required_file_path) { + $include_file_analyzer->addRequiredFilePath($required_file_path); + } + + foreach ($current_file_analyzer->getParentFilePaths() as $parent_file_path) { + $include_file_analyzer->addParentFilePath($parent_file_path); + } + + try { + $include_file_analyzer->analyze( + $context, + false, + $global_context + ); + } catch (\Psalm\Exception\UnpreparedAnalysisException $e) { + if ($config->skip_checks_on_unresolvable_includes) { + $context->check_classes = false; + $context->check_variables = false; + $context->check_functions = false; + } + } + + $included_return_type = $include_file_analyzer->getReturnType(); + + if ($included_return_type) { + $statements_analyzer->node_data->setType($stmt, $included_return_type); + } + + $context->has_returned = false; + + foreach ($include_file_analyzer->getRequiredFilePaths() as $required_file_path) { + $current_file_analyzer->addRequiredFilePath($required_file_path); + } + + $include_file_analyzer->clearSourceBeforeDestruction(); + + return true; + } + + $source = $statements_analyzer->getSource(); + + if (IssueBuffer::accepts( + new MissingFile( + 'Cannot find file ' . $path_to_file . ' to include', + new CodeLocation($source, $stmt) + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } else { + $var_id = ExpressionIdentifier::getArrayVarId($stmt->expr, null); + + if (!$var_id || !isset($context->phantom_files[$var_id])) { + $source = $statements_analyzer->getSource(); + + if (IssueBuffer::accepts( + new UnresolvableInclude( + 'Cannot resolve the given expression to a file path', + new CodeLocation($source, $stmt) + ), + $source->getSuppressedIssues() + )) { + // fall through + } + } + } + + if ($config->skip_checks_on_unresolvable_includes) { + $context->check_classes = false; + $context->check_variables = false; + $context->check_functions = false; + } + + return true; + } + + /** + * @psalm-suppress MixedAssignment + */ + public static function getPathTo( + PhpParser\Node\Expr $stmt, + ?\Psalm\Internal\Provider\NodeDataProvider $type_provider, + ?StatementsAnalyzer $statements_analyzer, + string $file_name, + Config $config + ): ?string { + if (DIRECTORY_SEPARATOR === '/') { + $is_path_relative = $file_name[0] !== DIRECTORY_SEPARATOR; + } else { + $is_path_relative = !preg_match('~^[A-Z]:\\\\~i', $file_name); + } + + if ($is_path_relative) { + $file_name = $config->base_dir . DIRECTORY_SEPARATOR . $file_name; + } + + if ($stmt instanceof PhpParser\Node\Scalar\String_) { + if (DIRECTORY_SEPARATOR !== '/') { + return str_replace('/', DIRECTORY_SEPARATOR, $stmt->value); + } + return $stmt->value; + } + + $stmt_type = $type_provider ? $type_provider->getType($stmt) : null; + + if ($stmt_type && $stmt_type->isSingleStringLiteral()) { + if (DIRECTORY_SEPARATOR !== '/') { + return str_replace( + '/', + DIRECTORY_SEPARATOR, + $stmt_type->getSingleStringLiteral()->value + ); + } + + return $stmt_type->getSingleStringLiteral()->value; + } + + if ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch) { + if ($stmt->var instanceof PhpParser\Node\Expr\Variable + && $stmt->var->name === 'GLOBALS' + && $stmt->dim instanceof PhpParser\Node\Scalar\String_ + ) { + if (isset($GLOBALS[$stmt->dim->value]) && is_string($GLOBALS[$stmt->dim->value])) { + /** @var string */ + return $GLOBALS[$stmt->dim->value]; + } + } + } elseif ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) { + $left_string = self::getPathTo($stmt->left, $type_provider, $statements_analyzer, $file_name, $config); + $right_string = self::getPathTo($stmt->right, $type_provider, $statements_analyzer, $file_name, $config); + + if ($left_string && $right_string) { + return $left_string . $right_string; + } + } elseif ($stmt instanceof PhpParser\Node\Expr\FuncCall && + $stmt->name instanceof PhpParser\Node\Name && + $stmt->name->parts === ['dirname'] + ) { + if ($stmt->args) { + $dir_level = 1; + + if (isset($stmt->args[1])) { + if ($stmt->args[1]->value instanceof PhpParser\Node\Scalar\LNumber) { + $dir_level = $stmt->args[1]->value->value; + } else { + return null; + } + } + + $evaled_path = self::getPathTo( + $stmt->args[0]->value, + $type_provider, + $statements_analyzer, + $file_name, + $config + ); + + if (!$evaled_path) { + return null; + } + + return dirname($evaled_path, $dir_level); + } + } elseif ($stmt instanceof PhpParser\Node\Expr\ConstFetch) { + $const_name = implode('', $stmt->name->parts); + + if (defined($const_name)) { + $constant_value = constant($const_name); + + if (is_string($constant_value)) { + return $constant_value; + } + } + } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Dir) { + return dirname($file_name); + } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\File) { + return $file_name; + } + + return null; + } + + public static function resolveIncludePath(string $file_name, string $current_directory): ?string + { + if (!$current_directory) { + return $file_name; + } + + $paths = PATH_SEPARATOR === ':' + ? preg_split('#(?inside_use; + $context->inside_use = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + + $context->inside_use = $was_inside_use; + + if ($stmt->class instanceof PhpParser\Node\Expr) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->class, $context) === false) { + return false; + } + } elseif (!in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)) { + if ($context->check_classes) { + $codebase = $statements_analyzer->getCodebase(); + + $fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject( + $stmt->class, + $statements_analyzer->getAliases() + ); + + if ($codebase->store_node_types + && $fq_class_name + && !$context->collect_initializations + && !$context->collect_mutations + ) { + $codebase->analyzer->addNodeReference( + $statements_analyzer->getFilePath(), + $stmt->class, + $codebase->classlikes->classOrInterfaceExists($fq_class_name) + ? $fq_class_name + : '*' . implode('\\', $stmt->class->parts) + ); + } + + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $fq_class_name, + new CodeLocation($statements_analyzer->getSource(), $stmt->class), + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues(), + false + ) === false) { + return false; + } + + if ($codebase->alter_code) { + $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $statements_analyzer, + $stmt->class, + $fq_class_name, + $context->calling_method_id + ); + } + } + } + + $statements_analyzer->node_data->setType($stmt, Type::getBool()); + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..bd655104dc23e8888f480c111aa4230d06374ccc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php @@ -0,0 +1,51 @@ +vars as $isset_var) { + if ($isset_var instanceof PhpParser\Node\Expr\PropertyFetch + && $isset_var->var instanceof PhpParser\Node\Expr\Variable + && $isset_var->var->name === 'this' + && $isset_var->name instanceof PhpParser\Node\Identifier + ) { + $var_id = '$this->' . $isset_var->name->name; + + if (!isset($context->vars_in_scope[$var_id])) { + $context->vars_in_scope[$var_id] = Type::getMixed(); + $context->vars_possibly_in_scope[$var_id] = true; + } + } + + self::analyzeIssetVar($statements_analyzer, $isset_var, $context); + } + + $statements_analyzer->node_data->setType($stmt, Type::getBool()); + } + + public static function analyzeIssetVar( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr $stmt, + Context $context + ) : bool { + + $context->inside_isset = true; + + $result = ExpressionAnalyzer::analyze($statements_analyzer, $stmt, $context); + + $context->inside_isset = false; + + return $result; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..3857da9d4351045e86ff54a84818b91f20e965bf --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php @@ -0,0 +1,86 @@ +node_data->setType($stmt, Type::getInt()); + } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Class_) { + $codebase = $statements_analyzer->getCodebase(); + + if (!$context->self) { + if (IssueBuffer::accepts( + new UndefinedConstant( + 'Cannot get __class__ outside a class', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + $statements_analyzer->node_data->setType($stmt, Type::getClassString()); + } else { + if ($codebase->alter_code) { + $codebase->classlikes->handleClassLikeReferenceInMigration( + $codebase, + $statements_analyzer, + $stmt, + $context->self, + $context->calling_method_id + ); + } + + $statements_analyzer->node_data->setType($stmt, Type::getLiteralClassString($context->self)); + } + } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Namespace_) { + $namespace = $statements_analyzer->getNamespace(); + if ($namespace === null + && IssueBuffer::accepts( + new UndefinedConstant( + 'Cannot get __namespace__ outside a namespace', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + ) + ) { + // fall through + } + + $statements_analyzer->node_data->setType($stmt, Type::getString($namespace)); + } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Method + || $stmt instanceof PhpParser\Node\Scalar\MagicConst\Function_ + ) { + $source = $statements_analyzer->getSource(); + if ($source instanceof FunctionLikeAnalyzer) { + $statements_analyzer->node_data->setType($stmt, Type::getString($source->getId())); + } else { + $statements_analyzer->node_data->setType($stmt, new Type\Union([new Type\Atomic\TCallableString])); + } + } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\File + || $stmt instanceof PhpParser\Node\Scalar\MagicConst\Dir + ) { + $statements_analyzer->node_data->setType($stmt, new Type\Union([new Type\Atomic\TNonEmptyString()])); + } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Trait_) { + if ($statements_analyzer->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) { + $statements_analyzer->node_data->setType($stmt, new Type\Union([new Type\Atomic\TNonEmptyString()])); + } else { + $statements_analyzer->node_data->setType($stmt, Type::getString()); + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..9b25d55c57cd42bbc67ed04b6cc4062be283b352 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php @@ -0,0 +1,259 @@ +inside_call; + + $context->inside_call = true; + + $was_inside_conditional = $context->inside_conditional; + + $context->inside_conditional = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->cond, $context) === false) { + $context->inside_conditional = $was_inside_conditional; + + return false; + } + + $context->inside_conditional = $was_inside_conditional; + + $switch_var_id = ExpressionIdentifier::getArrayVarId( + $stmt->cond, + null, + $statements_analyzer + ); + + $match_condition = $stmt->cond; + + if (!$switch_var_id + && ($stmt->cond instanceof PhpParser\Node\Expr\FuncCall + || $stmt->cond instanceof PhpParser\Node\Expr\MethodCall + || $stmt->cond instanceof PhpParser\Node\Expr\StaticCall + ) + ) { + $switch_var_id = '$__tmp_switch__' . (int) $stmt->cond->getAttribute('startFilePos'); + + $condition_type = $statements_analyzer->node_data->getType($stmt->cond) ?: Type::getMixed(); + + $context->vars_in_scope[$switch_var_id] = $condition_type; + + $match_condition = new PhpParser\Node\Expr\Variable( + substr($switch_var_id, 1), + $stmt->cond->getAttributes() + ); + } + + $arms = $stmt->arms; + + foreach ($arms as $i => $arm) { + // move default to the end + if ($arm->conds === null) { + unset($arms[$i]); + $arms[] = $arm; + } + } + + $arms = array_reverse($arms); + + $last_arm = array_shift($arms); + + $old_node_data = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + if (!$last_arm->conds) { + $ternary = $last_arm->body; + } else { + $ternary = new PhpParser\Node\Expr\Ternary( + self::convertCondsToConditional($last_arm->conds, $match_condition, $last_arm->getAttributes()), + $last_arm->body, + new PhpParser\Node\Expr\Throw_( + new PhpParser\Node\Expr\New_( + new PhpParser\Node\Name\FullyQualified( + 'UnhandledMatchError' + ) + ) + ) + ); + } + + foreach ($arms as $arm) { + if (!$arm->conds) { + continue; + } + + $ternary = new PhpParser\Node\Expr\Ternary( + self::convertCondsToConditional($arm->conds, $match_condition, $arm->getAttributes()), + $arm->body, + $ternary, + $arm->getAttributes() + ); + } + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('RedundantCondition', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['RedundantCondition']); + } + + if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['RedundantConditionGivenDocblockType']); + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $ternary, $context) === false) { + return false; + } + + if (!in_array('RedundantCondition', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['RedundantCondition']); + } + + if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['RedundantConditionGivenDocblockType']); + } + + if ($switch_var_id && $last_arm->conds) { + $codebase = $statements_analyzer->getCodebase(); + + $all_conds = $last_arm->conds; + + foreach ($arms as $arm) { + if (!$arm->conds) { + throw new \UnexpectedValueException('bad'); + } + + $all_conds = \array_merge($arm->conds, $all_conds); + } + + $all_match_condition = self::convertCondsToConditional( + \array_values($all_conds), + $match_condition, + $match_condition->getAttributes() + ); + + ExpressionAnalyzer::analyze($statements_analyzer, $all_match_condition, $context); + + $clauses = \Psalm\Type\Algebra::getFormula( + \spl_object_id($all_match_condition), + \spl_object_id($all_match_condition), + $all_match_condition, + $context->self, + $statements_analyzer, + $codebase, + false, + false + ); + + $reconcilable_types = \Psalm\Type\Algebra::getTruthsFromFormula( + \Psalm\Type\Algebra::negateFormula($clauses) + ); + + // if the if has an || in the conditional, we cannot easily reason about it + if ($reconcilable_types) { + $changed_var_ids = []; + + $vars_in_scope_reconciled = \Psalm\Type\Reconciler::reconcileKeyedTypes( + $reconcilable_types, + [], + $context->vars_in_scope, + $changed_var_ids, + [], + $statements_analyzer, + [], + $context->inside_loop, + null + ); + + if (isset($vars_in_scope_reconciled[$switch_var_id])) { + if ($vars_in_scope_reconciled[$switch_var_id]->hasLiteralValue()) { + if (\Psalm\IssueBuffer::accepts( + new UnhandledMatchCondition( + 'This match expression is not exhaustive - consider values ' + . $vars_in_scope_reconciled[$switch_var_id]->getId(), + new \Psalm\CodeLocation($statements_analyzer->getSource(), $match_condition) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // continue + } + } + } + } + } + + $stmt_expr_type = $statements_analyzer->node_data->getType($ternary); + + $old_node_data->setType($stmt, $stmt_expr_type ?: Type::getMixed()); + + $statements_analyzer->node_data = $old_node_data; + + $context->inside_call = $was_inside_call; + + return true; + } + + /** + * @param non-empty-list $conds + */ + private static function convertCondsToConditional( + array $conds, + PhpParser\Node\Expr $match_condition, + array $attributes + ) : PhpParser\Node\Expr { + if (count($conds) === 1) { + return new PhpParser\Node\Expr\BinaryOp\Identical( + $match_condition, + $conds[0], + $attributes + ); + } + + $array_items = array_map( + function ($cond): PhpParser\Node\Expr\ArrayItem { + return new PhpParser\Node\Expr\ArrayItem($cond, null, false, $cond->getAttributes()); + }, + $conds + ); + + return new PhpParser\Node\Expr\FuncCall( + new PhpParser\Node\Name\FullyQualified(['in_array']), + [ + new PhpParser\Node\Arg( + $match_condition + ), + new PhpParser\Node\Arg( + new PhpParser\Node\Expr\Array_( + $array_items + ) + ), + new PhpParser\Node\Arg( + new PhpParser\Node\Expr\ConstFetch( + new PhpParser\Node\Name\FullyQualified(['true']) + ) + ), + ], + $attributes + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/NullsafeAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/NullsafeAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..599dc8ca410e3380d29b386dbfb1aa85d56ecd4c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/NullsafeAnalyzer.php @@ -0,0 +1,86 @@ +var instanceof PhpParser\Node\Expr\Variable) { + ExpressionAnalyzer::analyze($statements_analyzer, $stmt->var, $context); + + $tmp_name = '__tmp_nullsafe__' . (int) $stmt->var->getAttribute('startFilePos'); + + $condition_type = $statements_analyzer->node_data->getType($stmt->var); + + if ($condition_type) { + $context->vars_in_scope['$' . $tmp_name] = $condition_type; + + $tmp_var = new PhpParser\Node\Expr\Variable($tmp_name, $stmt->var->getAttributes()); + } else { + $tmp_var = $stmt->var; + } + } else { + $tmp_var = $stmt->var; + } + + $old_node_data = $statements_analyzer->node_data; + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $null_value1 = new PhpParser\Node\Expr\ConstFetch( + new PhpParser\Node\Name('null'), + $stmt->var->getAttributes() + ); + + $null_comparison = new PhpParser\Node\Expr\BinaryOp\Identical( + $tmp_var, + $null_value1, + $stmt->var->getAttributes() + ); + + $null_value2 = new PhpParser\Node\Expr\ConstFetch( + new PhpParser\Node\Name('null'), + $stmt->var->getAttributes() + ); + + if ($stmt instanceof PhpParser\Node\Expr\NullsafePropertyFetch) { + $ternary = new PhpParser\Node\Expr\Ternary( + $null_comparison, + $null_value2, + new PhpParser\Node\Expr\PropertyFetch($tmp_var, $stmt->name, $stmt->getAttributes()), + $stmt->getAttributes() + ); + } else { + $ternary = new PhpParser\Node\Expr\Ternary( + $null_comparison, + $null_value2, + new PhpParser\Node\Expr\MethodCall($tmp_var, $stmt->name, $stmt->args, $stmt->getAttributes()), + $stmt->getAttributes() + ); + } + + ExpressionAnalyzer::analyze($statements_analyzer, $ternary, $context); + + $ternary_type = $statements_analyzer->node_data->getType($ternary); + + $statements_analyzer->node_data = $old_node_data; + + $statements_analyzer->node_data->setType($stmt, $ternary_type ?: Type::getMixed()); + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..ad98949b132f6e30c0e4ae181dc520d08b389fd4 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php @@ -0,0 +1,87 @@ +getCodebase(); + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $call_location = new CodeLocation($statements_analyzer->getSource(), $stmt); + + $print_param_sink = TaintSink::getForMethodArgument( + 'print', + 'print', + 0, + null, + $call_location + ); + + $print_param_sink->taints = [ + Type\TaintKind::INPUT_HTML, + Type\TaintKind::USER_SECRET, + Type\TaintKind::SYSTEM_SECRET + ]; + + $statements_analyzer->data_flow_graph->addSink($print_param_sink); + } + + if ($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr)) { + if (Call\ArgumentAnalyzer::verifyType( + $statements_analyzer, + $stmt_expr_type, + Type::getString(), + null, + 'print', + 0, + new CodeLocation($statements_analyzer->getSource(), $stmt->expr), + $stmt->expr, + $context, + new FunctionLikeParameter('var', false), + false, + null, + true, + true, + new CodeLocation($statements_analyzer->getSource(), $stmt) + ) === false) { + return false; + } + } + + if (isset($codebase->config->forbidden_functions['print'])) { + if (IssueBuffer::accepts( + new ForbiddenCode( + 'You have forbidden the use of print', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // continue + } + } + + $statements_analyzer->node_data->setType($stmt, Type::getInt(false, 1)); + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php new file mode 100644 index 0000000000000000000000000000000000000000..97196e85de5ee118fb5ceac99a97e624f6db556b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php @@ -0,0 +1,535 @@ + $existing_class_constants + */ + public static function infer( + \Psalm\Codebase $codebase, + \Psalm\Internal\Provider\NodeDataProvider $nodes, + PhpParser\Node\Expr $stmt, + \Psalm\Aliases $aliases, + \Psalm\FileSource $file_source = null, + ?array $existing_class_constants = null, + ?string $fq_classlike_name = null + ): ?Type\Union { + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) { + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) { + $left = self::infer( + $codebase, + $nodes, + $stmt->left, + $aliases, + $file_source, + $existing_class_constants, + $fq_classlike_name + ); + $right = self::infer( + $codebase, + $nodes, + $stmt->right, + $aliases, + $file_source, + $existing_class_constants, + $fq_classlike_name + ); + + if ($left + && $right + ) { + if ($left->isSingleStringLiteral() + && $right->isSingleStringLiteral() + ) { + $result = $left->getSingleStringLiteral()->value . $right->getSingleStringLiteral()->value; + + return Type::getString($result); + } + + if ($left->isString()) { + $left_string_types = $left->getAtomicTypes(); + $left_string_type = reset($left_string_types); + if ($left_string_type instanceof Type\Atomic\TNonEmptyString) { + return new Type\Union([new Type\Atomic\TNonEmptyString()]); + } + } + + if ($right->isString()) { + $right_string_types = $right->getAtomicTypes(); + $right_string_type = reset($right_string_types); + if ($right_string_type instanceof Type\Atomic\TNonEmptyString) { + return new Type\Union([new Type\Atomic\TNonEmptyString()]); + } + } + } + + return Type::getString(); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalAnd + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalOr + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotEqual + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Greater + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Smaller + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual + ) { + return Type::getBool(); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Coalesce) { + return null; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Spaceship) { + return Type::getInt(); + } + + $stmt_left_type = self::infer( + $codebase, + $nodes, + $stmt->left, + $aliases, + $file_source, + $existing_class_constants, + $fq_classlike_name + ); + + $stmt_right_type = self::infer( + $codebase, + $nodes, + $stmt->right, + $aliases, + $file_source, + $existing_class_constants, + $fq_classlike_name + ); + + if (!$stmt_left_type || !$stmt_right_type) { + return null; + } + + $nodes->setType( + $stmt->left, + $stmt_left_type + ); + + $nodes->setType( + $stmt->right, + $stmt_right_type + ); + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Plus + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Minus + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Mod + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Mul + || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Pow + ) { + NonDivArithmeticOpAnalyzer::analyze( + $file_source instanceof StatementsSource ? $file_source : null, + $nodes, + $stmt->left, + $stmt->right, + $stmt, + $result_type + ); + + if ($result_type) { + return $result_type; + } + + return null; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Div + && ($stmt_left_type->hasInt() || $stmt_left_type->hasFloat()) + && ($stmt_right_type->hasInt() || $stmt_right_type->hasFloat()) + ) { + return Type::combineUnionTypes(Type::getFloat(), Type::getInt()); + } + } + + if ($stmt instanceof PhpParser\Node\Expr\ConstFetch) { + if (strtolower($stmt->name->parts[0]) === 'false') { + return Type::getFalse(); + } elseif (strtolower($stmt->name->parts[0]) === 'true') { + return Type::getTrue(); + } elseif (strtolower($stmt->name->parts[0]) === 'null') { + return Type::getNull(); + } elseif ($stmt->name->parts[0] === '__NAMESPACE__') { + return Type::getString($aliases->namespace); + } + + return null; + } + + if ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Dir + || $stmt instanceof PhpParser\Node\Scalar\MagicConst\File + ) { + return new Type\Union([new Type\Atomic\TNonEmptyString()]); + } + + if ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Line) { + return Type::getInt(); + } + + if ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Class_ + || $stmt instanceof PhpParser\Node\Scalar\MagicConst\Method + || $stmt instanceof PhpParser\Node\Scalar\MagicConst\Trait_ + || $stmt instanceof PhpParser\Node\Scalar\MagicConst\Function_ + ) { + return Type::getString(); + } + + if ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Namespace_) { + return Type::getString($aliases->namespace); + } + + if ($stmt instanceof PhpParser\Node\Expr\ClassConstFetch) { + if ($stmt->class instanceof PhpParser\Node\Name + && $stmt->name instanceof PhpParser\Node\Identifier + && $fq_classlike_name + && $stmt->class->parts !== ['static'] + && $stmt->class->parts !== ['parent'] + ) { + if (isset($existing_class_constants[$stmt->name->name]) + && $existing_class_constants[$stmt->name->name]->type + ) { + if ($stmt->class->parts === ['self']) { + return clone $existing_class_constants[$stmt->name->name]->type; + } + } + + if ($stmt->class->parts === ['self']) { + $const_fq_class_name = $fq_classlike_name; + } else { + $const_fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject( + $stmt->class, + $aliases + ); + } + + if (strtolower($const_fq_class_name) === strtolower($fq_classlike_name) + && isset($existing_class_constants[$stmt->name->name]) + && $existing_class_constants[$stmt->name->name]->type + ) { + return clone $existing_class_constants[$stmt->name->name]->type; + } + + if (strtolower($stmt->name->name) === 'class') { + return Type::getLiteralClassString($const_fq_class_name); + } + + if ($existing_class_constants === null + && $file_source instanceof StatementsAnalyzer + ) { + try { + $foreign_class_constant = $codebase->classlikes->getClassConstantType( + $const_fq_class_name, + $stmt->name->name, + \ReflectionProperty::IS_PRIVATE, + $file_source + ); + + if ($foreign_class_constant) { + return clone $foreign_class_constant; + } + + return null; + } catch (\InvalidArgumentException $e) { + return null; + } catch (\Psalm\Exception\CircularReferenceException $e) { + return null; + } + } + } + + if ($stmt->name instanceof PhpParser\Node\Identifier && strtolower($stmt->name->name) === 'class') { + return Type::getClassString(); + } + + return null; + } + + if ($stmt instanceof PhpParser\Node\Scalar\String_) { + return Type::getString($stmt->value); + } + + if ($stmt instanceof PhpParser\Node\Scalar\LNumber) { + return Type::getInt(false, $stmt->value); + } + + if ($stmt instanceof PhpParser\Node\Scalar\DNumber) { + return Type::getFloat($stmt->value); + } + + if ($stmt instanceof PhpParser\Node\Expr\Array_) { + if (count($stmt->items) === 0) { + return Type::getEmptyArray(); + } + + $item_key_type = null; + $item_value_type = null; + + $property_types = []; + $class_strings = []; + + $can_create_objectlike = true; + + $is_list = true; + + foreach ($stmt->items as $int_offset => $item) { + if ($item === null) { + continue; + } + + $single_item_key_type = null; + + if ($item->key) { + $single_item_key_type = self::infer( + $codebase, + $nodes, + $item->key, + $aliases, + $file_source, + $existing_class_constants, + $fq_classlike_name + ); + + if ($single_item_key_type) { + if ($item_key_type) { + $item_key_type = Type::combineUnionTypes( + $single_item_key_type, + $item_key_type, + null, + false, + true, + 30 + ); + } else { + $item_key_type = $single_item_key_type; + } + } + } else { + $item_key_type = Type::getInt(); + } + + $single_item_value_type = self::infer( + $codebase, + $nodes, + $item->value, + $aliases, + $file_source, + $existing_class_constants, + $fq_classlike_name + ); + + if (!$single_item_value_type) { + return null; + } + + if ($item->key instanceof PhpParser\Node\Scalar\String_ + || $item->key instanceof PhpParser\Node\Scalar\LNumber + || !$item->key + ) { + if (count($property_types) <= 50) { + $property_types[$item->key ? $item->key->value : $int_offset] = $single_item_value_type; + } else { + $can_create_objectlike = false; + } + + if ($item->key + && (!$item->key instanceof PhpParser\Node\Scalar\LNumber + || $item->key->value !== $int_offset) + ) { + $is_list = false; + } + } else { + $is_list = false; + $dim_type = $single_item_key_type; + + if (!$dim_type) { + return null; + } + + $dim_atomic_types = $dim_type->getAtomicTypes(); + + if (count($dim_atomic_types) > 1 || $dim_type->hasMixed() || count($property_types) > 50) { + $can_create_objectlike = false; + } else { + $atomic_type = array_shift($dim_atomic_types); + + if ($atomic_type instanceof Type\Atomic\TLiteralInt + || $atomic_type instanceof Type\Atomic\TLiteralString + ) { + if ($atomic_type instanceof Type\Atomic\TLiteralClassString) { + $class_strings[$atomic_type->value] = true; + } + + $property_types[$atomic_type->value] = $single_item_value_type; + } else { + $can_create_objectlike = false; + } + } + } + + if ($item_value_type) { + $item_value_type = Type::combineUnionTypes( + $single_item_value_type, + $item_value_type, + null, + false, + true, + 30 + ); + } else { + $item_value_type = $single_item_value_type; + } + } + + // if this array looks like an object-like array, let's return that instead + if ($item_value_type + && $item_key_type + && ($item_key_type->hasString() || $item_key_type->hasInt()) + && $can_create_objectlike + && $property_types + ) { + $objectlike = new Type\Atomic\TKeyedArray($property_types, $class_strings); + $objectlike->sealed = true; + $objectlike->is_list = $is_list; + return new Type\Union([$objectlike]); + } + + if (!$item_key_type || !$item_value_type) { + return null; + } + + return new Type\Union([ + new Type\Atomic\TNonEmptyArray([ + $item_key_type, + $item_value_type, + ]), + ]); + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\Int_) { + return Type::getInt(); + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\Double) { + return Type::getFloat(); + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\Bool_) { + return Type::getBool(); + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\String_) { + return Type::getString(); + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\Object_) { + return Type::getObject(); + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast\Array_) { + return Type::getArray(); + } + + if ($stmt instanceof PhpParser\Node\Expr\UnaryMinus || $stmt instanceof PhpParser\Node\Expr\UnaryPlus) { + $type_to_invert = self::infer( + $codebase, + $nodes, + $stmt->expr, + $aliases, + $file_source, + $existing_class_constants, + $fq_classlike_name + ); + + if (!$type_to_invert) { + return null; + } + + foreach ($type_to_invert->getAtomicTypes() as $type_part) { + if ($type_part instanceof Type\Atomic\TLiteralInt + && $stmt instanceof PhpParser\Node\Expr\UnaryMinus + ) { + $type_part->value = -$type_part->value; + } elseif ($type_part instanceof Type\Atomic\TLiteralFloat + && $stmt instanceof PhpParser\Node\Expr\UnaryMinus + ) { + $type_part->value = -$type_part->value; + } + } + + return $type_to_invert; + } + + if ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch) { + if ($stmt->var instanceof PhpParser\Node\Expr\ClassConstFetch + && $stmt->dim + ) { + $array_type = self::infer( + $codebase, + $nodes, + $stmt->var, + $aliases, + $file_source, + $existing_class_constants, + $fq_classlike_name + ); + + $dim_type = self::infer( + $codebase, + $nodes, + $stmt->dim, + $aliases, + $file_source, + $existing_class_constants, + $fq_classlike_name + ); + + if ($array_type !== null && $dim_type !== null) { + if ($dim_type->isSingleStringLiteral()) { + $dim_value = $dim_type->getSingleStringLiteral()->value; + } elseif ($dim_type->isSingleIntLiteral()) { + $dim_value = $dim_type->getSingleIntLiteral()->value; + } else { + return null; + } + + foreach ($array_type->getAtomicTypes() as $array_atomic_type) { + if ($array_atomic_type instanceof Type\Atomic\TKeyedArray) { + if (isset($array_atomic_type->properties[$dim_value])) { + return clone $array_atomic_type->properties[$dim_value]; + } + + return null; + } + } + } + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..2da763456b044b32b6d9dc9078f5fa4bad30bddc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php @@ -0,0 +1,273 @@ +getCodebase(); + + $if_scope = new \Psalm\Internal\Scope\IfScope(); + + try { + $if_conditional_scope = IfAnalyzer::analyzeIfConditional( + $statements_analyzer, + $stmt->cond, + $context, + $codebase, + $if_scope, + $context->branch_point ?: (int) $stmt->getAttribute('startFilePos') + ); + + $if_context = $if_conditional_scope->if_context; + + $cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids; + $cond_assigned_var_ids = $if_conditional_scope->cond_assigned_var_ids; + } catch (\Psalm\Exception\ScopeAnalysisException $e) { + return false; + } + + $codebase = $statements_analyzer->getCodebase(); + + $cond_id = \spl_object_id($stmt->cond); + + $if_clauses = \Psalm\Type\Algebra::getFormula( + $cond_id, + $cond_id, + $stmt->cond, + $context->self, + $statements_analyzer, + $codebase + ); + + $mixed_var_ids = []; + + foreach ($context->vars_in_scope as $var_id => $type) { + if ($type->hasMixed()) { + $mixed_var_ids[] = $var_id; + } + } + + foreach ($context->vars_possibly_in_scope as $var_id => $_) { + if (!isset($context->vars_in_scope[$var_id])) { + $mixed_var_ids[] = $var_id; + } + } + + $if_clauses = array_values( + array_map( + /** + * @return \Psalm\Internal\Clause + */ + function (\Psalm\Internal\Clause $c) use ($mixed_var_ids, $cond_id): \Psalm\Internal\Clause { + $keys = array_keys($c->possibilities); + + $mixed_var_ids = \array_diff($mixed_var_ids, $keys); + + foreach ($keys as $key) { + foreach ($mixed_var_ids as $mixed_var_id) { + if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { + return new \Psalm\Internal\Clause([], $cond_id, $cond_id, true); + } + } + } + + return $c; + }, + $if_clauses + ) + ); + + // this will see whether any of the clauses in set A conflict with the clauses in set B + AlgebraAnalyzer::checkForParadox( + $context->clauses, + $if_clauses, + $statements_analyzer, + $stmt->cond, + $cond_assigned_var_ids + ); + + $ternary_clauses = array_merge($context->clauses, $if_clauses); + + if ($if_context->reconciled_expression_clauses) { + $reconciled_expression_clauses = $if_context->reconciled_expression_clauses; + + $ternary_clauses = array_values( + array_filter( + $ternary_clauses, + function ($c) use ($reconciled_expression_clauses): bool { + return !\in_array($c->hash, $reconciled_expression_clauses); + } + ) + ); + } + + $ternary_clauses = Algebra::simplifyCNF($ternary_clauses); + + $negated_clauses = Algebra::negateFormula($if_clauses); + + $negated_if_types = Algebra::getTruthsFromFormula( + Algebra::simplifyCNF( + array_merge($context->clauses, $negated_clauses) + ) + ); + + $active_if_types = []; + + $reconcilable_if_types = Algebra::getTruthsFromFormula( + $ternary_clauses, + $cond_id, + $cond_referenced_var_ids, + $active_if_types + ); + + $changed_var_ids = []; + + if ($reconcilable_if_types) { + $if_vars_in_scope_reconciled = Reconciler::reconcileKeyedTypes( + $reconcilable_if_types, + $active_if_types, + $if_context->vars_in_scope, + $changed_var_ids, + $cond_referenced_var_ids, + $statements_analyzer, + $statements_analyzer->getTemplateTypeMap() ?: [], + $if_context->inside_loop, + new CodeLocation($statements_analyzer->getSource(), $stmt->cond) + ); + + $if_context->vars_in_scope = $if_vars_in_scope_reconciled; + } + + $t_else_context = clone $context; + + if ($stmt->if) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->if, $if_context) === false) { + return false; + } + + foreach ($if_context->vars_in_scope as $var_id => $type) { + if (isset($context->vars_in_scope[$var_id])) { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes($context->vars_in_scope[$var_id], $type); + } + } + + $context->referenced_var_ids = array_merge( + $context->referenced_var_ids, + $if_context->referenced_var_ids + ); + } + + $t_else_context->clauses = Algebra::simplifyCNF( + array_merge( + $t_else_context->clauses, + $negated_clauses + ) + ); + + if ($negated_if_types) { + $changed_var_ids = []; + + $t_else_vars_in_scope_reconciled = Reconciler::reconcileKeyedTypes( + $negated_if_types, + $negated_if_types, + $t_else_context->vars_in_scope, + $changed_var_ids, + $cond_referenced_var_ids, + $statements_analyzer, + $statements_analyzer->getTemplateTypeMap() ?: [], + $t_else_context->inside_loop, + new CodeLocation($statements_analyzer->getSource(), $stmt->else) + ); + + $t_else_context->vars_in_scope = $t_else_vars_in_scope_reconciled; + + $t_else_context->clauses = Context::removeReconciledClauses($t_else_context->clauses, $changed_var_ids)[0]; + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->else, $t_else_context) === false) { + return false; + } + + foreach ($t_else_context->vars_in_scope as $var_id => $type) { + if (isset($context->vars_in_scope[$var_id])) { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $context->vars_in_scope[$var_id], + $type + ); + } elseif (isset($if_context->vars_in_scope[$var_id]) + && isset($if_context->assigned_var_ids[$var_id]) + ) { + $context->vars_in_scope[$var_id] = Type::combineUnionTypes( + $if_context->vars_in_scope[$var_id], + $type + ); + } + } + + $context->vars_possibly_in_scope = array_merge( + $context->vars_possibly_in_scope, + $if_context->vars_possibly_in_scope, + $t_else_context->vars_possibly_in_scope + ); + + $context->referenced_var_ids = array_merge( + $context->referenced_var_ids, + $t_else_context->referenced_var_ids + ); + + $lhs_type = null; + + if ($stmt->if) { + if ($stmt_if_type = $statements_analyzer->node_data->getType($stmt->if)) { + $lhs_type = $stmt_if_type; + } + } elseif ($stmt_cond_type = $statements_analyzer->node_data->getType($stmt->cond)) { + $if_return_type_reconciled = AssertionReconciler::reconcile( + '!falsy', + clone $stmt_cond_type, + '', + $statements_analyzer, + $context->inside_loop, + [], + new CodeLocation($statements_analyzer->getSource(), $stmt), + $statements_analyzer->getSuppressedIssues() + ); + + $lhs_type = $if_return_type_reconciled; + } + + if ($lhs_type && ($stmt_else_type = $statements_analyzer->node_data->getType($stmt->else))) { + $statements_analyzer->node_data->setType($stmt, Type::combineUnionTypes($lhs_type, $stmt_else_type)); + } else { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..b6ef32bb1f7525162c6dcd6a86d55acdca9f2f09 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php @@ -0,0 +1,60 @@ +expr, $context) === false) { + return false; + } + + if (!($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr))) { + $statements_analyzer->node_data->setType($stmt, new Type\Union([new TInt, new TFloat])); + } elseif ($stmt_expr_type->isMixed()) { + $statements_analyzer->node_data->setType($stmt, Type::getMixed()); + } else { + $acceptable_types = []; + + foreach ($stmt_expr_type->getAtomicTypes() as $type_part) { + if ($type_part instanceof TInt || $type_part instanceof TFloat) { + if ($type_part instanceof Type\Atomic\TLiteralInt + && $stmt instanceof PhpParser\Node\Expr\UnaryMinus + ) { + $type_part->value = -$type_part->value; + } elseif ($type_part instanceof Type\Atomic\TLiteralFloat + && $stmt instanceof PhpParser\Node\Expr\UnaryMinus + ) { + $type_part->value = -$type_part->value; + } + + $acceptable_types[] = $type_part; + } elseif ($type_part instanceof TString) { + $acceptable_types[] = new TInt; + $acceptable_types[] = new TFloat; + } else { + $acceptable_types[] = new TInt; + } + } + + $statements_analyzer->node_data->setType($stmt, new Type\Union($acceptable_types)); + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..068dddac5811d5583ad91dacaba4b5a67b7c7899 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php @@ -0,0 +1,216 @@ +getDocComment(); + + $var_comments = []; + $var_comment_type = null; + + $codebase = $statements_analyzer->getCodebase(); + + if ($doc_comment) { + try { + $var_comments = CommentAnalyzer::getTypeFromComment( + $doc_comment, + $statements_analyzer, + $statements_analyzer->getAliases() + ); + } catch (DocblockParseException $e) { + if (IssueBuffer::accepts( + new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($statements_analyzer->getSource(), $stmt) + ) + )) { + // fall through + } + } + + foreach ($var_comments as $var_comment) { + if (!$var_comment->type) { + continue; + } + + $comment_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $var_comment->type, + $context->self, + $context->self ? new Type\Atomic\TNamedObject($context->self) : null, + $statements_analyzer->getParentFQCLN() + ); + + $type_location = null; + + if ($var_comment->type_start + && $var_comment->type_end + && $var_comment->line_number + ) { + $type_location = new CodeLocation\DocblockTypeLocation( + $statements_analyzer, + $var_comment->type_start, + $var_comment->type_end, + $var_comment->line_number + ); + } + + if (!$var_comment->var_id) { + $var_comment_type = $comment_type; + continue; + } + + if ($codebase->find_unused_variables + && $type_location + && isset($context->vars_in_scope[$var_comment->var_id]) + && $context->vars_in_scope[$var_comment->var_id]->getId() === $comment_type->getId() + ) { + $project_analyzer = $statements_analyzer->getProjectAnalyzer(); + + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['UnnecessaryVarAnnotation']) + ) { + FileManipulationBuffer::addVarAnnotationToRemove($type_location); + } elseif (IssueBuffer::accepts( + new UnnecessaryVarAnnotation( + 'The @var annotation for ' . $var_comment->var_id . ' is unnecessary', + $type_location + ), + [], + true + )) { + // fall through + } + } + + if (isset($context->vars_in_scope[$var_comment->var_id])) { + $comment_type->parent_nodes = $context->vars_in_scope[$var_comment->var_id]->parent_nodes; + } + + $context->vars_in_scope[$var_comment->var_id] = $comment_type; + } + } + + if ($stmt->key) { + $context->inside_call = true; + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->key, $context) === false) { + return false; + } + $context->inside_call = false; + } + + if ($stmt->value) { + $context->inside_call = true; + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->value, $context) === false) { + return false; + } + $context->inside_call = false; + + if ($var_comment_type) { + $expression_type = clone $var_comment_type; + } elseif ($stmt_var_type = $statements_analyzer->node_data->getType($stmt->value)) { + $expression_type = clone $stmt_var_type; + } else { + $expression_type = Type::getMixed(); + } + } else { + $expression_type = Type::getEmpty(); + } + + $yield_type = null; + + foreach ($expression_type->getAtomicTypes() as $expression_atomic_type) { + if ($expression_atomic_type instanceof Type\Atomic\TNamedObject) { + $classlike_storage = $codebase->classlike_storage_provider->get($expression_atomic_type->value); + + if ($classlike_storage->yield) { + if ($expression_atomic_type instanceof Type\Atomic\TGenericObject) { + $yield_candidate_type = AtomicPropertyFetchAnalyzer::localizePropertyType( + $codebase, + clone $classlike_storage->yield, + $expression_atomic_type, + $classlike_storage, + $classlike_storage + ); + + if ($yield_type) { + $yield_type = Type::combineUnionTypes( + $yield_type, + $yield_candidate_type, + $codebase + ); + } else { + $yield_type = $yield_candidate_type; + } + } else { + $yield_type = Type::getMixed(); + } + } + } + } + + if ($yield_type) { + $expression_type->substitute($expression_type, $yield_type); + } + + $statements_analyzer->node_data->setType($stmt, $expression_type); + + $source = $statements_analyzer->getSource(); + + if ($source instanceof FunctionLikeAnalyzer + && !($source->getSource() instanceof TraitAnalyzer) + ) { + $source->examineParamTypes($statements_analyzer, $context, $codebase, $stmt); + + $storage = $source->getFunctionLikeStorage($statements_analyzer); + + if ($storage->return_type && !$yield_type) { + foreach ($storage->return_type->getAtomicTypes() as $atomic_return_type) { + if ($atomic_return_type instanceof Type\Atomic\TNamedObject + && $atomic_return_type->value === 'Generator' + ) { + if ($atomic_return_type instanceof Type\Atomic\TGenericObject) { + if (!$atomic_return_type->type_params[2]->isVoid()) { + $statements_analyzer->node_data->setType( + $stmt, + clone $atomic_return_type->type_params[2] + ); + } + } else { + $statements_analyzer->node_data->setType( + $stmt, + Type::combineUnionTypes( + Type::getMixed(), + $expression_type + ) + ); + } + } + } + } + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..f884038557441c26e3836dbb8d867cdb148ec700 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php @@ -0,0 +1,56 @@ +inside_call; + + $context->inside_call = true; + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + $context->inside_call = $was_inside_call; + + return false; + } + + if ($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr)) { + $yield_from_type = null; + + foreach ($stmt_expr_type->getAtomicTypes() as $atomic_type) { + if ($yield_from_type === null) { + if ($atomic_type instanceof Type\Atomic\TGenericObject + && strtolower($atomic_type->value) === 'generator' + && isset($atomic_type->type_params[3]) + ) { + $yield_from_type = clone $atomic_type->type_params[3]; + } elseif ($atomic_type instanceof Type\Atomic\TArray) { + $yield_from_type = clone $atomic_type->type_params[1]; + } elseif ($atomic_type instanceof Type\Atomic\TKeyedArray) { + $yield_from_type = $atomic_type->getGenericValueType(); + } + } else { + $yield_from_type = Type::getMixed(); + } + } + + // this should be whatever the generator above returns, but *not* the return type + $statements_analyzer->node_data->setType($stmt, $yield_from_type ?: Type::getMixed()); + } + + $context->inside_call = $was_inside_call; + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..889ae1fb78909d3bfbab178109ecda4da0f3beb0 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php @@ -0,0 +1,411 @@ +getCodebase(); + + if (self::handleExpression( + $statements_analyzer, + $stmt, + $context, + $array_assignment, + $global_context, + $from_stmt + ) === false + ) { + return false; + } + + if (!$context->inside_conditional + && ($stmt instanceof PhpParser\Node\Expr\BinaryOp + || $stmt instanceof PhpParser\Node\Expr\Instanceof_ + || $stmt instanceof PhpParser\Node\Expr\Assign + || $stmt instanceof PhpParser\Node\Expr\BooleanNot + || $stmt instanceof PhpParser\Node\Expr\Empty_ + || $stmt instanceof PhpParser\Node\Expr\Isset_ + || $stmt instanceof PhpParser\Node\Expr\FuncCall) + ) { + $assertions = $statements_analyzer->node_data->getAssertions($stmt); + + if ($assertions === null) { + Expression\AssertionFinder::scrapeAssertions( + $stmt, + $context->self, + $statements_analyzer, + $codebase, + false, + true, + false + ); + } + } + + $plugin_classes = $codebase->config->after_expression_checks; + + if ($plugin_classes) { + $file_manipulations = []; + + foreach ($plugin_classes as $plugin_fq_class_name) { + if ($plugin_fq_class_name::afterExpressionAnalysis( + $stmt, + $context, + $statements_analyzer, + $codebase, + $file_manipulations + ) === false) { + return false; + } + } + + if ($file_manipulations) { + FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations); + } + } + + return true; + } + + private static function handleExpression( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr $stmt, + Context $context, + bool $array_assignment, + ?Context $global_context, + bool $from_stmt + ) : bool { + if ($stmt instanceof PhpParser\Node\Expr\Variable) { + return Expression\Fetch\VariableFetchAnalyzer::analyze( + $statements_analyzer, + $stmt, + $context, + false, + null, + $array_assignment + ); + } + + if ($stmt instanceof PhpParser\Node\Expr\Assign) { + $assignment_type = Expression\AssignmentAnalyzer::analyze( + $statements_analyzer, + $stmt->var, + $stmt->expr, + null, + $context, + $stmt->getDocComment() + ); + + if ($assignment_type === false) { + return false; + } + + if (!$from_stmt) { + $statements_analyzer->node_data->setType($stmt, $assignment_type); + } + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\AssignOp) { + return Expression\AssignmentAnalyzer::analyzeAssignmentOperation($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\MethodCall) { + return MethodCallAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\StaticCall) { + return StaticCallAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\ConstFetch) { + Expression\Fetch\ConstFetchAnalyzer::analyze($statements_analyzer, $stmt, $context); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Scalar\String_) { + $statements_analyzer->node_data->setType($stmt, Type::getString($stmt->value)); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Scalar\EncapsedStringPart) { + return true; + } + + if ($stmt instanceof PhpParser\Node\Scalar\MagicConst) { + Expression\MagicConstAnalyzer::analyze($statements_analyzer, $stmt, $context); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Scalar\LNumber) { + $statements_analyzer->node_data->setType($stmt, Type::getInt(false, $stmt->value)); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Scalar\DNumber) { + $statements_analyzer->node_data->setType($stmt, Type::getFloat($stmt->value)); + + return true; + } + + + if ($stmt instanceof PhpParser\Node\Expr\UnaryMinus || $stmt instanceof PhpParser\Node\Expr\UnaryPlus) { + return Expression\UnaryPlusMinusAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\Isset_) { + Expression\IssetAnalyzer::analyze($statements_analyzer, $stmt, $context); + $statements_analyzer->node_data->setType($stmt, Type::getBool()); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\ClassConstFetch) { + return Expression\Fetch\ClassConstFetchAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch) { + return Expression\Fetch\InstancePropertyFetchAnalyzer::analyze( + $statements_analyzer, + $stmt, + $context, + $array_assignment + ); + } + + if ($stmt instanceof PhpParser\Node\Expr\StaticPropertyFetch) { + return Expression\Fetch\StaticPropertyFetchAnalyzer::analyze( + $statements_analyzer, + $stmt, + $context + ); + } + + if ($stmt instanceof PhpParser\Node\Expr\BitwiseNot) { + return Expression\BitwiseNotAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) { + return Expression\BinaryOpAnalyzer::analyze( + $statements_analyzer, + $stmt, + $context, + 0, + $from_stmt + ); + } + + if ($stmt instanceof PhpParser\Node\Expr\PostInc + || $stmt instanceof PhpParser\Node\Expr\PostDec + || $stmt instanceof PhpParser\Node\Expr\PreInc + || $stmt instanceof PhpParser\Node\Expr\PreDec + ) { + Expression\IncDecExpressionAnalyzer::analyze($statements_analyzer, $stmt, $context); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\New_) { + return NewAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\Array_) { + return Expression\ArrayAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Scalar\Encapsed) { + return Expression\EncapsulatedStringAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\FuncCall) { + return FunctionCallAnalyzer::analyze( + $statements_analyzer, + $stmt, + $context + ); + } + + if ($stmt instanceof PhpParser\Node\Expr\Ternary) { + return Expression\TernaryAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\BooleanNot) { + return Expression\BooleanNotAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\Empty_) { + Expression\EmptyAnalyzer::analyze($statements_analyzer, $stmt, $context); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\Closure + || $stmt instanceof PhpParser\Node\Expr\ArrowFunction + ) { + ClosureAnalyzer::analyzeExpression($statements_analyzer, $stmt, $context); + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch) { + return Expression\Fetch\ArrayFetchAnalyzer::analyze( + $statements_analyzer, + $stmt, + $context + ); + } + + if ($stmt instanceof PhpParser\Node\Expr\Cast) { + return Expression\CastAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\Clone_) { + return Expression\CloneAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\Instanceof_) { + return Expression\InstanceofAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\Exit_) { + return Expression\ExitAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\Include_) { + return Expression\IncludeAnalyzer::analyze($statements_analyzer, $stmt, $context, $global_context); + } + + if ($stmt instanceof PhpParser\Node\Expr\Eval_) { + Expression\EvalAnalyzer::analyze($statements_analyzer, $stmt, $context); + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\AssignRef) { + return Expression\AssignmentAnalyzer::analyzeAssignmentRef($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\ErrorSuppress) { + $context->error_suppressing = true; + if (self::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + $context->error_suppressing = false; + + $expr_type = $statements_analyzer->node_data->getType($stmt->expr); + + if ($expr_type) { + $statements_analyzer->node_data->setType($stmt, $expr_type); + } + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\ShellExec) { + if (IssueBuffer::accepts( + new ForbiddenCode( + 'Use of shell_exec', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // continue + } + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\Print_) { + $was_inside_call = $context->inside_call; + $context->inside_call = true; + if (Expression\PrintAnalyzer::analyze($statements_analyzer, $stmt, $context) === false) { + return false; + } + $context->inside_call = $was_inside_call; + + return true; + } + + if ($stmt instanceof PhpParser\Node\Expr\Yield_) { + return Expression\YieldAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\YieldFrom) { + return Expression\YieldFromAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\Match_ + && $statements_analyzer->getCodebase()->php_major_version >= 8 + ) { + return Expression\MatchAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\Throw_ + && $statements_analyzer->getCodebase()->php_major_version >= 8 + ) { + return ThrowAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if (($stmt instanceof PhpParser\Node\Expr\NullsafePropertyFetch + || $stmt instanceof PhpParser\Node\Expr\NullsafeMethodCall) + && $statements_analyzer->getCodebase()->php_major_version >= 8 + ) { + return Expression\NullsafeAnalyzer::analyze($statements_analyzer, $stmt, $context); + } + + if ($stmt instanceof PhpParser\Node\Expr\Error) { + // do nothing + return true; + } + + if (IssueBuffer::accepts( + new UnrecognizedExpression( + 'Psalm does not understand ' . get_class($stmt), + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return false; + } + + public static function isMock(string $fq_class_name): bool + { + return in_array(strtolower($fq_class_name), Config::getInstance()->getMockClasses(), true); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..9edc4af37352a23ba7d69e337e9aaac4fdd2b35b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php @@ -0,0 +1,63 @@ +collect_initializations && !$global_context) { + if (IssueBuffer::accepts( + new InvalidGlobal( + 'Cannot use global scope here', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSource()->getSuppressedIssues() + )) { + // fall through + } + } + + $source = $statements_analyzer->getSource(); + $function_storage = $source instanceof FunctionLikeAnalyzer + ? $source->getFunctionLikeStorage($statements_analyzer) + : null; + + foreach ($stmt->vars as $var) { + if ($var instanceof PhpParser\Node\Expr\Variable) { + if (is_string($var->name)) { + $var_id = '$' . $var->name; + + if ($var->name === 'argv' || $var->name === 'argc') { + $context->vars_in_scope[$var_id] = VariableFetchAnalyzer::getGlobalType($var_id); + } elseif (isset($function_storage->global_types[$var_id])) { + $context->vars_in_scope[$var_id] = clone $function_storage->global_types[$var_id]; + $context->vars_possibly_in_scope[$var_id] = true; + } else { + $context->vars_in_scope[$var_id] = + $global_context && $global_context->hasVariable($var_id) + ? clone $global_context->vars_in_scope[$var_id] + : VariableFetchAnalyzer::getGlobalType($var_id); + + $context->vars_possibly_in_scope[$var_id] = true; + + $context->byref_constraints[$var_id] = new \Psalm\Internal\ReferenceConstraint(); + } + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..49fb62e6dc6b72fef61b9258a98c8c3d1956cd71 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php @@ -0,0 +1,639 @@ +getDocComment(); + + $var_comments = []; + $var_comment_type = null; + + $source = $statements_analyzer->getSource(); + + $codebase = $statements_analyzer->getCodebase(); + + if ($doc_comment && ($parsed_docblock = $statements_analyzer->getParsedDocblock())) { + $file_storage_provider = $codebase->file_storage_provider; + + $file_storage = $file_storage_provider->get($statements_analyzer->getFilePath()); + + try { + $var_comments = CommentAnalyzer::arrayToDocblocks( + $doc_comment, + $parsed_docblock, + $statements_analyzer->getSource(), + $statements_analyzer->getAliases(), + $statements_analyzer->getTemplateTypeMap(), + $file_storage->type_aliases + ); + } catch (DocblockParseException $e) { + if (IssueBuffer::accepts( + new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($source, $stmt) + ) + )) { + // fall through + } + } + + foreach ($var_comments as $var_comment) { + if (!$var_comment->type) { + continue; + } + + $comment_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $var_comment->type, + $context->self, + $context->self, + $statements_analyzer->getParentFQCLN() + ); + + if ($codebase->alter_code + && $var_comment->type_start + && $var_comment->type_end + && $var_comment->line_number + ) { + $type_location = new CodeLocation\DocblockTypeLocation( + $statements_analyzer, + $var_comment->type_start, + $var_comment->type_end, + $var_comment->line_number + ); + + $codebase->classlikes->handleDocblockTypeInMigration( + $codebase, + $statements_analyzer, + $comment_type, + $type_location, + $context->calling_method_id + ); + } + + if (!$var_comment->var_id) { + $var_comment_type = $comment_type; + continue; + } + + if (isset($context->vars_in_scope[$var_comment->var_id])) { + $comment_type->parent_nodes = $context->vars_in_scope[$var_comment->var_id]->parent_nodes; + } + + $context->vars_in_scope[$var_comment->var_id] = $comment_type; + } + } + + if ($stmt->expr) { + $context->inside_call = true; + + if ($stmt->expr instanceof PhpParser\Node\Expr\Closure + || $stmt->expr instanceof PhpParser\Node\Expr\ArrowFunction + ) { + self::potentiallyInferTypesOnClosureFromParentReturnType( + $statements_analyzer, + $stmt->expr, + $context + ); + } + + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + + $stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr); + + if ($var_comment_type) { + $stmt_type = $var_comment_type; + + if ($stmt_expr_type && $stmt_expr_type->parent_nodes) { + $stmt_type->parent_nodes = $stmt_expr_type->parent_nodes; + } + + $statements_analyzer->node_data->setType($stmt, $var_comment_type); + } elseif ($stmt_expr_type) { + $stmt_type = $stmt_expr_type; + + if ($stmt_type->isNever()) { + if (IssueBuffer::accepts( + new NoValue( + 'This function or method call never returns output', + new CodeLocation($source, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + $stmt_type = Type::getEmpty(); + } + + if ($stmt_type->isVoid()) { + $stmt_type = Type::getNull(); + } + } else { + $stmt_type = Type::getMixed(); + } + } else { + $stmt_type = Type::getVoid(); + } + + $statements_analyzer->node_data->setType($stmt, $stmt_type); + + if ($context->finally_scope) { + foreach ($context->vars_in_scope as $var_id => $type) { + if (isset($context->finally_scope->vars_in_scope[$var_id])) { + if ($context->finally_scope->vars_in_scope[$var_id] !== $type) { + $context->finally_scope->vars_in_scope[$var_id] = Type::combineUnionTypes( + $context->finally_scope->vars_in_scope[$var_id], + $type, + $statements_analyzer->getCodebase() + ); + } + } else { + $context->finally_scope->vars_in_scope[$var_id] = $type; + } + } + } + + if ($source instanceof FunctionLikeAnalyzer + && !($source->getSource() instanceof TraitAnalyzer) + ) { + $source->addReturnTypes($context); + + $source->examineParamTypes($statements_analyzer, $context, $codebase, $stmt); + + $storage = $source->getFunctionLikeStorage($statements_analyzer); + + $cased_method_id = $source->getCorrectlyCasedMethodId(); + + if ($stmt->expr && $storage->location) { + $inferred_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $stmt_type, + $source->getFQCLN(), + $source->getFQCLN(), + $source->getParentFQCLN() + ); + + if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + self::handleTaints( + $statements_analyzer, + $stmt, + $cased_method_id, + $inferred_type, + $storage + ); + } + + if ($storage instanceof \Psalm\Storage\MethodStorage && $context->self) { + $self_class = $context->self; + + $declared_return_type = $codebase->methods->getMethodReturnType( + \Psalm\Internal\MethodIdentifier::wrap($cased_method_id), + $self_class, + $statements_analyzer, + null + ); + } else { + $declared_return_type = $storage->return_type; + } + + if ($declared_return_type && !$declared_return_type->hasMixed()) { + $local_return_type = $source->getLocalReturnType( + $declared_return_type, + $storage instanceof \Psalm\Storage\MethodStorage && $storage->final + ); + + if ($storage instanceof \Psalm\Storage\MethodStorage) { + [$fq_class_name, $method_name] = explode('::', $cased_method_id); + + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + + $found_generic_params = ClassTemplateParamCollector::collect( + $codebase, + $class_storage, + $class_storage, + strtolower($method_name), + null, + '$this' + ); + + if ($found_generic_params) { + foreach ($found_generic_params as $template_name => $_) { + unset($found_generic_params[$template_name][$fq_class_name]); + } + + $local_return_type = clone $local_return_type; + + $local_return_type->replaceTemplateTypesWithArgTypes( + new TemplateResult([], $found_generic_params), + $codebase + ); + } + } + + if ($local_return_type->isGenerator() && $storage->has_yield) { + return null; + } + + if ($stmt_type->hasMixed()) { + if ($local_return_type->isVoid() || $local_return_type->isNever()) { + if (IssueBuffer::accepts( + new InvalidReturnStatement( + 'No return values are expected for ' . $cased_method_id, + new CodeLocation($source, $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && !($source->getSource() instanceof TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + + if ($stmt_type->isMixed()) { + if (IssueBuffer::accepts( + new MixedReturnStatement( + 'Could not infer a return type', + new CodeLocation($source, $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + + return null; + } + + if (IssueBuffer::accepts( + new MixedReturnStatement( + 'Possibly-mixed return value', + new CodeLocation($source, $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + if ($local_return_type->isMixed()) { + return null; + } + + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && !($source->getSource() instanceof TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } + + if ($local_return_type->isVoid()) { + if (IssueBuffer::accepts( + new InvalidReturnStatement( + 'No return values are expected for ' . $cased_method_id, + new CodeLocation($source, $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + + return null; + } + + $union_comparison_results = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $inferred_type, + $local_return_type, + true, + true, + $union_comparison_results + ) + ) { + // is the declared return type more specific than the inferred one? + if ($union_comparison_results->type_coerced) { + if ($union_comparison_results->type_coerced_from_mixed) { + if (!$union_comparison_results->type_coerced_from_as_mixed) { + if ($inferred_type->hasMixed()) { + if (IssueBuffer::accepts( + new MixedReturnStatement( + 'Could not infer a return type', + new CodeLocation($source, $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new MixedReturnTypeCoercion( + 'The type \'' . $stmt_type->getId() . '\' is more general than the' + . ' declared return type \'' . $local_return_type->getId() . '\'' + . ' for ' . $cased_method_id, + new CodeLocation($source, $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } else { + if (IssueBuffer::accepts( + new LessSpecificReturnStatement( + 'The type \'' . $stmt_type->getId() . '\' is more general than the' + . ' declared return type \'' . $local_return_type->getId() . '\'' + . ' for ' . $cased_method_id, + new CodeLocation($source, $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall throuhg + } + } + + foreach ($local_return_type->getAtomicTypes() as $local_type_part) { + if ($local_type_part instanceof Type\Atomic\TClassString + && $stmt->expr instanceof PhpParser\Node\Scalar\String_ + ) { + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $stmt->expr->value, + new CodeLocation($source, $stmt->expr), + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues() + ) === false + ) { + return false; + } + } elseif ($local_type_part instanceof Type\Atomic\TArray + && $stmt->expr instanceof PhpParser\Node\Expr\Array_ + ) { + $value_param = $local_type_part->type_params[1]; + + foreach ($value_param->getAtomicTypes() as $local_array_type_part) { + if ($local_array_type_part instanceof Type\Atomic\TClassString) { + foreach ($stmt->expr->items as $item) { + if ($item && $item->value instanceof PhpParser\Node\Scalar\String_) { + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $statements_analyzer, + $item->value->value, + new CodeLocation($source, $item->value), + $context->self, + $context->calling_method_id, + $statements_analyzer->getSuppressedIssues() + ) === false + ) { + return false; + } + } + } + } + } + } + } + } else { + if (IssueBuffer::accepts( + new InvalidReturnStatement( + 'The inferred type \'' . $inferred_type->getId() + . '\' does not match the declared return ' + . 'type \'' . $local_return_type->getId() . '\' for ' . $cased_method_id, + new CodeLocation($source, $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + if (!$stmt_type->ignore_nullable_issues + && $inferred_type->isNullable() + && !$local_return_type->isNullable() + && !$local_return_type->hasTemplate() + ) { + if (IssueBuffer::accepts( + new NullableReturnStatement( + 'The declared return type \'' . $local_return_type->getId() . '\' for ' + . $cased_method_id . ' is not nullable, but the function returns \'' + . $inferred_type->getId() . '\'', + new CodeLocation($source, $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + //fall through + } + } + + if (!$stmt_type->ignore_falsable_issues + && $inferred_type->isFalsable() + && !$local_return_type->isFalsable() + && (!$local_return_type->hasBool() || $local_return_type->isTrue()) + && !$local_return_type->hasScalar() + ) { + if (IssueBuffer::accepts( + new FalsableReturnStatement( + 'The declared return type \'' . $local_return_type . '\' for ' + . $cased_method_id . ' does not allow false, but the function returns \'' + . $inferred_type . '\'', + new CodeLocation($source, $stmt->expr) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall throughg + } + } + } + } else { + if ($storage->signature_return_type + && !$storage->signature_return_type->isVoid() + && !$storage->has_yield + ) { + if (IssueBuffer::accepts( + new InvalidReturnStatement( + 'Empty return statement is not expected in ' . $cased_method_id, + new CodeLocation($source, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + + return null; + } + + private static function handleTaints( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Stmt\Return_ $stmt, + string $cased_method_id, + Type\Union $inferred_type, + \Psalm\Storage\FunctionLikeStorage $storage + ) : void { + if (!$statements_analyzer->data_flow_graph instanceof TaintFlowGraph + || !$stmt->expr + || !$storage->location + ) { + return; + } + + $method_node = DataFlowNode::getForMethodReturn( + strtolower($cased_method_id), + $cased_method_id, + $storage->signature_return_type_location ?: $storage->location, + ); + + $statements_analyzer->data_flow_graph->addNode($method_node); + + if ($inferred_type->parent_nodes) { + foreach ($inferred_type->parent_nodes as $parent_node) { + $statements_analyzer->data_flow_graph->addPath( + $parent_node, + $method_node, + 'return', + $storage->added_taints, + $storage->removed_taints + ); + } + } + } + + /** + * If a function returns a closure, we try to infer the param/return types of + * the inner closure. + * @see \Psalm\Tests\ReturnTypeTest:756 + * @param PhpParser\Node\Expr\Closure|PhpParser\Node\Expr\ArrowFunction $expr + */ + private static function potentiallyInferTypesOnClosureFromParentReturnType( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\FunctionLike $expr, + Context $context + ): void { + // if not returning from inside of a function, return + if (!$context->calling_method_id && !$context->calling_function_id) { + return; + } + + $closure_id = (new ClosureAnalyzer($expr, $statements_analyzer))->getClosureId(); + $closure_storage = $statements_analyzer + ->getCodebase() + ->getFunctionLikeStorage($statements_analyzer, $closure_id); + + /** @psalm-suppress ArgumentTypeCoercion */ + $parent_fn_storage = $statements_analyzer + ->getCodebase() + ->getFunctionLikeStorage( + $statements_analyzer, + $context->calling_function_id ?: $context->calling_method_id + ); + + if ($parent_fn_storage->return_type === null) { + return; + } + + // can't infer returned closure if the parent doesn't have a callable return type + if (!$parent_fn_storage->return_type->hasCallableType()) { + return; + } + + // cannot infer if we have union/intersection types + if (!$parent_fn_storage->return_type->isSingle()) { + return; + } + + /** @var Type\Atomic\TClosure|Type\Atomic\TCallable $parent_callable_return_type */ + $parent_callable_return_type = \current($parent_fn_storage->return_type->getAtomicTypes()); + + if ($parent_callable_return_type->params === null && $parent_callable_return_type->return_type === null) { + return; + } + + foreach ($closure_storage->params as $key => $param) { + $parent_param = $parent_callable_return_type->params[$key] ?? null; + $param->type = self::inferInnerClosureTypeFromParent( + $statements_analyzer->getCodebase(), + $param->type, + $parent_param ? $parent_param->type : null + ); + } + + $closure_storage->return_type = self::inferInnerClosureTypeFromParent( + $statements_analyzer->getCodebase(), + $closure_storage->return_type, + $parent_callable_return_type->return_type + ); + } + + /** + * - If non parent type, do nothing + * - If no return type, infer from parent + * - If parent return type is more specific, infer from parent + * - else, do nothing + */ + private static function inferInnerClosureTypeFromParent( + Codebase $codebase, + ?Type\Union $return_type, + ?Type\Union $parent_return_type + ): ?Type\Union { + if (!$parent_return_type) { + return $return_type; + } + if (!$return_type || UnionTypeComparator::isContainedBy($codebase, $parent_return_type, $return_type)) { + return $parent_return_type; + } + return $return_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..4828bc6c8247e843b0b0692268bdf754b7361234 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php @@ -0,0 +1,191 @@ +getCodebase(); + + if ($context->mutation_free) { + if (IssueBuffer::accepts( + new \Psalm\Issue\ImpureStaticVariable( + 'Cannot use a static variable in a mutation-free context', + new CodeLocation($statements_analyzer, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + foreach ($stmt->vars as $var) { + if (!is_string($var->var->name)) { + continue; + } + + $var_id = '$' . $var->var->name; + + $doc_comment = $stmt->getDocComment(); + + $comment_type = null; + + if ($doc_comment && ($parsed_docblock = $statements_analyzer->getParsedDocblock())) { + $var_comments = []; + + try { + $var_comments = CommentAnalyzer::arrayToDocblocks( + $doc_comment, + $parsed_docblock, + $statements_analyzer->getSource(), + $statements_analyzer->getSource()->getAliases(), + $statements_analyzer->getSource()->getTemplateTypeMap() + ); + } catch (\Psalm\Exception\IncorrectDocblockException $e) { + if (IssueBuffer::accepts( + new \Psalm\Issue\MissingDocblockType( + (string)$e->getMessage(), + new CodeLocation($statements_analyzer, $var) + ) + )) { + // fall through + } + } catch (DocblockParseException $e) { + if (IssueBuffer::accepts( + new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($statements_analyzer->getSource(), $var) + ) + )) { + // fall through + } + } + + foreach ($var_comments as $var_comment) { + if (!$var_comment->type) { + continue; + } + + try { + $var_comment_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $var_comment->type, + $context->self, + $context->self, + $statements_analyzer->getParentFQCLN() + ); + + $var_comment_type->setFromDocblock(); + + $var_comment_type->check( + $statements_analyzer, + new CodeLocation($statements_analyzer->getSource(), $var), + $statements_analyzer->getSuppressedIssues() + ); + + if ($codebase->alter_code + && $var_comment->type_start + && $var_comment->type_end + && $var_comment->line_number + ) { + $type_location = new CodeLocation\DocblockTypeLocation( + $statements_analyzer, + $var_comment->type_start, + $var_comment->type_end, + $var_comment->line_number + ); + + $codebase->classlikes->handleDocblockTypeInMigration( + $codebase, + $statements_analyzer, + $var_comment_type, + $type_location, + $context->calling_method_id + ); + } + + if (!$var_comment->var_id || $var_comment->var_id === $var_id) { + $comment_type = $var_comment_type; + continue; + } + + $context->vars_in_scope[$var_comment->var_id] = $var_comment_type; + } catch (\UnexpectedValueException $e) { + if (IssueBuffer::accepts( + new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($statements_analyzer, $var) + ) + )) { + // fall through + } + } + } + + if ($comment_type) { + $context->byref_constraints[$var_id] = new \Psalm\Internal\ReferenceConstraint($comment_type); + } + } + + if ($var->default) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $var->default, $context) === false) { + return false; + } + + if ($comment_type + && ($var_default_type = $statements_analyzer->node_data->getType($var->default)) + && !UnionTypeComparator::isContainedBy( + $codebase, + $var_default_type, + $comment_type + ) + ) { + if (IssueBuffer::accepts( + new \Psalm\Issue\ReferenceConstraintViolation( + $var_id . ' of type ' . $comment_type->getId() . ' cannot be assigned type ' + . $var_default_type->getId(), + new CodeLocation($statements_analyzer, $var) + ) + )) { + // fall through + } + } + } + + if ($context->check_variables) { + $context->vars_in_scope[$var_id] = $comment_type ? clone $comment_type : Type::getMixed(); + $context->vars_possibly_in_scope[$var_id] = true; + $context->assigned_var_ids[$var_id] = true; + $statements_analyzer->byref_uses[$var_id] = true; + + $location = new CodeLocation($statements_analyzer, $var); + + $statements_analyzer->registerVariable( + $var_id, + $location, + $context->branch_point + ); + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..319812b2d27ec4f1cf5bb00804d6d131ec102391 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php @@ -0,0 +1,92 @@ +inside_throw = true; + if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { + return false; + } + $context->inside_throw = false; + + if ($context->finally_scope) { + foreach ($context->vars_in_scope as $var_id => $type) { + if (isset($context->finally_scope->vars_in_scope[$var_id])) { + if ($context->finally_scope->vars_in_scope[$var_id] !== $type) { + $context->finally_scope->vars_in_scope[$var_id] = Type::combineUnionTypes( + $context->finally_scope->vars_in_scope[$var_id], + $type, + $statements_analyzer->getCodebase() + ); + } + } else { + $context->finally_scope->vars_in_scope[$var_id] = $type; + } + } + } + + if ($context->check_classes + && ($throw_type = $statements_analyzer->node_data->getType($stmt->expr)) + && !$throw_type->hasMixed() + ) { + $exception_type = new Union([new TNamedObject('Exception'), new TNamedObject('Throwable')]); + + $file_analyzer = $statements_analyzer->getFileAnalyzer(); + $codebase = $statements_analyzer->getCodebase(); + + foreach ($throw_type->getAtomicTypes() as $throw_type_part) { + $throw_type_candidate = new Union([$throw_type_part]); + + if (!UnionTypeComparator::isContainedBy($codebase, $throw_type_candidate, $exception_type)) { + if (IssueBuffer::accepts( + new InvalidThrow( + 'Cannot throw ' . $throw_type_part + . ' as it does not extend Exception or implement Throwable', + new CodeLocation($file_analyzer, $stmt), + (string) $throw_type_part + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } elseif (!$context->isSuppressingExceptions($statements_analyzer)) { + $codelocation = new CodeLocation($file_analyzer, $stmt); + $hash = $codelocation->getHash(); + foreach ($throw_type->getAtomicTypes() as $throw_atomic_type) { + if ($throw_atomic_type instanceof TNamedObject) { + $context->possibly_thrown_exceptions[$throw_atomic_type->value][$hash] = $codelocation; + } + } + } + } + } + + if ($stmt instanceof PhpParser\Node\Expr\Throw_) { + $statements_analyzer->node_data->setType($stmt, \Psalm\Type::getEmpty()); + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..7ecd878eb379b0cf55c286f146a847e050a941ae --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php @@ -0,0 +1,118 @@ +inside_unset = true; + + foreach ($stmt->vars as $var) { + $was_inside_use = $context->inside_use; + $context->inside_use = true; + + ExpressionAnalyzer::analyze($statements_analyzer, $var, $context); + + $context->inside_use = $was_inside_use; + + $var_id = ExpressionIdentifier::getArrayVarId( + $var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($var_id) { + $context->remove($var_id); + } + + if ($var instanceof PhpParser\Node\Expr\ArrayDimFetch && $var->dim) { + $root_var_id = ExpressionIdentifier::getArrayVarId( + $var->var, + $statements_analyzer->getFQCLN(), + $statements_analyzer + ); + + if ($root_var_id && isset($context->vars_in_scope[$root_var_id])) { + $root_type = clone $context->vars_in_scope[$root_var_id]; + + foreach ($root_type->getAtomicTypes() as $atomic_root_type) { + if ($atomic_root_type instanceof Type\Atomic\TKeyedArray) { + if ($var->dim instanceof PhpParser\Node\Scalar\String_ + || $var->dim instanceof PhpParser\Node\Scalar\LNumber + ) { + if (isset($atomic_root_type->properties[$var->dim->value])) { + unset($atomic_root_type->properties[$var->dim->value]); + } + + if (!$atomic_root_type->properties) { + if ($atomic_root_type->previous_value_type) { + $root_type->addType( + new Type\Atomic\TArray([ + $atomic_root_type->previous_key_type + ? clone $atomic_root_type->previous_key_type + : new Type\Union([new Type\Atomic\TArrayKey]), + clone $atomic_root_type->previous_value_type, + ]) + ); + } else { + $root_type->addType( + new Type\Atomic\TArray([ + new Type\Union([new Type\Atomic\TEmpty]), + new Type\Union([new Type\Atomic\TEmpty]), + ]) + ); + } + } + } else { + foreach ($atomic_root_type->properties as $key => $type) { + $atomic_root_type->properties[$key] = clone $type; + $atomic_root_type->properties[$key]->possibly_undefined = true; + } + + $atomic_root_type->sealed = false; + + $root_type->addType( + $atomic_root_type->getGenericArrayType() + ); + } + } elseif ($atomic_root_type instanceof Type\Atomic\TNonEmptyArray) { + $root_type->addType( + new Type\Atomic\TArray($atomic_root_type->type_params) + ); + } elseif ($atomic_root_type instanceof Type\Atomic\TNonEmptyMixed) { + $root_type->addType( + new Type\Atomic\TMixed() + ); + } elseif ($atomic_root_type instanceof Type\Atomic\TList) { + $root_type->addType( + new Type\Atomic\TArray([ + Type::getInt(), + $atomic_root_type->type_param + ]) + ); + } + } + + $context->vars_in_scope[$root_var_id] = $root_type; + + $context->removeVarFromConflictingClauses( + $root_var_id, + $context->vars_in_scope[$root_var_id], + $statements_analyzer + ); + } + } + } + + $context->inside_unset = false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php new file mode 100644 index 0000000000000000000000000000000000000000..9fe5a2a2cc52107f07f77822327884cd807dd049 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php @@ -0,0 +1,356 @@ + + */ + private $removed_unref_vars = []; + + /** + * @param array $stmts + * @param array $var_loc_map + * + */ + public function findUnusedAssignment( + Codebase $codebase, + array $stmts, + array $var_loc_map, + string $var_id, + CodeLocation $original_location + ): void { + $search_result = $this->findAssignStmt($stmts, $var_id, $original_location); + [$assign_stmt, $assign_exp] = $search_result; + $chain_assignment = false; + + if ($assign_stmt !== null && $assign_exp !== null) { + // Check if we have to remove assignment statemnt as expression (i.e. just "$var = ") + + // Consider chain of assignments + $rhs_exp = $assign_exp->expr; + if ($rhs_exp instanceof PhpParser\Node\Expr\Assign + || $rhs_exp instanceof PhpParser\Node\Expr\AssignOp + || $rhs_exp instanceof PhpParser\Node\Expr\AssignRef + ) { + $chain_assignment = true; + $removable_stmt = $this->checkRemovableChainAssignment($assign_exp, $var_loc_map); + } else { + $removable_stmt = true; + } + + if ($removable_stmt) { + $traverser = new PhpParser\NodeTraverser(); + $visitor = new CheckTrivialExprVisitor(); + $traverser->addVisitor($visitor); + $traverser->traverse([$rhs_exp]); + + $rhs_exp_trivial = (count($visitor->getNonTrivialExpr()) === 0); + + if ($rhs_exp_trivial) { + $treat_as_expr = false; + } else { + $treat_as_expr = true; + } + } else { + $treat_as_expr = true; + } + + if ($treat_as_expr) { + $is_assign_ref = $assign_exp instanceof PhpParser\Node\Expr\AssignRef; + $new_file_manipulation = self::getPartialRemovalBounds( + $codebase, + $original_location, + $assign_stmt->getEndFilePos(), + $is_assign_ref + ); + $this->removed_unref_vars[$var_id] = $original_location; + } else { + // Remove whole assignment statement + $new_file_manipulation = new FileManipulation( + $assign_stmt->getStartFilePos(), + $assign_stmt->getEndFilePos() + 1, + "", + false, + true + ); + + // If statement we are removing is a chain of assignments, mark other variables as removed + if ($chain_assignment) { + $this->markRemovedChainAssignVar($assign_exp, $var_loc_map); + } else { + $this->removed_unref_vars[$var_id] = $original_location; + } + } + + FileManipulationBuffer::add($original_location->file_path, [$new_file_manipulation]); + } elseif ($assign_exp !== null) { + $is_assign_ref = $assign_exp instanceof PhpParser\Node\Expr\AssignRef; + $new_file_manipulation = self::getPartialRemovalBounds( + $codebase, + $original_location, + $assign_exp->getEndFilePos(), + $is_assign_ref + ); + + FileManipulationBuffer::add($original_location->file_path, [$new_file_manipulation]); + $this->removed_unref_vars[$var_id] = $original_location; + } + } + + private static function getPartialRemovalBounds( + Codebase $codebase, + CodeLocation $var_loc, + int $end_bound, + bool $assign_ref = false + ): FileManipulation { + $var_start_loc= $var_loc->raw_file_start; + $stmt_content = $codebase->file_provider->getContents( + $var_loc->file_path + ); + $str_for_token = " $var_loc_map + */ + private function markRemovedChainAssignVar(PhpParser\Node\Expr $cur_assign, array $var_loc_map): void + { + $var = $cur_assign->var; + if ($var instanceof PhpParser\Node\Expr\Variable && is_string($var->name)) { + $var_name = "$" . $var->name; + $var_loc = $var_loc_map[$var_name]; + $this->removed_unref_vars[$var_name] = $var_loc; + + $rhs_exp = $cur_assign->expr; + if ($rhs_exp instanceof PhpParser\Node\Expr\Assign + || $rhs_exp instanceof PhpParser\Node\Expr\AssignOp + || $rhs_exp instanceof PhpParser\Node\Expr\AssignRef + ) { + $this->markRemovedChainAssignVar($rhs_exp, $var_loc_map); + } + } + } + + /** + * @param PhpParser\Node\Expr\Assign|PhpParser\Node\Expr\AssignOp|PhpParser\Node\Expr\AssignRef $cur_assign + * @param array $var_loc_map + */ + private function checkRemovableChainAssignment(PhpParser\Node\Expr $cur_assign, array $var_loc_map): bool + { + // Check if current assignment expr's variable is removable + $var = $cur_assign->var; + if ($var instanceof PhpParser\Node\Expr\Variable && is_string($var->name)) { + $var_loc = $cur_assign->var->getStartFilePos(); + $var_name = "$" . $var->name; + + if (array_key_exists($var_name, $var_loc_map) && + $var_loc_map[$var_name]->raw_file_start === $var_loc) { + $curr_removable = true; + } else { + $curr_removable = false; + } + + if ($curr_removable) { + $rhs_exp = $cur_assign->expr; + + if ($rhs_exp instanceof PhpParser\Node\Expr\Assign + || $rhs_exp instanceof PhpParser\Node\Expr\AssignOp + || $rhs_exp instanceof PhpParser\Node\Expr\AssignRef + ) { + $rhs_removable = $this->checkRemovableChainAssignment($rhs_exp, $var_loc_map); + return $rhs_removable; + } + } + return $curr_removable; + } else { + return false; + } + } + + /** + * @param array $stmts + * @return array{ + * 0: PhpParser\Node\Stmt|null, + * 1: PhpParser\Node\Expr\Assign|PhpParser\Node\Expr\AssignOp|PhpParser\Node\Expr\AssignRef|null + * } + */ + private function findAssignStmt(array $stmts, string $var_id, CodeLocation $original_location): array + { + $assign_stmt = null; + $assign_exp = null; + $assign_exp_found = false; + + $i = 0; + + while ($i < count($stmts) && !$assign_exp_found) { + $stmt = $stmts[$i]; + if ($stmt instanceof PhpParser\Node\Stmt\Expression) { + $search_result = $this->findAssignExp($stmt->expr, $var_id, $original_location->raw_file_start); + + [$target_exp, $levels_taken] = $search_result; + + if ($target_exp !== null) { + $assign_exp_found = true; + $assign_exp = $target_exp; + $assign_stmt = $levels_taken === 1 ? $stmt : null; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\TryCatch) { + $search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location); + + if ($search_result[0] && $search_result[1]) { + return $search_result; + } + + foreach ($stmt->catches as $catch_stmt) { + $search_result = $this->findAssignStmt($catch_stmt->stmts, $var_id, $original_location); + + if ($search_result[0] && $search_result[1]) { + return $search_result; + } + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\Do_ + || $stmt instanceof PhpParser\Node\Stmt\While_ + ) { + $search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location); + + if ($search_result[0] && $search_result[1]) { + return $search_result; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\Foreach_) { + $search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location); + + if ($search_result[0] && $search_result[1]) { + return $search_result; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\For_) { + $search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location); + + if ($search_result[0] && $search_result[1]) { + return $search_result; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\If_) { + $search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location); + + if ($search_result[0] && $search_result[1]) { + return $search_result; + } + + foreach ($stmt->elseifs as $elseif_stmt) { + $search_result = $this->findAssignStmt($elseif_stmt->stmts, $var_id, $original_location); + + if ($search_result[0] && $search_result[1]) { + return $search_result; + } + } + + if ($stmt->else) { + $search_result = $this->findAssignStmt($stmt->else->stmts, $var_id, $original_location); + + if ($search_result[0] && $search_result[1]) { + return $search_result; + } + } + } + + $i++; + } + + return [$assign_stmt, $assign_exp]; + } + + /** + * @return array{ + * 0: PhpParser\Node\Expr\Assign|PhpParser\Node\Expr\AssignOp|PhpParser\Node\Expr\AssignRef|null, + * 1: int + * } + */ + private function findAssignExp( + PhpParser\Node\Expr $current_node, + string $var_id, + int $var_start_loc, + int $search_level = 1 + ): array { + if ($current_node instanceof PhpParser\Node\Expr\Assign + || $current_node instanceof PhpPArser\Node\Expr\AssignOp + || $current_node instanceof PhpParser\Node\Expr\AssignRef + ) { + $var = $current_node->var; + + if ($var instanceof PhpParser\Node\Expr\Variable + && $var->name === substr($var_id, 1) + && $var->getStartFilePos() === $var_start_loc + ) { + return [$current_node, $search_level]; + } + + $rhs_exp = $current_node->expr; + $rhs_search_result = $this->findAssignExp($rhs_exp, $var_id, $var_start_loc, $search_level + 1); + return [$rhs_search_result[0], $rhs_search_result[1]]; + } else { + return [null, $search_level]; + } + } + + public function checkIfVarRemoved(string $var_id, CodeLocation $var_loc): bool + { + return array_key_exists($var_id, $this->removed_unref_vars) + && $this->removed_unref_vars[$var_id] === $var_loc; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..80a572cbf0fedb6d813c1f8edb6fb6b5db0bfcac --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php @@ -0,0 +1,956 @@ + + */ + private $all_vars = []; + + /** + * @var array + */ + private $var_branch_points = []; + + /** + * Possibly undefined variables should be initialised if we're altering code + * + * @var array|null + */ + private $vars_to_initialize; + + /** + * @var array + */ + private $function_analyzers = []; + + /** + * @var array + */ + private $unused_var_locations = []; + + /** + * @var ?array + */ + public $byref_uses; + + /** + * @var ParsedDocblock|null + */ + private $parsed_docblock = null; + + /** + * @var ?string + */ + private $fake_this_class = null; + + /** @var \Psalm\Internal\Provider\NodeDataProvider */ + public $node_data; + + /** @var ?DataFlowGraph */ + public $data_flow_graph; + + public function __construct(SourceAnalyzer $source, \Psalm\Internal\Provider\NodeDataProvider $node_data) + { + $this->source = $source; + $this->file_analyzer = $source->getFileAnalyzer(); + $this->codebase = $source->getCodebase(); + $this->node_data = $node_data; + + if ($this->codebase->taint_flow_graph) { + $this->data_flow_graph = new TaintFlowGraph(); + } elseif ($this->codebase->find_unused_variables) { + $this->data_flow_graph = new VariableUseGraph(); + } + } + + /** + * Checks an array of statements for validity + * + * @param array $stmts + * + * @return null|false + */ + public function analyze( + array $stmts, + Context $context, + ?Context $global_context = null, + bool $root_scope = false + ): ?bool { + if (!$stmts) { + return null; + } + + // hoist functions to the top + $this->hoistFunctions($stmts, $context); + + $project_analyzer = $this->getFileAnalyzer()->project_analyzer; + $codebase = $project_analyzer->getCodebase(); + + if ($codebase->config->hoist_constants) { + self::hoistConstants($this, $stmts, $context); + } + + foreach ($stmts as $stmt) { + if (self::analyzeStatement($this, $stmt, $context, $global_context) === false) { + return false; + } + } + + if ($root_scope + && !$context->collect_initializations + && $codebase->find_unused_variables + && $context->check_variables + ) { + //var_dump($this->data_flow_graph); + $this->checkUnreferencedVars($stmts); + } + + if ($codebase->alter_code && $root_scope && $this->vars_to_initialize) { + $file_contents = $codebase->getFileContents($this->getFilePath()); + + foreach ($this->vars_to_initialize as $var_id => $branch_point) { + $newline_pos = (int)strrpos($file_contents, "\n", $branch_point - strlen($file_contents)) + 1; + $indentation = substr($file_contents, $newline_pos, $branch_point - $newline_pos); + FileManipulationBuffer::add($this->getFilePath(), [ + new FileManipulation($branch_point, $branch_point, $var_id . ' = null;' . "\n" . $indentation), + ]); + } + } + + if ($root_scope + && $this->data_flow_graph instanceof TaintFlowGraph + && $this->codebase->taint_flow_graph + && $codebase->config->trackTaintsInPath($this->getFilePath()) + ) { + $this->codebase->taint_flow_graph->addGraph($this->data_flow_graph); + } + + return null; + } + + /** + * @param array $stmts + */ + private function hoistFunctions(array $stmts, Context $context) : void + { + foreach ($stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\Function_) { + $function_name = strtolower($stmt->name->name); + + if ($ns = $this->getNamespace()) { + $fq_function_name = strtolower($ns) . '\\' . $function_name; + } else { + $fq_function_name = $function_name; + } + + if ($this->data_flow_graph + && $this->codebase->find_unused_variables + ) { + foreach ($stmt->stmts as $function_stmt) { + if ($function_stmt instanceof PhpParser\Node\Stmt\Global_) { + foreach ($function_stmt->vars as $var) { + if (!$var instanceof PhpParser\Node\Expr\Variable + || !\is_string($var->name) + ) { + continue; + } + + $var_id = '$' . $var->name; + + if ($var_id !== '$argv' && $var_id !== '$argc') { + $context->byref_constraints[$var_id] = new \Psalm\Internal\ReferenceConstraint(); + } + } + } + } + } + + try { + $function_analyzer = new FunctionAnalyzer($stmt, $this->source); + $this->function_analyzers[$fq_function_name] = $function_analyzer; + } catch (\UnexpectedValueException $e) { + // do nothing + } + } + } + } + + /** + * @param array $stmts + */ + private static function hoistConstants( + StatementsAnalyzer $statements_analyzer, + array $stmts, + Context $context + ) : void { + $codebase = $statements_analyzer->getCodebase(); + + foreach ($stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\Const_) { + foreach ($stmt->consts as $const) { + ConstFetchAnalyzer::setConstType( + $statements_analyzer, + $const->name->name, + SimpleTypeInferer::infer( + $codebase, + $statements_analyzer->node_data, + $const->value, + $statements_analyzer->getAliases(), + $statements_analyzer + ) ?: Type::getMixed(), + $context + ); + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\Expression + && $stmt->expr instanceof PhpParser\Node\Expr\FuncCall + && $stmt->expr->name instanceof PhpParser\Node\Name + && $stmt->expr->name->parts === ['define'] + && isset($stmt->expr->args[1]) + ) { + $const_name = ConstFetchAnalyzer::getConstName( + $stmt->expr->args[0]->value, + $statements_analyzer->node_data, + $codebase, + $statements_analyzer->getAliases() + ); + + if ($const_name !== null) { + ConstFetchAnalyzer::setConstType( + $statements_analyzer, + $const_name, + Statements\Expression\SimpleTypeInferer::infer( + $codebase, + $statements_analyzer->node_data, + $stmt->expr->args[1]->value, + $statements_analyzer->getAliases(), + $statements_analyzer + ) ?: Type::getMixed(), + $context + ); + } + } + } + } + + /** + * @return false|null + */ + private static function analyzeStatement( + StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Stmt $stmt, + Context $context, + ?Context $global_context + ): ?bool { + $ignore_variable_property = false; + $ignore_variable_method = false; + + $codebase = $statements_analyzer->getCodebase(); + + if ($context->has_returned + && !$context->collect_initializations + && !$context->collect_mutations + && !($stmt instanceof PhpParser\Node\Stmt\Nop) + && !($stmt instanceof PhpParser\Node\Stmt\InlineHTML) + ) { + if ($codebase->find_unused_variables) { + if (IssueBuffer::accepts( + new UnevaluatedCode( + 'Expressions after return/throw/continue', + new CodeLocation($statements_analyzer->source, $stmt) + ), + $statements_analyzer->source->getSuppressedIssues() + )) { + return false; + } + } + + return null; + } + + if ($statements_analyzer->getProjectAnalyzer()->debug_lines) { + fwrite(STDERR, $statements_analyzer->getFilePath() . ':' . $stmt->getLine() . "\n"); + } + + /* + if (isset($context->vars_in_scope['$array']) && !$stmt instanceof PhpParser\Node\Stmt\Nop) { + var_dump($stmt->getLine(), $context->vars_in_scope['$array']); + } + */ + + $new_issues = null; + $traced_variables = []; + + if ($docblock = $stmt->getDocComment()) { + $statements_analyzer->parseStatementDocblock($docblock, $stmt, $context); + + if (isset($statements_analyzer->parsed_docblock->tags['psalm-trace'])) { + foreach ($statements_analyzer->parsed_docblock->tags['psalm-trace'] as $traced_variable_line) { + $possible_traced_variable_names = preg_split('/[\s]+/', $traced_variable_line); + if ($possible_traced_variable_names) { + $traced_variables = array_merge( + $traced_variables, + array_filter($possible_traced_variable_names) + ); + } + } + } + + if (isset($statements_analyzer->parsed_docblock->tags['psalm-ignore-variable-method'])) { + $context->ignore_variable_method = $ignore_variable_method = true; + } + + if (isset($statements_analyzer->parsed_docblock->tags['psalm-ignore-variable-property'])) { + $context->ignore_variable_property = $ignore_variable_property = true; + } + + if (isset($statements_analyzer->parsed_docblock->tags['psalm-suppress'])) { + $suppressed = $statements_analyzer->parsed_docblock->tags['psalm-suppress']; + if ($suppressed) { + $new_issues = []; + + foreach ($suppressed as $offset => $suppress_entry) { + foreach (DocComment::parseSuppressList($suppress_entry) as $issue_offset => $issue_type) { + $new_issues[$issue_offset + $offset + $docblock->getStartFilePos()] = $issue_type; + + if ($issue_type === 'InaccessibleMethod') { + continue; + } + + if ($codebase->track_unused_suppressions) { + IssueBuffer::addUnusedSuppression( + $statements_analyzer->getFilePath(), + $issue_offset + $offset + $docblock->getStartFilePos(), + $issue_type + ); + } + } + } + + $statements_analyzer->addSuppressedIssues($new_issues); + } + } + + if (isset($statements_analyzer->parsed_docblock->combined_tags['var']) + && !($stmt instanceof PhpParser\Node\Stmt\Expression + && $stmt->expr instanceof PhpParser\Node\Expr\Assign) + && !$stmt instanceof PhpParser\Node\Stmt\Foreach_ + && !$stmt instanceof PhpParser\Node\Stmt\Return_ + ) { + $file_path = $statements_analyzer->getRootFilePath(); + + $file_storage_provider = $codebase->file_storage_provider; + + $file_storage = $file_storage_provider->get($file_path); + + $template_type_map = $statements_analyzer->getTemplateTypeMap(); + + $var_comments = []; + + try { + $var_comments = CommentAnalyzer::arrayToDocblocks( + $docblock, + $statements_analyzer->parsed_docblock, + $statements_analyzer->getSource(), + $statements_analyzer->getAliases(), + $template_type_map, + $file_storage->type_aliases + ); + } catch (\Psalm\Exception\IncorrectDocblockException $e) { + if (IssueBuffer::accepts( + new MissingDocblockType( + (string)$e->getMessage(), + new CodeLocation($statements_analyzer->getSource(), $stmt) + ) + )) { + // fall through + } + } catch (\Psalm\Exception\DocblockParseException $e) { + if (IssueBuffer::accepts( + new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($statements_analyzer->getSource(), $stmt) + ) + )) { + // fall through + } + } + + foreach ($var_comments as $var_comment) { + AssignmentAnalyzer::assignTypeFromVarDocblock( + $statements_analyzer, + $stmt, + $var_comment, + $context + ); + } + } + } else { + $statements_analyzer->parsed_docblock = null; + } + + if ($stmt instanceof PhpParser\Node\Stmt\If_) { + if (IfAnalyzer::analyze($statements_analyzer, $stmt, $context) === false) { + return false; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\TryCatch) { + if (TryAnalyzer::analyze($statements_analyzer, $stmt, $context) === false) { + return false; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\For_) { + if (ForAnalyzer::analyze($statements_analyzer, $stmt, $context) === false) { + return false; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\Foreach_) { + if (ForeachAnalyzer::analyze($statements_analyzer, $stmt, $context) === false) { + return false; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\While_) { + if (WhileAnalyzer::analyze($statements_analyzer, $stmt, $context) === false) { + return false; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\Do_) { + DoAnalyzer::analyze($statements_analyzer, $stmt, $context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Const_) { + ConstFetchAnalyzer::analyzeConstAssignment($statements_analyzer, $stmt, $context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Unset_) { + Statements\UnsetAnalyzer::analyze($statements_analyzer, $stmt, $context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Return_) { + ReturnAnalyzer::analyze($statements_analyzer, $stmt, $context); + $context->has_returned = true; + } elseif ($stmt instanceof PhpParser\Node\Stmt\Throw_) { + ThrowAnalyzer::analyze($statements_analyzer, $stmt, $context); + $context->has_returned = true; + } elseif ($stmt instanceof PhpParser\Node\Stmt\Switch_) { + SwitchAnalyzer::analyze($statements_analyzer, $stmt, $context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Break_) { + Statements\BreakAnalyzer::analyze($statements_analyzer, $stmt, $context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Continue_) { + Statements\ContinueAnalyzer::analyze($statements_analyzer, $stmt, $context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Static_) { + Statements\StaticAnalyzer::analyze($statements_analyzer, $stmt, $context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Echo_) { + if (Statements\EchoAnalyzer::analyze($statements_analyzer, $stmt, $context) === false) { + return false; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\Function_) { + FunctionAnalyzer::analyzeStatement($statements_analyzer, $stmt, $context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Expression) { + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $stmt->expr, + $context, + false, + $global_context, + true + ) === false) { + return false; + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\InlineHTML) { + // do nothing + } elseif ($stmt instanceof PhpParser\Node\Stmt\Global_) { + Statements\GlobalAnalyzer::analyze($statements_analyzer, $stmt, $context, $global_context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Property) { + InstancePropertyAssignmentAnalyzer::analyzeStatement($statements_analyzer, $stmt, $context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\ClassConst) { + ClassConstFetchAnalyzer::analyzeClassConstAssignment($statements_analyzer, $stmt, $context); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Class_) { + try { + $class_analyzer = new ClassAnalyzer( + $stmt, + $statements_analyzer->source, + $stmt->name ? $stmt->name->name : null + ); + + $class_analyzer->analyze(null, $global_context); + } catch (\InvalidArgumentException $e) { + // disregard this exception, we'll likely see it elsewhere in the form + // of an issue + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\Nop) { + // do nothing + } elseif ($stmt instanceof PhpParser\Node\Stmt\Goto_) { + // do nothing + } elseif ($stmt instanceof PhpParser\Node\Stmt\Label) { + // do nothing + } elseif ($stmt instanceof PhpParser\Node\Stmt\Declare_) { + foreach ($stmt->declares as $declaration) { + if ((string) $declaration->key === 'strict_types' + && $declaration->value instanceof PhpParser\Node\Scalar\LNumber + && $declaration->value->value === 1 + ) { + $context->strict_types = true; + } + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\HaltCompiler) { + $context->has_returned = true; + } else { + if (IssueBuffer::accepts( + new UnrecognizedStatement( + 'Psalm does not understand ' . get_class($stmt), + new CodeLocation($statements_analyzer->source, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + return false; + } + } + + $codebase = $statements_analyzer->getCodebase(); + + $plugin_classes = $codebase->config->after_statement_checks; + + if ($plugin_classes) { + $file_manipulations = []; + + foreach ($plugin_classes as $plugin_fq_class_name) { + if ($plugin_fq_class_name::afterStatementAnalysis( + $stmt, + $context, + $statements_analyzer, + $codebase, + $file_manipulations + ) === false) { + return false; + } + } + + if ($file_manipulations) { + FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations); + } + } + + if ($new_issues) { + $statements_analyzer->removeSuppressedIssues($new_issues); + } + + if ($ignore_variable_property) { + $context->ignore_variable_property = false; + } + + if ($ignore_variable_method) { + $context->ignore_variable_method = false; + } + + foreach ($traced_variables as $traced_variable) { + if (isset($context->vars_in_scope[$traced_variable])) { + if (IssueBuffer::accepts( + new Trace( + $traced_variable . ': ' . $context->vars_in_scope[$traced_variable]->getId(), + new CodeLocation($statements_analyzer->source, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new UndefinedTrace( + 'Attempt to trace undefined variable ' . $traced_variable, + new CodeLocation($statements_analyzer->source, $stmt) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + + return null; + } + + private function parseStatementDocblock( + PhpParser\Comment\Doc $docblock, + PhpParser\Node\Stmt $stmt, + Context $context + ) : void { + $codebase = $this->getCodebase(); + + try { + $this->parsed_docblock = DocComment::parsePreservingLength($docblock); + } catch (DocblockParseException $e) { + if (IssueBuffer::accepts( + new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($this->getSource(), $stmt, null, true) + ) + )) { + // fall through + } + + $this->parsed_docblock = null; + } + + $comments = $this->parsed_docblock; + + if (isset($comments->tags['psalm-scope-this'])) { + $trimmed = trim(\reset($comments->tags['psalm-scope-this'])); + + if (!$codebase->classExists($trimmed)) { + if (IssueBuffer::accepts( + new \Psalm\Issue\UndefinedDocblockClass( + 'Scope class ' . $trimmed . ' does not exist', + new CodeLocation($this->getSource(), $stmt, null, true), + $trimmed + ) + )) { + // fall through + } + } else { + $this_type = Type::parseString($trimmed); + $context->self = $trimmed; + $context->vars_in_scope['$this'] = $this_type; + $this->setFQCLN($trimmed); + } + } + } + + /** + * @param array $stmts + */ + public function checkUnreferencedVars(array $stmts): void + { + $source = $this->getSource(); + $codebase = $source->getCodebase(); + $function_storage = $source instanceof FunctionLikeAnalyzer ? $source->getFunctionLikeStorage($this) : null; + $var_list = array_column($this->unused_var_locations, 0); + $loc_list = array_column($this->unused_var_locations, 1); + + $project_analyzer = $this->getProjectAnalyzer(); + + $unused_var_remover = new Statements\UnusedAssignmentRemover(); + + foreach ($this->unused_var_locations as [$var_id, $original_location]) { + if (substr($var_id, 0, 2) === '$_') { + continue; + } + + if ($function_storage) { + $param_index = \array_search(substr($var_id, 1), array_keys($function_storage->param_lookup)); + if ($param_index !== false) { + $param = $function_storage->params[$param_index]; + + if ($param->location + && ($original_location->raw_file_end === $param->location->raw_file_end + || $param->by_ref) + ) { + continue; + } + } + } + + $assignment_node = DataFlowNode::getForAssignment($var_id, $original_location); + + if (!isset($this->byref_uses[$var_id]) + && !VariableFetchAnalyzer::isSuperGlobal($var_id) + && $this->data_flow_graph instanceof VariableUseGraph + && !$this->data_flow_graph->isVariableUsed($assignment_node) + ) { + $issue = new UnusedVariable( + 'Variable ' . $var_id . ' is never referenced', + $original_location + ); + + if ($codebase->alter_code + && !$unused_var_remover->checkIfVarRemoved($var_id, $original_location) + && isset($project_analyzer->getIssuesToFix()['UnusedVariable']) + && !IssueBuffer::isSuppressed($issue, $this->getSuppressedIssues()) + ) { + $unused_var_remover->findUnusedAssignment( + $this->getCodebase(), + $stmts, + array_combine($var_list, $loc_list), + $var_id, + $original_location + ); + } + + if (IssueBuffer::accepts( + $issue, + $this->getSuppressedIssues(), + true + )) { + // fall through + } + } + } + } + + public function hasVariable(string $var_name): bool + { + return isset($this->all_vars[$var_name]); + } + + public function registerVariable(string $var_id, CodeLocation $location, ?int $branch_point): void + { + $this->all_vars[$var_id] = $location; + + if ($branch_point) { + $this->var_branch_points[$var_id] = $branch_point; + } + + $this->registerVariableAssignment($var_id, $location); + } + + public function registerVariableAssignment(string $var_id, CodeLocation $location): void + { + $this->unused_var_locations[$location->getHash()] = [$var_id, $location]; + } + + /** + * @return array + */ + public function getUnusedVarLocations(): array + { + return $this->unused_var_locations; + } + + public function registerPossiblyUndefinedVariable( + string $undefined_var_id, + PhpParser\Node\Expr\Variable $stmt + ) : void { + if (!$this->data_flow_graph) { + return; + } + + $use_location = new CodeLocation($this->getSource(), $stmt); + $use_node = DataFlowNode::getForAssignment($undefined_var_id, $use_location); + + $stmt_type = $this->node_data->getType($stmt); + + if ($stmt_type) { + $stmt_type->parent_nodes[$use_node->id] = $use_node; + } + + foreach ($this->unused_var_locations as [$var_id, $original_location]) { + if ($var_id === $undefined_var_id) { + $parent_node = DataFlowNode::getForAssignment($var_id, $original_location); + + $this->data_flow_graph->addPath($parent_node, $use_node, '='); + } + } + } + + /** + * @return array + */ + public function getParentNodesForPossiblyUndefinedVariable(string $undefined_var_id) : array + { + if (!$this->data_flow_graph) { + return []; + } + + $parent_nodes = []; + + foreach ($this->unused_var_locations as [$var_id, $original_location]) { + if ($var_id === $undefined_var_id) { + $assignment_node = DataFlowNode::getForAssignment($var_id, $original_location); + $parent_nodes[$assignment_node->id] = $assignment_node; + } + } + + return $parent_nodes; + } + + /** + * The first appearance of the variable in this set of statements being evaluated + */ + public function getFirstAppearance(string $var_id): ?CodeLocation + { + return isset($this->all_vars[$var_id]) ? $this->all_vars[$var_id] : null; + } + + public function getBranchPoint(string $var_id): ?int + { + return isset($this->var_branch_points[$var_id]) ? $this->var_branch_points[$var_id] : null; + } + + public function addVariableInitialization(string $var_id, int $branch_point): void + { + $this->vars_to_initialize[$var_id] = $branch_point; + } + + public function getFileAnalyzer() : FileAnalyzer + { + return $this->file_analyzer; + } + + public function getCodebase() : Codebase + { + return $this->codebase; + } + + /** + * @return array + */ + public function getFunctionAnalyzers(): array + { + return $this->function_analyzers; + } + + /** + * @param array $byref_uses + */ + public function setByRefUses(array $byref_uses): void + { + $this->byref_uses = $byref_uses; + } + + /** + * @return array> + */ + public function getUncaughtThrows(Context $context): array + { + $uncaught_throws = []; + + if ($context->collect_exceptions) { + if ($context->possibly_thrown_exceptions) { + $config = $this->codebase->config; + $ignored_exceptions = array_change_key_case( + $context->is_global ? + $config->ignored_exceptions_in_global_scope : + $config->ignored_exceptions + ); + $ignored_exceptions_and_descendants = array_change_key_case( + $context->is_global ? + $config->ignored_exceptions_and_descendants_in_global_scope : + $config->ignored_exceptions_and_descendants + ); + + foreach ($context->possibly_thrown_exceptions as $possibly_thrown_exception => $codelocations) { + if (isset($ignored_exceptions[strtolower($possibly_thrown_exception)])) { + continue; + } + + $is_expected = false; + + foreach ($ignored_exceptions_and_descendants as $expected_exception => $_) { + try { + if ($expected_exception === strtolower($possibly_thrown_exception) + || $this->codebase->classExtends($possibly_thrown_exception, $expected_exception) + ) { + $is_expected = true; + break; + } + } catch (\InvalidArgumentException $e) { + $is_expected = true; + break; + } + } + + if (!$is_expected) { + $uncaught_throws[$possibly_thrown_exception] = $codelocations; + } + } + } + } + + return $uncaught_throws; + } + + public function getFunctionAnalyzer(string $function_id) : ?FunctionAnalyzer + { + return $this->function_analyzers[$function_id] ?? null; + } + + public function getParsedDocblock() : ?ParsedDocblock + { + return $this->parsed_docblock; + } + + public function getFQCLN(): ?string + { + if ($this->fake_this_class) { + return $this->fake_this_class; + } + + return parent::getFQCLN(); + } + + public function setFQCLN(string $fake_this_class) : void + { + $this->fake_this_class = $fake_this_class; + } + + /** + * @return \Psalm\Internal\Provider\NodeDataProvider + */ + public function getNodeTypeProvider() : \Psalm\NodeTypeProvider + { + return $this->node_data; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/TraitAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/TraitAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..9f15edcd5594383ef2a78478f3d1a9e229cf35f4 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/TraitAnalyzer.php @@ -0,0 +1,58 @@ +source = $source; + $this->file_analyzer = $source->getFileAnalyzer(); + $this->aliases = $source->getAliases(); + $this->class = $class; + $this->fq_class_name = $fq_class_name; + $codebase = $source->getCodebase(); + $this->storage = $codebase->classlike_storage_provider->get($fq_class_name); + $this->aliases = $aliases; + } + + public function getNamespace(): ?string + { + return $this->aliases->namespace; + } + + public function getAliases(): Aliases + { + return $this->aliases; + } + + /** + * @return array + */ + public function getAliasedClassesFlipped(): array + { + return []; + } + + /** + * @return array + */ + public function getAliasedClassesFlippedReplaceable(): array + { + return []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/TypeAnalyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/TypeAnalyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..68fe956dde7ba6122475c95f8bc2c7470c9d7530 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Analyzer/TypeAnalyzer.php @@ -0,0 +1,115 @@ + $new_types + * @param array $existing_types + * + * @return array + */ + public static function combineKeyedTypes(array $new_types, array $existing_types): array + { + $keys = array_merge(array_keys($new_types), array_keys($existing_types)); + $keys = array_unique($keys); + + $result_types = []; + + if (empty($new_types)) { + return $existing_types; + } + + if (empty($existing_types)) { + return $new_types; + } + + foreach ($keys as $key) { + if (!isset($existing_types[$key])) { + $result_types[$key] = $new_types[$key]; + continue; + } + + if (!isset($new_types[$key])) { + $result_types[$key] = $existing_types[$key]; + continue; + } + + $existing_var_types = $existing_types[$key]; + $new_var_types = $new_types[$key]; + + if ($new_var_types->getId() === $existing_var_types->getId()) { + $result_types[$key] = $new_var_types; + } else { + $result_types[$key] = Type::combineUnionTypes($new_var_types, $existing_var_types); + } + } + + return $result_types; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Clause.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Clause.php new file mode 100644 index 0000000000000000000000000000000000000000..dac28eee862f7d19f0fd3dd3d4b7dfe21def7a5a --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Clause.php @@ -0,0 +1,282 @@ + ['falsy'], + * '$b' => ['!falsy'], + * '$c' => ['!null'], + * '$d' => ['string', 'int'] + * ] + * + * representing the formula + * + * !$a || $b || $c !== null || is_string($d) || is_int($d) + * + * @var array> + */ + public $possibilities; + + /** + * An array of things that are not true + * [ + * '$a' => ['!falsy'], + * '$b' => ['falsy'], + * '$c' => ['null'], + * '$d' => ['!string', '!int'] + * ] + * represents the formula + * + * $a && !$b && $c === null && !is_string($d) && !is_int($d) + * + * @var array>|null + */ + public $impossibilities; + + /** @var bool */ + public $wedge; + + /** @var bool */ + public $reconcilable; + + /** @var bool */ + public $generated = false; + + /** @var array */ + public $redefined_vars = []; + + /** @var string|int */ + public $hash; + + /** + * @param array> $possibilities + * @param array $redefined_vars + */ + public function __construct( + array $possibilities, + int $creating_conditional_id, + int $creating_object_id, + bool $wedge = false, + bool $reconcilable = true, + bool $generated = false, + array $redefined_vars = [] + ) { + $this->possibilities = $possibilities; + $this->wedge = $wedge; + $this->reconcilable = $reconcilable; + $this->generated = $generated; + $this->redefined_vars = $redefined_vars; + $this->creating_conditional_id = $creating_conditional_id; + $this->creating_object_id = $creating_object_id; + + if ($wedge || !$reconcilable) { + $this->hash = ($wedge ? 'w' : '') . $creating_object_id; + } else { + ksort($possibilities); + + foreach ($possibilities as $i => $_) { + sort($possibilities[$i]); + } + + $this->hash = md5((string) json_encode($possibilities)); + } + } + + public function contains(Clause $other_clause): bool + { + if (count($other_clause->possibilities) > count($this->possibilities)) { + return false; + } + + foreach ($other_clause->possibilities as $var => $possible_types) { + if (!isset($this->possibilities[$var]) || count(array_diff($possible_types, $this->possibilities[$var]))) { + return false; + } + } + + return true; + } + + /** + * @psalm-mutation-free + */ + public function __toString(): string + { + $clause_strings = array_map( + /** + * @param string $var_id + * @param non-empty-list $values + * + * @return string + */ + function ($var_id, $values): string { + $var_id_clauses = array_map( + /** + * @param string $value + * + * @return string + */ + function ($value) use ($var_id): string { + if ($value === 'falsy') { + return '!' . $var_id; + } + + if ($value === '!falsy') { + return $var_id; + } + + $negate = false; + + if ($value[0] === '!') { + $negate = true; + $value = \substr($value, 1); + } + + if ($value[0] === '=') { + $value = \substr($value, 1); + } + + if ($negate) { + return $var_id . ' is not ' . $value; + } + + return $var_id . ' is ' . $value; + }, + $values + ); + + if (count($var_id_clauses) > 1) { + return '(' . implode(') || (', $var_id_clauses) . ')'; + } + + return $var_id_clauses[0]; + }, + array_keys($this->possibilities), + array_values($this->possibilities) + ); + + if (count($clause_strings) > 1) { + return '(' . implode(') || (', $clause_strings) . ')'; + } + + return \reset($clause_strings); + } + + public function makeUnique() : self + { + $possibilities = $this->possibilities; + + foreach ($possibilities as $var_id => $var_possibilities) { + $possibilities[$var_id] = array_values(array_unique($var_possibilities)); + } + + return new self( + $possibilities, + $this->creating_conditional_id, + $this->creating_object_id, + $this->wedge, + $this->reconcilable, + $this->generated, + $this->redefined_vars + ); + } + + public function removePossibilities(string $var_id) : ?self + { + $possibilities = $this->possibilities; + unset($possibilities[$var_id]); + + if (!$possibilities) { + return null; + } + + return new self( + $possibilities, + $this->creating_conditional_id, + $this->creating_object_id, + $this->wedge, + $this->reconcilable, + $this->generated, + $this->redefined_vars + ); + } + + /** + * @param non-empty-list $clause_var_possibilities + */ + public function addPossibilities(string $var_id, array $clause_var_possibilities) : self + { + $possibilities = $this->possibilities; + $possibilities[$var_id] = $clause_var_possibilities; + + return new self( + $possibilities, + $this->creating_conditional_id, + $this->creating_object_id, + $this->wedge, + $this->reconcilable, + $this->generated, + $this->redefined_vars + ); + } + + public function calculateNegation() : self + { + if ($this->impossibilities !== null) { + return $this; + } + + $impossibilities = []; + + foreach ($this->possibilities as $var_id => $possibility) { + $impossibility = []; + + foreach ($possibility as $type) { + if (($type[0] !== '=' && $type[0] !== '~' + && (!isset($type[1]) || ($type[1] !== '=' && $type[1] !== '~'))) + || strpos($type, '(') + || strpos($type, 'getclass-') + ) { + $impossibility[] = \Psalm\Type\Algebra::negateType($type); + } + } + + if ($impossibility) { + $impossibilities[$var_id] = $impossibility; + } + } + + $clause = clone $this; + + $clause->impossibilities = $impossibilities; + + return $clause; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php new file mode 100644 index 0000000000000000000000000000000000000000..b7787416b2c0027bf23808693799163c698bd229 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php @@ -0,0 +1,1498 @@ + + * + * @psalm-type FileMapType = array{ + * 0: TaggedCodeType, + * 1: TaggedCodeType, + * 2: array + * } + * + * @psalm-type WorkerData = array{ + * issues: array>, + * fixable_issue_counts: array, + * nonmethod_references_to_classes: array>, + * method_references_to_classes: array>, + * file_references_to_class_members: array>, + * file_references_to_missing_class_members: array>, + * mixed_counts: array, + * mixed_member_names: array>, + * function_timings: array, + * file_manipulations: array, + * method_references_to_class_members: array>, + * method_references_to_missing_class_members: array>, + * method_param_uses: array>>, + * analyzed_methods: array>, + * file_maps: array, + * class_locations: array>, + * class_method_locations: array>, + * class_property_locations: array>, + * possible_method_param_types: array>, + * taint_data: ?TaintFlowGraph, + * unused_suppressions: array>, + * used_suppressions: array>, + * function_docblock_manipulators: array>, + * mutable_classes: array, + * } + */ + +/** + * @internal + * + * Called in the analysis phase of Psalm's execution + */ +class Analyzer +{ + /** + * @var Config + */ + private $config; + + /** + * @var FileProvider + */ + private $file_provider; + + /** + * @var FileStorageProvider + */ + private $file_storage_provider; + + /** + * @var Progress + */ + private $progress; + + /** + * Used to store counts of mixed vs non-mixed variables + * + * @var array + */ + private $mixed_counts = []; + + /** + * Used to store member names of mixed property/method access + * + * @var array> + */ + private $mixed_member_names = []; + + /** + * @var bool + */ + private $count_mixed = true; + + /** + * Used to store debug performance data + * + * @var array + */ + private $function_timings = []; + + /** + * We analyze more files than we necessarily report errors in + * + * @var array + */ + private $files_to_analyze = []; + + /** + * We can show analysis results on more files than we analyze + * because the results can be cached + * + * @var array + */ + private $files_with_analysis_results = []; + + /** + * We may update fewer files than we analyse (i.e. for dead code detection) + * + * @var array|null + */ + private $files_to_update = null; + + /** + * @var array> + */ + private $analyzed_methods = []; + + /** + * @var array> + */ + private $existing_issues = []; + + /** + * @var array> + */ + private $reference_map = []; + + /** + * @var array> + */ + private $type_map = []; + + /** + * @var array> + */ + private $argument_map = []; + + /** + * @var array> + */ + public $possible_method_param_types = []; + + /** + * @var array + */ + public $mutable_classes = []; + + public function __construct( + Config $config, + FileProvider $file_provider, + FileStorageProvider $file_storage_provider, + Progress $progress + ) { + $this->config = $config; + $this->file_provider = $file_provider; + $this->file_storage_provider = $file_storage_provider; + $this->progress = $progress; + } + + /** + * @param array $files_to_analyze + * + */ + public function addFilesToAnalyze(array $files_to_analyze): void + { + $this->files_to_analyze += $files_to_analyze; + $this->files_with_analysis_results += $files_to_analyze; + } + + /** + * @param array $files_to_analyze + * + */ + public function addFilesToShowResults(array $files_to_analyze): void + { + $this->files_with_analysis_results += $files_to_analyze; + } + + /** + * @param array $files_to_update + * + */ + public function setFilesToUpdate(array $files_to_update): void + { + $this->files_to_update = $files_to_update; + } + + public function canReportIssues(string $file_path): bool + { + return isset($this->files_with_analysis_results[$file_path]); + } + + /** + * @param array> $filetype_analyzers + */ + private function getFileAnalyzer( + ProjectAnalyzer $project_analyzer, + string $file_path, + array $filetype_analyzers + ): FileAnalyzer { + $extension = pathinfo($file_path, PATHINFO_EXTENSION); + + $file_name = $this->config->shortenFileName($file_path); + + if (isset($filetype_analyzers[$extension])) { + $file_analyzer = new $filetype_analyzers[$extension]($project_analyzer, $file_path, $file_name); + } else { + $file_analyzer = new FileAnalyzer($project_analyzer, $file_path, $file_name); + } + + $this->progress->debug('Getting ' . $file_path . "\n"); + + return $file_analyzer; + } + + public function analyzeFiles( + ProjectAnalyzer $project_analyzer, + int $pool_size, + bool $alter_code, + bool $consolidate_analyzed_data = false + ): void { + $this->loadCachedResults($project_analyzer); + + $codebase = $project_analyzer->getCodebase(); + + if ($alter_code) { + $project_analyzer->interpretRefactors(); + } + + $this->files_to_analyze = array_filter( + $this->files_to_analyze, + function (string $file_path) : bool { + return $this->file_provider->fileExists($file_path); + } + ); + + $this->doAnalysis($project_analyzer, $pool_size); + + $scanned_files = $codebase->scanner->getScannedFiles(); + + if ($codebase->taint_flow_graph) { + $codebase->taint_flow_graph->connectSinksAndSources(); + } + + $this->progress->finish(); + + if ($consolidate_analyzed_data) { + $project_analyzer->consolidateAnalyzedData(); + } + + foreach (IssueBuffer::getIssuesData() as $file_path => $file_issues) { + $codebase->file_reference_provider->clearExistingIssuesForFile($file_path); + + foreach ($file_issues as $issue_data) { + $codebase->file_reference_provider->addIssue($file_path, $issue_data); + } + } + + $codebase->file_reference_provider->updateReferenceCache($codebase, $scanned_files); + + if ($codebase->track_unused_suppressions) { + IssueBuffer::processUnusedSuppressions($codebase->file_provider); + } + + $codebase->file_reference_provider->setAnalyzedMethods($this->analyzed_methods); + $codebase->file_reference_provider->setFileMaps($this->getFileMaps()); + $codebase->file_reference_provider->setTypeCoverage($this->mixed_counts); + $codebase->file_reference_provider->updateReferenceCache($codebase, $scanned_files); + + if ($codebase->diff_methods) { + $codebase->statements_provider->resetDiffs(); + } + + if ($alter_code) { + $this->progress->startAlteringFiles(); + + $project_analyzer->prepareMigration(); + + $files_to_update = $this->files_to_update !== null ? $this->files_to_update : $this->files_to_analyze; + + foreach ($files_to_update as $file_path) { + $this->updateFile($file_path, $project_analyzer->dry_run); + } + + $project_analyzer->migrateCode(); + } + } + + private function doAnalysis(ProjectAnalyzer $project_analyzer, int $pool_size) : void + { + $this->progress->start(count($this->files_to_analyze)); + + \ksort($this->files_to_analyze); + + $codebase = $project_analyzer->getCodebase(); + + $filetype_analyzers = $this->config->getFiletypeAnalyzers(); + + $analysis_worker = + /** + * @return list<\Psalm\Internal\Analyzer\IssueData> + */ + function (int $_, string $file_path) use ($project_analyzer, $filetype_analyzers): array { + $file_analyzer = $this->getFileAnalyzer($project_analyzer, $file_path, $filetype_analyzers); + + $this->progress->debug('Analyzing ' . $file_analyzer->getFilePath() . "\n"); + + $file_analyzer->analyze(null); + $file_analyzer->context = null; + $file_analyzer->clearSourceBeforeDestruction(); + unset($file_analyzer); + + return IssueBuffer::getIssuesDataForFile($file_path); + }; + + $task_done_closure = + /** + * @param array $issues + */ + function (array $issues): void { + $has_error = false; + $has_info = false; + + foreach ($issues as $issue) { + if ($issue->severity === 'error') { + $has_error = true; + break; + } + + if ($issue->severity === 'info') { + $has_info = true; + } + } + + $this->progress->taskDone($has_error ? 2 : ($has_info ? 1 : 0)); + }; + + if ($pool_size > 1 && count($this->files_to_analyze) > $pool_size) { + $shuffle_count = $pool_size + 1; + + $file_paths = \array_values($this->files_to_analyze); + + $count = count($file_paths); + $middle = \intdiv($count, $shuffle_count); + $remainder = $count % $shuffle_count; + + $new_file_paths = []; + + for ($i = 0; $i < $shuffle_count; $i++) { + for ($j = 0; $j < $middle; $j++) { + if ($j * $shuffle_count + $i < $count) { + $new_file_paths[] = $file_paths[$j * $shuffle_count + $i]; + } + } + + if ($remainder) { + $new_file_paths[] = $file_paths[$middle * $shuffle_count + $remainder - 1]; + $remainder--; + } + } + + $process_file_paths = []; + + $i = 0; + + foreach ($new_file_paths as $file_path) { + $process_file_paths[$i % $pool_size][] = $file_path; + ++$i; + } + + // Run analysis one file at a time, splitting the set of + // files up among a given number of child processes. + $pool = new \Psalm\Internal\Fork\Pool( + $process_file_paths, + function (): void { + $project_analyzer = ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + + $file_reference_provider = $codebase->file_reference_provider; + + if ($codebase->taint_flow_graph) { + $codebase->taint_flow_graph = new TaintFlowGraph(); + } + + $file_reference_provider->setNonMethodReferencesToClasses([]); + $file_reference_provider->setCallingMethodReferencesToClassMembers([]); + $file_reference_provider->setFileReferencesToClassMembers([]); + $file_reference_provider->setCallingMethodReferencesToMissingClassMembers([]); + $file_reference_provider->setFileReferencesToMissingClassMembers([]); + $file_reference_provider->setReferencesToMixedMemberNames([]); + $file_reference_provider->setMethodParamUses([]); + }, + $analysis_worker, + /** @return WorkerData */ + function () { + $project_analyzer = ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + $analyzer = $codebase->analyzer; + $file_reference_provider = $codebase->file_reference_provider; + + $this->progress->debug('Gathering data for forked process' . "\n"); + + // @codingStandardsIgnoreStart + return [ + 'issues' => IssueBuffer::getIssuesData(), + 'fixable_issue_counts' => IssueBuffer::getFixableIssues(), + 'nonmethod_references_to_classes' => $file_reference_provider->getAllNonMethodReferencesToClasses(), + 'method_references_to_classes' => $file_reference_provider->getAllMethodReferencesToClasses(), + 'file_references_to_class_members' => $file_reference_provider->getAllFileReferencesToClassMembers(), + 'method_references_to_class_members' => $file_reference_provider->getAllMethodReferencesToClassMembers(), + 'file_references_to_missing_class_members' => $file_reference_provider->getAllFileReferencesToMissingClassMembers(), + 'method_references_to_missing_class_members' => $file_reference_provider->getAllMethodReferencesToMissingClassMembers(), + 'method_param_uses' => $file_reference_provider->getAllMethodParamUses(), + 'mixed_member_names' => $analyzer->getMixedMemberNames(), + 'file_manipulations' => FileManipulationBuffer::getAll(), + 'mixed_counts' => $analyzer->getMixedCounts(), + 'function_timings' => $analyzer->getFunctionTimings(), + 'analyzed_methods' => $analyzer->getAnalyzedMethods(), + 'file_maps' => $analyzer->getFileMaps(), + 'class_locations' => $file_reference_provider->getAllClassLocations(), + 'class_method_locations' => $file_reference_provider->getAllClassMethodLocations(), + 'class_property_locations' => $file_reference_provider->getAllClassPropertyLocations(), + 'possible_method_param_types' => $analyzer->getPossibleMethodParamTypes(), + 'taint_data' => $codebase->taint_flow_graph, + 'unused_suppressions' => $codebase->track_unused_suppressions ? IssueBuffer::getUnusedSuppressions() : [], + 'used_suppressions' => $codebase->track_unused_suppressions ? IssueBuffer::getUsedSuppressions() : [], + 'function_docblock_manipulators' => FunctionDocblockManipulator::getManipulators(), + 'mutable_classes' => $codebase->analyzer->mutable_classes, + ]; + // @codingStandardsIgnoreEnd + }, + $task_done_closure + ); + + $this->progress->debug('Forking analysis' . "\n"); + + // Wait for all tasks to complete and collect the results. + /** + * @var array + */ + $forked_pool_data = $pool->wait(); + + $this->progress->debug('Collecting forked analysis results' . "\n"); + + foreach ($forked_pool_data as $pool_data) { + IssueBuffer::addIssues($pool_data['issues']); + IssueBuffer::addFixableIssues($pool_data['fixable_issue_counts']); + + if ($codebase->track_unused_suppressions) { + IssueBuffer::addUnusedSuppressions($pool_data['unused_suppressions']); + IssueBuffer::addUsedSuppressions($pool_data['used_suppressions']); + } + + if ($codebase->taint_flow_graph && $pool_data['taint_data']) { + $codebase->taint_flow_graph->addGraph($pool_data['taint_data']); + } + + $codebase->file_reference_provider->addNonMethodReferencesToClasses( + $pool_data['nonmethod_references_to_classes'] + ); + $codebase->file_reference_provider->addMethodReferencesToClasses( + $pool_data['method_references_to_classes'] + ); + $codebase->file_reference_provider->addFileReferencesToClassMembers( + $pool_data['file_references_to_class_members'] + ); + $codebase->file_reference_provider->addMethodReferencesToClassMembers( + $pool_data['method_references_to_class_members'] + ); + $codebase->file_reference_provider->addFileReferencesToMissingClassMembers( + $pool_data['file_references_to_missing_class_members'] + ); + $codebase->file_reference_provider->addMethodReferencesToMissingClassMembers( + $pool_data['method_references_to_missing_class_members'] + ); + $codebase->file_reference_provider->addMethodParamUses( + $pool_data['method_param_uses'] + ); + $this->addMixedMemberNames( + $pool_data['mixed_member_names'] + ); + $this->function_timings += $pool_data['function_timings']; + $codebase->file_reference_provider->addClassLocations( + $pool_data['class_locations'] + ); + $codebase->file_reference_provider->addClassMethodLocations( + $pool_data['class_method_locations'] + ); + $codebase->file_reference_provider->addClassPropertyLocations( + $pool_data['class_property_locations'] + ); + + $this->mutable_classes = array_merge($this->mutable_classes, $pool_data['mutable_classes']); + + FunctionDocblockManipulator::addManipulators($pool_data['function_docblock_manipulators']); + + $this->analyzed_methods = array_merge($pool_data['analyzed_methods'], $this->analyzed_methods); + + foreach ($pool_data['mixed_counts'] as $file_path => [$mixed_count, $nonmixed_count]) { + if (!isset($this->mixed_counts[$file_path])) { + $this->mixed_counts[$file_path] = [$mixed_count, $nonmixed_count]; + } else { + $this->mixed_counts[$file_path][0] += $mixed_count; + $this->mixed_counts[$file_path][1] += $nonmixed_count; + } + } + + foreach ($pool_data['possible_method_param_types'] as $declaring_method_id => $possible_param_types) { + if (!isset($this->possible_method_param_types[$declaring_method_id])) { + $this->possible_method_param_types[$declaring_method_id] = $possible_param_types; + } else { + foreach ($possible_param_types as $offset => $possible_param_type) { + if (!isset($this->possible_method_param_types[$declaring_method_id][$offset])) { + $this->possible_method_param_types[$declaring_method_id][$offset] + = $possible_param_type; + } else { + $this->possible_method_param_types[$declaring_method_id][$offset] + = \Psalm\Type::combineUnionTypes( + $this->possible_method_param_types[$declaring_method_id][$offset], + $possible_param_type, + $codebase + ); + } + } + } + } + + foreach ($pool_data['file_manipulations'] as $file_path => $manipulations) { + FileManipulationBuffer::add($file_path, $manipulations); + } + + foreach ($pool_data['file_maps'] as $file_path => $file_maps) { + [$reference_map, $type_map, $argument_map] = $file_maps; + $this->reference_map[$file_path] = $reference_map; + $this->type_map[$file_path] = $type_map; + $this->argument_map[$file_path] = $argument_map; + } + } + + if ($pool->didHaveError()) { + exit(1); + } + } else { + $i = 0; + + foreach ($this->files_to_analyze as $file_path => $_) { + $analysis_worker($i, $file_path); + ++$i; + + $issues = IssueBuffer::getIssuesDataForFile($file_path); + $task_done_closure($issues); + } + } + } + + public function loadCachedResults(ProjectAnalyzer $project_analyzer): void + { + $codebase = $project_analyzer->getCodebase(); + + if ($codebase->diff_methods) { + $this->analyzed_methods = $codebase->file_reference_provider->getAnalyzedMethods(); + $this->existing_issues = $codebase->file_reference_provider->getExistingIssues(); + $file_maps = $codebase->file_reference_provider->getFileMaps(); + + foreach ($file_maps as $file_path => [$reference_map, $type_map, $argument_map]) { + $this->reference_map[$file_path] = $reference_map; + $this->type_map[$file_path] = $type_map; + $this->argument_map[$file_path] = $argument_map; + } + } + + $statements_provider = $codebase->statements_provider; + $file_reference_provider = $codebase->file_reference_provider; + + $changed_members = $statements_provider->getChangedMembers(); + $unchanged_signature_members = $statements_provider->getUnchangedSignatureMembers(); + + $diff_map = $statements_provider->getDiffMap(); + + $method_references_to_class_members + = $file_reference_provider->getAllMethodReferencesToClassMembers(); + $method_references_to_missing_class_members = + $file_reference_provider->getAllMethodReferencesToMissingClassMembers(); + + $all_referencing_methods = $method_references_to_class_members + $method_references_to_missing_class_members; + + $nonmethod_references_to_classes = $file_reference_provider->getAllNonMethodReferencesToClasses(); + + $method_references_to_classes = $file_reference_provider->getAllMethodReferencesToClasses(); + + $method_param_uses = $file_reference_provider->getAllMethodParamUses(); + + $file_references_to_class_members + = $file_reference_provider->getAllFileReferencesToClassMembers(); + $file_references_to_missing_class_members + = $file_reference_provider->getAllFileReferencesToMissingClassMembers(); + + $references_to_mixed_member_names = $file_reference_provider->getAllReferencesToMixedMemberNames(); + + $this->mixed_counts = $file_reference_provider->getTypeCoverage(); + + foreach ($changed_members as $file_path => $members_by_file) { + foreach ($members_by_file as $changed_member => $_) { + if (!strpos($changed_member, '&')) { + continue; + } + + [$base_class, $trait] = explode('&', $changed_member); + + foreach ($all_referencing_methods as $member_id => $_) { + if (strpos($member_id, $base_class . '::') !== 0) { + continue; + } + + $member_bit = substr($member_id, \strlen($base_class) + 2); + + if (isset($all_referencing_methods[$trait . '::' . $member_bit])) { + $changed_members[$file_path][$member_id] = true; + } + } + } + } + + $newly_invalidated_methods = []; + + foreach ($unchanged_signature_members as $file_unchanged_signature_members) { + $newly_invalidated_methods = array_merge($newly_invalidated_methods, $file_unchanged_signature_members); + + foreach ($file_unchanged_signature_members as $unchanged_signature_member_id => $_) { + // also check for things that might invalidate constructor property initialisation + if (isset($all_referencing_methods[$unchanged_signature_member_id])) { + foreach ($all_referencing_methods[$unchanged_signature_member_id] as $referencing_method_id => $_) { + if (substr($referencing_method_id, -13) === '::__construct') { + $referencing_base_classlike = explode('::', $referencing_method_id)[0]; + $unchanged_signature_classlike = explode('::', $unchanged_signature_member_id)[0]; + + if ($referencing_base_classlike === $unchanged_signature_classlike) { + $newly_invalidated_methods[$referencing_method_id] = true; + } else { + try { + $referencing_storage = $codebase->classlike_storage_provider->get( + $referencing_base_classlike + ); + } catch (InvalidArgumentException $_) { + // Workaround for #3671 + $newly_invalidated_methods[$referencing_method_id] = true; + $referencing_storage = null; + } + + if (isset($referencing_storage->used_traits[$unchanged_signature_classlike]) + || isset($referencing_storage->parent_classes[$unchanged_signature_classlike]) + ) { + $newly_invalidated_methods[$referencing_method_id] = true; + } + } + } + } + } + } + } + + foreach ($changed_members as $file_changed_members) { + foreach ($file_changed_members as $member_id => $_) { + $newly_invalidated_methods[$member_id] = true; + + if (isset($all_referencing_methods[$member_id])) { + $newly_invalidated_methods = array_merge( + $all_referencing_methods[$member_id], + $newly_invalidated_methods + ); + } + + unset( + $method_references_to_class_members[$member_id], + $file_references_to_class_members[$member_id], + $method_references_to_missing_class_members[$member_id], + $file_references_to_missing_class_members[$member_id], + $references_to_mixed_member_names[$member_id], + $method_param_uses[$member_id] + ); + + $member_stub = preg_replace('/::.*$/', '::*', $member_id); + + if (isset($all_referencing_methods[$member_stub])) { + $newly_invalidated_methods = array_merge( + $all_referencing_methods[$member_stub], + $newly_invalidated_methods + ); + } + } + } + + foreach ($newly_invalidated_methods as $method_id => $_) { + foreach ($method_references_to_class_members as $i => $_) { + unset($method_references_to_class_members[$i][$method_id]); + } + + foreach ($method_references_to_classes as $i => $_) { + unset($method_references_to_classes[$i][$method_id]); + } + + foreach ($method_references_to_missing_class_members as $i => $_) { + unset($method_references_to_missing_class_members[$i][$method_id]); + } + + foreach ($references_to_mixed_member_names as $i => $_) { + unset($references_to_mixed_member_names[$i][$method_id]); + } + + foreach ($method_param_uses as $i => $_) { + foreach ($method_param_uses[$i] as $j => $_) { + unset($method_param_uses[$i][$j][$method_id]); + } + } + } + + foreach ($this->analyzed_methods as $file_path => $analyzed_methods) { + foreach ($analyzed_methods as $correct_method_id => $_) { + $trait_safe_method_id = $correct_method_id; + + $correct_method_ids = explode('&', $correct_method_id); + + $correct_method_id = $correct_method_ids[0]; + + if (isset($newly_invalidated_methods[$correct_method_id]) + || (isset($correct_method_ids[1]) + && isset($newly_invalidated_methods[$correct_method_ids[1]])) + ) { + unset($this->analyzed_methods[$file_path][$trait_safe_method_id]); + } + } + } + + $this->shiftFileOffsets($diff_map); + + foreach ($this->files_to_analyze as $file_path) { + $file_reference_provider->clearExistingIssuesForFile($file_path); + $file_reference_provider->clearExistingFileMapsForFile($file_path); + + $this->setMixedCountsForFile($file_path, [0, 0]); + + foreach ($file_references_to_class_members as $i => $_) { + unset($file_references_to_class_members[$i][$file_path]); + } + + foreach ($nonmethod_references_to_classes as $i => $_) { + unset($nonmethod_references_to_classes[$i][$file_path]); + } + + foreach ($references_to_mixed_member_names as $i => $_) { + unset($references_to_mixed_member_names[$i][$file_path]); + } + + foreach ($file_references_to_missing_class_members as $i => $_) { + unset($file_references_to_missing_class_members[$i][$file_path]); + } + } + + foreach ($this->existing_issues as $file_path => $issues) { + if (!isset($this->files_to_analyze[$file_path])) { + unset($this->existing_issues[$file_path]); + + if ($this->file_provider->fileExists($file_path)) { + IssueBuffer::addIssues([$file_path => array_values($issues)]); + } + } + } + + $method_references_to_class_members = array_filter( + $method_references_to_class_members + ); + + $method_references_to_missing_class_members = array_filter( + $method_references_to_missing_class_members + ); + + $file_references_to_class_members = array_filter( + $file_references_to_class_members + ); + + $file_references_to_missing_class_members = array_filter( + $file_references_to_missing_class_members + ); + + $references_to_mixed_member_names = array_filter( + $references_to_mixed_member_names + ); + + $nonmethod_references_to_classes = array_filter( + $nonmethod_references_to_classes + ); + + $method_references_to_classes = array_filter( + $method_references_to_classes + ); + + $method_param_uses = array_filter( + $method_param_uses + ); + + $file_reference_provider->setCallingMethodReferencesToClassMembers( + $method_references_to_class_members + ); + + $file_reference_provider->setFileReferencesToClassMembers( + $file_references_to_class_members + ); + + $file_reference_provider->setCallingMethodReferencesToMissingClassMembers( + $method_references_to_missing_class_members + ); + + $file_reference_provider->setFileReferencesToMissingClassMembers( + $file_references_to_missing_class_members + ); + + $file_reference_provider->setReferencesToMixedMemberNames( + $references_to_mixed_member_names + ); + + $file_reference_provider->setCallingMethodReferencesToClasses( + $method_references_to_classes + ); + + $file_reference_provider->setNonMethodReferencesToClasses( + $nonmethod_references_to_classes + ); + + $file_reference_provider->setMethodParamUses( + $method_param_uses + ); + } + + /** + * @param array> $diff_map + * + */ + public function shiftFileOffsets(array $diff_map): void + { + foreach ($this->existing_issues as $file_path => &$file_issues) { + if (!isset($this->analyzed_methods[$file_path])) { + continue; + } + + $file_diff_map = $diff_map[$file_path] ?? []; + + if (!$file_diff_map) { + continue; + } + + $first_diff_offset = $file_diff_map[0][0]; + $last_diff_offset = $file_diff_map[count($file_diff_map) - 1][1]; + + foreach ($file_issues as $i => &$issue_data) { + if ($issue_data->to < $first_diff_offset || $issue_data->from > $last_diff_offset) { + unset($file_issues[$i]); + continue; + } + + $matched = false; + + foreach ($file_diff_map as [$from, $to, $file_offset, $line_offset]) { + if ($issue_data->from >= $from + && $issue_data->from <= $to + && !$matched + ) { + $issue_data->from += $file_offset; + $issue_data->to += $file_offset; + $issue_data->snippet_from += $file_offset; + $issue_data->snippet_to += $file_offset; + $issue_data->line_from += $line_offset; + $issue_data->line_to += $line_offset; + $matched = true; + } + } + + if (!$matched) { + unset($file_issues[$i]); + } + } + } + + foreach ($this->reference_map as $file_path => &$reference_map) { + if (!isset($this->analyzed_methods[$file_path])) { + unset($this->reference_map[$file_path]); + continue; + } + + $file_diff_map = $diff_map[$file_path] ?? []; + + if (!$file_diff_map) { + continue; + } + + $first_diff_offset = $file_diff_map[0][0]; + $last_diff_offset = $file_diff_map[count($file_diff_map) - 1][1]; + + foreach ($reference_map as $reference_from => [$reference_to, $tag]) { + if ($reference_to < $first_diff_offset || $reference_from > $last_diff_offset) { + continue; + } + + foreach ($file_diff_map as [$from, $to, $file_offset]) { + if ($reference_from >= $from && $reference_from <= $to) { + unset($reference_map[$reference_from]); + $reference_map[$reference_from += $file_offset] = [ + $reference_to += $file_offset, + $tag, + ]; + } + } + } + } + + foreach ($this->type_map as $file_path => &$type_map) { + if (!isset($this->analyzed_methods[$file_path])) { + unset($this->type_map[$file_path]); + continue; + } + + $file_diff_map = $diff_map[$file_path] ?? []; + + if (!$file_diff_map) { + continue; + } + + $first_diff_offset = $file_diff_map[0][0]; + $last_diff_offset = $file_diff_map[count($file_diff_map) - 1][1]; + + foreach ($type_map as $type_from => [$type_to, $tag]) { + if ($type_to < $first_diff_offset || $type_from > $last_diff_offset) { + continue; + } + + foreach ($file_diff_map as [$from, $to, $file_offset]) { + if ($type_from >= $from && $type_from <= $to) { + unset($type_map[$type_from]); + $type_map[$type_from += $file_offset] = [ + $type_to += $file_offset, + $tag, + ]; + } + } + } + } + + foreach ($this->argument_map as $file_path => &$argument_map) { + if (!isset($this->analyzed_methods[$file_path])) { + unset($this->argument_map[$file_path]); + continue; + } + + $file_diff_map = $diff_map[$file_path] ?? []; + + if (!$file_diff_map) { + continue; + } + + $first_diff_offset = $file_diff_map[0][0]; + $last_diff_offset = $file_diff_map[count($file_diff_map) - 1][1]; + + foreach ($argument_map as $argument_from => [$argument_to, $method_id, $argument_number]) { + if ($argument_to < $first_diff_offset || $argument_from > $last_diff_offset) { + continue; + } + + foreach ($file_diff_map as [$from, $to, $file_offset]) { + if ($argument_from >= $from && $argument_from <= $to) { + unset($argument_map[$argument_from]); + $argument_map[$argument_from += $file_offset] = [ + $argument_to += $file_offset, + $method_id, + $argument_number, + ]; + } + } + } + } + } + + /** + * @return array> + */ + public function getMixedMemberNames() : array + { + return $this->mixed_member_names; + } + + public function addMixedMemberName(string $member_id, string $reference): void + { + $this->mixed_member_names[$member_id][$reference] = true; + } + + public function hasMixedMemberName(string $member_id) : bool + { + return isset($this->mixed_member_names[$member_id]); + } + + /** + * @param array> $names + * + */ + public function addMixedMemberNames(array $names): void + { + foreach ($names as $key => $name) { + if (isset($this->mixed_member_names[$key])) { + $this->mixed_member_names[$key] = array_merge( + $this->mixed_member_names[$key], + $name + ); + } else { + $this->mixed_member_names[$key] = $name; + } + } + } + + /** + * @return array{0:int, 1:int} + */ + public function getMixedCountsForFile(string $file_path): array + { + if (!isset($this->mixed_counts[$file_path])) { + $this->mixed_counts[$file_path] = [0, 0]; + } + + return $this->mixed_counts[$file_path]; + } + + /** + * @param array{0:int, 1:int} $mixed_counts + * + */ + public function setMixedCountsForFile(string $file_path, array $mixed_counts): void + { + $this->mixed_counts[$file_path] = $mixed_counts; + } + + public function incrementMixedCount(string $file_path): void + { + if (!$this->count_mixed) { + return; + } + + if (!isset($this->mixed_counts[$file_path])) { + $this->mixed_counts[$file_path] = [0, 0]; + } + + ++$this->mixed_counts[$file_path][0]; + } + + public function decrementMixedCount(string $file_path): void + { + if (!$this->count_mixed) { + return; + } + + if (!isset($this->mixed_counts[$file_path])) { + return; + } + + --$this->mixed_counts[$file_path][0]; + } + + public function incrementNonMixedCount(string $file_path): void + { + if (!$this->count_mixed) { + return; + } + + if (!isset($this->mixed_counts[$file_path])) { + $this->mixed_counts[$file_path] = [0, 0]; + } + + ++$this->mixed_counts[$file_path][1]; + } + + /** + * @return array + */ + public function getMixedCounts(): array + { + $all_deep_scanned_files = []; + + foreach ($this->files_to_analyze as $file_path => $_) { + $all_deep_scanned_files[$file_path] = true; + } + + return array_intersect_key($this->mixed_counts, $all_deep_scanned_files); + } + + /** + * @return array + */ + public function getFunctionTimings(): array + { + return $this->function_timings; + } + + public function addFunctionTiming(string $function_id, float $time_per_node) : void + { + $this->function_timings[$function_id] = $time_per_node; + } + + public function addNodeType( + string $file_path, + PhpParser\Node $node, + string $node_type, + PhpParser\Node $parent_node = null + ): void { + if (!$node_type) { + throw new \UnexpectedValueException('non-empty node_type expected'); + } + + $this->type_map[$file_path][(int)$node->getAttribute('startFilePos')] = [ + ($parent_node ? (int)$parent_node->getAttribute('endFilePos') : (int)$node->getAttribute('endFilePos')) + 1, + $node_type, + ]; + } + + public function addNodeArgument( + string $file_path, + int $start_position, + int $end_position, + string $reference, + int $argument_number + ): void { + if (!$reference) { + throw new \UnexpectedValueException('non-empty node_type expected'); + } + + $this->argument_map[$file_path][$start_position] = [ + $end_position, + $reference, + $argument_number, + ]; + } + + public function addNodeReference(string $file_path, PhpParser\Node $node, string $reference): void + { + if (!$reference) { + throw new \UnexpectedValueException('non-empty node_type expected'); + } + + $this->reference_map[$file_path][(int)$node->getAttribute('startFilePos')] = [ + (int)$node->getAttribute('endFilePos') + 1, + $reference, + ]; + } + + public function addOffsetReference(string $file_path, int $start, int $end, string $reference): void + { + if (!$reference) { + throw new \UnexpectedValueException('non-empty node_type expected'); + } + + $this->reference_map[$file_path][$start] = [ + $end, + $reference, + ]; + } + + /** + * @return array{int, int} + */ + public function getTotalTypeCoverage(\Psalm\Codebase $codebase): array + { + $mixed_count = 0; + $nonmixed_count = 0; + + foreach ($codebase->file_reference_provider->getTypeCoverage() as $file_path => $counts) { + if (!$this->config->reportTypeStatsForFile($file_path)) { + continue; + } + + [$path_mixed_count, $path_nonmixed_count] = $counts; + + if (isset($this->mixed_counts[$file_path])) { + $mixed_count += $path_mixed_count; + $nonmixed_count += $path_nonmixed_count; + } + } + + return [$mixed_count, $nonmixed_count]; + } + + public function getTypeInferenceSummary(\Psalm\Codebase $codebase): string + { + $all_deep_scanned_files = []; + + foreach ($this->files_to_analyze as $file_path => $_) { + $all_deep_scanned_files[$file_path] = true; + + foreach ($this->file_storage_provider->get($file_path)->required_file_paths as $required_file_path) { + $all_deep_scanned_files[$required_file_path] = true; + } + } + + [$mixed_count, $nonmixed_count] = $this->getTotalTypeCoverage($codebase); + + $total = $mixed_count + $nonmixed_count; + + $total_files = count($all_deep_scanned_files); + + if (!$total_files) { + return 'No files analyzed'; + } + + if (!$total) { + return 'Psalm was unable to infer types in the codebase'; + } + + $percentage = $nonmixed_count === $total ? '100' : number_format(100 * $nonmixed_count / $total, 4); + + return 'Psalm was able to infer types for ' . $percentage . '%' + . ' of the codebase'; + } + + public function getNonMixedStats(): string + { + $stats = ''; + + $all_deep_scanned_files = []; + + foreach ($this->files_to_analyze as $file_path => $_) { + $all_deep_scanned_files[$file_path] = true; + + if (!$this->config->reportTypeStatsForFile($file_path)) { + continue; + } + + foreach ($this->file_storage_provider->get($file_path)->required_file_paths as $required_file_path) { + $all_deep_scanned_files[$required_file_path] = true; + } + } + + foreach ($all_deep_scanned_files as $file_path => $_) { + if (isset($this->mixed_counts[$file_path])) { + [$path_mixed_count, $path_nonmixed_count] = $this->mixed_counts[$file_path]; + + if ($path_mixed_count + $path_nonmixed_count) { + $stats .= number_format(100 * $path_nonmixed_count / ($path_mixed_count + $path_nonmixed_count), 0) + . '% ' . $this->config->shortenFileName($file_path) + . ' (' . $path_mixed_count . ' mixed)' . "\n"; + } + } + } + + return $stats; + } + + public function disableMixedCounts(): void + { + $this->count_mixed = false; + } + + public function enableMixedCounts(): void + { + $this->count_mixed = true; + } + + public function updateFile(string $file_path, bool $dry_run): void + { + FileManipulationBuffer::add( + $file_path, + FunctionDocblockManipulator::getManipulationsForFile($file_path) + ); + + FileManipulationBuffer::add( + $file_path, + PropertyDocblockManipulator::getManipulationsForFile($file_path) + ); + + FileManipulationBuffer::add( + $file_path, + ClassDocblockManipulator::getManipulationsForFile($file_path) + ); + + $file_manipulations = FileManipulationBuffer::getManipulationsForFile($file_path); + + if (!$file_manipulations) { + return; + } + + usort( + $file_manipulations, + function (FileManipulation $a, FileManipulation $b): int { + if ($b->end === $a->end) { + if ($a->start === $b->start) { + return $b->insertion_text > $a->insertion_text ? 1 : -1; + } + + return $b->start > $a->start ? 1 : -1; + } + + return $b->end > $a->end ? 1 : -1; + } + ); + + $last_start = \PHP_INT_MAX; + $existing_contents = $this->file_provider->getContents($file_path); + + foreach ($file_manipulations as $manipulation) { + if ($manipulation->start <= $last_start) { + $existing_contents = $manipulation->transform($existing_contents); + $last_start = $manipulation->start; + } + } + + if ($dry_run) { + echo $file_path . ':' . "\n"; + + $differ = new \SebastianBergmann\Diff\Differ( + new \SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder([ + 'fromFile' => $file_path, + 'toFile' => $file_path, + ]) + ); + + echo $differ->diff($this->file_provider->getContents($file_path), $existing_contents); + + return; + } + + $this->progress->alterFileDone($file_path); + + $this->file_provider->setContents($file_path, $existing_contents); + } + + /** + * @return list + */ + public function getExistingIssuesForFile(string $file_path, int $start, int $end, ?string $issue_type = null): array + { + if (!isset($this->existing_issues[$file_path])) { + return []; + } + + $applicable_issues = []; + + foreach ($this->existing_issues[$file_path] as $issue_data) { + if ($issue_data->from >= $start && $issue_data->from <= $end) { + if ($issue_type === null || $issue_type === $issue_data->type) { + $applicable_issues[] = $issue_data; + } + } + } + + return $applicable_issues; + } + + public function removeExistingDataForFile(string $file_path, int $start, int $end, ?string $issue_type = null): void + { + if (isset($this->existing_issues[$file_path])) { + foreach ($this->existing_issues[$file_path] as $i => $issue_data) { + if ($issue_data->from >= $start && $issue_data->from <= $end) { + if ($issue_type === null || $issue_type === $issue_data->type) { + unset($this->existing_issues[$file_path][$i]); + } + } + } + } + + if (isset($this->type_map[$file_path])) { + foreach ($this->type_map[$file_path] as $map_start => $_) { + if ($map_start >= $start && $map_start <= $end) { + unset($this->type_map[$file_path][$map_start]); + } + } + } + + if (isset($this->reference_map[$file_path])) { + foreach ($this->reference_map[$file_path] as $map_start => $_) { + if ($map_start >= $start && $map_start <= $end) { + unset($this->reference_map[$file_path][$map_start]); + } + } + } + + if (isset($this->argument_map[$file_path])) { + foreach ($this->argument_map[$file_path] as $map_start => $_) { + if ($map_start >= $start && $map_start <= $end) { + unset($this->argument_map[$file_path][$map_start]); + } + } + } + } + + /** + * @return array> + */ + public function getAnalyzedMethods(): array + { + return $this->analyzed_methods; + } + + /** + * @return array + */ + public function getFileMaps(): array + { + $file_maps = []; + + foreach ($this->reference_map as $file_path => $reference_map) { + $file_maps[$file_path] = [$reference_map, [], []]; + } + + foreach ($this->type_map as $file_path => $type_map) { + if (isset($file_maps[$file_path])) { + $file_maps[$file_path][1] = $type_map; + } else { + $file_maps[$file_path] = [[], $type_map, []]; + } + } + + foreach ($this->argument_map as $file_path => $argument_map) { + if (isset($file_maps[$file_path])) { + $file_maps[$file_path][2] = $argument_map; + } else { + $file_maps[$file_path] = [[], [], $argument_map]; + } + } + + return $file_maps; + } + + /** + * @return FileMapType + */ + public function getMapsForFile(string $file_path): array + { + return [ + $this->reference_map[$file_path] ?? [], + $this->type_map[$file_path] ?? [], + $this->argument_map[$file_path] ?? [], + ]; + } + + /** + * @return array> + */ + public function getPossibleMethodParamTypes(): array + { + return $this->possible_method_param_types; + } + + public function addMutableClass(string $fqcln) : void + { + $this->mutable_classes[\strtolower($fqcln)] = true; + } + + public function setAnalyzedMethod(string $file_path, string $method_id, bool $is_constructor = false): void + { + $this->analyzed_methods[$file_path][$method_id] = $is_constructor ? 2 : 1; + } + + public function isMethodAlreadyAnalyzed(string $file_path, string $method_id, bool $is_constructor = false): bool + { + if ($is_constructor) { + return isset($this->analyzed_methods[$file_path][$method_id]) + && $this->analyzed_methods[$file_path][$method_id] === 2; + } + + return isset($this->analyzed_methods[$file_path][$method_id]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/ClassLikes.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/ClassLikes.php new file mode 100644 index 0000000000000000000000000000000000000000..b838f1a92ce049f66943fc9eec720d64d8fd95a9 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/ClassLikes.php @@ -0,0 +1,2066 @@ + + */ + private $existing_classlikes_lc = []; + + /** + * @var array + */ + private $existing_classes_lc = []; + + /** + * @var array + */ + private $existing_classes = []; + + /** + * @var array + */ + private $existing_interfaces_lc = []; + + /** + * @var array + */ + private $existing_interfaces = []; + + /** + * @var array + */ + private $existing_traits_lc = []; + + /** + * @var array + */ + private $existing_traits = []; + + /** + * @var array + */ + private $classlike_aliases = []; + + /** + * @var array + */ + private $trait_nodes = []; + + /** + * @var bool + */ + public $collect_references = false; + + /** + * @var bool + */ + public $collect_locations = false; + + /** + * @var StatementsProvider + */ + private $statements_provider; + + /** + * @var Config + */ + private $config; + + /** + * @var Scanner + */ + private $scanner; + + public function __construct( + Config $config, + ClassLikeStorageProvider $storage_provider, + FileReferenceProvider $file_reference_provider, + StatementsProvider $statements_provider, + Scanner $scanner + ) { + $this->config = $config; + $this->classlike_storage_provider = $storage_provider; + $this->file_reference_provider = $file_reference_provider; + $this->statements_provider = $statements_provider; + $this->scanner = $scanner; + + $this->collectPredefinedClassLikes(); + } + + private function collectPredefinedClassLikes(): void + { + /** @var array */ + $predefined_classes = get_declared_classes(); + + foreach ($predefined_classes as $predefined_class) { + $predefined_class = preg_replace('/^\\\/', '', $predefined_class); + /** @psalm-suppress ArgumentTypeCoercion */ + $reflection_class = new \ReflectionClass($predefined_class); + + if (!$reflection_class->isUserDefined()) { + $predefined_class_lc = strtolower($predefined_class); + $this->existing_classlikes_lc[$predefined_class_lc] = true; + $this->existing_classes_lc[$predefined_class_lc] = true; + $this->existing_classes[$predefined_class] = true; + } + } + + /** @var array */ + $predefined_interfaces = get_declared_interfaces(); + + foreach ($predefined_interfaces as $predefined_interface) { + $predefined_interface = preg_replace('/^\\\/', '', $predefined_interface); + /** @psalm-suppress ArgumentTypeCoercion */ + $reflection_class = new \ReflectionClass($predefined_interface); + + if (!$reflection_class->isUserDefined()) { + $predefined_interface_lc = strtolower($predefined_interface); + $this->existing_classlikes_lc[$predefined_interface_lc] = true; + $this->existing_interfaces_lc[$predefined_interface_lc] = true; + $this->existing_interfaces[$predefined_interface] = true; + } + } + } + + public function addFullyQualifiedClassName(string $fq_class_name, ?string $file_path = null): void + { + $fq_class_name_lc = strtolower($fq_class_name); + $this->existing_classlikes_lc[$fq_class_name_lc] = true; + $this->existing_classes_lc[$fq_class_name_lc] = true; + $this->existing_traits_lc[$fq_class_name_lc] = false; + $this->existing_interfaces_lc[$fq_class_name_lc] = false; + $this->existing_classes[$fq_class_name] = true; + + if ($file_path) { + $this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path); + } + } + + public function addFullyQualifiedInterfaceName(string $fq_class_name, ?string $file_path = null): void + { + $fq_class_name_lc = strtolower($fq_class_name); + $this->existing_classlikes_lc[$fq_class_name_lc] = true; + $this->existing_interfaces_lc[$fq_class_name_lc] = true; + $this->existing_classes_lc[$fq_class_name_lc] = false; + $this->existing_traits_lc[$fq_class_name_lc] = false; + $this->existing_interfaces[$fq_class_name] = true; + + if ($file_path) { + $this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path); + } + } + + public function addFullyQualifiedTraitName(string $fq_class_name, ?string $file_path = null): void + { + $fq_class_name_lc = strtolower($fq_class_name); + $this->existing_classlikes_lc[$fq_class_name_lc] = true; + $this->existing_traits_lc[$fq_class_name_lc] = true; + $this->existing_classes_lc[$fq_class_name_lc] = false; + $this->existing_interfaces_lc[$fq_class_name_lc] = false; + $this->existing_traits[$fq_class_name] = true; + + if ($file_path) { + $this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path); + } + } + + public function addFullyQualifiedClassLikeName(string $fq_class_name_lc, ?string $file_path = null): void + { + if ($file_path) { + $this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path); + } + } + + /** + * @return list + */ + public function getMatchingClassLikeNames(string $stub) : array + { + $matching_classes = []; + + if ($stub[0] === '*') { + $stub = substr($stub, 1); + } + + $stub = strtolower($stub); + + foreach ($this->existing_classes as $fq_classlike_name => $found) { + if (!$found) { + continue; + } + + if (preg_match('@(^|\\\)' . $stub . '.*@i', $fq_classlike_name)) { + $matching_classes[] = $fq_classlike_name; + } + } + + foreach ($this->existing_interfaces as $fq_classlike_name => $found) { + if (!$found) { + continue; + } + + if (preg_match('@(^|\\\)' . $stub . '.*@i', $fq_classlike_name)) { + $matching_classes[] = $fq_classlike_name; + } + } + + return $matching_classes; + } + + public function hasFullyQualifiedClassName( + string $fq_class_name, + ?CodeLocation $code_location = null, + ?string $calling_fq_class_name = null, + ?string $calling_method_id = null + ): bool { + $fq_class_name_lc = strtolower($fq_class_name); + + if (isset($this->classlike_aliases[$fq_class_name_lc])) { + $fq_class_name_lc = strtolower($this->classlike_aliases[$fq_class_name_lc]); + } + + if ($code_location) { + if ($calling_method_id) { + $this->file_reference_provider->addMethodReferenceToClass( + $calling_method_id, + $fq_class_name_lc + ); + } elseif (!$calling_fq_class_name || strtolower($calling_fq_class_name) !== $fq_class_name_lc) { + $this->file_reference_provider->addNonMethodReferenceToClass( + $code_location->file_path, + $fq_class_name_lc + ); + + if ($calling_fq_class_name) { + $class_storage = $this->classlike_storage_provider->get($calling_fq_class_name); + + if ($class_storage->location + && $class_storage->location->file_path !== $code_location->file_path + ) { + $this->file_reference_provider->addNonMethodReferenceToClass( + $class_storage->location->file_path, + $fq_class_name_lc + ); + } + } + } + } + + if (!isset($this->existing_classes_lc[$fq_class_name_lc]) + || !$this->existing_classes_lc[$fq_class_name_lc] + || !$this->classlike_storage_provider->has($fq_class_name_lc) + ) { + if (( + !isset($this->existing_classes_lc[$fq_class_name_lc]) + || $this->existing_classes_lc[$fq_class_name_lc] + ) + && !$this->classlike_storage_provider->has($fq_class_name_lc) + ) { + if (!isset($this->existing_classes_lc[$fq_class_name_lc])) { + $this->existing_classes_lc[$fq_class_name_lc] = false; + + return false; + } + + return $this->existing_classes_lc[$fq_class_name_lc]; + } + + return false; + } + + if ($this->collect_locations && $code_location) { + $this->file_reference_provider->addCallingLocationForClass( + $code_location, + strtolower($fq_class_name) + ); + } + + return true; + } + + public function hasFullyQualifiedInterfaceName( + string $fq_class_name, + ?CodeLocation $code_location = null, + ?string $calling_fq_class_name = null, + ?string $calling_method_id = null + ): bool { + $fq_class_name_lc = strtolower($fq_class_name); + + if (isset($this->classlike_aliases[$fq_class_name_lc])) { + $fq_class_name_lc = strtolower($this->classlike_aliases[$fq_class_name_lc]); + } + + if (!isset($this->existing_interfaces_lc[$fq_class_name_lc]) + || !$this->existing_interfaces_lc[$fq_class_name_lc] + || !$this->classlike_storage_provider->has($fq_class_name_lc) + ) { + if (( + !isset($this->existing_classes_lc[$fq_class_name_lc]) + || $this->existing_classes_lc[$fq_class_name_lc] + ) + && !$this->classlike_storage_provider->has($fq_class_name_lc) + ) { + if (!isset($this->existing_interfaces_lc[$fq_class_name_lc])) { + $this->existing_interfaces_lc[$fq_class_name_lc] = false; + + return false; + } + + return $this->existing_interfaces_lc[$fq_class_name_lc]; + } + + return false; + } + + if ($this->collect_references && $code_location) { + if ($calling_method_id) { + $this->file_reference_provider->addMethodReferenceToClass( + $calling_method_id, + $fq_class_name_lc + ); + } else { + $this->file_reference_provider->addNonMethodReferenceToClass( + $code_location->file_path, + $fq_class_name_lc + ); + + if ($calling_fq_class_name) { + $class_storage = $this->classlike_storage_provider->get($calling_fq_class_name); + + if ($class_storage->location + && $class_storage->location->file_path !== $code_location->file_path + ) { + $this->file_reference_provider->addNonMethodReferenceToClass( + $class_storage->location->file_path, + $fq_class_name_lc + ); + } + } + } + } + + if ($this->collect_locations && $code_location) { + $this->file_reference_provider->addCallingLocationForClass( + $code_location, + strtolower($fq_class_name) + ); + } + + return true; + } + + public function hasFullyQualifiedTraitName(string $fq_class_name, ?CodeLocation $code_location = null): bool + { + $fq_class_name_lc = strtolower($fq_class_name); + + if (isset($this->classlike_aliases[$fq_class_name_lc])) { + $fq_class_name_lc = strtolower($this->classlike_aliases[$fq_class_name_lc]); + } + + if (!isset($this->existing_traits_lc[$fq_class_name_lc]) || + !$this->existing_traits_lc[$fq_class_name_lc] + ) { + return false; + } + + if ($this->collect_references && $code_location) { + $this->file_reference_provider->addNonMethodReferenceToClass( + $code_location->file_path, + $fq_class_name_lc + ); + } + + return true; + } + + /** + * Check whether a class/interface exists + */ + public function classOrInterfaceExists( + string $fq_class_name, + ?CodeLocation $code_location = null, + ?string $calling_fq_class_name = null, + ?string $calling_method_id = null + ): bool { + if (!$this->classExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id) + && !$this->interfaceExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id) + ) { + return false; + } + + return true; + } + + /** + * Determine whether or not a given class exists + */ + public function classExists( + string $fq_class_name, + ?CodeLocation $code_location = null, + ?string $calling_fq_class_name = null, + ?string $calling_method_id = null + ): bool { + if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[$fq_class_name])) { + return false; + } + + if ($fq_class_name === 'Generator') { + return true; + } + + return $this->hasFullyQualifiedClassName( + $fq_class_name, + $code_location, + $calling_fq_class_name, + $calling_method_id + ); + } + + /** + * Determine whether or not a class extends a parent + * + * @throws UnpopulatedClasslikeException when called on unpopulated class + * @throws \InvalidArgumentException when class does not exist + */ + public function classExtends(string $fq_class_name, string $possible_parent, bool $from_api = false): bool + { + $fq_class_name_lc = strtolower($fq_class_name); + + if ($fq_class_name_lc === 'generator') { + return false; + } + + $fq_class_name = $this->classlike_aliases[$fq_class_name_lc] ?? $fq_class_name; + + $class_storage = $this->classlike_storage_provider->get($fq_class_name_lc); + + if ($from_api && !$class_storage->populated) { + throw new UnpopulatedClasslikeException($fq_class_name); + } + + return isset($class_storage->parent_classes[strtolower($possible_parent)]); + } + + /** + * Check whether a class implements an interface + */ + public function classImplements(string $fq_class_name, string $interface): bool + { + $interface_id = strtolower($interface); + + $fq_class_name = strtolower($fq_class_name); + + if ($interface_id === 'callable' && $fq_class_name === 'closure') { + return true; + } + + if ($interface_id === 'traversable' && $fq_class_name === 'generator') { + return true; + } + + if ($interface_id === 'traversable' && $fq_class_name === 'iterator') { + return true; + } + + if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[$interface_id]) + || isset(ClassLikeAnalyzer::SPECIAL_TYPES[$fq_class_name]) + ) { + return false; + } + + if (isset($this->classlike_aliases[$fq_class_name])) { + $fq_class_name = $this->classlike_aliases[$fq_class_name]; + } + + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + + return isset($class_storage->class_implements[$interface_id]); + } + + public function interfaceExists( + string $fq_interface_name, + ?CodeLocation $code_location = null, + ?string $calling_fq_class_name = null, + ?string $calling_method_id = null + ): bool { + if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[strtolower($fq_interface_name)])) { + return false; + } + + return $this->hasFullyQualifiedInterfaceName( + $fq_interface_name, + $code_location, + $calling_fq_class_name, + $calling_method_id + ); + } + + public function interfaceExtends(string $interface_name, string $possible_parent): bool + { + return isset($this->getParentInterfaces($interface_name)[strtolower($possible_parent)]); + } + + /** + * @return array all interfaces extended by $interface_name + */ + public function getParentInterfaces(string $fq_interface_name): array + { + $fq_interface_name = strtolower($fq_interface_name); + + $storage = $this->classlike_storage_provider->get($fq_interface_name); + + return $storage->parent_interfaces; + } + + public function traitExists(string $fq_trait_name, ?CodeLocation $code_location = null): bool + { + return $this->hasFullyQualifiedTraitName($fq_trait_name, $code_location); + } + + /** + * Determine whether or not a class has the correct casing + */ + public function classHasCorrectCasing(string $fq_class_name): bool + { + if ($fq_class_name === 'Generator') { + return true; + } + + if (isset($this->classlike_aliases[strtolower($fq_class_name)])) { + return true; + } + + return isset($this->existing_classes[$fq_class_name]); + } + + public function interfaceHasCorrectCasing(string $fq_interface_name): bool + { + if (isset($this->classlike_aliases[strtolower($fq_interface_name)])) { + return true; + } + + return isset($this->existing_interfaces[$fq_interface_name]); + } + + public function traitHasCorrectCase(string $fq_trait_name): bool + { + if (isset($this->classlike_aliases[strtolower($fq_trait_name)])) { + return true; + } + + return isset($this->existing_traits[$fq_trait_name]); + } + + /** + * @param lowercase-string $fq_class_name + */ + public function isUserDefined(string $fq_class_name): bool + { + return $this->classlike_storage_provider->get($fq_class_name)->user_defined; + } + + public function getTraitNode(string $fq_trait_name): PhpParser\Node\Stmt\Trait_ + { + $fq_trait_name_lc = strtolower($fq_trait_name); + + if (isset($this->trait_nodes[$fq_trait_name_lc])) { + return $this->trait_nodes[$fq_trait_name_lc]; + } + + $storage = $this->classlike_storage_provider->get($fq_trait_name); + + if (!$storage->location) { + throw new \UnexpectedValueException('Storage should exist for ' . $fq_trait_name); + } + + $file_statements = $this->statements_provider->getStatementsForFile($storage->location->file_path, '7.4'); + + $trait_finder = new \Psalm\Internal\PhpVisitor\TraitFinder($fq_trait_name); + + $traverser = new \PhpParser\NodeTraverser(); + $traverser->addVisitor( + $trait_finder + ); + + $traverser->traverse($file_statements); + + $trait_node = $trait_finder->getNode(); + + if ($trait_node) { + $this->trait_nodes[$fq_trait_name_lc] = $trait_node; + + return $trait_node; + } + + throw new \UnexpectedValueException('Could not locate trait statement'); + } + + /** + * @param lowercase-string $alias_name + */ + public function addClassAlias(string $fq_class_name, string $alias_name): void + { + $this->classlike_aliases[$alias_name] = $fq_class_name; + } + + public function getUnAliasedName(string $alias_name): string + { + $alias_name_lc = strtolower($alias_name); + if ($this->existing_classlikes_lc[$alias_name_lc] ?? false) { + return $alias_name; + } + + return $this->classlike_aliases[$alias_name_lc] ?? $alias_name; + } + + public function consolidateAnalyzedData(Methods $methods, ?Progress $progress, bool $find_unused_code): void + { + if ($progress === null) { + $progress = new VoidProgress(); + } + + $progress->debug('Checking class references' . PHP_EOL); + + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + + foreach ($this->existing_classlikes_lc as $fq_class_name_lc => $_) { + try { + $classlike_storage = $this->classlike_storage_provider->get($fq_class_name_lc); + } catch (\InvalidArgumentException $e) { + continue; + } + + if ($classlike_storage->location + && $this->config->isInProjectDirs($classlike_storage->location->file_path) + && !$classlike_storage->is_trait + ) { + if ($find_unused_code) { + if (!$this->file_reference_provider->isClassReferenced($fq_class_name_lc)) { + if (IssueBuffer::accepts( + new UnusedClass( + 'Class ' . $classlike_storage->name . ' is never used', + $classlike_storage->location, + $classlike_storage->name + ), + $classlike_storage->suppressed_issues + )) { + // fall through + } + } else { + $this->checkMethodReferences($classlike_storage, $methods); + $this->checkPropertyReferences($classlike_storage); + } + } + + $this->findPossibleMethodParamTypes($classlike_storage); + + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['MissingImmutableAnnotation']) + && !isset($codebase->analyzer->mutable_classes[$fq_class_name_lc]) + && !$classlike_storage->external_mutation_free + && $classlike_storage->properties + && isset($classlike_storage->methods['__construct']) + ) { + $stmts = $codebase->getStatementsForFile( + $classlike_storage->location->file_path + ); + + foreach ($stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\Namespace_) { + foreach ($stmt->stmts as $namespace_stmt) { + if ($namespace_stmt instanceof PhpParser\Node\Stmt\Class_ + && \strtolower((string) $stmt->name . '\\' . (string) $namespace_stmt->name) + === $fq_class_name_lc + ) { + self::makeImmutable( + $namespace_stmt, + $project_analyzer, + $classlike_storage->location->file_path + ); + } + } + } elseif ($stmt instanceof PhpParser\Node\Stmt\Class_ + && \strtolower((string) $stmt->name) === $fq_class_name_lc + ) { + self::makeImmutable( + $stmt, + $project_analyzer, + $classlike_storage->location->file_path + ); + } + } + } + } + } + } + + public static function makeImmutable( + PhpParser\Node\Stmt\Class_ $class_stmt, + \Psalm\Internal\Analyzer\ProjectAnalyzer $project_analyzer, + string $file_path + ) : void { + $manipulator = ClassDocblockManipulator::getForClass( + $project_analyzer, + $file_path, + $class_stmt + ); + + $manipulator->makeImmutable(); + } + + public function moveMethods(Methods $methods, ?Progress $progress = null): void + { + if ($progress === null) { + $progress = new VoidProgress(); + } + + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + + if (!$codebase->methods_to_move) { + return; + } + + $progress->debug('Refactoring methods ' . PHP_EOL); + + $code_migrations = []; + + foreach ($codebase->methods_to_move as $source => $destination) { + $source_parts = explode('::', $source); + + try { + $source_method_storage = $methods->getStorage( + new \Psalm\Internal\MethodIdentifier(...$source_parts) + ); + } catch (\InvalidArgumentException $e) { + continue; + } + + [$destination_fq_class_name, $destination_name] = explode('::', $destination); + + try { + $classlike_storage = $this->classlike_storage_provider->get($destination_fq_class_name); + } catch (\InvalidArgumentException $e) { + continue; + } + + if ($classlike_storage->stmt_location + && $this->config->isInProjectDirs($classlike_storage->stmt_location->file_path) + && $source_method_storage->stmt_location + && $source_method_storage->stmt_location->file_path + && $source_method_storage->location + ) { + $new_class_bounds = $classlike_storage->stmt_location->getSnippetBounds(); + $old_method_bounds = $source_method_storage->stmt_location->getSnippetBounds(); + + $old_method_name_bounds = $source_method_storage->location->getSelectionBounds(); + + FileManipulationBuffer::add( + $source_method_storage->stmt_location->file_path, + [ + new \Psalm\FileManipulation( + $old_method_name_bounds[0], + $old_method_name_bounds[1], + $destination_name + ), + ] + ); + + $selection = $classlike_storage->stmt_location->getSnippet(); + + $insert_pos = strrpos($selection, "\n", -1); + + if (!$insert_pos) { + $insert_pos = strlen($selection) - 1; + } else { + ++$insert_pos; + } + + $code_migrations[] = new \Psalm\Internal\FileManipulation\CodeMigration( + $source_method_storage->stmt_location->file_path, + $old_method_bounds[0], + $old_method_bounds[1], + $classlike_storage->stmt_location->file_path, + $new_class_bounds[0] + $insert_pos + ); + } + } + + FileManipulationBuffer::addCodeMigrations($code_migrations); + } + + public function moveProperties(Properties $properties, ?Progress $progress = null): void + { + if ($progress === null) { + $progress = new VoidProgress(); + } + + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + + if (!$codebase->properties_to_move) { + return; + } + + $progress->debug('Refacting properties ' . PHP_EOL); + + $code_migrations = []; + + foreach ($codebase->properties_to_move as $source => $destination) { + try { + $source_property_storage = $properties->getStorage($source); + } catch (\InvalidArgumentException $e) { + continue; + } + + [$source_fq_class_name] = explode('::$', $source); + [$destination_fq_class_name, $destination_name] = explode('::$', $destination); + + $source_classlike_storage = $this->classlike_storage_provider->get($source_fq_class_name); + $destination_classlike_storage = $this->classlike_storage_provider->get($destination_fq_class_name); + + if ($destination_classlike_storage->stmt_location + && $this->config->isInProjectDirs($destination_classlike_storage->stmt_location->file_path) + && $source_property_storage->stmt_location + && $source_property_storage->stmt_location->file_path + && $source_property_storage->location + ) { + if ($source_property_storage->type + && $source_property_storage->type_location + && $source_property_storage->type_location !== $source_property_storage->signature_type_location + ) { + $bounds = $source_property_storage->type_location->getSelectionBounds(); + + $replace_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $source_property_storage->type, + $source_classlike_storage->name, + $source_classlike_storage->name, + $source_classlike_storage->parent_class + ); + + $this->airliftClassDefinedDocblockType( + $replace_type, + $destination_fq_class_name, + $source_property_storage->stmt_location->file_path, + $bounds[0], + $bounds[1] + ); + } + + $new_class_bounds = $destination_classlike_storage->stmt_location->getSnippetBounds(); + $old_property_bounds = $source_property_storage->stmt_location->getSnippetBounds(); + + $old_property_name_bounds = $source_property_storage->location->getSelectionBounds(); + + FileManipulationBuffer::add( + $source_property_storage->stmt_location->file_path, + [ + new \Psalm\FileManipulation( + $old_property_name_bounds[0], + $old_property_name_bounds[1], + '$' . $destination_name + ), + ] + ); + + $selection = $destination_classlike_storage->stmt_location->getSnippet(); + + $insert_pos = strrpos($selection, "\n", -1); + + if (!$insert_pos) { + $insert_pos = strlen($selection) - 1; + } else { + ++$insert_pos; + } + + $code_migrations[] = new \Psalm\Internal\FileManipulation\CodeMigration( + $source_property_storage->stmt_location->file_path, + $old_property_bounds[0], + $old_property_bounds[1], + $destination_classlike_storage->stmt_location->file_path, + $new_class_bounds[0] + $insert_pos + ); + } + } + + FileManipulationBuffer::addCodeMigrations($code_migrations); + } + + public function moveClassConstants(?Progress $progress = null): void + { + if ($progress === null) { + $progress = new VoidProgress(); + } + + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + + if (!$codebase->class_constants_to_move) { + return; + } + + $progress->debug('Refacting constants ' . PHP_EOL); + + $code_migrations = []; + + foreach ($codebase->class_constants_to_move as $source => $destination) { + [$source_fq_class_name, $source_const_name] = explode('::', $source); + [$destination_fq_class_name, $destination_name] = explode('::', $destination); + + $source_classlike_storage = $this->classlike_storage_provider->get($source_fq_class_name); + $destination_classlike_storage = $this->classlike_storage_provider->get($destination_fq_class_name); + + $constant_storage = $source_classlike_storage->constants[$source_const_name]; + + $source_const_stmt_location = $constant_storage->stmt_location; + $source_const_location = $constant_storage->location; + + if (!$source_const_location || !$source_const_stmt_location) { + continue; + } + + if ($destination_classlike_storage->stmt_location + && $this->config->isInProjectDirs($destination_classlike_storage->stmt_location->file_path) + && $source_const_stmt_location->file_path + ) { + $new_class_bounds = $destination_classlike_storage->stmt_location->getSnippetBounds(); + $old_const_bounds = $source_const_stmt_location->getSnippetBounds(); + + $old_const_name_bounds = $source_const_location->getSelectionBounds(); + + FileManipulationBuffer::add( + $source_const_stmt_location->file_path, + [ + new \Psalm\FileManipulation( + $old_const_name_bounds[0], + $old_const_name_bounds[1], + $destination_name + ), + ] + ); + + $selection = $destination_classlike_storage->stmt_location->getSnippet(); + + $insert_pos = strrpos($selection, "\n", -1); + + if (!$insert_pos) { + $insert_pos = strlen($selection) - 1; + } else { + ++$insert_pos; + } + + $code_migrations[] = new \Psalm\Internal\FileManipulation\CodeMigration( + $source_const_stmt_location->file_path, + $old_const_bounds[0], + $old_const_bounds[1], + $destination_classlike_storage->stmt_location->file_path, + $new_class_bounds[0] + $insert_pos + ); + } + } + + FileManipulationBuffer::addCodeMigrations($code_migrations); + } + + /** + * @param lowercase-string|null $calling_method_id + */ + public function handleClassLikeReferenceInMigration( + \Psalm\Codebase $codebase, + \Psalm\StatementsSource $source, + PhpParser\Node $class_name_node, + string $fq_class_name, + ?string $calling_method_id, + bool $force_change = false, + bool $was_self = false + ) : bool { + $calling_fq_class_name = $source->getFQCLN(); + + // if we're inside a moved class static method + if ($codebase->methods_to_move + && $calling_fq_class_name + && $calling_method_id + && isset($codebase->methods_to_move[$calling_method_id]) + ) { + $destination_class = explode('::', $codebase->methods_to_move[$calling_method_id])[0]; + + $intended_fq_class_name = strtolower($calling_fq_class_name) === strtolower($fq_class_name) + && isset($codebase->classes_to_move[strtolower($calling_fq_class_name)]) + ? $destination_class + : $fq_class_name; + + $this->airliftClassLikeReference( + $intended_fq_class_name, + $destination_class, + $source->getFilePath(), + (int) $class_name_node->getAttribute('startFilePos'), + (int) $class_name_node->getAttribute('endFilePos') + 1, + $class_name_node instanceof PhpParser\Node\Scalar\MagicConst\Class_, + $was_self + ); + + return true; + } + + // if we're outside a moved class, but we're changing all references to a class + if (isset($codebase->class_transforms[strtolower($fq_class_name)])) { + $new_fq_class_name = $codebase->class_transforms[strtolower($fq_class_name)]; + $file_manipulations = []; + + if ($class_name_node instanceof PhpParser\Node\Identifier) { + $destination_parts = explode('\\', $new_fq_class_name); + + $destination_class_name = array_pop($destination_parts); + $file_manipulations = []; + + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $class_name_node->getAttribute('startFilePos'), + (int) $class_name_node->getAttribute('endFilePos') + 1, + $destination_class_name + ); + + FileManipulationBuffer::add($source->getFilePath(), $file_manipulations); + + return true; + } + + $uses_flipped = $source->getAliasedClassesFlipped(); + $uses_flipped_replaceable = $source->getAliasedClassesFlippedReplaceable(); + + $old_fq_class_name = strtolower($fq_class_name); + + $migrated_source_fqcln = $calling_fq_class_name; + + if ($calling_fq_class_name + && isset($codebase->class_transforms[strtolower($calling_fq_class_name)]) + ) { + $migrated_source_fqcln = $codebase->class_transforms[strtolower($calling_fq_class_name)]; + } + + $source_namespace = $source->getNamespace(); + + if ($migrated_source_fqcln && $calling_fq_class_name !== $migrated_source_fqcln) { + $new_source_parts = explode('\\', $migrated_source_fqcln, -1); + $source_namespace = implode('\\', $new_source_parts); + } + + if (isset($uses_flipped_replaceable[$old_fq_class_name])) { + $alias = $uses_flipped_replaceable[$old_fq_class_name]; + unset($uses_flipped[$old_fq_class_name]); + $old_class_name_parts = explode('\\', $old_fq_class_name); + $old_class_name = end($old_class_name_parts); + if ($old_class_name === strtolower($alias)) { + $new_class_name_parts = explode('\\', $new_fq_class_name); + $new_class_name = end($new_class_name_parts); + $uses_flipped[strtolower($new_fq_class_name)] = $new_class_name; + } else { + $uses_flipped[strtolower($new_fq_class_name)] = $alias; + } + } + + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $class_name_node->getAttribute('startFilePos'), + (int) $class_name_node->getAttribute('endFilePos') + 1, + Type::getStringFromFQCLN( + $new_fq_class_name, + $source_namespace, + $uses_flipped, + $migrated_source_fqcln, + $was_self + ) + . ($class_name_node instanceof PhpParser\Node\Scalar\MagicConst\Class_ ? '::class' : '') + ); + + FileManipulationBuffer::add($source->getFilePath(), $file_manipulations); + + return true; + } + + // if we're inside a moved class (could be a method, could be a property/class const default) + if ($codebase->classes_to_move + && $calling_fq_class_name + && isset($codebase->classes_to_move[strtolower($calling_fq_class_name)]) + ) { + $destination_class = $codebase->classes_to_move[strtolower($calling_fq_class_name)]; + + if ($class_name_node instanceof PhpParser\Node\Identifier) { + $destination_parts = explode('\\', $destination_class); + + $destination_class_name = array_pop($destination_parts); + $file_manipulations = []; + + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $class_name_node->getAttribute('startFilePos'), + (int) $class_name_node->getAttribute('endFilePos') + 1, + $destination_class_name + ); + + FileManipulationBuffer::add($source->getFilePath(), $file_manipulations); + } else { + $this->airliftClassLikeReference( + strtolower($calling_fq_class_name) === strtolower($fq_class_name) + ? $destination_class + : $fq_class_name, + $destination_class, + $source->getFilePath(), + (int) $class_name_node->getAttribute('startFilePos'), + (int) $class_name_node->getAttribute('endFilePos') + 1, + $class_name_node instanceof PhpParser\Node\Scalar\MagicConst\Class_ + ); + } + + return true; + } + + if ($force_change) { + if ($calling_fq_class_name) { + $this->airliftClassLikeReference( + $fq_class_name, + $calling_fq_class_name, + $source->getFilePath(), + (int) $class_name_node->getAttribute('startFilePos'), + (int) $class_name_node->getAttribute('endFilePos') + 1 + ); + } else { + $file_manipulations = []; + + $file_manipulations[] = new \Psalm\FileManipulation( + (int) $class_name_node->getAttribute('startFilePos'), + (int) $class_name_node->getAttribute('endFilePos') + 1, + Type::getStringFromFQCLN( + $fq_class_name, + $source->getNamespace(), + $source->getAliasedClassesFlipped(), + null + ) + ); + + FileManipulationBuffer::add($source->getFilePath(), $file_manipulations); + } + + return true; + } + + return false; + } + + /** + * @param lowercase-string|null $calling_method_id + */ + public function handleDocblockTypeInMigration( + \Psalm\Codebase $codebase, + \Psalm\StatementsSource $source, + Type\Union $type, + CodeLocation $type_location, + ?string $calling_method_id + ) : void { + $calling_fq_class_name = $source->getFQCLN(); + + $moved_type = false; + + // if we're inside a moved class static method + if ($codebase->methods_to_move + && $calling_fq_class_name + && $calling_method_id + && isset($codebase->methods_to_move[$calling_method_id]) + ) { + $bounds = $type_location->getSelectionBounds(); + + $destination_class = explode('::', $codebase->methods_to_move[$calling_method_id])[0]; + + $this->airliftClassDefinedDocblockType( + $type, + $destination_class, + $source->getFilePath(), + $bounds[0], + $bounds[1] + ); + + $moved_type = true; + } + + // if we're outside a moved class, but we're changing all references to a class + if (!$moved_type && $codebase->class_transforms) { + $uses_flipped = $source->getAliasedClassesFlipped(); + $uses_flipped_replaceable = $source->getAliasedClassesFlippedReplaceable(); + + $migrated_source_fqcln = $calling_fq_class_name; + + if ($calling_fq_class_name + && isset($codebase->class_transforms[strtolower($calling_fq_class_name)]) + ) { + $migrated_source_fqcln = $codebase->class_transforms[strtolower($calling_fq_class_name)]; + } + + $source_namespace = $source->getNamespace(); + + if ($migrated_source_fqcln && $calling_fq_class_name !== $migrated_source_fqcln) { + $new_source_parts = explode('\\', $migrated_source_fqcln, -1); + $source_namespace = implode('\\', $new_source_parts); + } + + foreach ($codebase->class_transforms as $old_fq_class_name => $new_fq_class_name) { + if (isset($uses_flipped_replaceable[$old_fq_class_name])) { + $alias = $uses_flipped_replaceable[$old_fq_class_name]; + unset($uses_flipped[$old_fq_class_name]); + $old_class_name_parts = explode('\\', $old_fq_class_name); + $old_class_name = end($old_class_name_parts); + if ($old_class_name === strtolower($alias)) { + $new_class_name_parts = explode('\\', $new_fq_class_name); + $new_class_name = end($new_class_name_parts); + $uses_flipped[strtolower($new_fq_class_name)] = $new_class_name; + } else { + $uses_flipped[strtolower($new_fq_class_name)] = $alias; + } + } + } + + foreach ($codebase->class_transforms as $old_fq_class_name => $new_fq_class_name) { + if ($type->containsClassLike($old_fq_class_name)) { + $type = clone $type; + + $type->replaceClassLike($old_fq_class_name, $new_fq_class_name); + + $bounds = $type_location->getSelectionBounds(); + + $file_manipulations = []; + + $file_manipulations[] = new \Psalm\FileManipulation( + $bounds[0], + $bounds[1], + $type->toNamespacedString( + $source_namespace, + $uses_flipped, + $migrated_source_fqcln, + false + ) + ); + + FileManipulationBuffer::add( + $source->getFilePath(), + $file_manipulations + ); + + $moved_type = true; + } + } + } + + // if we're inside a moved class (could be a method, could be a property/class const default) + if (!$moved_type + && $codebase->classes_to_move + && $calling_fq_class_name + && isset($codebase->classes_to_move[strtolower($calling_fq_class_name)]) + ) { + $bounds = $type_location->getSelectionBounds(); + + $destination_class = $codebase->classes_to_move[strtolower($calling_fq_class_name)]; + + if ($type->containsClassLike(strtolower($calling_fq_class_name))) { + $type = clone $type; + + $type->replaceClassLike(strtolower($calling_fq_class_name), $destination_class); + } + + $this->airliftClassDefinedDocblockType( + $type, + $destination_class, + $source->getFilePath(), + $bounds[0], + $bounds[1] + ); + } + } + + public function airliftClassLikeReference( + string $fq_class_name, + string $destination_fq_class_name, + string $source_file_path, + int $source_start, + int $source_end, + bool $add_class_constant = false, + bool $allow_self = false + ) : void { + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + + $destination_class_storage = $codebase->classlike_storage_provider->get($destination_fq_class_name); + + if (!$destination_class_storage->aliases) { + throw new \UnexpectedValueException('Aliases should not be null'); + } + + $file_manipulations = []; + + $file_manipulations[] = new \Psalm\FileManipulation( + $source_start, + $source_end, + Type::getStringFromFQCLN( + $fq_class_name, + $destination_class_storage->aliases->namespace, + $destination_class_storage->aliases->uses_flipped, + $destination_class_storage->name, + $allow_self + ) . ($add_class_constant ? '::class' : '') + ); + + FileManipulationBuffer::add( + $source_file_path, + $file_manipulations + ); + } + + public function airliftClassDefinedDocblockType( + Type\Union $type, + string $destination_fq_class_name, + string $source_file_path, + int $source_start, + int $source_end + ) : void { + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + + $destination_class_storage = $codebase->classlike_storage_provider->get($destination_fq_class_name); + + if (!$destination_class_storage->aliases) { + throw new \UnexpectedValueException('Aliases should not be null'); + } + + $file_manipulations = []; + + $file_manipulations[] = new \Psalm\FileManipulation( + $source_start, + $source_end, + $type->toNamespacedString( + $destination_class_storage->aliases->namespace, + $destination_class_storage->aliases->uses_flipped, + $destination_class_storage->name, + false + ) + ); + + FileManipulationBuffer::add( + $source_file_path, + $file_manipulations + ); + } + + /** + * @param ReflectionProperty::IS_PUBLIC|ReflectionProperty::IS_PROTECTED|ReflectionProperty::IS_PRIVATE + * $visibility + * + * @return array + */ + public function getConstantsForClass(string $class_name, int $visibility): array + { + $class_name = strtolower($class_name); + + $storage = $this->classlike_storage_provider->get($class_name); + + if ($visibility === ReflectionProperty::IS_PUBLIC) { + return \array_filter( + $storage->constants, + function ($constant) { + return $constant->type + && $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC; + } + ); + } + + if ($visibility === ReflectionProperty::IS_PROTECTED) { + return \array_filter( + $storage->constants, + function ($constant) { + return $constant->type + && ($constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC + || $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED); + } + ); + } + + return \array_filter( + $storage->constants, + function ($constant) { + return $constant->type !== null; + } + ); + } + + /** + * @param ReflectionProperty::IS_PUBLIC|ReflectionProperty::IS_PROTECTED|ReflectionProperty::IS_PRIVATE + * $visibility + */ + public function getClassConstantType( + string $class_name, + string $constant_name, + int $visibility, + ?\Psalm\Internal\Analyzer\StatementsAnalyzer $statements_analyzer = null, + array $visited_constant_ids = [] + ) : ?Type\Union { + $class_name = strtolower($class_name); + $storage = $this->classlike_storage_provider->get($class_name); + + if (!isset($storage->constants[$constant_name])) { + return null; + } + + $constant_storage = $storage->constants[$constant_name]; + + if ($visibility === ReflectionProperty::IS_PUBLIC + && $constant_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PUBLIC + ) { + return null; + } + + if ($visibility === ReflectionProperty::IS_PROTECTED + && $constant_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PUBLIC + && $constant_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PROTECTED + ) { + return null; + } + + if ($constant_storage->unresolved_node) { + return new Type\Union([ + ConstantTypeResolver::resolve( + $this, + $constant_storage->unresolved_node, + $statements_analyzer, + $visited_constant_ids + ) + ]); + } + + return $constant_storage->type; + } + + private function checkMethodReferences(ClassLikeStorage $classlike_storage, Methods $methods): void + { + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + + foreach ($classlike_storage->appearing_method_ids as $method_name => $appearing_method_id) { + $appearing_fq_classlike_name = $appearing_method_id->fq_class_name; + + if ($appearing_fq_classlike_name !== $classlike_storage->name) { + continue; + } + + $method_id = $appearing_method_id; + + $declaring_classlike_storage = $classlike_storage; + + if (isset($classlike_storage->methods[$method_name])) { + $method_storage = $classlike_storage->methods[$method_name]; + } else { + $declaring_method_id = $classlike_storage->declaring_method_ids[$method_name]; + + $declaring_fq_classlike_name = $declaring_method_id->fq_class_name; + $declaring_method_name = $declaring_method_id->method_name; + + try { + $declaring_classlike_storage = $this->classlike_storage_provider->get($declaring_fq_classlike_name); + } catch (\InvalidArgumentException $e) { + continue; + } + + $method_storage = $declaring_classlike_storage->methods[$declaring_method_name]; + $method_id = $declaring_method_id; + } + + if ($method_storage->location + && !$project_analyzer->canReportIssues($method_storage->location->file_path) + && !$codebase->analyzer->canReportIssues($method_storage->location->file_path) + ) { + continue; + } + + $method_referenced = $this->file_reference_provider->isClassMethodReferenced( + strtolower((string) $method_id) + ); + + if (!$method_referenced + && $method_name !== '__destruct' + && $method_name !== '__clone' + && $method_name !== '__invoke' + && $method_name !== '__unset' + && $method_name !== '__isset' + && $method_name !== '__sleep' + && $method_name !== '__wakeup' + && $method_name !== '__serialize' + && $method_name !== '__unserialize' + && $method_name !== '__set_state' + && $method_name !== '__debuginfo' + && $method_name !== '__tostring' // can be called in array_unique + && $method_storage->location + ) { + $method_location = $method_storage->location; + + $method_id = $classlike_storage->name . '::' . $method_storage->cased_name; + + if ($method_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PRIVATE) { + $has_parent_references = false; + + if ($codebase->classImplements($classlike_storage->name, 'Serializable') + && ($method_name === 'serialize' || $method_name === 'unserialize') + ) { + continue; + } + + $has_variable_calls = $codebase->analyzer->hasMixedMemberName($method_name) + || $codebase->analyzer->hasMixedMemberName(strtolower($classlike_storage->name . '::')); + + if (isset($classlike_storage->overridden_method_ids[$method_name])) { + foreach ($classlike_storage->overridden_method_ids[$method_name] as $parent_method_id) { + $parent_method_storage = $methods->getStorage($parent_method_id); + + if ($parent_method_storage->location + && !$project_analyzer->canReportIssues($parent_method_storage->location->file_path) + ) { + // here we just don’t know + $has_parent_references = true; + break; + } + + $parent_method_referenced = $this->file_reference_provider->isClassMethodReferenced( + strtolower((string) $parent_method_id) + ); + + if (!$parent_method_storage->abstract || $parent_method_referenced) { + $has_parent_references = true; + break; + } + } + } + + foreach ($classlike_storage->parent_classes as $parent_method_fqcln) { + if ($codebase->analyzer->hasMixedMemberName( + strtolower($parent_method_fqcln) . '::' + )) { + $has_variable_calls = true; + break; + } + } + + foreach ($classlike_storage->class_implements as $fq_interface_name_lc => $_) { + try { + $interface_storage = $this->classlike_storage_provider->get($fq_interface_name_lc); + } catch (\InvalidArgumentException $e) { + continue; + } + + if ($codebase->analyzer->hasMixedMemberName( + $fq_interface_name_lc . '::' + )) { + $has_variable_calls = true; + } + + if (isset($interface_storage->methods[$method_name])) { + $interface_method_referenced = $this->file_reference_provider->isClassMethodReferenced( + $fq_interface_name_lc . '::' . $method_name + ); + + if ($interface_method_referenced) { + $has_parent_references = true; + } + } + } + + if (!$has_parent_references) { + $issue = new PossiblyUnusedMethod( + 'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any') + . ' calls to method ' . $method_id + . ($has_variable_calls ? ' (but did find some potential callers)' : ''), + $method_storage->location, + $method_id + ); + + if ($codebase->alter_code) { + if ($method_storage->stmt_location + && !$declaring_classlike_storage->is_trait + && isset($project_analyzer->getIssuesToFix()['PossiblyUnusedMethod']) + && !$has_variable_calls + && !IssueBuffer::isSuppressed($issue, $method_storage->suppressed_issues) + ) { + FileManipulationBuffer::addForCodeLocation( + $method_storage->stmt_location, + '', + true + ); + } + } elseif (IssueBuffer::accepts( + $issue, + $method_storage->suppressed_issues, + $method_storage->stmt_location + && !$declaring_classlike_storage->is_trait + && !$has_variable_calls + )) { + // fall through + } + } + } elseif (!isset($classlike_storage->declaring_method_ids['__call'])) { + $has_variable_calls = $codebase->analyzer->hasMixedMemberName( + strtolower($classlike_storage->name . '::') + ) || $codebase->analyzer->hasMixedMemberName($method_name); + + $issue = new UnusedMethod( + 'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any') + . ' calls to private method ' . $method_id + . ($has_variable_calls ? ' (but did find some potential callers)' : ''), + $method_location, + $method_id + ); + + if ($codebase->alter_code) { + if ($method_storage->stmt_location + && !$declaring_classlike_storage->is_trait + && isset($project_analyzer->getIssuesToFix()['UnusedMethod']) + && !$has_variable_calls + && !IssueBuffer::isSuppressed($issue, $method_storage->suppressed_issues) + ) { + FileManipulationBuffer::addForCodeLocation( + $method_storage->stmt_location, + '', + true + ); + } + } elseif (IssueBuffer::accepts( + $issue, + $method_storage->suppressed_issues, + $method_storage->stmt_location + && !$declaring_classlike_storage->is_trait + && !$has_variable_calls + )) { + // fall through + } + } + } else { + if ($method_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PRIVATE + && !$classlike_storage->is_interface + ) { + foreach ($method_storage->params as $offset => $param_storage) { + if (!$this->file_reference_provider->isMethodParamUsed( + strtolower((string) $method_id), + $offset + ) + && $param_storage->location + ) { + if ($method_storage->final) { + if (IssueBuffer::accepts( + new \Psalm\Issue\UnusedParam( + 'Param #' . ($offset + 1) . ' is never referenced in this method', + $param_storage->location + ), + $method_storage->suppressed_issues + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new PossiblyUnusedParam( + 'Param #' . ($offset + 1) . ' is never referenced in this method', + $param_storage->location + ), + $method_storage->suppressed_issues + )) { + // fall through + } + } + } + } + } + } + } + } + + private function findPossibleMethodParamTypes(ClassLikeStorage $classlike_storage): void + { + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + + foreach ($classlike_storage->appearing_method_ids as $method_name => $appearing_method_id) { + $appearing_fq_classlike_name = $appearing_method_id->fq_class_name; + + if ($appearing_fq_classlike_name !== $classlike_storage->name) { + continue; + } + + $method_id = $appearing_method_id; + + $declaring_classlike_storage = $classlike_storage; + + if (isset($classlike_storage->methods[$method_name])) { + $method_storage = $classlike_storage->methods[$method_name]; + } else { + $declaring_method_id = $classlike_storage->declaring_method_ids[$method_name]; + + $declaring_fq_classlike_name = $declaring_method_id->fq_class_name; + $declaring_method_name = $declaring_method_id->method_name; + + try { + $declaring_classlike_storage = $this->classlike_storage_provider->get($declaring_fq_classlike_name); + } catch (\InvalidArgumentException $e) { + continue; + } + + $method_storage = $declaring_classlike_storage->methods[$declaring_method_name]; + $method_id = $declaring_method_id; + } + + if ($method_storage->location + && !$project_analyzer->canReportIssues($method_storage->location->file_path) + && !$codebase->analyzer->canReportIssues($method_storage->location->file_path) + ) { + continue; + } + + if ($declaring_classlike_storage->is_trait) { + continue; + } + + $method_id_lc = strtolower((string) $method_id); + + if (isset($codebase->analyzer->possible_method_param_types[$method_id_lc])) { + if ($method_storage->location) { + $possible_param_types + = $codebase->analyzer->possible_method_param_types[$method_id_lc]; + + if ($possible_param_types) { + foreach ($possible_param_types as $offset => $possible_type) { + if (!isset($method_storage->params[$offset])) { + continue; + } + + $param_name = $method_storage->params[$offset]->name; + + if ($possible_type->hasMixed() || $possible_type->isNull()) { + continue; + } + + if ($method_storage->params[$offset]->default_type) { + $possible_type = \Psalm\Type::combineUnionTypes( + $possible_type, + $method_storage->params[$offset]->default_type + ); + } + + if ($codebase->alter_code + && isset($project_analyzer->getIssuesToFix()['MissingParamType']) + ) { + $function_analyzer = $project_analyzer->getFunctionLikeAnalyzer( + $method_id, + $method_storage->location->file_path + ); + + $has_variable_calls = $codebase->analyzer->hasMixedMemberName( + $method_name + ) + || $codebase->analyzer->hasMixedMemberName( + strtolower($classlike_storage->name . '::') + ); + + if ($has_variable_calls) { + $possible_type->from_docblock = true; + } + + if ($function_analyzer) { + $function_analyzer->addOrUpdateParamType( + $project_analyzer, + $param_name, + $possible_type, + $possible_type->from_docblock + && $project_analyzer->only_replace_php_types_with_non_docblock_types + ); + } + } else { + IssueBuffer::addFixableIssue('MissingParamType'); + } + } + } + } + } + } + } + + private function checkPropertyReferences(ClassLikeStorage $classlike_storage): void + { + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + + foreach ($classlike_storage->properties as $property_name => $property_storage) { + $referenced_property_name = strtolower($classlike_storage->name) . '::$' . $property_name; + $property_referenced = $this->file_reference_provider->isClassPropertyReferenced( + $referenced_property_name + ); + + $property_constructor_referenced = false; + if ($property_referenced && $property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE) { + $all_method_references = $this->file_reference_provider->getAllMethodReferencesToClassMembers(); + + if (isset($all_method_references[$referenced_property_name]) + && count($all_method_references[$referenced_property_name]) === 1) { + $constructor_name = strtolower($classlike_storage->name) . '::__construct'; + $property_references = $all_method_references[$referenced_property_name]; + + $property_constructor_referenced = isset($property_references[$constructor_name]) + && !$property_storage->is_static; + } + } + + if ((!$property_referenced || $property_constructor_referenced) + && $property_storage->location + ) { + $property_id = $classlike_storage->name . '::$' . $property_name; + + if ($property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC + || $property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED + ) { + $has_parent_references = isset($classlike_storage->overridden_property_ids[$property_name]); + + $has_variable_calls = $codebase->analyzer->hasMixedMemberName('$' . $property_name) + || $codebase->analyzer->hasMixedMemberName(strtolower($classlike_storage->name) . '::$'); + + foreach ($classlike_storage->parent_classes as $parent_method_fqcln) { + if ($codebase->analyzer->hasMixedMemberName( + strtolower($parent_method_fqcln) . '::$' + )) { + $has_variable_calls = true; + break; + } + } + + foreach ($classlike_storage->class_implements as $fq_interface_name) { + if ($codebase->analyzer->hasMixedMemberName( + strtolower($fq_interface_name) . '::$' + )) { + $has_variable_calls = true; + break; + } + } + + if (!$has_parent_references + && ($property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC + || !isset($classlike_storage->declaring_method_ids['__get'])) + ) { + $issue = new PossiblyUnusedProperty( + 'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any') + . ' references to property ' . $property_id + . ($has_variable_calls ? ' (but did find some potential references)' : ''), + $property_storage->location + ); + + if ($codebase->alter_code) { + if ($property_storage->stmt_location + && isset($project_analyzer->getIssuesToFix()['PossiblyUnusedProperty']) + && !$has_variable_calls + && !IssueBuffer::isSuppressed($issue, $classlike_storage->suppressed_issues) + ) { + FileManipulationBuffer::addForCodeLocation( + $property_storage->stmt_location, + '', + true + ); + } + } elseif (IssueBuffer::accepts( + $issue, + $classlike_storage->suppressed_issues + )) { + // fall through + } + } + } elseif (!isset($classlike_storage->declaring_method_ids['__get'])) { + $has_variable_calls = $codebase->analyzer->hasMixedMemberName('$' . $property_name); + + $issue = new UnusedProperty( + 'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any') + . ' references to private property ' . $property_id + . ($has_variable_calls ? ' (but did find some potential references)' : ''), + $property_storage->location + ); + + if ($codebase->alter_code) { + if (!$property_constructor_referenced + && $property_storage->stmt_location + && isset($project_analyzer->getIssuesToFix()['UnusedProperty']) + && !$has_variable_calls + && !IssueBuffer::isSuppressed($issue, $classlike_storage->suppressed_issues) + ) { + FileManipulationBuffer::addForCodeLocation( + $property_storage->stmt_location, + '', + true + ); + } + } elseif (IssueBuffer::accepts( + $issue, + $classlike_storage->suppressed_issues + )) { + // fall through + } + } + } + } + } + + /** + * @param lowercase-string $fq_classlike_name_lc + */ + public function registerMissingClassLike(string $fq_classlike_name_lc): void + { + $this->existing_classlikes_lc[$fq_classlike_name_lc] = false; + } + + /** + * @param lowercase-string $fq_classlike_name_lc + */ + public function isMissingClassLike(string $fq_classlike_name_lc): bool + { + return isset($this->existing_classlikes_lc[$fq_classlike_name_lc]) + && $this->existing_classlikes_lc[$fq_classlike_name_lc] === false; + } + + /** + * @param lowercase-string $fq_classlike_name_lc + */ + public function doesClassLikeExist(string $fq_classlike_name_lc): bool + { + return isset($this->existing_classlikes_lc[$fq_classlike_name_lc]) + && $this->existing_classlikes_lc[$fq_classlike_name_lc]; + } + + public function forgetMissingClassLikes() : void + { + $this->existing_classlikes_lc = \array_filter($this->existing_classlikes_lc); + } + + public function removeClassLike(string $fq_class_name): void + { + $fq_class_name_lc = strtolower($fq_class_name); + + unset( + $this->existing_classlikes_lc[$fq_class_name_lc], + $this->existing_classes_lc[$fq_class_name_lc], + $this->existing_traits_lc[$fq_class_name_lc], + $this->existing_traits[$fq_class_name], + $this->existing_interfaces_lc[$fq_class_name_lc], + $this->existing_interfaces[$fq_class_name], + $this->existing_classes[$fq_class_name], + $this->trait_nodes[$fq_class_name_lc] + ); + + $this->scanner->removeClassLike($fq_class_name_lc); + } + + /** + * @return array{ + * 0: array, + * 1: array, + * 2: array, + * 3: array, + * 4: array, + * 5: array, + * 6: array, + * } + */ + public function getThreadData(): array + { + return [ + $this->existing_classlikes_lc, + $this->existing_classes_lc, + $this->existing_traits_lc, + $this->existing_traits, + $this->existing_interfaces_lc, + $this->existing_interfaces, + $this->existing_classes, + ]; + } + + /** + * @param array{ + * 0: array, + * 1: array, + * 2: array, + * 3: array, + * 4: array, + * 5: array, + * 6: array, + * } $thread_data + * + */ + public function addThreadData(array $thread_data): void + { + [ + $existing_classlikes_lc, + $existing_classes_lc, + $existing_traits_lc, + $existing_traits, + $existing_interfaces_lc, + $existing_interfaces, + $existing_classes + ] = $thread_data; + + $this->existing_classlikes_lc = array_merge($existing_classlikes_lc, $this->existing_classlikes_lc); + $this->existing_classes_lc = array_merge($existing_classes_lc, $this->existing_classes_lc); + $this->existing_traits_lc = array_merge($existing_traits_lc, $this->existing_traits_lc); + $this->existing_traits = array_merge($existing_traits, $this->existing_traits); + $this->existing_interfaces_lc = array_merge($existing_interfaces_lc, $this->existing_interfaces_lc); + $this->existing_interfaces = array_merge($existing_interfaces, $this->existing_interfaces); + $this->existing_classes = array_merge($existing_classes, $this->existing_classes); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/ConstantTypeResolver.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/ConstantTypeResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..e11562e211eeefa79067cdaf8f39fbb5086e6a14 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/ConstantTypeResolver.php @@ -0,0 +1,259 @@ +value); + } + + if ($c instanceof UnresolvedConstant\UnresolvedBinaryOp) { + $left = self::resolve( + $classlikes, + $c->left, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + ); + $right = self::resolve( + $classlikes, + $c->right, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + ); + + if ($left instanceof Type\Atomic\TMixed || $right instanceof Type\Atomic\TMixed) { + return new Type\Atomic\TMixed; + } + + if ($c instanceof UnresolvedConstant\UnresolvedConcatOp) { + if ($left instanceof Type\Atomic\TLiteralString && $right instanceof Type\Atomic\TLiteralString) { + return new Type\Atomic\TLiteralString($left->value . $right->value); + } + + return new Type\Atomic\TMixed; + } + + if ($c instanceof UnresolvedConstant\UnresolvedAdditionOp + || $c instanceof UnresolvedConstant\UnresolvedSubtractionOp + || $c instanceof UnresolvedConstant\UnresolvedDivisionOp + || $c instanceof UnresolvedConstant\UnresolvedMultiplicationOp + || $c instanceof UnresolvedConstant\UnresolvedBitwiseOr + ) { + if (($left instanceof Type\Atomic\TLiteralFloat || $left instanceof Type\Atomic\TLiteralInt) + && ($right instanceof Type\Atomic\TLiteralFloat || $right instanceof Type\Atomic\TLiteralInt) + ) { + if ($c instanceof UnresolvedConstant\UnresolvedAdditionOp) { + return self::getLiteralTypeFromScalarValue($left->value + $right->value); + } + + if ($c instanceof UnresolvedConstant\UnresolvedSubtractionOp) { + return self::getLiteralTypeFromScalarValue($left->value - $right->value); + } + + if ($c instanceof UnresolvedConstant\UnresolvedDivisionOp) { + return self::getLiteralTypeFromScalarValue($left->value / $right->value); + } + + if ($c instanceof UnresolvedConstant\UnresolvedBitwiseOr) { + return self::getLiteralTypeFromScalarValue($left->value | $right->value); + } + + return self::getLiteralTypeFromScalarValue($left->value * $right->value); + } + + return new Type\Atomic\TMixed; + } + + return new Type\Atomic\TMixed; + } + + if ($c instanceof UnresolvedConstant\UnresolvedTernary) { + $cond = self::resolve( + $classlikes, + $c->cond, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + ); + $if = $c->if ? self::resolve( + $classlikes, + $c->if, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + ) : null; + $else = self::resolve( + $classlikes, + $c->else, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + ); + + if ($cond instanceof Type\Atomic\TLiteralFloat + || $cond instanceof Type\Atomic\TLiteralInt + || $cond instanceof Type\Atomic\TLiteralString + ) { + if ($cond->value) { + return $if ? $if : $cond; + } + } elseif ($cond instanceof Type\Atomic\TFalse || $cond instanceof Type\Atomic\TNull) { + return $else; + } elseif ($cond instanceof Type\Atomic\TTrue) { + return $if ? $if : $cond; + } + } + + if ($c instanceof UnresolvedConstant\ArrayValue) { + $properties = []; + + if (!$c->entries) { + return new Type\Atomic\TArray([Type::getEmpty(), Type::getEmpty()]); + } + + $is_list = true; + + foreach ($c->entries as $i => $entry) { + if ($entry->key) { + $key_type = self::resolve( + $classlikes, + $entry->key, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + ); + + if (!$key_type instanceof Type\Atomic\TLiteralInt + || $key_type->value !== $i + ) { + $is_list = false; + } + } else { + $key_type = new Type\Atomic\TLiteralInt($i); + } + + if ($key_type instanceof Type\Atomic\TLiteralInt + || $key_type instanceof Type\Atomic\TLiteralString + ) { + $key_value = $key_type->value; + } else { + return new Type\Atomic\TArray([Type::getArrayKey(), Type::getMixed()]); + } + + $value_type = new Type\Union([self::resolve( + $classlikes, + $entry->value, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + )]); + + $properties[$key_value] = $value_type; + } + + $objectlike = new Type\Atomic\TKeyedArray($properties); + + $objectlike->is_list = $is_list; + $objectlike->sealed = true; + + return $objectlike; + } + + if ($c instanceof UnresolvedConstant\ClassConstant) { + if ($c->name === 'class') { + return new Type\Atomic\TLiteralClassString($c->fqcln); + } + + $found_type = $classlikes->getClassConstantType( + $c->fqcln, + $c->name, + ReflectionProperty::IS_PRIVATE, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + ); + + if ($found_type) { + return \array_values($found_type->getAtomicTypes())[0]; + } + } + + if ($c instanceof UnresolvedConstant\ArrayOffsetFetch) { + $var_type = self::resolve( + $classlikes, + $c->array, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + ); + + $offset_type = self::resolve( + $classlikes, + $c->offset, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + ); + + if ($var_type instanceof Type\Atomic\TKeyedArray + && ($offset_type instanceof Type\Atomic\TLiteralInt + || $offset_type instanceof Type\Atomic\TLiteralString) + ) { + $union = $var_type->properties[$offset_type->value] ?? null; + + if ($union && $union->isSingle()) { + return \array_values($union->getAtomicTypes())[0]; + } + } + } + + if ($c instanceof UnresolvedConstant\Constant) { + if ($statements_analyzer) { + $found_type = ConstFetchAnalyzer::getConstType( + $statements_analyzer, + $c->name, + $c->is_fully_qualified, + null + ); + + if ($found_type) { + return \array_values($found_type->getAtomicTypes())[0]; + } + } + } + + return new Type\Atomic\TMixed; + } + + /** + * @param string|int|float|bool|null $value + */ + private static function getLiteralTypeFromScalarValue($value) : Type\Atomic + { + if (\is_string($value)) { + return new Type\Atomic\TLiteralString($value); + } elseif (\is_int($value)) { + return new Type\Atomic\TLiteralInt($value); + } elseif (\is_float($value)) { + return new Type\Atomic\TLiteralFloat($value); + } elseif ($value === false) { + return new Type\Atomic\TFalse; + } elseif ($value === true) { + return new Type\Atomic\TTrue; + } else { + return new Type\Atomic\TNull; + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/DataFlowGraph.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/DataFlowGraph.php new file mode 100644 index 0000000000000000000000000000000000000000..08c3586cd64b084dda00a135ffc1d46c54b55d71 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/DataFlowGraph.php @@ -0,0 +1,86 @@ +> */ + protected $forward_edges = []; + + abstract public function addNode(DataFlowNode $node) : void; + + /** + * @param array $added_taints + * @param array $removed_taints + */ + public function addPath( + DataFlowNode $from, + DataFlowNode $to, + string $path_type, + ?array $added_taints = null, + ?array $removed_taints = null + ) : void { + $from_id = $from->id; + $to_id = $to->id; + + if ($from_id === $to_id) { + return; + } + + $this->forward_edges[$from_id][$to_id] = new Path($path_type, $added_taints, $removed_taints); + } + + /** + * @param array $previous_path_types + * + * @psalm-pure + */ + protected static function shouldIgnoreFetch( + string $path_type, + string $expression_type, + array $previous_path_types + ) : bool { + $el = strlen($expression_type); + + if (substr($path_type, 0, $el + 7) === $expression_type . '-fetch-') { + $fetch_nesting = 0; + + $previous_path_types = array_reverse($previous_path_types); + + foreach ($previous_path_types as $previous_path_type) { + if ($previous_path_type === $expression_type . '-assignment') { + if ($fetch_nesting === 0) { + return false; + } + + $fetch_nesting--; + } + + if (substr($previous_path_type, 0, $el + 6) === $expression_type . '-fetch') { + $fetch_nesting++; + } + + if (substr($previous_path_type, 0, $el + 12) === $expression_type . '-assignment-') { + if ($fetch_nesting > 0) { + $fetch_nesting--; + continue; + } + + if (substr($previous_path_type, $el + 12) === substr($path_type, $el + 7)) { + return false; + } + + return true; + } + } + } + + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Functions.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Functions.php new file mode 100644 index 0000000000000000000000000000000000000000..ee943283a1284c709a2bd98ca4d7390c4569fa41 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Functions.php @@ -0,0 +1,469 @@ + + */ + private static $stubbed_functions; + + /** @var FunctionReturnTypeProvider */ + public $return_type_provider; + + /** @var FunctionExistenceProvider */ + public $existence_provider; + + /** @var FunctionParamsProvider */ + public $params_provider; + + /** + * @var Reflection + */ + private $reflection; + + public function __construct(FileStorageProvider $storage_provider, Reflection $reflection) + { + $this->file_storage_provider = $storage_provider; + $this->reflection = $reflection; + $this->return_type_provider = new FunctionReturnTypeProvider(); + $this->existence_provider = new FunctionExistenceProvider(); + $this->params_provider = new FunctionParamsProvider(); + + self::$stubbed_functions = []; + } + + /** + * @param non-empty-lowercase-string $function_id + */ + public function getStorage( + ?StatementsAnalyzer $statements_analyzer, + string $function_id, + ?string $root_file_path = null, + ?string $checked_file_path = null + ) : FunctionStorage { + if ($function_id[0] === '\\') { + $function_id = substr($function_id, 1); + } + + if (isset(self::$stubbed_functions[$function_id])) { + return self::$stubbed_functions[$function_id]; + } + + $file_storage = null; + + if ($statements_analyzer) { + $root_file_path = $statements_analyzer->getRootFilePath(); + $checked_file_path = $statements_analyzer->getFilePath(); + + $file_storage = $this->file_storage_provider->get($root_file_path); + + $function_analyzers = $statements_analyzer->getFunctionAnalyzers(); + + if (isset($function_analyzers[$function_id])) { + $function_id = $function_analyzers[$function_id]->getFunctionId(); + + if (isset($file_storage->functions[$function_id])) { + return $file_storage->functions[$function_id]; + } + } + + // closures can be returned here + if (isset($file_storage->functions[$function_id])) { + return $file_storage->functions[$function_id]; + } + } + + if (!$root_file_path || !$checked_file_path) { + if ($this->reflection->hasFunction($function_id)) { + return $this->reflection->getFunctionStorage($function_id); + } + + throw new \UnexpectedValueException( + 'Expecting non-empty $root_file_path and $checked_file_path' + ); + } + + if ($this->reflection->hasFunction($function_id)) { + return $this->reflection->getFunctionStorage($function_id); + } + + if (!isset($file_storage->declaring_function_ids[$function_id])) { + if ($checked_file_path !== $root_file_path) { + $file_storage = $this->file_storage_provider->get($checked_file_path); + + if (isset($file_storage->functions[$function_id])) { + return $file_storage->functions[$function_id]; + } + } + + throw new \UnexpectedValueException( + 'Expecting ' . $function_id . ' to have storage in ' . $checked_file_path + ); + } + + $declaring_file_path = $file_storage->declaring_function_ids[$function_id]; + + $declaring_file_storage = $this->file_storage_provider->get($declaring_file_path); + + if (!isset($declaring_file_storage->functions[$function_id])) { + throw new \UnexpectedValueException( + 'Not expecting ' . $function_id . ' to not have storage in ' . $declaring_file_path + ); + } + + return $declaring_file_storage->functions[$function_id]; + } + + public function addGlobalFunction(string $function_id, FunctionStorage $storage): void + { + self::$stubbed_functions[strtolower($function_id)] = $storage; + } + + public function hasStubbedFunction(string $function_id): bool + { + return isset(self::$stubbed_functions[strtolower($function_id)]); + } + + /** + * @return array + */ + public function getAllStubbedFunctions(): array + { + return self::$stubbed_functions; + } + + /** + * @param lowercase-string $function_id + */ + public function functionExists( + StatementsAnalyzer $statements_analyzer, + string $function_id + ): bool { + if ($this->existence_provider->has($function_id)) { + $function_exists = $this->existence_provider->doesFunctionExist($statements_analyzer, $function_id); + + if ($function_exists !== null) { + return $function_exists; + } + } + + $file_storage = $this->file_storage_provider->get($statements_analyzer->getRootFilePath()); + + if (isset($file_storage->declaring_function_ids[$function_id])) { + return true; + } + + if ($this->reflection->hasFunction($function_id)) { + return true; + } + + if (isset(self::$stubbed_functions[$function_id])) { + return true; + } + + if (isset($statements_analyzer->getFunctionAnalyzers()[$function_id])) { + return true; + } + + $predefined_functions = $statements_analyzer->getCodebase()->config->getPredefinedFunctions(); + + if (isset($predefined_functions[$function_id])) { + /** @psalm-suppress ArgumentTypeCoercion */ + if ($this->reflection->registerFunction($function_id) === false) { + return false; + } + + return true; + } + + return false; + } + + /** + * @param non-empty-string $function_name + * + * @return non-empty-string + */ + public function getFullyQualifiedFunctionNameFromString(string $function_name, StatementsSource $source): string + { + if ($function_name[0] === '\\') { + $function_name = substr($function_name, 1); + + if ($function_name === '') { + throw new \UnexpectedValueException('Malformed function name'); + } + + return $function_name; + } + + $function_name_lcase = strtolower($function_name); + + $aliases = $source->getAliases(); + + $imported_function_namespaces = $aliases->functions; + $imported_namespaces = $aliases->uses; + + if (strpos($function_name, '\\') !== false) { + $function_name_parts = explode('\\', $function_name); + $first_namespace = array_shift($function_name_parts); + $first_namespace_lcase = strtolower($first_namespace); + + if (isset($imported_namespaces[$first_namespace_lcase])) { + return $imported_namespaces[$first_namespace_lcase] . '\\' . implode('\\', $function_name_parts); + } + + if (isset($imported_function_namespaces[$first_namespace_lcase])) { + return $imported_function_namespaces[$first_namespace_lcase] . '\\' . + implode('\\', $function_name_parts); + } + } elseif (isset($imported_function_namespaces[$function_name_lcase])) { + return $imported_function_namespaces[$function_name_lcase]; + } + + $namespace = $source->getNamespace(); + + return ($namespace ? $namespace . '\\' : '') . $function_name; + } + + public static function isVariadic(Codebase $codebase, string $function_id, string $file_path): bool + { + $file_storage = $codebase->file_storage_provider->get($file_path); + + if (!isset($file_storage->declaring_function_ids[$function_id])) { + return false; + } + + $declaring_file_path = $file_storage->declaring_function_ids[$function_id]; + + $file_storage = $declaring_file_path === $file_path + ? $file_storage + : $codebase->file_storage_provider->get($declaring_file_path); + + return isset($file_storage->functions[$function_id]) && $file_storage->functions[$function_id]->variadic; + } + + /** + * @param ?array $args + */ + public function isCallMapFunctionPure( + Codebase $codebase, + ?\Psalm\NodeTypeProvider $type_provider, + string $function_id, + ?array $args, + bool &$must_use = true + ) : bool { + $impure_functions = [ + // file io + 'chdir', 'chgrp', 'chmod', 'chown', 'chroot', 'copy', 'file_put_contents', + 'opendir', 'readdir', 'closedir', 'rewinddir', 'scandir', + 'fopen', 'fread', 'fwrite', 'fclose', 'touch', 'fpassthru', 'fputs', 'fscanf', 'fseek', + 'ftruncate', 'fprintf', 'symlink', 'mkdir', 'unlink', 'rename', 'rmdir', 'popen', 'pclose', + 'fgetcsv', 'fputcsv', 'umask', 'finfo_close', 'readline_add_history', 'stream_set_timeout', + 'fgets', 'fflush', 'move_uploaded_file', 'file_exists', 'realpath', 'glob', + + // stream/socket io + 'stream_context_set_option', 'socket_write', 'stream_set_blocking', 'socket_close', + 'socket_set_option', 'stream_set_write_buffer', + + // meta calls + 'call_user_func', 'call_user_func_array', 'define', 'create_function', + + // http + 'header', 'header_remove', 'http_response_code', 'setcookie', + + // output buffer + 'ob_start', 'ob_end_clean', 'readfile', 'printf', 'var_dump', 'phpinfo', + 'ob_implicit_flush', 'vprintf', + + // mcrypt + 'mcrypt_generic_init', 'mcrypt_generic_deinit', 'mcrypt_module_close', + + // internal optimisation + 'opcache_compile_file', 'clearstatcache', + + // process-related + 'pcntl_signal', 'posix_kill', 'cli_set_process_title', 'pcntl_async_signals', 'proc_close', + 'proc_nice', 'proc_open', 'proc_terminate', + + // curl + 'curl_setopt', 'curl_close', 'curl_multi_add_handle', 'curl_multi_remove_handle', + 'curl_multi_select', 'curl_multi_close', 'curl_setopt_array', + + // apc, apcu + 'apc_store', 'apc_delete', 'apc_clear_cache', 'apc_add', 'apc_inc', 'apc_dec', 'apc_cas', + 'apcu_store', 'apcu_delete', 'apcu_clear_cache', 'apcu_add', 'apcu_inc', 'apcu_dec', 'apcu_cas', + + // gz + 'gzwrite', 'gzrewind', 'gzseek', 'gzclose', + + // newrelic + 'newrelic_start_transaction', 'newrelic_name_transaction', 'newrelic_add_custom_parameter', + 'newrelic_add_custom_tracer', 'newrelic_background_job', 'newrelic_end_transaction', + 'newrelic_set_appname', + + // execution + 'shell_exec', 'exec', 'system', 'passthru', 'pcntl_exec', + + // well-known functions + 'libxml_use_internal_errors', 'libxml_disable_entity_loader', 'curl_exec', + 'mt_srand', 'openssl_pkcs7_sign', + 'mt_rand', 'rand', 'random_int', 'random_bytes', + 'wincache_ucache_delete', 'wincache_ucache_set', 'wincache_ucache_inc', + 'class_alias', + 'class_exists', // impure by virtue of triggering autoloader + + // php environment + 'ini_set', 'sleep', 'usleep', 'register_shutdown_function', + 'error_reporting', 'register_tick_function', 'unregister_tick_function', + 'set_error_handler', 'user_error', 'trigger_error', 'restore_error_handler', + 'date_default_timezone_set', 'assert_options', 'setlocale', + 'set_exception_handler', 'set_time_limit', 'putenv', 'spl_autoload_register', + 'microtime', 'array_rand', + + // logging + 'openlog', 'syslog', 'error_log', 'define_syslog_variables', + + // session + 'session_id', 'session_decode', 'session_name', 'session_set_cookie_params', + 'session_set_save_handler', 'session_regenerate_id', 'mb_internal_encoding', + 'session_start', + + // ldap + 'ldap_set_option', + + // iterators + 'rewind', 'iterator_apply', + + // mysqli + 'mysqli_select_db', 'mysqli_dump_debug_info', 'mysqli_kill', 'mysqli_multi_query', + 'mysqli_next_result', 'mysqli_options', 'mysqli_ping', 'mysqli_query', 'mysqli_report', + 'mysqli_rollback', 'mysqli_savepoint', 'mysqli_set_charset', 'mysqli_ssl_set', + + // script execution + 'ignore_user_abort', + + // ftp + 'ftp_close', + + // bcmath + 'bcscale', + ]; + + if (\in_array(strtolower($function_id), $impure_functions, true)) { + return false; + } + + if (strpos($function_id, 'image') === 0) { + return false; + } + + if (($function_id === 'var_export' || $function_id === 'print_r') && !isset($args[1])) { + return false; + } + + if ($function_id === 'assert') { + $must_use = false; + return true; + } + + if ($function_id === 'func_num_args' || $function_id === 'func_get_args') { + return true; + } + + if ($function_id === 'count' && isset($args[0]) && $type_provider) { + $count_type = $type_provider->getType($args[0]->value); + + if ($count_type) { + foreach ($count_type->getAtomicTypes() as $atomic_count_type) { + if ($atomic_count_type instanceof TNamedObject) { + $count_method_id = new MethodIdentifier( + $atomic_count_type->value, + 'count' + ); + + try { + $method_storage = $codebase->methods->getStorage($count_method_id); + return $method_storage->mutation_free; + } catch (\Exception $e) { + // do nothing + } + } + } + } + } + + $function_callable = \Psalm\Internal\Codebase\InternalCallMapHandler::getCallableFromCallMapById( + $codebase, + $function_id, + $args ?: [], + null + ); + + if (!$function_callable->params + || ($args !== null && \count($args) === 0) + || ($function_callable->return_type && $function_callable->return_type->isVoid()) + ) { + return false; + } + + $must_use = $function_id !== 'array_map' + || (isset($args[0]) && !$args[0]->value instanceof \PhpParser\Node\Expr\Closure); + + foreach ($function_callable->params as $i => $param) { + if ($type_provider && $param->type && $param->type->hasCallableType() && isset($args[$i])) { + $arg_type = $type_provider->getType($args[$i]->value); + + if ($arg_type) { + foreach ($arg_type->getAtomicTypes() as $possible_callable) { + $possible_callable = CallableTypeComparator::getCallableFromAtomic( + $codebase, + $possible_callable + ); + + if ($possible_callable && !$possible_callable->is_pure) { + return false; + } + } + } + } + + if ($param->by_ref && isset($args[$i])) { + $must_use = false; + } + } + + return true; + } + + public static function clearCache() : void + { + self::$stubbed_functions = []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/InternalCallMapHandler.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/InternalCallMapHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..6928b8442d1077f18c10e3475e557b2aa3a0fe99 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/InternalCallMapHandler.php @@ -0,0 +1,420 @@ +>|null + */ + private static $call_map = null; + + /** + * @var array>|null + */ + private static $call_map_callables = []; + + /** + * @var array>> + */ + private static $taint_sink_map = []; + + /** + * @param array $args + */ + public static function getCallableFromCallMapById( + Codebase $codebase, + string $method_id, + array $args, + ?\Psalm\Internal\Provider\NodeDataProvider $nodes + ): TCallable { + $possible_callables = self::getCallablesFromCallMap($method_id); + + if ($possible_callables === null) { + throw new \UnexpectedValueException( + 'Not expecting $function_param_options to be null for ' . $method_id + ); + } + + return self::getMatchingCallableFromCallMapOptions( + $codebase, + $possible_callables, + $args, + $nodes, + $method_id + ); + } + + /** + * @param array $callables + * @param array $args + * + */ + public static function getMatchingCallableFromCallMapOptions( + Codebase $codebase, + array $callables, + array $args, + ?\Psalm\NodeTypeProvider $nodes, + string $method_id + ): TCallable { + if (count($callables) === 1) { + return $callables[0]; + } + + $matching_param_count_callable = null; + $matching_coerced_param_count_callable = null; + + foreach ($callables as $possible_callable) { + $possible_function_params = $possible_callable->params; + + assert($possible_function_params !== null); + + $all_args_match = true; + $type_coerced = false; + + $last_param = count($possible_function_params) + ? $possible_function_params[count($possible_function_params) - 1] + : null; + + $mandatory_param_count = count($possible_function_params); + + foreach ($possible_function_params as $i => $possible_function_param) { + if ($possible_function_param->is_optional) { + $mandatory_param_count = $i; + break; + } + } + + if ($mandatory_param_count > count($args) && !($last_param && $last_param->is_variadic)) { + continue; + } + + foreach ($args as $argument_offset => $arg) { + if ($argument_offset >= count($possible_function_params)) { + if (!$last_param || !$last_param->is_variadic) { + $all_args_match = false; + break; + } + + $function_param = $last_param; + } else { + $function_param = $possible_function_params[$argument_offset]; + } + + $param_type = $function_param->type; + + if (!$param_type) { + continue; + } + + if (!$nodes + || !($arg_type = $nodes->getType($arg->value)) + ) { + continue; + } + + if ($arg_type->hasMixed()) { + continue; + } + + if ($arg->unpack && !$function_param->is_variadic) { + if ($arg_type->hasArray()) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var Type\Atomic\TArray|Type\Atomic\TKeyedArray|Type\Atomic\TList + */ + $array_atomic_type = $arg_type->getAtomicTypes()['array']; + + if ($array_atomic_type instanceof Type\Atomic\TKeyedArray) { + $arg_type = $array_atomic_type->getGenericValueType(); + } elseif ($array_atomic_type instanceof Type\Atomic\TList) { + $arg_type = $array_atomic_type->type_param; + } else { + $arg_type = $array_atomic_type->type_params[1]; + } + } + } + + $arg_result = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + if (UnionTypeComparator::isContainedBy( + $codebase, + $arg_type, + $param_type, + true, + true, + $arg_result + ) || $arg_result->type_coerced) { + if ($arg_result->type_coerced) { + $type_coerced = true; + } + + continue; + } + + $all_args_match = false; + break; + } + + if (count($args) === count($possible_function_params)) { + $matching_param_count_callable = $possible_callable; + } + + if ($all_args_match && (!$type_coerced || $method_id === 'max' || $method_id === 'min')) { + return $possible_callable; + } + + if ($all_args_match) { + $matching_coerced_param_count_callable = $possible_callable; + } + } + + if ($matching_coerced_param_count_callable) { + return $matching_coerced_param_count_callable; + } + + if ($matching_param_count_callable) { + return $matching_param_count_callable; + } + + // if we don't succeed in finding a match, set to the first possible and wait for issues below + return $callables[0]; + } + + /** + * @return array|null + */ + public static function getCallablesFromCallMap(string $function_id): ?array + { + $call_map_key = strtolower($function_id); + + if (isset(self::$call_map_callables[$call_map_key])) { + return self::$call_map_callables[$call_map_key]; + } + + $call_map = self::getCallMap(); + + if (!isset($call_map[$call_map_key])) { + return null; + } + + $call_map_functions = []; + $call_map_functions[] = $call_map[$call_map_key]; + + for ($i = 1; $i < 10; ++$i) { + if (!isset($call_map[$call_map_key . '\'' . $i])) { + break; + } + + $call_map_functions[] = $call_map[$call_map_key . '\'' . $i]; + } + + $possible_callables = []; + + foreach ($call_map_functions as $call_map_function_args) { + $return_type_string = array_shift($call_map_function_args); + + if (!$return_type_string) { + $return_type = Type::getMixed(); + } else { + $return_type = Type::parseString($return_type_string); + } + + $function_params = []; + + $arg_offset = 0; + + /** @var string $arg_name - key type changed with above array_shift */ + foreach ($call_map_function_args as $arg_name => $arg_type) { + $by_reference = false; + $optional = false; + $variadic = false; + + if ($arg_name[0] === '&') { + $arg_name = substr($arg_name, 1); + $by_reference = true; + } + + if (substr($arg_name, -1) === '=') { + $arg_name = substr($arg_name, 0, -1); + $optional = true; + } + + if (substr($arg_name, 0, 3) === '...') { + $arg_name = substr($arg_name, 3); + $variadic = true; + } + + $param_type = $arg_type + ? Type::parseString($arg_type) + : Type::getMixed(); + + $out_type = null; + + if (\strlen($arg_name) > 2 && $arg_name[0] === 'w' && $arg_name[1] === '_') { + $out_type = $param_type; + $param_type = Type::getMixed(); + } + + $function_param = new FunctionLikeParameter( + $arg_name, + $by_reference, + $param_type, + null, + null, + $optional, + false, + $variadic + ); + + if ($out_type) { + $function_param->out_type = $out_type; + } + + if ($arg_name === 'haystack') { + $function_param->expect_variable = true; + } + + if (isset(self::$taint_sink_map[$call_map_key][$arg_offset])) { + $function_param->sinks = self::$taint_sink_map[$call_map_key][$arg_offset]; + } + + $function_param->signature_type = null; + + $function_params[] = $function_param; + + $arg_offset++; + } + + $possible_callables[] = new TCallable('callable', $function_params, $return_type); + } + + self::$call_map_callables[$call_map_key] = $possible_callables; + + return $possible_callables; + } + + /** + * Gets the method/function call map + * + * @return array> + * @psalm-suppress MixedInferredReturnType as the use of require buggers things up + * @psalm-suppress MixedReturnStatement + * @psalm-suppress MixedReturnTypeCoercion + */ + public static function getCallMap(): array + { + $codebase = ProjectAnalyzer::getInstance()->getCodebase(); + $analyzer_major_version = $codebase->php_major_version; + $analyzer_minor_version = $codebase->php_minor_version; + + $analyzer_version = $analyzer_major_version . '.' . $analyzer_minor_version; + $current_version = self::PHP_MAJOR_VERSION . '.' . self::PHP_MINOR_VERSION; + + $analyzer_version_int = (int) ($analyzer_major_version . $analyzer_minor_version); + $current_version_int = (int) (self::PHP_MAJOR_VERSION . self::PHP_MINOR_VERSION); + + if (self::$call_map !== null + && $analyzer_major_version === self::$loaded_php_major_version + && $analyzer_minor_version === self::$loaded_php_minor_version + ) { + return self::$call_map; + } + + /** @var array> */ + $call_map = require(dirname(__DIR__, 4) . '/dictionaries/CallMap.php'); + + self::$call_map = []; + + foreach ($call_map as $key => $value) { + $cased_key = strtolower($key); + self::$call_map[$cased_key] = $value; + } + + /** + * @var array>> + */ + $taint_map = require(dirname(__DIR__, 4) . '/dictionaries/InternalTaintSinkMap.php'); + + foreach ($taint_map as $key => $value) { + $cased_key = strtolower($key); + self::$taint_sink_map[$cased_key] = $value; + } + + if (version_compare($analyzer_version, $current_version, '<')) { + // the following assumes both minor and major versions a single digits + for ($i = $current_version_int; $i > $analyzer_version_int && $i >= self::LOWEST_AVAILABLE_DELTA; --$i) { + $delta_file = dirname(__DIR__, 4) . '/dictionaries/CallMap_' . $i . '_delta.php'; + if (!file_exists($delta_file)) { + continue; + } + /** + * @var array{ + * old: array>, + * new: array> + * } + * @psalm-suppress UnresolvableInclude + */ + $diff_call_map = require($delta_file); + + foreach ($diff_call_map['new'] as $key => $_) { + $cased_key = strtolower($key); + unset(self::$call_map[$cased_key]); + } + + foreach ($diff_call_map['old'] as $key => $value) { + $cased_key = strtolower($key); + self::$call_map[$cased_key] = $value; + } + } + } + + self::$loaded_php_major_version = $analyzer_major_version; + self::$loaded_php_minor_version = $analyzer_minor_version; + + return self::$call_map; + } + + public static function inCallMap(string $key): bool + { + return isset(self::getCallMap()[strtolower($key)]); + } + + public static function clearCache() : void + { + self::$call_map_callables = []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Methods.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Methods.php new file mode 100644 index 0000000000000000000000000000000000000000..bf585e26aa3f4edc8ae44aa9563735d3af295d65 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Methods.php @@ -0,0 +1,1056 @@ +classlike_storage_provider = $storage_provider; + $this->file_reference_provider = $file_reference_provider; + $this->classlikes = $classlikes; + $this->return_type_provider = new MethodReturnTypeProvider(); + $this->existence_provider = new MethodExistenceProvider(); + $this->visibility_provider = new MethodVisibilityProvider(); + $this->params_provider = new MethodParamsProvider(); + } + + /** + * Whether or not a given method exists + * @param lowercase-string|null $calling_method_id + */ + public function methodExists( + MethodIdentifier $method_id, + ?string $calling_method_id = null, + ?CodeLocation $code_location = null, + ?StatementsSource $source = null, + ?string $source_file_path = null, + bool $use_method_existence_provider = true + ) : bool { + $fq_class_name = $method_id->fq_class_name; + $method_name = $method_id->method_name; + + if ($use_method_existence_provider && $this->existence_provider->has($fq_class_name)) { + $method_exists = $this->existence_provider->doesMethodExist( + $fq_class_name, + $method_name, + $source, + $code_location + ); + + if ($method_exists !== null) { + return $method_exists; + } + } + + $old_method_id = null; + + $fq_class_name = strtolower($this->classlikes->getUnAliasedName($fq_class_name)); + + try { + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + } catch (\InvalidArgumentException $e) { + return false; + } + + $source_file_path = $source ? $source->getFilePath() : $source_file_path; + + $calling_class_name = $source ? $source->getFQCLN() : null; + + if (!$calling_class_name && $calling_method_id) { + $calling_class_name = explode('::', $calling_method_id)[0]; + } + + if (isset($class_storage->declaring_method_ids[$method_name])) { + $declaring_method_id = $class_storage->declaring_method_ids[$method_name]; + + if ($calling_method_id === strtolower((string) $declaring_method_id)) { + return true; + } + + $declaring_fq_class_name = strtolower($declaring_method_id->fq_class_name); + + if ($declaring_fq_class_name !== strtolower((string) $calling_class_name)) { + if ($calling_method_id) { + $this->file_reference_provider->addMethodReferenceToClass( + $calling_method_id, + $declaring_fq_class_name + ); + } elseif ($source_file_path) { + $this->file_reference_provider->addNonMethodReferenceToClass( + $source_file_path, + $declaring_fq_class_name + ); + } + } + + if ((string) $method_id !== (string) $declaring_method_id + && $class_storage->user_defined + && isset($class_storage->potential_declaring_method_ids[$method_name]) + ) { + foreach ($class_storage->potential_declaring_method_ids[$method_name] as $potential_id => $_) { + if ($calling_method_id) { + $this->file_reference_provider->addMethodReferenceToClassMember( + $calling_method_id, + $potential_id + ); + } elseif ($source_file_path) { + $this->file_reference_provider->addFileReferenceToClassMember( + $source_file_path, + $potential_id + ); + } + } + } else { + if ($calling_method_id) { + $this->file_reference_provider->addMethodReferenceToClassMember( + $calling_method_id, + strtolower((string) $declaring_method_id) + ); + } elseif ($source_file_path) { + $this->file_reference_provider->addFileReferenceToClassMember( + $source_file_path, + strtolower((string) $declaring_method_id) + ); + } + } + + if ($this->collect_locations && $code_location) { + $this->file_reference_provider->addCallingLocationForClassMethod( + $code_location, + strtolower((string) $declaring_method_id) + ); + } + + foreach ($class_storage->class_implements as $fq_interface_name) { + $interface_method_id_lc = strtolower($fq_interface_name . '::' . $method_name); + + if ($this->collect_locations && $code_location) { + $this->file_reference_provider->addCallingLocationForClassMethod( + $code_location, + $interface_method_id_lc + ); + } + + if ($calling_method_id) { + $this->file_reference_provider->addMethodReferenceToClassMember( + $calling_method_id, + $interface_method_id_lc + ); + } elseif ($source_file_path) { + $this->file_reference_provider->addFileReferenceToClassMember( + $source_file_path, + $interface_method_id_lc + ); + } + } + + $declaring_method_class = $declaring_method_id->fq_class_name; + $declaring_method_name = $declaring_method_id->method_name; + + $declaring_class_storage = $this->classlike_storage_provider->get($declaring_method_class); + + if (isset($declaring_class_storage->overridden_method_ids[$declaring_method_name])) { + $overridden_method_ids = $declaring_class_storage->overridden_method_ids[$declaring_method_name]; + + foreach ($overridden_method_ids as $overridden_method_id) { + if ($this->collect_locations && $code_location) { + $this->file_reference_provider->addCallingLocationForClassMethod( + $code_location, + strtolower((string) $overridden_method_id) + ); + } + + if ($calling_method_id) { + // also store failures in case the method is added later + $this->file_reference_provider->addMethodReferenceToClassMember( + $calling_method_id, + strtolower((string) $overridden_method_id) + ); + } elseif ($source_file_path) { + $this->file_reference_provider->addFileReferenceToClassMember( + $source_file_path, + strtolower((string) $overridden_method_id) + ); + } + } + } + + return true; + } + + if ($source_file_path && $fq_class_name !== strtolower((string) $calling_class_name)) { + if ($calling_method_id) { + $this->file_reference_provider->addMethodReferenceToClass( + $calling_method_id, + $fq_class_name + ); + } else { + $this->file_reference_provider->addNonMethodReferenceToClass( + $source_file_path, + $fq_class_name + ); + } + } + + if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$method_name])) { + return true; + } + + // support checking oldstyle constructors + if ($method_name === '__construct') { + $method_name_parts = explode('\\', $fq_class_name); + $old_constructor_name = array_pop($method_name_parts); + $old_method_id = $fq_class_name . '::' . $old_constructor_name; + } + + if (!$class_storage->user_defined + && (InternalCallMapHandler::inCallMap((string) $method_id) + || ($old_method_id && InternalCallMapHandler::inCallMap($old_method_id))) + ) { + return true; + } + + foreach ($class_storage->parent_classes + $class_storage->used_traits as $potential_future_declaring_fqcln) { + $potential_id = strtolower($potential_future_declaring_fqcln) . '::' . $method_name; + + if ($calling_method_id) { + // also store failures in case the method is added later + $this->file_reference_provider->addMethodReferenceToMissingClassMember( + $calling_method_id, + $potential_id + ); + } elseif ($source_file_path) { + $this->file_reference_provider->addFileReferenceToMissingClassMember( + $source_file_path, + $potential_id + ); + } + } + + if ($calling_method_id) { + // also store failures in case the method is added later + $this->file_reference_provider->addMethodReferenceToMissingClassMember( + $calling_method_id, + strtolower((string) $method_id) + ); + } elseif ($source_file_path) { + $this->file_reference_provider->addFileReferenceToMissingClassMember( + $source_file_path, + strtolower((string) $method_id) + ); + } + + return false; + } + + /** + * @param array $args + * + * @return array + */ + public function getMethodParams( + MethodIdentifier $method_id, + ?StatementsSource $source = null, + ?array $args = null, + ?Context $context = null + ) : array { + $fq_class_name = $method_id->fq_class_name; + $method_name = $method_id->method_name; + + if ($this->params_provider->has($fq_class_name)) { + $method_params = $this->params_provider->getMethodParams( + $fq_class_name, + $method_name, + $args, + $source, + $context + ); + + if ($method_params !== null) { + return $method_params; + } + } + + $declaring_method_id = $this->getDeclaringMethodId($method_id); + + $callmap_id = $declaring_method_id ?: $method_id; + + // functions + if (InternalCallMapHandler::inCallMap((string) $callmap_id)) { + $class_storage = $this->classlike_storage_provider->get($callmap_id->fq_class_name); + + if (!$class_storage->stubbed) { + $function_callables = InternalCallMapHandler::getCallablesFromCallMap((string) $callmap_id); + + if ($function_callables === null) { + throw new \UnexpectedValueException( + 'Not expecting $function_callables to be null for ' . $callmap_id + ); + } + + if (!$source || $args === null || count($function_callables) === 1) { + assert($function_callables[0]->params !== null); + + return $function_callables[0]->params; + } + + if ($context && $source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + $was_inside_call = $context->inside_call; + + $context->inside_call = true; + + foreach ($args as $arg) { + \Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze( + $source, + $arg->value, + $context + ); + } + + if (!$was_inside_call) { + $context->inside_call = false; + } + } + + $matching_callable = InternalCallMapHandler::getMatchingCallableFromCallMapOptions( + $source->getCodebase(), + $function_callables, + $args, + $source->getNodeTypeProvider(), + (string) $callmap_id + ); + + assert($matching_callable->params !== null); + + return $matching_callable->params; + } + } + + if ($declaring_method_id) { + $storage = $this->getStorage($declaring_method_id); + + $params = $storage->params; + + if ($storage->has_docblock_param_types) { + return $params; + } + + $appearing_method_id = $this->getAppearingMethodId($declaring_method_id); + + if (!$appearing_method_id) { + return $params; + } + + $appearing_fq_class_name = $appearing_method_id->fq_class_name; + $appearing_method_name = $appearing_method_id->method_name; + + $class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name); + + if (!isset($class_storage->overridden_method_ids[$appearing_method_name])) { + return $params; + } + + if (!isset($class_storage->documenting_method_ids[$appearing_method_name])) { + return $params; + } + + $overridden_method_id = $class_storage->documenting_method_ids[$appearing_method_name]; + + $overridden_storage = $this->getStorage($overridden_method_id); + + $overriding_fq_class_name = $overridden_method_id->fq_class_name; + + foreach ($params as $i => $param) { + if (isset($overridden_storage->params[$i]->type) + && $overridden_storage->params[$i]->has_docblock_type + ) { + $params[$i] = clone $param; + /** @var Type\Union $params[$i]->type */ + $params[$i]->type = clone $overridden_storage->params[$i]->type; + + if ($source) { + $overridden_class_storage = $this->classlike_storage_provider->get($overriding_fq_class_name); + $params[$i]->type = self::localizeType( + $source->getCodebase(), + $params[$i]->type, + $appearing_fq_class_name, + $overridden_class_storage->name + ); + } + + if ($params[$i]->signature_type + && $params[$i]->signature_type->isNullable() + ) { + $params[$i]->type->addType(new Type\Atomic\TNull); + } + + $params[$i]->type_location = $overridden_storage->params[$i]->type_location; + } + } + + return $params; + } + + throw new \UnexpectedValueException('Cannot get method params for ' . $method_id); + } + + public static function localizeType( + Codebase $codebase, + Type\Union $type, + string $appearing_fq_class_name, + string $base_fq_class_name + ) : Type\Union { + $class_storage = $codebase->classlike_storage_provider->get($appearing_fq_class_name); + $extends = $class_storage->template_type_extends; + + if (!$extends) { + return $type; + } + + $type = clone $type; + + foreach ($type->getAtomicTypes() as $key => $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TTemplateParam + && ($atomic_type->defining_class === $base_fq_class_name + || isset($extends[$atomic_type->defining_class])) + ) { + $types_to_add = self::getExtendedTemplatedTypes( + $atomic_type, + $extends + ); + + if ($types_to_add) { + $type->removeType($key); + + foreach ($types_to_add as $extra_added_type) { + $type->addType($extra_added_type); + } + } + } + + if ($atomic_type instanceof Type\Atomic\TTemplateParamClass) { + if ($atomic_type->defining_class === $base_fq_class_name) { + if (isset($extends[$base_fq_class_name][$atomic_type->param_name])) { + $extended_param = $extends[$base_fq_class_name][$atomic_type->param_name]; + + $types = \array_values($extended_param->getAtomicTypes()); + + if (count($types) === 1 && $types[0] instanceof Type\Atomic\TNamedObject) { + $atomic_type->as_type = $types[0]; + } else { + $atomic_type->as_type = null; + } + } + } + } + + if ($atomic_type instanceof Type\Atomic\TArray + || $atomic_type instanceof Type\Atomic\TIterable + || $atomic_type instanceof Type\Atomic\TGenericObject + ) { + foreach ($atomic_type->type_params as &$type_param) { + $type_param = self::localizeType( + $codebase, + $type_param, + $appearing_fq_class_name, + $base_fq_class_name + ); + } + } + + if ($atomic_type instanceof Type\Atomic\TList) { + $atomic_type->type_param = self::localizeType( + $codebase, + $atomic_type->type_param, + $appearing_fq_class_name, + $base_fq_class_name + ); + } + + if ($atomic_type instanceof Type\Atomic\TKeyedArray) { + foreach ($atomic_type->properties as &$property_type) { + $property_type = self::localizeType( + $codebase, + $property_type, + $appearing_fq_class_name, + $base_fq_class_name + ); + } + } + + if ($atomic_type instanceof Type\Atomic\TCallable + || $atomic_type instanceof Type\Atomic\TClosure + ) { + if ($atomic_type->params) { + foreach ($atomic_type->params as $param) { + if ($param->type) { + $param->type = self::localizeType( + $codebase, + $param->type, + $appearing_fq_class_name, + $base_fq_class_name + ); + } + } + } + + if ($atomic_type->return_type) { + $atomic_type->return_type = self::localizeType( + $codebase, + $atomic_type->return_type, + $appearing_fq_class_name, + $base_fq_class_name + ); + } + } + } + + $type->bustCache(); + + return $type; + } + + /** + * @param array> $extends + * @return list + */ + public static function getExtendedTemplatedTypes( + Type\Atomic\TTemplateParam $atomic_type, + array $extends + ) : array { + $extra_added_types = []; + + if (isset($extends[$atomic_type->defining_class][$atomic_type->param_name])) { + $extended_param = clone $extends[$atomic_type->defining_class][$atomic_type->param_name]; + + foreach ($extended_param->getAtomicTypes() as $extended_atomic_type) { + if ($extended_atomic_type instanceof Type\Atomic\TTemplateParam) { + $extra_added_types = \array_merge( + $extra_added_types, + self::getExtendedTemplatedTypes( + $extended_atomic_type, + $extends + ) + ); + } else { + $extra_added_types[] = $extended_atomic_type; + } + } + } else { + $extra_added_types[] = $atomic_type; + } + + return $extra_added_types; + } + + public function isVariadic(MethodIdentifier $method_id): bool + { + $declaring_method_id = $this->getDeclaringMethodId($method_id); + + if (!$declaring_method_id) { + return false; + } + + return $this->getStorage($declaring_method_id)->variadic; + } + + /** + * @param array|null $args + * + */ + public function getMethodReturnType( + MethodIdentifier $method_id, + ?string &$self_class, + ?\Psalm\Internal\Analyzer\SourceAnalyzer $source_analyzer = null, + ?array $args = null + ): ?Type\Union { + $original_fq_class_name = $method_id->fq_class_name; + $original_method_name = $method_id->method_name; + + $adjusted_fq_class_name = $this->classlikes->getUnAliasedName($original_fq_class_name); + + if ($adjusted_fq_class_name !== $original_fq_class_name) { + $original_fq_class_name = strtolower($adjusted_fq_class_name); + } + + $original_class_storage = $this->classlike_storage_provider->get($original_fq_class_name); + + if (isset($original_class_storage->pseudo_methods[$original_method_name])) { + return $original_class_storage->pseudo_methods[$original_method_name]->return_type; + } + + $declaring_method_id = $this->getDeclaringMethodId($method_id); + + if (!$declaring_method_id) { + return null; + } + + $appearing_method_id = $this->getAppearingMethodId($method_id); + + if (!$appearing_method_id) { + $class_storage = $this->classlike_storage_provider->get($original_fq_class_name); + + if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$original_method_name])) { + $appearing_method_id = reset($class_storage->overridden_method_ids[$original_method_name]); + } else { + return null; + } + } + + $appearing_fq_class_name = $appearing_method_id->fq_class_name; + $appearing_method_name = $appearing_method_id->method_name; + + $appearing_fq_class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name); + + if (!$appearing_fq_class_storage->user_defined + && !$appearing_fq_class_storage->stubbed + && InternalCallMapHandler::inCallMap((string) $appearing_method_id) + ) { + if ((string) $appearing_method_id === 'Closure::fromcallable' + && isset($args[0]) + && $source_analyzer + && ($first_arg_type = $source_analyzer->getNodeTypeProvider()->getType($args[0]->value)) + && $first_arg_type->isSingle() + ) { + foreach ($first_arg_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TCallable + || $atomic_type instanceof Type\Atomic\TClosure + ) { + $callable_type = clone $atomic_type; + + return new Type\Union([new Type\Atomic\TClosure( + 'Closure', + $callable_type->params, + $callable_type->return_type + )]); + } + + if ($atomic_type instanceof Type\Atomic\TNamedObject + && $this->methodExists( + new MethodIdentifier($atomic_type->value, '__invoke') + ) + ) { + $invokable_storage = $this->getStorage( + new MethodIdentifier($atomic_type->value, '__invoke') + ); + + return new Type\Union([new Type\Atomic\TClosure( + 'Closure', + $invokable_storage->params, + $invokable_storage->return_type + )]); + } + } + } + + $callmap_callables = InternalCallMapHandler::getCallablesFromCallMap((string) $appearing_method_id); + + if (!$callmap_callables || $callmap_callables[0]->return_type === null) { + throw new \UnexpectedValueException('Shouldn’t get here'); + } + + $return_type_candidate = $callmap_callables[0]->return_type; + + if ($return_type_candidate->isFalsable()) { + $return_type_candidate->ignore_falsable_issues = true; + } + + return $return_type_candidate; + } + + $storage = $this->getStorage($declaring_method_id); + + if ($storage->return_type) { + $self_class = $appearing_fq_class_storage->name; + + return clone $storage->return_type; + } + + $class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name); + + if (!isset($class_storage->overridden_method_ids[$appearing_method_name])) { + return null; + } + + $candidate_type = null; + + foreach ($class_storage->overridden_method_ids[$appearing_method_name] as $overridden_method_id) { + $overridden_storage = $this->getStorage($overridden_method_id); + + if ($overridden_storage->return_type) { + if ($overridden_storage->return_type->isNull()) { + if ($candidate_type && !$candidate_type->isVoid()) { + return null; + } + + $candidate_type = Type::getVoid(); + continue; + } + + $fq_overridden_class = $overridden_method_id->fq_class_name; + + $overridden_class_storage = + $this->classlike_storage_provider->get($fq_overridden_class); + + $overridden_return_type = clone $overridden_storage->return_type; + + $self_class = $overridden_class_storage->name; + + if ($candidate_type + && $source_analyzer + ) { + $old_contained_by_new = UnionTypeComparator::isContainedBy( + $source_analyzer->getCodebase(), + $candidate_type, + $overridden_return_type + ); + + $new_contained_by_old = UnionTypeComparator::isContainedBy( + $source_analyzer->getCodebase(), + $overridden_return_type, + $candidate_type + ); + + if (!$old_contained_by_new && !$new_contained_by_old) { + $attempted_intersection = Type::intersectUnionTypes( + $candidate_type, + $overridden_return_type, + $source_analyzer->getCodebase() + ); + + if ($attempted_intersection) { + $candidate_type = $attempted_intersection; + continue; + } + + return null; + } + + if ($old_contained_by_new) { + continue; + } + } + + $candidate_type = $overridden_return_type; + } + } + + return $candidate_type; + } + + public function getMethodReturnsByRef(MethodIdentifier $method_id): bool + { + $method_id = $this->getDeclaringMethodId($method_id); + + if (!$method_id) { + return false; + } + + $fq_class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name); + + if (!$fq_class_storage->user_defined && InternalCallMapHandler::inCallMap((string) $method_id)) { + return false; + } + + $storage = $this->getStorage($method_id); + + return $storage->returns_by_ref; + } + + /** + * @param CodeLocation|null $defined_location + * + */ + public function getMethodReturnTypeLocation( + MethodIdentifier $method_id, + CodeLocation &$defined_location = null + ): ?CodeLocation { + $method_id = $this->getDeclaringMethodId($method_id); + + if ($method_id === null) { + return null; + } + + $storage = $this->getStorage($method_id); + + if (!$storage->return_type_location) { + $overridden_method_ids = $this->getOverriddenMethodIds($method_id); + + foreach ($overridden_method_ids as $overridden_method_id) { + $overridden_storage = $this->getStorage($overridden_method_id); + + if ($overridden_storage->return_type_location) { + $defined_location = $overridden_storage->return_type_location; + break; + } + } + } + + return $storage->return_type_location; + } + + /** + * @param lowercase-string $method_name_lc + * @param lowercase-string $declaring_method_name_lc + * + */ + public function setDeclaringMethodId( + string $fq_class_name, + string $method_name_lc, + string $declaring_fq_class_name, + string $declaring_method_name_lc + ): void { + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + + $class_storage->declaring_method_ids[$method_name_lc] = new MethodIdentifier( + $declaring_fq_class_name, + $declaring_method_name_lc + ); + } + + /** + * @param lowercase-string $method_name_lc + * @param lowercase-string $appearing_method_name_lc + * + */ + public function setAppearingMethodId( + string $fq_class_name, + string $method_name_lc, + string $appearing_fq_class_name, + string $appearing_method_name_lc + ): void { + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + + $class_storage->appearing_method_ids[$method_name_lc] = new MethodIdentifier( + $appearing_fq_class_name, + $appearing_method_name_lc + ); + } + + public function getDeclaringMethodId( + MethodIdentifier $method_id + ) : ?MethodIdentifier { + $fq_class_name = $this->classlikes->getUnAliasedName($method_id->fq_class_name); + + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + + $method_name = $method_id->method_name; + + if (isset($class_storage->declaring_method_ids[$method_name])) { + return $class_storage->declaring_method_ids[$method_name]; + } + + if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$method_name])) { + return reset($class_storage->overridden_method_ids[$method_name]); + } + + return null; + } + + /** + * Get the class this method appears in (vs is declared in, which could give a trait + */ + public function getAppearingMethodId( + MethodIdentifier $method_id + ) : ?MethodIdentifier { + $fq_class_name = $this->classlikes->getUnAliasedName($method_id->fq_class_name); + + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + + $method_name = $method_id->method_name; + + if (isset($class_storage->appearing_method_ids[$method_name])) { + return $class_storage->appearing_method_ids[$method_name]; + } + + return null; + } + + /** + * @return array + */ + public function getOverriddenMethodIds(MethodIdentifier $method_id): array + { + $class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name); + $method_name = $method_id->method_name; + + if (isset($class_storage->overridden_method_ids[$method_name])) { + return $class_storage->overridden_method_ids[$method_name]; + } + + return []; + } + + public function getCasedMethodId(MethodIdentifier $original_method_id): string + { + $method_id = $this->getDeclaringMethodId($original_method_id); + + if ($method_id === null) { + return $original_method_id; + } + + $fq_class_name = $method_id->fq_class_name; + $new_method_name = $method_id->method_name; + + $old_fq_class_name = $original_method_id->fq_class_name; + $old_method_name = $original_method_id->method_name; + + $storage = $this->getStorage($method_id); + + if ($old_method_name === $new_method_name + && strtolower($old_fq_class_name) !== $old_fq_class_name + ) { + return $old_fq_class_name . '::' . $storage->cased_name; + } + + return $fq_class_name . '::' . $storage->cased_name; + } + + public function getUserMethodStorage(MethodIdentifier $method_id): ?MethodStorage + { + $declaring_method_id = $this->getDeclaringMethodId($method_id); + + if (!$declaring_method_id) { + throw new \UnexpectedValueException('$storage should not be null for ' . $method_id); + } + + $storage = $this->getStorage($declaring_method_id); + + if (!$storage->location) { + return null; + } + + return $storage; + } + + public function getClassLikeStorageForMethod(MethodIdentifier $method_id): ClassLikeStorage + { + $fq_class_name = $method_id->fq_class_name; + $method_name = $method_id->method_name; + + if ($this->existence_provider->has($fq_class_name)) { + if ($this->existence_provider->doesMethodExist( + $fq_class_name, + $method_name, + null, + null + )) { + return $this->classlike_storage_provider->get($fq_class_name); + } + } + + $declaring_method_id = $this->getDeclaringMethodId($method_id); + + if ($declaring_method_id === null) { + if (InternalCallMapHandler::inCallMap((string) $method_id)) { + $declaring_method_id = $method_id; + } else { + throw new \UnexpectedValueException('$storage should not be null for ' . $method_id); + } + } + + $declaring_fq_class_name = $declaring_method_id->fq_class_name; + + return $this->classlike_storage_provider->get($declaring_fq_class_name); + } + + public function getStorage(MethodIdentifier $method_id): MethodStorage + { + try { + $class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name); + } catch (\InvalidArgumentException $e) { + throw new \UnexpectedValueException($e->getMessage()); + } + + $method_name = $method_id->method_name; + + if (!isset($class_storage->methods[$method_name])) { + throw new \UnexpectedValueException( + '$storage should not be null for ' . $method_id + ); + } + + return $class_storage->methods[$method_name]; + } + + public function hasStorage(MethodIdentifier $method_id): bool + { + try { + $class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name); + } catch (\InvalidArgumentException $e) { + return false; + } + + $method_name = $method_id->method_name; + + if (!isset($class_storage->methods[$method_name])) { + return false; + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Populator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Populator.php new file mode 100644 index 0000000000000000000000000000000000000000..3c20018fb27b6c0c21ab1792d892fce3343965fd --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Populator.php @@ -0,0 +1,1228 @@ +> + */ + private $invalid_class_storages = []; + + /** + * @var Progress + */ + private $progress; + + /** + * @var ClassLikes + */ + private $classlikes; + + /** + * @var Config + */ + private $config; + + /** + * @var FileReferenceProvider + */ + private $file_reference_provider; + + public function __construct( + Config $config, + ClassLikeStorageProvider $classlike_storage_provider, + FileStorageProvider $file_storage_provider, + ClassLikes $classlikes, + FileReferenceProvider $file_reference_provider, + Progress $progress + ) { + $this->classlike_storage_provider = $classlike_storage_provider; + $this->file_storage_provider = $file_storage_provider; + $this->classlikes = $classlikes; + $this->progress = $progress; + $this->config = $config; + $this->file_reference_provider = $file_reference_provider; + } + + public function populateCodebase(): void + { + $this->progress->debug('ClassLikeStorage is populating' . "\n"); + + foreach ($this->classlike_storage_provider->getNew() as $class_storage) { + $this->populateClassLikeStorage($class_storage); + } + + $this->progress->debug('ClassLikeStorage is populated' . "\n"); + + $this->progress->debug('FileStorage is populating' . "\n"); + + $all_file_storage = $this->file_storage_provider->getNew(); + + foreach ($all_file_storage as $file_storage) { + $this->populateFileStorage($file_storage); + } + + foreach ($this->classlike_storage_provider->getNew() as $class_storage) { + if ($this->config->allow_phpstorm_generics) { + foreach ($class_storage->properties as $property_storage) { + if ($property_storage->type) { + $this->convertPhpStormGenericToPsalmGeneric($property_storage->type, true); + } + } + + foreach ($class_storage->methods as $method_storage) { + if ($method_storage->return_type) { + $this->convertPhpStormGenericToPsalmGeneric($method_storage->return_type); + } + + foreach ($method_storage->params as $param_storage) { + if ($param_storage->type) { + $this->convertPhpStormGenericToPsalmGeneric($param_storage->type); + } + } + } + } + + foreach ($class_storage->dependent_classlikes as $dependent_classlike_lc => $_) { + $dependee_storage = $this->classlike_storage_provider->get($dependent_classlike_lc); + + $class_storage->dependent_classlikes += $dependee_storage->dependent_classlikes; + } + } + + if ($this->config->allow_phpstorm_generics) { + foreach ($all_file_storage as $file_storage) { + foreach ($file_storage->functions as $function_storage) { + if ($function_storage->return_type) { + $this->convertPhpStormGenericToPsalmGeneric($function_storage->return_type); + } + + foreach ($function_storage->params as $param_storage) { + if ($param_storage->type) { + $this->convertPhpStormGenericToPsalmGeneric($param_storage->type); + } + } + } + } + } + + $this->progress->debug('FileStorage is populated' . "\n"); + + $this->classlike_storage_provider->populated(); + $this->file_storage_provider->populated(); + } + + private function populateClassLikeStorage(ClassLikeStorage $storage, array $dependent_classlikes = []): void + { + if ($storage->populated) { + return; + } + + $fq_classlike_name_lc = strtolower($storage->name); + + if (isset($dependent_classlikes[$fq_classlike_name_lc])) { + if ($storage->location && IssueBuffer::accepts( + new CircularReference( + 'Circular reference discovered when loading ' . $storage->name, + $storage->location + ) + )) { + // fall through + } + + return; + } + + $storage_provider = $this->classlike_storage_provider; + + $dependent_classlikes[$fq_classlike_name_lc] = true; + + $this->populateDataFromTraits($storage, $storage_provider, $dependent_classlikes); + + if ($storage->parent_classes) { + $this->populateDataFromParentClass($storage, $storage_provider, $dependent_classlikes); + } + + if (!strpos($fq_classlike_name_lc, '\\') + && !isset($storage->methods['__construct']) + && isset($storage->methods[$fq_classlike_name_lc]) + && !$storage->is_interface + && !$storage->is_trait + ) { + /** @psalm-suppress PropertyTypeCoercion */ + $storage->methods['__construct'] = $storage->methods[$fq_classlike_name_lc]; + } + + $this->populateInterfaceDataFromParentInterfaces($storage, $storage_provider, $dependent_classlikes); + + $this->populateDataFromImplementedInterfaces($storage, $storage_provider, $dependent_classlikes); + + if ($storage->location) { + $file_path = $storage->location->file_path; + + foreach ($storage->parent_interfaces as $parent_interface_lc) { + $this->file_reference_provider->addFileInheritanceToClass($file_path, $parent_interface_lc); + } + + foreach ($storage->parent_classes as $parent_class_lc => $_) { + $this->file_reference_provider->addFileInheritanceToClass($file_path, $parent_class_lc); + } + + foreach ($storage->class_implements as $implemented_interface) { + $this->file_reference_provider->addFileInheritanceToClass( + $file_path, + strtolower($implemented_interface) + ); + } + + foreach ($storage->used_traits as $used_trait_lc => $_) { + $this->file_reference_provider->addFileInheritanceToClass($file_path, $used_trait_lc); + } + } + + if ($storage->mutation_free || $storage->external_mutation_free) { + foreach ($storage->methods as $method) { + if (!$method->is_static && !$method->external_mutation_free) { + $method->mutation_free = $storage->mutation_free; + $method->external_mutation_free = $storage->external_mutation_free; + } + } + + if ($storage->mutation_free) { + foreach ($storage->properties as $property) { + if (!$property->is_static) { + $property->readonly = true; + } + } + } + } + + if ($storage->specialize_instance) { + foreach ($storage->methods as $method) { + if (!$method->is_static) { + $method->specialize_call = true; + } + } + } + + if (!$storage->is_interface && !$storage->is_trait) { + foreach ($storage->methods as $method) { + if (strlen($storage->internal) > strlen($method->internal)) { + $method->internal = $storage->internal; + } + } + + + foreach ($storage->properties as $property) { + if (strlen($storage->internal) > strlen($property->internal)) { + $property->internal = $storage->internal; + } + } + } + + $this->populateOverriddenMethods($storage); + + $this->progress->debug('Have populated ' . $storage->name . "\n"); + + $storage->populated = true; + + if (isset($this->invalid_class_storages[$fq_classlike_name_lc])) { + foreach ($this->invalid_class_storages[$fq_classlike_name_lc] as $dependency) { + $dependency->populated = false; + $this->populateClassLikeStorage($dependency, $dependent_classlikes); + } + + unset($this->invalid_class_storages[$fq_classlike_name_lc]); + } + } + + private function populateOverriddenMethods( + ClassLikeStorage $storage + ): void { + foreach ($storage->methods as $method_name => $method_storage) { + if (isset($storage->overridden_method_ids[$method_name])) { + $overridden_method_ids = $storage->overridden_method_ids[$method_name]; + + $candidate_overridden_ids = null; + + $declaring_class_storages = []; + + foreach ($overridden_method_ids as $declaring_method_id) { + $declaring_class = $declaring_method_id->fq_class_name; + $declaring_class_storage + = $declaring_class_storages[$declaring_class] + = $this->classlike_storage_provider->get($declaring_class); + + if ($candidate_overridden_ids === null) { + $candidate_overridden_ids + = ($declaring_class_storage->overridden_method_ids[$method_name] ?? []) + + [$declaring_method_id->fq_class_name => $declaring_method_id]; + } else { + $candidate_overridden_ids = \array_intersect_key( + $candidate_overridden_ids, + ($declaring_class_storage->overridden_method_ids[$method_name] ?? []) + + [$declaring_method_id->fq_class_name => $declaring_method_id] + ); + } + } + + foreach ($overridden_method_ids as $declaring_method_id) { + $declaring_class = $declaring_method_id->fq_class_name; + $declaring_method_name = $declaring_method_id->method_name; + $declaring_class_storage = $declaring_class_storages[$declaring_class]; + + $declaring_method_storage = $declaring_class_storage->methods[$declaring_method_name]; + + if ($declaring_method_storage->has_docblock_param_types + && !$method_storage->has_docblock_param_types + && !isset($storage->documenting_method_ids[$method_name]) + ) { + $storage->documenting_method_ids[$method_name] = $declaring_method_id; + } + + // tell the declaring class it's overridden downstream + $declaring_method_storage->overridden_downstream = true; + $declaring_method_storage->overridden_somewhere = true; + + if ($declaring_method_storage->mutation_free_inferred) { + $declaring_method_storage->mutation_free = false; + $declaring_method_storage->external_mutation_free = false; + $declaring_method_storage->mutation_free_inferred = false; + } + + if ($declaring_method_storage->throws + && (!$method_storage->throws || $method_storage->inheritdoc) + ) { + $method_storage->throws += $declaring_method_storage->throws; + } + + if ((count($overridden_method_ids) === 1 + || $candidate_overridden_ids) + && $method_storage->signature_return_type + && !$method_storage->signature_return_type->isVoid() + && ($method_storage->return_type === $method_storage->signature_return_type + || $method_storage->inherited_return_type) + ) { + if (isset($declaring_class_storage->methods[$method_name])) { + $declaring_method_storage = $declaring_class_storage->methods[$method_name]; + + if ($declaring_method_storage->return_type + && $declaring_method_storage->return_type + !== $declaring_method_storage->signature_return_type + ) { + if ($declaring_method_storage->signature_return_type + && UnionTypeComparator::isSimplyContainedBy( + $method_storage->signature_return_type, + $declaring_method_storage->signature_return_type + ) + ) { + $method_storage->return_type = clone $declaring_method_storage->return_type; + $method_storage->inherited_return_type = true; + } elseif (UnionTypeComparator::isSimplyContainedBy( + $declaring_method_storage->return_type, + $method_storage->signature_return_type + )) { + $method_storage->return_type = clone $declaring_method_storage->return_type; + $method_storage->inherited_return_type = true; + $method_storage->return_type->from_docblock = false; + } + } + } + } + } + } + } + } + + private function populateDataFromTraits( + ClassLikeStorage $storage, + ClassLikeStorageProvider $storage_provider, + array $dependent_classlikes + ): void { + foreach ($storage->used_traits as $used_trait_lc => $_) { + try { + $used_trait_lc = strtolower( + $this->classlikes->getUnAliasedName( + $used_trait_lc + ) + ); + $trait_storage = $storage_provider->get($used_trait_lc); + } catch (\InvalidArgumentException $e) { + continue; + } + + $this->populateClassLikeStorage($trait_storage, $dependent_classlikes); + + $this->inheritMethodsFromParent($storage, $trait_storage); + $this->inheritPropertiesFromParent($storage, $trait_storage); + + if ($trait_storage->template_types) { + if (isset($storage->template_type_extends[$trait_storage->name])) { + foreach ($storage->template_type_extends[$trait_storage->name] as $i => $type) { + $trait_template_type_names = array_keys($trait_storage->template_types); + + $mapped_name = $trait_template_type_names[$i] ?? null; + + if ($mapped_name) { + $storage->template_type_extends[$trait_storage->name][$mapped_name] = $type; + } + } + + if ($trait_storage->template_type_extends) { + foreach ($trait_storage->template_type_extends as $t_storage_class => $type_map) { + foreach ($type_map as $i => $type) { + if (is_int($i)) { + continue; + } + + $storage->template_type_extends[$t_storage_class][$i] = self::extendType( + $type, + $storage + ); + } + } + } + } else { + $storage->template_type_extends[$trait_storage->name] = []; + + foreach ($trait_storage->template_types as $template_name => $template_type_map) { + foreach ($template_type_map as $template_type) { + $default_param = clone $template_type[0]; + $default_param->from_docblock = false; + $storage->template_type_extends[$trait_storage->name][$template_name] + = $default_param; + } + } + } + } elseif ($trait_storage->template_type_extends) { + $storage->template_type_extends = array_merge( + $storage->template_type_extends ?: [], + $trait_storage->template_type_extends + ); + } + + $storage->pseudo_property_get_types += $trait_storage->pseudo_property_get_types; + $storage->pseudo_property_set_types += $trait_storage->pseudo_property_set_types; + + $storage->pseudo_methods += $trait_storage->pseudo_methods; + } + } + + private static function extendType( + Type\Union $type, + ClassLikeStorage $storage + ) : Type\Union { + $extended_types = []; + + foreach ($type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TTemplateParam) { + $referenced_type + = $storage->template_type_extends[$atomic_type->defining_class][$atomic_type->param_name] + ?? null; + + if ($referenced_type) { + foreach ($referenced_type->getAtomicTypes() as $atomic_referenced_type) { + if (!$atomic_referenced_type instanceof Type\Atomic\TTemplateParam) { + $extended_types[] = $atomic_referenced_type; + } else { + $extended_types[] = $atomic_type; + } + } + } else { + $extended_types[] = $atomic_type; + } + } else { + $extended_types[] = $atomic_type; + } + } + + return new Type\Union($extended_types); + } + + private function populateDataFromParentClass( + ClassLikeStorage $storage, + ClassLikeStorageProvider $storage_provider, + array $dependent_classlikes + ): void { + $parent_storage_class = reset($storage->parent_classes); + + $parent_storage_class = strtolower( + $this->classlikes->getUnAliasedName( + $parent_storage_class + ) + ); + + try { + $parent_storage = $storage_provider->get($parent_storage_class); + } catch (\InvalidArgumentException $e) { + $this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n"); + + $storage->invalid_dependencies[] = $parent_storage_class; + + $this->invalid_class_storages[strtolower($parent_storage_class)][] = $storage; + + return; + } + + $this->populateClassLikeStorage($parent_storage, $dependent_classlikes); + + $storage->parent_classes = array_merge($storage->parent_classes, $parent_storage->parent_classes); + + if ($parent_storage->template_types) { + if (isset($storage->template_type_extends[$parent_storage->name])) { + foreach ($storage->template_type_extends[$parent_storage->name] as $i => $type) { + $parent_template_type_names = array_keys($parent_storage->template_types); + + $mapped_name = $parent_template_type_names[$i] ?? null; + + if ($mapped_name) { + $storage->template_type_extends[$parent_storage->name][$mapped_name] = $type; + } + } + + if ($parent_storage->template_type_extends) { + foreach ($parent_storage->template_type_extends as $t_storage_class => $type_map) { + foreach ($type_map as $i => $type) { + if (is_int($i)) { + continue; + } + + $storage->template_type_extends[$t_storage_class][$i] = self::extendType( + $type, + $storage + ); + } + } + } + } else { + $storage->template_type_extends[$parent_storage->name] = []; + + foreach ($parent_storage->template_types as $template_name => $template_type_map) { + foreach ($template_type_map as $template_type) { + $default_param = clone $template_type[0]; + $default_param->from_docblock = false; + $storage->template_type_extends[$parent_storage->name][$template_name] + = $default_param; + } + } + + if ($parent_storage->template_type_extends) { + $storage->template_type_extends = array_merge( + $storage->template_type_extends, + $parent_storage->template_type_extends + ); + } + } + } elseif ($parent_storage->template_type_extends) { + $storage->template_type_extends = array_merge( + $storage->template_type_extends ?: [], + $parent_storage->template_type_extends + ); + } + + $this->inheritMethodsFromParent($storage, $parent_storage); + $this->inheritPropertiesFromParent($storage, $parent_storage); + + $storage->class_implements = array_merge($storage->class_implements, $parent_storage->class_implements); + $storage->invalid_dependencies = array_merge( + $storage->invalid_dependencies, + $parent_storage->invalid_dependencies + ); + + if ($parent_storage->has_visitor_issues) { + $storage->has_visitor_issues = true; + } + + $storage->constants = array_merge( + \array_filter( + $parent_storage->constants, + function ($constant) { + return $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC + || $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED; + } + ), + $storage->constants + ); + + if ($parent_storage->preserve_constructor_signature) { + $storage->preserve_constructor_signature = true; + } + + if (($parent_storage->namedMixins || $parent_storage->templatedMixins) + && (!$storage->namedMixins || !$storage->templatedMixins)) { + $storage->mixin_declaring_fqcln = $parent_storage->mixin_declaring_fqcln; + + if (!$storage->namedMixins) { + $storage->namedMixins = $parent_storage->namedMixins; + } + + if (!$storage->templatedMixins) { + $storage->templatedMixins = $parent_storage->templatedMixins; + } + } + + $storage->pseudo_property_get_types += $parent_storage->pseudo_property_get_types; + $storage->pseudo_property_set_types += $parent_storage->pseudo_property_set_types; + + $parent_storage->dependent_classlikes[strtolower($storage->name)] = true; + + $storage->pseudo_methods += $parent_storage->pseudo_methods; + } + + private function populateInterfaceDataFromParentInterfaces( + ClassLikeStorage $storage, + ClassLikeStorageProvider $storage_provider, + array $dependent_classlikes + ): void { + $parent_interfaces = []; + + foreach ($storage->parent_interfaces as $parent_interface_lc => $_) { + try { + $parent_interface_lc = strtolower( + $this->classlikes->getUnAliasedName( + $parent_interface_lc + ) + ); + $parent_interface_storage = $storage_provider->get($parent_interface_lc); + } catch (\InvalidArgumentException $e) { + $this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n"); + + $storage->invalid_dependencies[] = $parent_interface_lc; + continue; + } + + $this->populateClassLikeStorage($parent_interface_storage, $dependent_classlikes); + + // copy over any constants + $storage->constants = array_merge( + \array_filter( + $parent_interface_storage->constants, + function ($constant) { + return $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC; + } + ), + $storage->constants + ); + + $storage->invalid_dependencies = array_merge( + $storage->invalid_dependencies, + $parent_interface_storage->invalid_dependencies + ); + + if ($parent_interface_storage->template_types) { + if (isset($storage->template_type_extends[$parent_interface_storage->name])) { + foreach ($storage->template_type_extends[$parent_interface_storage->name] as $i => $type) { + $parent_template_type_names = array_keys($parent_interface_storage->template_types); + + $mapped_name = $parent_template_type_names[$i] ?? null; + + if ($mapped_name) { + $storage->template_type_extends[$parent_interface_storage->name][$mapped_name] = $type; + } + } + + if ($parent_interface_storage->template_type_extends) { + foreach ($parent_interface_storage->template_type_extends as $t_storage_class => $type_map) { + foreach ($type_map as $i => $type) { + if (is_int($i)) { + continue; + } + + $storage->template_type_extends[$t_storage_class][$i] = self::extendType( + $type, + $storage + ); + } + } + } + } else { + $storage->template_type_extends[$parent_interface_storage->name] = []; + + foreach ($parent_interface_storage->template_types as $template_name => $template_type_map) { + foreach ($template_type_map as $template_type) { + $default_param = clone $template_type[0]; + $default_param->from_docblock = false; + $storage->template_type_extends[$parent_interface_storage->name][$template_name] + = $default_param; + } + } + } + } elseif ($parent_interface_storage->template_type_extends) { + $storage->template_type_extends = array_merge( + $storage->template_type_extends ?: [], + $parent_interface_storage->template_type_extends + ); + } + + $parent_interface_storage->dependent_classlikes[strtolower($storage->name)] = true; + + $parent_interfaces = array_merge($parent_interfaces, $parent_interface_storage->parent_interfaces); + + $this->inheritMethodsFromParent($storage, $parent_interface_storage); + + $storage->pseudo_methods += $parent_interface_storage->pseudo_methods; + } + + $storage->parent_interfaces = array_merge($parent_interfaces, $storage->parent_interfaces); + } + + private function populateDataFromImplementedInterfaces( + ClassLikeStorage $storage, + ClassLikeStorageProvider $storage_provider, + array $dependent_classlikes + ): void { + $extra_interfaces = []; + + foreach ($storage->class_implements as $implemented_interface_lc => $_) { + try { + $implemented_interface_lc = strtolower( + $this->classlikes->getUnAliasedName( + $implemented_interface_lc + ) + ); + $implemented_interface_storage = $storage_provider->get($implemented_interface_lc); + } catch (\InvalidArgumentException $e) { + $this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n"); + + $storage->invalid_dependencies[] = $implemented_interface_lc; + continue; + } + + $this->populateClassLikeStorage($implemented_interface_storage, $dependent_classlikes); + + // copy over any constants + $storage->constants = array_merge( + \array_filter( + $implemented_interface_storage->constants, + function ($constant) { + return $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC; + } + ), + $storage->constants + ); + + $storage->invalid_dependencies = array_merge( + $storage->invalid_dependencies, + $implemented_interface_storage->invalid_dependencies + ); + + if ($implemented_interface_storage->template_types) { + if (isset($storage->template_type_extends[$implemented_interface_storage->name])) { + foreach ($storage->template_type_extends[$implemented_interface_storage->name] as $i => $type) { + $parent_template_type_names = array_keys($implemented_interface_storage->template_types); + + $mapped_name = $parent_template_type_names[$i] ?? null; + + if ($mapped_name) { + $storage->template_type_extends[$implemented_interface_storage->name][$mapped_name] = $type; + } + } + + if ($implemented_interface_storage->template_type_extends) { + foreach ($implemented_interface_storage->template_type_extends as $e_i => $type_map) { + foreach ($type_map as $i => $type) { + if (is_int($i)) { + continue; + } + + $storage->template_type_extends[$e_i][$i] = self::extendType( + $type, + $storage + ); + } + } + } + } else { + $storage->template_type_extends[$implemented_interface_storage->name] = []; + + foreach ($implemented_interface_storage->template_types as $template_name => $template_type_map) { + foreach ($template_type_map as $template_type) { + $default_param = clone $template_type[0]; + $default_param->from_docblock = false; + $storage->template_type_extends[$implemented_interface_storage->name][$template_name] + = $default_param; + } + } + } + } elseif ($implemented_interface_storage->template_type_extends) { + $storage->template_type_extends = array_merge( + $storage->template_type_extends ?: [], + $implemented_interface_storage->template_type_extends + ); + } + + $extra_interfaces = array_merge($extra_interfaces, $implemented_interface_storage->parent_interfaces); + } + + $storage->class_implements = array_merge($storage->class_implements, $extra_interfaces); + + $interface_method_implementers = []; + + foreach ($storage->class_implements as $implemented_interface_lc => $_) { + try { + $implemented_interface = strtolower( + $this->classlikes->getUnAliasedName( + $implemented_interface_lc + ) + ); + $implemented_interface_storage = $storage_provider->get($implemented_interface); + } catch (\InvalidArgumentException $e) { + continue; + } + + $implemented_interface_storage->dependent_classlikes[strtolower($storage->name)] = true; + + foreach ($implemented_interface_storage->methods as $method_name => $method) { + if ($method->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC) { + $interface_method_implementers[$method_name][] = new \Psalm\Internal\MethodIdentifier( + $implemented_interface_storage->name, + $method_name + ); + } + } + } + + foreach ($interface_method_implementers as $method_name => $interface_method_ids) { + if (count($interface_method_ids) === 1) { + if (isset($storage->methods[$method_name])) { + $method_storage = $storage->methods[$method_name]; + + if ($method_storage->signature_return_type + && !$method_storage->signature_return_type->isVoid() + && $method_storage->return_type === $method_storage->signature_return_type + ) { + $interface_fqcln = $interface_method_ids[0]->fq_class_name; + $interface_storage = $storage_provider->get($interface_fqcln); + + if (isset($interface_storage->methods[$method_name])) { + $interface_method_storage = $interface_storage->methods[$method_name]; + + if ($interface_method_storage->throws + && (!$method_storage->throws || $method_storage->inheritdoc) + ) { + $method_storage->throws += $interface_method_storage->throws; + } + + if ($interface_method_storage->return_type + && $interface_method_storage->signature_return_type + && $interface_method_storage->return_type + !== $interface_method_storage->signature_return_type + && UnionTypeComparator::isSimplyContainedBy( + $interface_method_storage->signature_return_type, + $method_storage->signature_return_type + ) + ) { + $method_storage->return_type = $interface_method_storage->return_type; + $method_storage->inherited_return_type = true; + } + } + } + } + } + + foreach ($interface_method_ids as $interface_method_id) { + $storage->overridden_method_ids[$method_name][$interface_method_id->fq_class_name] + = $interface_method_id; + } + } + } + + /** + * @param array $dependent_file_paths + */ + private function populateFileStorage(FileStorage $storage, array $dependent_file_paths = []): void + { + if ($storage->populated) { + return; + } + + $file_path_lc = strtolower($storage->file_path); + + if (isset($dependent_file_paths[$file_path_lc])) { + return; + } + + $dependent_file_paths[$file_path_lc] = true; + + $all_required_file_paths = $storage->required_file_paths; + + foreach ($storage->required_file_paths as $included_file_path => $_) { + try { + $included_file_storage = $this->file_storage_provider->get($included_file_path); + } catch (\InvalidArgumentException $e) { + continue; + } + + $this->populateFileStorage($included_file_storage, $dependent_file_paths); + + $all_required_file_paths = $all_required_file_paths + $included_file_storage->required_file_paths; + } + + foreach ($all_required_file_paths as $included_file_path => $_) { + try { + $included_file_storage = $this->file_storage_provider->get($included_file_path); + } catch (\InvalidArgumentException $e) { + continue; + } + + $storage->declaring_function_ids = array_merge( + $included_file_storage->declaring_function_ids, + $storage->declaring_function_ids + ); + + $storage->declaring_constants = array_merge( + $included_file_storage->declaring_constants, + $storage->declaring_constants + ); + } + + foreach ($storage->referenced_classlikes as $fq_class_name) { + try { + $classlike_storage = $this->classlike_storage_provider->get($fq_class_name); + } catch (\InvalidArgumentException $e) { + continue; + } + + if (!$classlike_storage->location) { + continue; + } + + try { + $included_file_storage = $this->file_storage_provider->get($classlike_storage->location->file_path); + } catch (\InvalidArgumentException $e) { + continue; + } + + foreach ($classlike_storage->used_traits as $used_trait) { + try { + $trait_storage = $this->classlike_storage_provider->get($used_trait); + } catch (\InvalidArgumentException $e) { + continue; + } + + if (!$trait_storage->location) { + continue; + } + + try { + $included_trait_file_storage = $this->file_storage_provider->get( + $trait_storage->location->file_path + ); + } catch (\InvalidArgumentException $e) { + continue; + } + + $storage->declaring_function_ids = array_merge( + $included_trait_file_storage->declaring_function_ids, + $storage->declaring_function_ids + ); + } + + $storage->declaring_function_ids = array_merge( + $included_file_storage->declaring_function_ids, + $storage->declaring_function_ids + ); + } + + $storage->required_file_paths = $all_required_file_paths; + + foreach ($all_required_file_paths as $required_file_path) { + try { + $required_file_storage = $this->file_storage_provider->get($required_file_path); + } catch (\InvalidArgumentException $e) { + continue; + } + + $required_file_storage->required_by_file_paths += [$file_path_lc => $storage->file_path]; + } + + foreach ($storage->required_classes as $required_classlike) { + try { + $classlike_storage = $this->classlike_storage_provider->get($required_classlike); + } catch (\InvalidArgumentException $e) { + continue; + } + + if (!$classlike_storage->location) { + continue; + } + + try { + $required_file_storage = $this->file_storage_provider->get($classlike_storage->location->file_path); + } catch (\InvalidArgumentException $e) { + continue; + } + + $required_file_storage->required_by_file_paths += [$file_path_lc => $storage->file_path]; + } + + $storage->populated = true; + } + + private function convertPhpStormGenericToPsalmGeneric(Type\Union $candidate, bool $is_property = false): void + { + $atomic_types = $candidate->getAtomicTypes(); + + if (isset($atomic_types['array']) && count($atomic_types) > 1 && !isset($atomic_types['null'])) { + $iterator_name = null; + $generic_params = null; + $iterator_key = null; + + try { + foreach ($atomic_types as $type_key => $type) { + if ($type instanceof Type\Atomic\TIterable + || ($type instanceof Type\Atomic\TNamedObject + && (!$type->from_docblock || $is_property) + && ( + strtolower($type->value) === 'traversable' + || $this->classlikes->interfaceExtends( + $type->value, + 'Traversable' + ) + || $this->classlikes->classImplements( + $type->value, + 'Traversable' + ) + )) + ) { + $iterator_name = $type->value; + $iterator_key = $type_key; + } elseif ($type instanceof Type\Atomic\TArray) { + $generic_params = $type->type_params; + } + } + } catch (\InvalidArgumentException $e) { + // ignore class-not-found issues + } + + if ($iterator_name && $iterator_key && $generic_params) { + if ($iterator_name === 'iterable') { + $generic_iterator = new Type\Atomic\TIterable($generic_params); + } else { + if (strtolower($iterator_name) === 'generator') { + $generic_params[] = Type::getMixed(); + $generic_params[] = Type::getMixed(); + } + $generic_iterator = new Type\Atomic\TGenericObject($iterator_name, $generic_params); + } + + $candidate->removeType('array'); + $candidate->removeType($iterator_key); + $candidate->addType($generic_iterator); + } + } + } + + protected function inheritMethodsFromParent( + ClassLikeStorage $storage, + ClassLikeStorage $parent_storage + ): void { + $fq_class_name = $storage->name; + $fq_class_name_lc = strtolower($fq_class_name); + + if ($parent_storage->sealed_methods) { + $storage->sealed_methods = true; + } + + // register where they appear (can never be in a trait) + foreach ($parent_storage->appearing_method_ids as $method_name_lc => $appearing_method_id) { + $aliased_method_names = [$method_name_lc]; + + if ($parent_storage->is_trait + && $storage->trait_alias_map + ) { + $aliased_method_names = array_merge( + $aliased_method_names, + array_keys($storage->trait_alias_map, $method_name_lc, true) + ); + } + + foreach ($aliased_method_names as $aliased_method_name) { + if (isset($storage->appearing_method_ids[$aliased_method_name])) { + continue; + } + + $implemented_method_id = new \Psalm\Internal\MethodIdentifier( + $fq_class_name, + $aliased_method_name + ); + + $storage->appearing_method_ids[$aliased_method_name] = + $parent_storage->is_trait ? $implemented_method_id : $appearing_method_id; + + $this_method_id = $fq_class_name_lc . '::' . $method_name_lc; + + if (isset($storage->methods[$aliased_method_name])) { + $storage->potential_declaring_method_ids[$aliased_method_name] = [$this_method_id => true]; + } else { + if (isset($parent_storage->potential_declaring_method_ids[$aliased_method_name])) { + $storage->potential_declaring_method_ids[$aliased_method_name] + = $parent_storage->potential_declaring_method_ids[$aliased_method_name]; + } + + $storage->potential_declaring_method_ids[$aliased_method_name][$this_method_id] = true; + + $parent_method_id = strtolower($parent_storage->name) . '::' . $method_name_lc; + $storage->potential_declaring_method_ids[$aliased_method_name][$parent_method_id] = true; + } + } + } + + // register where they're declared + foreach ($parent_storage->inheritable_method_ids as $method_name_lc => $declaring_method_id) { + if ($method_name_lc !== '__construct' + || $parent_storage->preserve_constructor_signature + ) { + if ($parent_storage->is_trait) { + $declaring_class = $declaring_method_id->fq_class_name; + $declaring_class_storage = $this->classlike_storage_provider->get($declaring_class); + + if (isset($declaring_class_storage->methods[$method_name_lc]) + && $declaring_class_storage->methods[$method_name_lc]->abstract + ) { + $storage->overridden_method_ids[$method_name_lc][$declaring_method_id->fq_class_name] + = $declaring_method_id; + } + } else { + $storage->overridden_method_ids[$method_name_lc][$declaring_method_id->fq_class_name] + = $declaring_method_id; + } + + if (isset($parent_storage->overridden_method_ids[$method_name_lc]) + && isset($storage->overridden_method_ids[$method_name_lc]) + ) { + $storage->overridden_method_ids[$method_name_lc] + += $parent_storage->overridden_method_ids[$method_name_lc]; + } + } + + $aliased_method_names = [$method_name_lc]; + + if ($parent_storage->is_trait + && $storage->trait_alias_map + ) { + $aliased_method_names = array_merge( + $aliased_method_names, + array_keys($storage->trait_alias_map, $method_name_lc, true) + ); + } + + foreach ($aliased_method_names as $aliased_method_name) { + if (isset($storage->declaring_method_ids[$aliased_method_name])) { + $implementing_method_id = $storage->declaring_method_ids[$aliased_method_name]; + + $implementing_class_storage = $this->classlike_storage_provider->get( + $implementing_method_id->fq_class_name + ); + + if (!$implementing_class_storage->methods[$implementing_method_id->method_name]->abstract + || !empty($storage->methods[$implementing_method_id->method_name]->abstract) + ) { + continue; + } + } + + /** @psalm-suppress PropertyTypeCoercion */ + $storage->declaring_method_ids[$aliased_method_name] = $declaring_method_id; + /** @psalm-suppress PropertyTypeCoercion */ + $storage->inheritable_method_ids[$aliased_method_name] = $declaring_method_id; + } + } + } + + private function inheritPropertiesFromParent( + ClassLikeStorage $storage, + ClassLikeStorage $parent_storage + ): void { + if ($parent_storage->sealed_properties) { + $storage->sealed_properties = true; + } + + // register where they appear (can never be in a trait) + foreach ($parent_storage->appearing_property_ids as $property_name => $appearing_property_id) { + if (isset($storage->appearing_property_ids[$property_name])) { + continue; + } + + if (!$parent_storage->is_trait + && isset($parent_storage->properties[$property_name]) + && $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE + ) { + continue; + } + + $implemented_property_id = $storage->name . '::$' . $property_name; + + $storage->appearing_property_ids[$property_name] = + $parent_storage->is_trait ? $implemented_property_id : $appearing_property_id; + } + + // register where they're declared + foreach ($parent_storage->declaring_property_ids as $property_name => $declaring_property_class) { + if (isset($storage->declaring_property_ids[$property_name])) { + continue; + } + + if (!$parent_storage->is_trait + && isset($parent_storage->properties[$property_name]) + && $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE + ) { + continue; + } + + $storage->declaring_property_ids[$property_name] = $declaring_property_class; + } + + // register where they're declared + foreach ($parent_storage->inheritable_property_ids as $property_name => $inheritable_property_id) { + if (!$parent_storage->is_trait + && isset($parent_storage->properties[$property_name]) + && $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE + ) { + continue; + } + + if (!$parent_storage->is_trait) { + $storage->overridden_property_ids[$property_name][] = $inheritable_property_id; + } + + $storage->inheritable_property_ids[$property_name] = $inheritable_property_id; + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Properties.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Properties.php new file mode 100644 index 0000000000000000000000000000000000000000..d563fed83e41b4dda0473a1a33023c602f9c514a --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Properties.php @@ -0,0 +1,308 @@ +classlike_storage_provider = $storage_provider; + $this->file_reference_provider = $file_reference_provider; + $this->property_existence_provider = new PropertyExistenceProvider(); + $this->property_visibility_provider = new PropertyVisibilityProvider(); + $this->property_type_provider = new PropertyTypeProvider(); + } + + /** + * Whether or not a given property exists + * + */ + public function propertyExists( + string $property_id, + bool $read_mode, + ?StatementsSource $source = null, + ?Context $context = null, + ?CodeLocation $code_location = null + ): bool { + // remove trailing backslash if it exists + $property_id = preg_replace('/^\\\\/', '', $property_id); + + [$fq_class_name, $property_name] = explode('::$', $property_id); + $fq_class_name_lc = strtolower($fq_class_name); + + if ($this->property_existence_provider->has($fq_class_name)) { + $property_exists = $this->property_existence_provider->doesPropertyExist( + $fq_class_name, + $property_name, + $read_mode, + $source, + $context, + $code_location + ); + + if ($property_exists !== null) { + return $property_exists; + } + } + + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + + if ($source + && $context + && $context->self !== $fq_class_name + && !$context->collect_initializations + && !$context->collect_mutations + ) { + if ($context->calling_method_id) { + $this->file_reference_provider->addMethodReferenceToClass( + $context->calling_method_id, + $fq_class_name_lc + ); + } else { + $this->file_reference_provider->addNonMethodReferenceToClass( + $source->getFilePath(), + $fq_class_name_lc + ); + } + } + + if (isset($class_storage->declaring_property_ids[$property_name])) { + $declaring_property_class = $class_storage->declaring_property_ids[$property_name]; + + if ($context && $context->calling_method_id) { + $this->file_reference_provider->addMethodReferenceToClassMember( + $context->calling_method_id, + strtolower($declaring_property_class) . '::$' . $property_name + ); + } elseif ($source) { + $this->file_reference_provider->addFileReferenceToClassMember( + $source->getFilePath(), + strtolower($declaring_property_class) . '::$' . $property_name + ); + } + + if ($this->collect_locations && $code_location) { + $this->file_reference_provider->addCallingLocationForClassProperty( + $code_location, + strtolower($declaring_property_class) . '::$' . $property_name + ); + } + + return true; + } + + if ($context && $context->calling_method_id) { + $this->file_reference_provider->addMethodReferenceToMissingClassMember( + $context->calling_method_id, + $fq_class_name_lc . '::$' . $property_name + ); + } elseif ($source) { + $this->file_reference_provider->addFileReferenceToMissingClassMember( + $source->getFilePath(), + $fq_class_name_lc . '::$' . $property_name + ); + } + + return false; + } + + public function getDeclaringClassForProperty( + string $property_id, + bool $read_mode, + ?StatementsSource $source = null + ): ?string { + [$fq_class_name, $property_name] = explode('::$', $property_id); + + if ($this->property_existence_provider->has($fq_class_name)) { + if ($this->property_existence_provider->doesPropertyExist( + $fq_class_name, + $property_name, + $read_mode, + $source, + null + )) { + return $fq_class_name; + } + } + + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + + if (isset($class_storage->declaring_property_ids[$property_name])) { + return $class_storage->declaring_property_ids[$property_name]; + } + + return null; + } + + /** + * Get the class this property appears in (vs is declared in, which could give a trait) + */ + public function getAppearingClassForProperty( + string $property_id, + bool $read_mode, + ?StatementsSource $source = null + ): ?string { + [$fq_class_name, $property_name] = explode('::$', $property_id); + + if ($this->property_existence_provider->has($fq_class_name)) { + if ($this->property_existence_provider->doesPropertyExist( + $fq_class_name, + $property_name, + $read_mode, + $source, + null + )) { + return $fq_class_name; + } + } + + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + + if (isset($class_storage->appearing_property_ids[$property_name])) { + $appearing_property_id = $class_storage->appearing_property_ids[$property_name]; + + return explode('::$', $appearing_property_id)[0]; + } + + return null; + } + + public function getStorage(string $property_id): \Psalm\Storage\PropertyStorage + { + // remove trailing backslash if it exists + $property_id = preg_replace('/^\\\\/', '', $property_id); + + [$fq_class_name, $property_name] = explode('::$', $property_id); + + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + + if (isset($class_storage->declaring_property_ids[$property_name])) { + $declaring_property_class = $class_storage->declaring_property_ids[$property_name]; + $declaring_class_storage = $this->classlike_storage_provider->get($declaring_property_class); + + if (isset($declaring_class_storage->properties[$property_name])) { + return $declaring_class_storage->properties[$property_name]; + } + } + + throw new \UnexpectedValueException('Property ' . $property_id . ' should exist'); + } + + public function getPropertyType( + string $property_id, + bool $property_set, + ?StatementsSource $source = null, + ?Context $context = null + ): ?Type\Union { + // remove trailing backslash if it exists + $property_id = preg_replace('/^\\\\/', '', $property_id); + + [$fq_class_name, $property_name] = explode('::$', $property_id); + + if ($this->property_type_provider->has($fq_class_name)) { + $property_type = $this->property_type_provider->getPropertyType( + $fq_class_name, + $property_name, + !$property_set, + $source, + $context + ); + + if ($property_type !== null) { + return $property_type; + } + } + + $class_storage = $this->classlike_storage_provider->get($fq_class_name); + + if (isset($class_storage->declaring_property_ids[$property_name])) { + $declaring_property_class = $class_storage->declaring_property_ids[$property_name]; + $declaring_class_storage = $this->classlike_storage_provider->get($declaring_property_class); + + if (isset($declaring_class_storage->properties[$property_name])) { + $storage = $declaring_class_storage->properties[$property_name]; + } else { + throw new \UnexpectedValueException('Property ' . $property_id . ' should exist'); + } + } else { + throw new \UnexpectedValueException('Property ' . $property_id . ' should exist'); + } + + if ($storage->type) { + if ($property_set) { + if (isset($class_storage->pseudo_property_set_types[$property_name])) { + return $class_storage->pseudo_property_set_types[$property_name]; + } + } else { + if (isset($class_storage->pseudo_property_get_types[$property_name])) { + return $class_storage->pseudo_property_get_types[$property_name]; + } + } + + return $storage->type; + } + + if (!isset($class_storage->overridden_property_ids[$property_name])) { + return null; + } + + foreach ($class_storage->overridden_property_ids[$property_name] as $overridden_property_id) { + $overridden_storage = $this->getStorage($overridden_property_id); + + if ($overridden_storage->type) { + return $overridden_storage->type; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/PropertyMap.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/PropertyMap.php new file mode 100644 index 0000000000000000000000000000000000000000..64644dab7c76ebb86db7627a1ea3b9dd10aa88cf --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/PropertyMap.php @@ -0,0 +1,44 @@ +>|null + */ + private static $property_map; + + /** + * Gets the method/function call map + * + * @return array> + */ + public static function getPropertyMap(): array + { + if (self::$property_map !== null) { + return self::$property_map; + } + + /** @var array> */ + $property_map = require(\dirname(__DIR__, 4) . '/dictionaries/PropertyMap.php'); + + self::$property_map = []; + + foreach ($property_map as $key => $value) { + $cased_key = strtolower($key); + self::$property_map[$cased_key] = $value; + } + + return self::$property_map; + } + + public static function inPropertyMap(string $class_name): bool + { + return isset(self::getPropertyMap()[strtolower($class_name)]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/ReferenceMapGenerator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/ReferenceMapGenerator.php new file mode 100644 index 0000000000000000000000000000000000000000..f8453a6c2f54e647566c92c62feaa38770f34fcc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/ReferenceMapGenerator.php @@ -0,0 +1,58 @@ + + */ + public static function getReferenceMap( + \Psalm\Internal\Provider\ClassLikeStorageProvider $classlike_storage_provider, + array $expected_references + ) : array { + $reference_dictionary = []; + + foreach ($classlike_storage_provider->getAll() as $storage) { + if (!$storage->location) { + continue; + } + + $fq_classlike_name = $storage->name; + + if (isset($expected_references[$fq_classlike_name])) { + $reference_dictionary[$fq_classlike_name] + = $storage->location->file_name + . ':' . $storage->location->getLineNumber() + . ':' . $storage->location->getColumn(); + } + + foreach ($storage->methods as $method_name => $method_storage) { + if (!$method_storage->location) { + continue; + } + + if (isset($expected_references[$fq_classlike_name . '::' . $method_name . '()'])) { + $reference_dictionary[$fq_classlike_name . '::' . $method_name . '()'] + = $method_storage->location->file_name + . ':' . $method_storage->location->getLineNumber() + . ':' . $method_storage->location->getColumn(); + } + } + + foreach ($storage->properties as $property_name => $property_storage) { + if (!$property_storage->location) { + continue; + } + + if (isset($expected_references[$fq_classlike_name . '::$' . $property_name])) { + $reference_dictionary[$fq_classlike_name . '::$' . $property_name] + = $property_storage->location->file_name + . ':' . $property_storage->location->getLineNumber() + . ':' . $property_storage->location->getColumn(); + } + } + } + + return $reference_dictionary; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Reflection.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Reflection.php new file mode 100644 index 0000000000000000000000000000000000000000..a82ca701120c41a1f302970d7e902a814922030c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Reflection.php @@ -0,0 +1,507 @@ + + */ + private static $builtin_functions = []; + + public function __construct(ClassLikeStorageProvider $storage_provider, Codebase $codebase) + { + $this->storage_provider = $storage_provider; + $this->codebase = $codebase; + self::$builtin_functions = []; + } + + public function registerClass(\ReflectionClass $reflected_class): void + { + $class_name = $reflected_class->name; + + if ($class_name === 'LibXMLError') { + $class_name = 'libXMLError'; + } + + $class_name_lower = strtolower($class_name); + + try { + $this->storage_provider->get($class_name_lower); + + return; + } catch (\Exception $e) { + // this is fine + } + + $reflected_parent_class = $reflected_class->getParentClass(); + + $storage = $this->storage_provider->create($class_name); + $storage->abstract = $reflected_class->isAbstract(); + $storage->is_interface = $reflected_class->isInterface(); + + /** @psalm-suppress PropertyTypeCoercion */ + $storage->potential_declaring_method_ids['__construct'][$class_name_lower . '::__construct'] = true; + + if ($reflected_parent_class) { + $parent_class_name = $reflected_parent_class->getName(); + $this->registerClass($reflected_parent_class); + $parent_class_name_lc = strtolower($parent_class_name); + + $parent_storage = $this->storage_provider->get($parent_class_name_lc); + + $this->registerInheritedMethods($class_name_lower, $parent_class_name_lc); + $this->registerInheritedProperties($class_name_lower, $parent_class_name_lc); + + $storage->class_implements = $parent_storage->class_implements; + + $storage->constants = $parent_storage->constants; + + $storage->parent_classes = array_merge( + [$parent_class_name_lc => $parent_class_name], + $parent_storage->parent_classes + ); + + $storage->used_traits = $parent_storage->used_traits; + } + + $class_properties = $reflected_class->getProperties(); + + $public_mapped_properties = PropertyMap::inPropertyMap($class_name) + ? PropertyMap::getPropertyMap()[strtolower($class_name)] + : []; + + foreach ($class_properties as $class_property) { + $property_name = $class_property->getName(); + $storage->properties[$property_name] = new PropertyStorage(); + + $storage->properties[$property_name]->type = Type::getMixed(); + + if ($class_property->isStatic()) { + $storage->properties[$property_name]->is_static = true; + } + + if ($class_property->isPublic()) { + $storage->properties[$property_name]->visibility = ClassLikeAnalyzer::VISIBILITY_PUBLIC; + } elseif ($class_property->isProtected()) { + $storage->properties[$property_name]->visibility = ClassLikeAnalyzer::VISIBILITY_PROTECTED; + } elseif ($class_property->isPrivate()) { + $storage->properties[$property_name]->visibility = ClassLikeAnalyzer::VISIBILITY_PRIVATE; + } + + $property_id = (string)$class_property->class . '::$' . $property_name; + + $storage->declaring_property_ids[$property_name] = (string)$class_property->class; + $storage->appearing_property_ids[$property_name] = $property_id; + + if (!$class_property->isPrivate()) { + $storage->inheritable_property_ids[$property_name] = $property_id; + } + } + + // have to do this separately as there can be new properties here + foreach ($public_mapped_properties as $property_name => $type_string) { + $property_id = $class_name . '::$' . $property_name; + + if (!isset($storage->properties[$property_name])) { + $storage->properties[$property_name] = new PropertyStorage(); + $storage->properties[$property_name]->visibility = ClassLikeAnalyzer::VISIBILITY_PUBLIC; + + $storage->declaring_property_ids[$property_name] = $class_name; + $storage->appearing_property_ids[$property_name] = $property_id; + $storage->inheritable_property_ids[$property_name] = $property_id; + } + + $type = Type::parseString($type_string); + + if ($property_id === 'DateInterval::$days') { + $type->ignore_falsable_issues = true; + } + + $storage->properties[$property_name]->type = $type; + } + + /** @var array */ + $class_constants = $reflected_class->getConstants(); + + foreach ($class_constants as $name => $value) { + $storage->constants[$name] = new \Psalm\Storage\ClassConstantStorage( + ClassLikeAnalyzer::getTypeFromValue($value), + ClassLikeAnalyzer::VISIBILITY_PUBLIC, + null + ); + } + + if ($reflected_class->isInterface()) { + $this->codebase->classlikes->addFullyQualifiedInterfaceName($class_name); + } elseif ($reflected_class->isTrait()) { + $this->codebase->classlikes->addFullyQualifiedTraitName($class_name); + } else { + $this->codebase->classlikes->addFullyQualifiedClassName($class_name); + } + + $reflection_methods = $reflected_class->getMethods( + (\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) + ); + + if ($class_name_lower === 'generator') { + $storage->template_types = [ + 'TKey' => ['Generator' => [Type::getMixed()]], + 'TValue' => ['Generator' => [Type::getMixed()]], + ]; + } + + $interfaces = $reflected_class->getInterfaces(); + + foreach ($interfaces as $interface) { + $interface_name = $interface->getName(); + $this->registerClass($interface); + + if ($reflected_class->isInterface()) { + $storage->parent_interfaces[strtolower($interface_name)] = $interface_name; + } else { + $storage->class_implements[strtolower($interface_name)] = $interface_name; + } + } + + foreach ($reflection_methods as $reflection_method) { + $method_reflection_class = $reflection_method->getDeclaringClass(); + + $this->registerClass($method_reflection_class); + + $this->extractReflectionMethodInfo($reflection_method); + + if ($reflection_method->class !== $class_name + && ($class_name !== 'SoapFault' || $reflection_method->name !== '__construct') + ) { + $reflection_method_name = strtolower($reflection_method->name); + $reflection_method_class = $reflection_method->class; + + $this->codebase->methods->setDeclaringMethodId( + $class_name, + $reflection_method_name, + $reflection_method_class, + $reflection_method_name + ); + + $this->codebase->methods->setAppearingMethodId( + $class_name, + $reflection_method_name, + $reflection_method_class, + $reflection_method_name + ); + } + } + } + + public function extractReflectionMethodInfo(\ReflectionMethod $method): void + { + $method_name_lc = strtolower($method->getName()); + + $fq_class_name = $method->class; + + $fq_class_name_lc = strtolower($fq_class_name); + + $class_storage = $this->storage_provider->get($fq_class_name_lc); + + if (isset($class_storage->methods[$method_name_lc])) { + return; + } + + $method_id = $method->class . '::' . $method_name_lc; + + $storage = $class_storage->methods[$method_name_lc] = new MethodStorage(); + + $storage->cased_name = $method->name; + $storage->defining_fqcln = $method->class; + + if ($method_name_lc === $fq_class_name_lc) { + $this->codebase->methods->setDeclaringMethodId( + $fq_class_name, + '__construct', + $fq_class_name, + $method_name_lc + ); + $this->codebase->methods->setAppearingMethodId( + $fq_class_name, + '__construct', + $fq_class_name, + $method_name_lc + ); + } + + $declaring_class = $method->getDeclaringClass(); + + $storage->is_static = $method->isStatic(); + $storage->abstract = $method->isAbstract(); + $storage->mutation_free = $storage->external_mutation_free + = $method_name_lc === '__construct' && $fq_class_name_lc === 'datetimezone'; + + $class_storage->declaring_method_ids[$method_name_lc] = new \Psalm\Internal\MethodIdentifier( + $declaring_class->name, + $method_name_lc + ); + + $class_storage->inheritable_method_ids[$method_name_lc] + = $class_storage->declaring_method_ids[$method_name_lc]; + $class_storage->appearing_method_ids[$method_name_lc] + = $class_storage->declaring_method_ids[$method_name_lc]; + $class_storage->overridden_method_ids[$method_name_lc] = []; + + $storage->visibility = $method->isPrivate() + ? ClassLikeAnalyzer::VISIBILITY_PRIVATE + : ($method->isProtected() ? ClassLikeAnalyzer::VISIBILITY_PROTECTED : ClassLikeAnalyzer::VISIBILITY_PUBLIC); + + $callables = InternalCallMapHandler::getCallablesFromCallMap($method_id); + + if ($callables && $callables[0]->params !== null && $callables[0]->return_type !== null) { + $storage->params = []; + + foreach ($callables[0]->params as $param) { + if ($param->type) { + $param->type->queueClassLikesForScanning($this->codebase); + } + } + + $storage->params = $callables[0]->params; + + $storage->return_type = $callables[0]->return_type; + $storage->return_type->queueClassLikesForScanning($this->codebase); + } else { + $params = $method->getParameters(); + + $storage->params = []; + + foreach ($params as $param) { + $param_array = $this->getReflectionParamData($param); + $storage->params[] = $param_array; + $storage->param_lookup[$param->name] = true; + } + } + + $storage->required_param_count = 0; + + foreach ($storage->params as $i => $param) { + if (!$param->is_optional && !$param->is_variadic) { + $storage->required_param_count = $i + 1; + } + } + } + + private function getReflectionParamData(\ReflectionParameter $param): FunctionLikeParameter + { + $param_type = self::getPsalmTypeFromReflectionType($param->getType()); + $param_name = (string)$param->getName(); + + $is_optional = (bool)$param->isOptional(); + + $parameter = new FunctionLikeParameter( + $param_name, + (bool)$param->isPassedByReference(), + $param_type, + null, + null, + $is_optional, + $param_type->isNullable(), + $param->isVariadic() + ); + + $parameter->signature_type = Type::getMixed(); + + return $parameter; + } + + /** + * @param callable-string $function_id + * + * @return false|null + */ + public function registerFunction(string $function_id): ?bool + { + try { + $reflection_function = new \ReflectionFunction($function_id); + + $callmap_callable = null; + + if (isset(self::$builtin_functions[$function_id])) { + return null; + } + + $storage = self::$builtin_functions[$function_id] = new FunctionStorage(); + + if (InternalCallMapHandler::inCallMap($function_id)) { + $callmap_callable = \Psalm\Internal\Codebase\InternalCallMapHandler::getCallableFromCallMapById( + $this->codebase, + $function_id, + [], + null + ); + } + + if ($callmap_callable !== null + && $callmap_callable->params !== null + && $callmap_callable->return_type !== null + ) { + $storage->params = $callmap_callable->params; + $storage->return_type = $callmap_callable->return_type; + } else { + $reflection_params = $reflection_function->getParameters(); + + foreach ($reflection_params as $param) { + $param_obj = $this->getReflectionParamData($param); + $storage->params[] = $param_obj; + } + + if ($reflection_return_type = $reflection_function->getReturnType()) { + $storage->return_type = self::getPsalmTypeFromReflectionType($reflection_return_type); + } + } + + $storage->pure = true; + + $storage->required_param_count = 0; + + foreach ($storage->params as $i => $param) { + if (!$param->is_optional && !$param->is_variadic) { + $storage->required_param_count = $i + 1; + } + } + + $storage->cased_name = $reflection_function->getName(); + } catch (\ReflectionException $e) { + return false; + } + + return null; + } + + public static function getPsalmTypeFromReflectionType(?\ReflectionType $reflection_type = null) : Type\Union + { + if (!$reflection_type) { + return Type::getMixed(); + } + + $suffix = ''; + + if ($reflection_type->allowsNull()) { + $suffix = '|null'; + } + + return Type::parseString($reflection_type->getName() . $suffix); + } + + private function registerInheritedMethods( + string $fq_class_name, + string $parent_class + ): void { + $parent_storage = $this->storage_provider->get($parent_class); + $storage = $this->storage_provider->get($fq_class_name); + + // register where they appear (can never be in a trait) + foreach ($parent_storage->appearing_method_ids as $method_name => $appearing_method_id) { + $storage->appearing_method_ids[$method_name] = $appearing_method_id; + } + + // register where they're declared + foreach ($parent_storage->inheritable_method_ids as $method_name => $declaring_method_id) { + $storage->declaring_method_ids[$method_name] = $declaring_method_id; + $storage->inheritable_method_ids[$method_name] = $declaring_method_id; + + $storage->overridden_method_ids[$method_name][$declaring_method_id->fq_class_name] + = $declaring_method_id; + } + } + + /** + * @param lowercase-string $fq_class_name + * @param lowercase-string $parent_class + * + */ + private function registerInheritedProperties( + string $fq_class_name, + string $parent_class + ): void { + $parent_storage = $this->storage_provider->get($parent_class); + $storage = $this->storage_provider->get($fq_class_name); + + // register where they appear (can never be in a trait) + foreach ($parent_storage->appearing_property_ids as $property_name => $appearing_property_id) { + if (!$parent_storage->is_trait + && isset($parent_storage->properties[$property_name]) + && $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE + ) { + continue; + } + + $storage->appearing_property_ids[$property_name] = $appearing_property_id; + } + + // register where they're declared + foreach ($parent_storage->declaring_property_ids as $property_name => $declaring_property_class) { + if (!$parent_storage->is_trait + && isset($parent_storage->properties[$property_name]) + && $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE + ) { + continue; + } + + $storage->declaring_property_ids[$property_name] = strtolower($declaring_property_class); + } + + // register where they're declared + foreach ($parent_storage->inheritable_property_ids as $property_name => $inheritable_property_id) { + if (!$parent_storage->is_trait + && isset($parent_storage->properties[$property_name]) + && $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE + ) { + continue; + } + + $storage->inheritable_property_ids[$property_name] = $inheritable_property_id; + } + } + + public function hasFunction(string $function_id): bool + { + return isset(self::$builtin_functions[$function_id]); + } + + public function getFunctionStorage(string $function_id): FunctionStorage + { + if (isset(self::$builtin_functions[$function_id])) { + return self::$builtin_functions[$function_id]; + } + + throw new \UnexpectedValueException('Expecting to have a function for ' . $function_id); + } + + public static function clearCache() : void + { + self::$builtin_functions = []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Scanner.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Scanner.php new file mode 100644 index 0000000000000000000000000000000000000000..b477051d9d8962b3f518a3ba5ab4c368c0ab15f0 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/Scanner.php @@ -0,0 +1,782 @@ +, + * 1: array, + * 2: array, + * 3: array, + * 4: array, + * 5: array, + * 6: array, + * 7: array, + * 8: array + * } + * + * @psalm-type PoolData = array{ + * classlikes_data:array{ + * 0:array, + * 1:array, + * 2:array, + * 3:array, + * 4:array, + * 5:array, + * 6:array + * }, + * scanner_data: ThreadData, + * issues:array>, + * changed_members:array>, + * unchanged_signature_members:array>, + * diff_map:array>, + * classlike_storage:array, + * file_storage:array, + * new_file_content_hashes: array, + * taint_data: ?TaintFlowGraph + * } + */ + +/** + * @internal + * + * Contains methods that aid in the scanning of Psalm's codebase + */ +class Scanner +{ + /** + * @var Codebase + */ + private $codebase; + + /** + * @var array + */ + private $classlike_files = []; + + /** + * @var array + */ + private $deep_scanned_classlike_files = []; + + /** + * @var array + */ + private $files_to_scan = []; + + /** + * @var array + */ + private $classes_to_scan = []; + + /** + * @var array + */ + private $classes_to_deep_scan = []; + + /** + * @var array + */ + private $files_to_deep_scan = []; + + /** + * @var array + */ + private $scanned_files = []; + + /** + * @var array + */ + private $store_scan_failure = []; + + /** + * @var array + */ + private $reflected_classlikes_lc = []; + + /** + * @var Reflection + */ + private $reflection; + + /** + * @var Config + */ + private $config; + + /** + * @var Progress + */ + private $progress; + + /** + * @var FileStorageProvider + */ + private $file_storage_provider; + + /** + * @var FileProvider + */ + private $file_provider; + + /** + * @var FileReferenceProvider + */ + private $file_reference_provider; + + /** + * @var bool + */ + private $is_forked = false; + + public function __construct( + Codebase $codebase, + Config $config, + FileStorageProvider $file_storage_provider, + FileProvider $file_provider, + Reflection $reflection, + FileReferenceProvider $file_reference_provider, + Progress $progress + ) { + $this->codebase = $codebase; + $this->reflection = $reflection; + $this->file_provider = $file_provider; + $this->progress = $progress; + $this->file_storage_provider = $file_storage_provider; + $this->config = $config; + $this->file_reference_provider = $file_reference_provider; + } + + /** + * @param array $files_to_scan + * + */ + public function addFilesToShallowScan(array $files_to_scan): void + { + $this->files_to_scan += $files_to_scan; + } + + /** + * @param array $files_to_scan + */ + public function addFilesToDeepScan(array $files_to_scan): void + { + $this->files_to_scan += $files_to_scan; + $this->files_to_deep_scan += $files_to_scan; + } + + public function addFileToShallowScan(string $file_path): void + { + $this->files_to_scan[$file_path] = $file_path; + } + + public function addFileToDeepScan(string $file_path): void + { + $this->files_to_scan[$file_path] = $file_path; + $this->files_to_deep_scan[$file_path] = $file_path; + } + + public function removeFile(string $file_path): void + { + unset($this->scanned_files[$file_path]); + } + + public function removeClassLike(string $fq_classlike_name_lc): void + { + unset( + $this->classlike_files[$fq_classlike_name_lc], + $this->deep_scanned_classlike_files[$fq_classlike_name_lc] + ); + } + + public function setClassLikeFilePath(string $fq_classlike_name_lc, string $file_path): void + { + $this->classlike_files[$fq_classlike_name_lc] = $file_path; + } + + public function getClassLikeFilePath(string $fq_classlike_name_lc): string + { + if (!isset($this->classlike_files[$fq_classlike_name_lc])) { + throw new \UnexpectedValueException('Could not find file for ' . $fq_classlike_name_lc); + } + + return $this->classlike_files[$fq_classlike_name_lc]; + } + + /** + * @param array $phantom_classes + */ + public function queueClassLikeForScanning( + string $fq_classlike_name, + bool $analyze_too = false, + bool $store_failure = true, + array $phantom_classes = [] + ): void { + if ($fq_classlike_name[0] === '\\') { + $fq_classlike_name = substr($fq_classlike_name, 1); + } + + $fq_classlike_name_lc = strtolower($fq_classlike_name); + + if ($fq_classlike_name_lc === 'static') { + return; + } + + // avoid checking classes that we know will just end in failure + if ($fq_classlike_name_lc === 'null' || substr($fq_classlike_name_lc, -5) === '\null') { + return; + } + + if (!isset($this->classlike_files[$fq_classlike_name_lc]) + || ($analyze_too && !isset($this->deep_scanned_classlike_files[$fq_classlike_name_lc])) + ) { + if (!isset($this->classes_to_scan[$fq_classlike_name_lc]) || $store_failure) { + $this->classes_to_scan[$fq_classlike_name_lc] = $fq_classlike_name; + } + + if ($analyze_too) { + $this->classes_to_deep_scan[$fq_classlike_name_lc] = true; + } + + $this->store_scan_failure[$fq_classlike_name] = $store_failure; + + if (PropertyMap::inPropertyMap($fq_classlike_name_lc)) { + $public_mapped_properties = PropertyMap::getPropertyMap()[$fq_classlike_name_lc]; + + foreach ($public_mapped_properties as $public_mapped_property) { + $property_type = \Psalm\Type::parseString($public_mapped_property); + $property_type->queueClassLikesForScanning( + $this->codebase, + null, + $phantom_classes + [$fq_classlike_name_lc => true] + ); + } + } + } + } + + public function scanFiles(ClassLikes $classlikes, int $pool_size = 1): bool + { + $has_changes = false; + while ($this->files_to_scan || $this->classes_to_scan) { + if ($this->files_to_scan) { + if ($this->scanFilePaths($pool_size)) { + $has_changes = true; + } + } else { + $this->convertClassesToFilePaths($classlikes); + } + } + + return $has_changes; + } + + private function scanFilePaths(int $pool_size) : bool + { + $filetype_scanners = $this->config->getFiletypeScanners(); + $files_to_scan = array_filter( + $this->files_to_scan, + function (string $file_path) : bool { + return $this->file_provider->fileExists($file_path) + && (!isset($this->scanned_files[$file_path]) + || (isset($this->files_to_deep_scan[$file_path]) && !$this->scanned_files[$file_path])); + } + ); + + $this->files_to_scan = []; + + if (!$files_to_scan) { + return false; + } + + $files_to_deep_scan = $this->files_to_deep_scan; + + $scanner_worker = + function (int $_, string $file_path) use ($filetype_scanners, $files_to_deep_scan): void { + $this->scanFile( + $file_path, + $filetype_scanners, + isset($files_to_deep_scan[$file_path]) + ); + }; + + if (!$this->is_forked && $pool_size > 1 && count($files_to_scan) > 512) { + $pool_size = ceil(min($pool_size, count($files_to_scan) / 256)); + } else { + $pool_size = 1; + } + + if ($pool_size > 1) { + $process_file_paths = []; + + $i = 0; + + foreach ($files_to_scan as $file_path) { + $process_file_paths[$i % $pool_size][] = $file_path; + ++$i; + } + + $this->progress->debug('Forking process for scanning' . PHP_EOL); + + // Run scanning one file at a time, splitting the set of + // files up among a given number of child processes. + $pool = new \Psalm\Internal\Fork\Pool( + $process_file_paths, + function () { + $this->progress->debug('Initialising forked process for scanning' . PHP_EOL); + + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + $statements_provider = $codebase->statements_provider; + + $codebase->scanner->isForked(); + $codebase->file_storage_provider->deleteAll(); + $codebase->classlike_storage_provider->deleteAll(); + + $statements_provider->resetDiffs(); + + $this->progress->debug('Have initialised forked process for scanning' . PHP_EOL); + }, + $scanner_worker, + /** + * @return PoolData + */ + function () { + $this->progress->debug('Collecting data from forked scanner process' . PHP_EOL); + + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + $statements_provider = $codebase->statements_provider; + + return [ + 'classlikes_data' => $codebase->classlikes->getThreadData(), + 'scanner_data' => $codebase->scanner->getThreadData(), + 'issues' => \Psalm\IssueBuffer::getIssuesData(), + 'changed_members' => $statements_provider->getChangedMembers(), + 'unchanged_signature_members' => $statements_provider->getUnchangedSignatureMembers(), + 'diff_map' => $statements_provider->getDiffMap(), + 'classlike_storage' => $codebase->classlike_storage_provider->getAll(), + 'file_storage' => $codebase->file_storage_provider->getAll(), + 'new_file_content_hashes' => $statements_provider->parser_cache_provider + ? $statements_provider->parser_cache_provider->getNewFileContentHashes() + : [], + 'taint_data' => $codebase->taint_flow_graph, + ]; + } + ); + + // Wait for all tasks to complete and collect the results. + /** + * @var array + */ + $forked_pool_data = $pool->wait(); + + foreach ($forked_pool_data as $pool_data) { + \Psalm\IssueBuffer::addIssues($pool_data['issues']); + + $this->codebase->statements_provider->addChangedMembers( + $pool_data['changed_members'] + ); + $this->codebase->statements_provider->addUnchangedSignatureMembers( + $pool_data['unchanged_signature_members'] + ); + $this->codebase->statements_provider->addDiffMap( + $pool_data['diff_map'] + ); + if ($this->codebase->taint_flow_graph && $pool_data['taint_data']) { + $this->codebase->taint_flow_graph->addGraph($pool_data['taint_data']); + } + + $this->codebase->file_storage_provider->addMore($pool_data['file_storage']); + $this->codebase->classlike_storage_provider->addMore($pool_data['classlike_storage']); + + $this->codebase->classlikes->addThreadData($pool_data['classlikes_data']); + + $this->addThreadData($pool_data['scanner_data']); + + if ($this->codebase->statements_provider->parser_cache_provider) { + $this->codebase->statements_provider->parser_cache_provider->addNewFileContentHashes( + $pool_data['new_file_content_hashes'] + ); + } + } + + if ($pool->didHaveError()) { + exit(1); + } + } else { + $i = 0; + + foreach ($files_to_scan as $file_path => $_) { + $scanner_worker($i, $file_path); + ++$i; + } + } + + if ($this->codebase->statements_provider->parser_cache_provider) { + $this->codebase->statements_provider->parser_cache_provider->saveFileContentHashes(); + } + + foreach ($files_to_scan as $scanned_file) { + if ($this->config->hasStubFile($scanned_file)) { + $file_storage = $this->file_storage_provider->get($scanned_file); + + foreach ($file_storage->functions as $function_storage) { + if ($function_storage->cased_name + && !$this->codebase->functions->hasStubbedFunction($function_storage->cased_name) + ) { + $this->codebase->functions->addGlobalFunction( + $function_storage->cased_name, + $function_storage + ); + } + } + + foreach ($file_storage->constants as $name => $type) { + $this->codebase->addGlobalConstantType($name, $type); + } + } + } + + $this->file_reference_provider->addClassLikeFiles($this->classlike_files); + + return true; + } + + private function convertClassesToFilePaths(ClassLikes $classlikes): void + { + $classes_to_scan = $this->classes_to_scan; + + $this->classes_to_scan = []; + + foreach ($classes_to_scan as $fq_classlike_name) { + $fq_classlike_name_lc = strtolower($fq_classlike_name); + + if (isset($this->reflected_classlikes_lc[$fq_classlike_name_lc])) { + continue; + } + + if ($classlikes->isMissingClassLike($fq_classlike_name_lc)) { + continue; + } + + if (!isset($this->classlike_files[$fq_classlike_name_lc])) { + if ($classlikes->doesClassLikeExist($fq_classlike_name_lc)) { + if ($fq_classlike_name_lc === 'self') { + continue; + } + + $this->progress->debug('Using reflection to get metadata for ' . $fq_classlike_name . "\n"); + + /** @psalm-suppress ArgumentTypeCoercion */ + $reflected_class = new \ReflectionClass($fq_classlike_name); + $this->reflection->registerClass($reflected_class); + $this->reflected_classlikes_lc[$fq_classlike_name_lc] = true; + } elseif ($this->fileExistsForClassLike($classlikes, $fq_classlike_name)) { + $fq_classlike_name_lc = strtolower($classlikes->getUnAliasedName( + $fq_classlike_name_lc + )); + + // even though we've checked this above, calling the method invalidates it + if (isset($this->classlike_files[$fq_classlike_name_lc])) { + $file_path = $this->classlike_files[$fq_classlike_name_lc]; + $this->files_to_scan[$file_path] = $file_path; + if (isset($this->classes_to_deep_scan[$fq_classlike_name_lc])) { + unset($this->classes_to_deep_scan[$fq_classlike_name_lc]); + $this->files_to_deep_scan[$file_path] = $file_path; + } + } + } elseif ($this->store_scan_failure[$fq_classlike_name]) { + $classlikes->registerMissingClassLike($fq_classlike_name_lc); + } + } elseif (isset($this->classes_to_deep_scan[$fq_classlike_name_lc]) + && !isset($this->deep_scanned_classlike_files[$fq_classlike_name_lc]) + ) { + $file_path = $this->classlike_files[$fq_classlike_name_lc]; + $this->files_to_scan[$file_path] = $file_path; + unset($this->classes_to_deep_scan[$fq_classlike_name_lc]); + $this->files_to_deep_scan[$file_path] = $file_path; + $this->deep_scanned_classlike_files[$fq_classlike_name_lc] = true; + } + } + } + + /** + * @param array> $filetype_scanners + */ + private function scanFile( + string $file_path, + array $filetype_scanners, + bool $will_analyze = false + ): FileScanner { + $file_scanner = $this->getScannerForPath($file_path, $filetype_scanners, $will_analyze); + + if (isset($this->scanned_files[$file_path]) + && (!$will_analyze || $this->scanned_files[$file_path]) + ) { + throw new \UnexpectedValueException('Should not be rescanning ' . $file_path); + } + + $file_contents = $this->file_provider->getContents($file_path); + + $from_cache = $this->file_storage_provider->has($file_path, $file_contents); + + if (!$from_cache) { + $this->file_storage_provider->create($file_path); + } + + $this->scanned_files[$file_path] = $will_analyze; + + $file_storage = $this->file_storage_provider->get($file_path); + + $file_scanner->scan( + $this->codebase, + $file_storage, + $from_cache, + $this->progress + ); + + if (!$from_cache) { + if (!$file_storage->has_visitor_issues && $this->file_storage_provider->cache) { + $this->file_storage_provider->cache->writeToCache($file_storage, $file_contents); + } + } else { + $this->codebase->statements_provider->setUnchangedFile($file_path); + + foreach ($file_storage->required_file_paths as $required_file_path) { + if ($will_analyze) { + $this->addFileToDeepScan($required_file_path); + } else { + $this->addFileToShallowScan($required_file_path); + } + } + + foreach ($file_storage->classlikes_in_file as $fq_classlike_name) { + $this->codebase->exhumeClassLikeStorage(strtolower($fq_classlike_name), $file_path); + } + + foreach ($file_storage->required_classes as $fq_classlike_name) { + $this->queueClassLikeForScanning($fq_classlike_name, $will_analyze, false); + } + + foreach ($file_storage->required_interfaces as $fq_classlike_name) { + $this->queueClassLikeForScanning($fq_classlike_name, false, false); + } + + foreach ($file_storage->referenced_classlikes as $fq_classlike_name) { + $this->queueClassLikeForScanning($fq_classlike_name, false, false); + } + + if ($this->codebase->register_autoload_files) { + foreach ($file_storage->functions as $function_storage) { + if ($function_storage->cased_name + && !$this->codebase->functions->hasStubbedFunction($function_storage->cased_name) + ) { + $this->codebase->functions->addGlobalFunction( + $function_storage->cased_name, + $function_storage + ); + } + } + + foreach ($file_storage->constants as $name => $type) { + $this->codebase->addGlobalConstantType($name, $type); + } + } + + foreach ($file_storage->classlike_aliases as $aliased_name => $unaliased_name) { + $this->codebase->classlikes->addClassAlias($unaliased_name, $aliased_name); + } + } + + return $file_scanner; + } + + /** + * @param array> $filetype_scanners + */ + private function getScannerForPath( + string $file_path, + array $filetype_scanners, + bool $will_analyze = false + ): FileScanner { + $path_parts = explode(DIRECTORY_SEPARATOR, $file_path); + $file_name_parts = explode('.', array_pop($path_parts)); + $extension = count($file_name_parts) > 1 ? array_pop($file_name_parts) : null; + + $file_name = $this->config->shortenFileName($file_path); + + if (isset($filetype_scanners[$extension])) { + return new $filetype_scanners[$extension]($file_path, $file_name, $will_analyze); + } + + return new FileScanner($file_path, $file_name, $will_analyze); + } + + /** + * @return array + */ + public function getScannedFiles(): array + { + return $this->scanned_files; + } + + /** + * Checks whether a class exists, and if it does then records what file it's in + * for later checking + */ + private function fileExistsForClassLike(ClassLikes $classlikes, string $fq_class_name): bool + { + $fq_class_name_lc = strtolower($fq_class_name); + + if (isset($this->classlike_files[$fq_class_name_lc])) { + return true; + } + + if ($fq_class_name === 'self') { + return false; + } + + if (isset($this->existing_classlikes_lc[$fq_class_name_lc])) { + throw new \InvalidArgumentException('Why are you asking about a builtin class?'); + } + + $composer_file_path = $this->config->getComposerFilePathForClassLike($fq_class_name); + + if ($composer_file_path && file_exists($composer_file_path)) { + $this->progress->debug('Using composer to locate file for ' . $fq_class_name . "\n"); + + $classlikes->addFullyQualifiedClassLikeName( + $fq_class_name_lc, + realpath($composer_file_path) + ); + + return true; + } + + $old_level = error_reporting(); + + $this->progress->setErrorReporting(); + + try { + $this->progress->debug('Using reflection to locate file for ' . $fq_class_name . "\n"); + + /** @psalm-suppress ArgumentTypeCoercion */ + $reflected_class = new \ReflectionClass($fq_class_name); + } catch (\Throwable $e) { + error_reporting($old_level); + + // do not cache any results here (as case-sensitive filenames can screw things up) + + return false; + } + + error_reporting($old_level); + + $file_path = (string)$reflected_class->getFileName(); + + // if the file was autoloaded but exists in evaled code only, return false + if (!file_exists($file_path)) { + return false; + } + + $new_fq_class_name = $reflected_class->getName(); + $new_fq_class_name_lc = strtolower($new_fq_class_name); + + if ($new_fq_class_name_lc !== $fq_class_name_lc) { + $classlikes->addClassAlias($new_fq_class_name, $fq_class_name_lc); + $fq_class_name_lc = $new_fq_class_name_lc; + } + + $fq_class_name = $new_fq_class_name; + $classlikes->addFullyQualifiedClassLikeName($fq_class_name_lc); + + if ($reflected_class->isInterface()) { + $classlikes->addFullyQualifiedInterfaceName($fq_class_name, $file_path); + } elseif ($reflected_class->isTrait()) { + $classlikes->addFullyQualifiedTraitName($fq_class_name, $file_path); + } else { + $classlikes->addFullyQualifiedClassName($fq_class_name, $file_path); + } + + return true; + } + + /** + * @return ThreadData + */ + public function getThreadData(): array + { + return [ + $this->files_to_scan, + $this->files_to_deep_scan, + $this->classes_to_scan, + $this->classes_to_deep_scan, + $this->store_scan_failure, + $this->classlike_files, + $this->deep_scanned_classlike_files, + $this->scanned_files, + $this->reflected_classlikes_lc, + ]; + } + + /** + * @param ThreadData $thread_data + * + */ + public function addThreadData(array $thread_data): void + { + [ + $files_to_scan, + $files_to_deep_scan, + $classes_to_scan, + $classes_to_deep_scan, + $store_scan_failure, + $classlike_files, + $deep_scanned_classlike_files, + $scanned_files, + $reflected_classlikes_lc + ] = $thread_data; + + $this->files_to_scan = array_merge($files_to_scan, $this->files_to_scan); + $this->files_to_deep_scan = array_merge($files_to_deep_scan, $this->files_to_deep_scan); + $this->classes_to_scan = array_merge($classes_to_scan, $this->classes_to_scan); + $this->classes_to_deep_scan = array_merge($classes_to_deep_scan, $this->classes_to_deep_scan); + $this->store_scan_failure = array_merge($store_scan_failure, $this->store_scan_failure); + $this->classlike_files = array_merge($classlike_files, $this->classlike_files); + $this->deep_scanned_classlike_files = array_merge( + $deep_scanned_classlike_files, + $this->deep_scanned_classlike_files + ); + $this->scanned_files = array_merge($scanned_files, $this->scanned_files); + $this->reflected_classlikes_lc = array_merge($reflected_classlikes_lc, $this->reflected_classlikes_lc); + } + + public function isForked(): void + { + $this->is_forked = true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/TaintFlowGraph.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/TaintFlowGraph.php new file mode 100644 index 0000000000000000000000000000000000000000..b26972da4677aa2ee12976a9ebcbc670e5bf917d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/TaintFlowGraph.php @@ -0,0 +1,320 @@ + */ + private $sources = []; + + /** @var array */ + private $nodes = []; + + /** @var array */ + private $sinks = []; + + /** @var array> */ + private $specialized_calls = []; + + /** @var array> */ + private $specializations = []; + + public function addNode(DataFlowNode $node) : void + { + $this->nodes[$node->id] = $node; + + if ($node->unspecialized_id && $node->specialization_key) { + $this->specialized_calls[$node->specialization_key][$node->unspecialized_id] = true; + $this->specializations[$node->unspecialized_id][$node->specialization_key] = true; + } + } + + public function addSource(TaintSource $node) : void + { + $this->sources[$node->id] = $node; + } + + public function addSink(TaintSink $node) : void + { + $this->sinks[$node->id] = $node; + // in the rare case the sink is the _next_ node, this is necessary + $this->nodes[$node->id] = $node; + } + + public function addGraph(self $taint) : void + { + $this->sources += $taint->sources; + $this->sinks += $taint->sinks; + $this->nodes += $taint->nodes; + $this->specialized_calls += $taint->specialized_calls; + + foreach ($taint->forward_edges as $key => $map) { + if (!isset($this->forward_edges[$key])) { + $this->forward_edges[$key] = $map; + } else { + $this->forward_edges[$key] += $map; + } + } + + foreach ($taint->specializations as $key => $map) { + if (!isset($this->specializations[$key])) { + $this->specializations[$key] = $map; + } else { + $this->specializations[$key] += $map; + } + } + } + + public function getPredecessorPath(DataFlowNode $source) : string + { + $location_summary = ''; + + if ($source->code_location) { + $location_summary = $source->code_location->getShortSummary(); + } + + $source_descriptor = $source->label . ($location_summary ? ' (' . $location_summary . ')' : ''); + + $previous_source = $source->previous; + + if ($previous_source) { + if ($previous_source === $source) { + return ''; + } + + return $this->getPredecessorPath($previous_source) . ' -> ' . $source_descriptor; + } + + return $source_descriptor; + } + + public function getSuccessorPath(DataFlowNode $sink) : string + { + $location_summary = ''; + + if ($sink->code_location) { + $location_summary = $sink->code_location->getShortSummary(); + } + + $sink_descriptor = $sink->label . ($location_summary ? ' (' . $location_summary . ')' : ''); + + $next_sink = $sink->previous; + + if ($next_sink) { + if ($next_sink === $sink) { + return ''; + } + + return $sink_descriptor . ' -> ' . $this->getSuccessorPath($next_sink); + } + + return $sink_descriptor; + } + + /** + * @return list + */ + public function getIssueTrace(DataFlowNode $source) : array + { + $previous_source = $source->previous; + + $node = [ + 'location' => $source->code_location, + 'label' => $source->label, + 'entry_path_type' => \end($source->path_types) ?: '' + ]; + + if ($previous_source) { + if ($previous_source === $source) { + return []; + } + + return array_merge($this->getIssueTrace($previous_source), [$node]); + } + + return [$node]; + } + + public function connectSinksAndSources() : void + { + $visited_source_ids = []; + + $sources = $this->sources; + $sinks = $this->sinks; + + for ($i = 0; count($sinks) && count($sources) && $i < 40; $i++) { + $new_sources = []; + + foreach ($sources as $source) { + $source_taints = $source->taints; + \sort($source_taints); + + $visited_source_ids[$source->id][implode(',', $source_taints)] = true; + + $generated_sources = $this->getSpecializedSources($source); + + foreach ($generated_sources as $generated_source) { + $new_sources = array_merge( + $new_sources, + $this->getChildNodes( + $generated_source, + $source_taints, + $sinks, + $visited_source_ids + ) + ); + } + } + + $sources = $new_sources; + } + } + + /** + * @param array $source_taints + * @param array $sinks + * @return array + */ + private function getChildNodes( + DataFlowNode $generated_source, + array $source_taints, + array $sinks, + array $visited_source_ids + ) : array { + $new_sources = []; + + foreach ($this->forward_edges[$generated_source->id] as $to_id => $path) { + $path_type = $path->type; + $added_taints = $path->unescaped_taints ?: []; + $removed_taints = $path->escaped_taints ?: []; + + if (!isset($this->nodes[$to_id])) { + continue; + } + + $new_taints = \array_unique( + \array_diff( + \array_merge($source_taints, $added_taints), + $removed_taints + ) + ); + + \sort($new_taints); + + $destination_node = $this->nodes[$to_id]; + + if (isset($visited_source_ids[$to_id][implode(',', $new_taints)])) { + continue; + } + + if (self::shouldIgnoreFetch($path_type, 'array', $generated_source->path_types)) { + continue; + } + + if (self::shouldIgnoreFetch($path_type, 'property', $generated_source->path_types)) { + continue; + } + + if (isset($sinks[$to_id])) { + $matching_taints = array_intersect($sinks[$to_id]->taints, $new_taints); + + if ($matching_taints && $generated_source->code_location) { + $config = \Psalm\Config::getInstance(); + + if ($sinks[$to_id]->code_location + && $config->reportIssueInFile('TaintedInput', $sinks[$to_id]->code_location->file_path) + ) { + $issue_location = $sinks[$to_id]->code_location; + } else { + $issue_location = $generated_source->code_location; + } + + if (IssueBuffer::accepts( + new TaintedInput( + 'Detected tainted ' . implode(', ', $matching_taints), + $issue_location, + $this->getIssueTrace($generated_source), + $this->getPredecessorPath($generated_source) + . ' -> ' . $this->getSuccessorPath($sinks[$to_id]) + ) + )) { + // fall through + } + + continue; + } + } + + $new_destination = clone $destination_node; + $new_destination->previous = $generated_source; + $new_destination->taints = $new_taints; + $new_destination->specialized_calls = $generated_source->specialized_calls; + $new_destination->path_types = array_merge($generated_source->path_types, [$path_type]); + + $new_sources[$to_id] = $new_destination; + } + + return $new_sources; + } + + /** @return array */ + private function getSpecializedSources(DataFlowNode $source) : array + { + $generated_sources = []; + + if (isset($this->forward_edges[$source->id])) { + return [$source]; + } + + if ($source->specialization_key && isset($this->specialized_calls[$source->specialization_key])) { + $generated_source = clone $source; + + $generated_source->specialized_calls[$source->specialization_key] + = $this->specialized_calls[$source->specialization_key]; + + $generated_source->id = substr($source->id, 0, -strlen($source->specialization_key) - 1); + + $generated_sources[] = $generated_source; + } elseif (isset($this->specializations[$source->id])) { + foreach ($this->specializations[$source->id] as $specialization => $_) { + if (!$source->specialized_calls || isset($source->specialized_calls[$specialization])) { + $new_source = clone $source; + + $new_source->id = $source->id . '-' . $specialization; + + $generated_sources[] = $new_source; + } + } + } else { + foreach ($source->specialized_calls as $key => $map) { + if (isset($map[$source->id]) && isset($this->forward_edges[$source->id . '-' . $key])) { + $new_source = clone $source; + + $new_source->id = $source->id . '-' . $key; + + $generated_sources[] = $new_source; + } + } + } + + return \array_filter( + $generated_sources, + function ($new_source): bool { + return isset($this->forward_edges[$new_source->id]); + } + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/VariableUseGraph.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/VariableUseGraph.php new file mode 100644 index 0000000000000000000000000000000000000000..5b258b3399b6bdadd7c53557af269f857d9bcbd4 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Codebase/VariableUseGraph.php @@ -0,0 +1,98 @@ +id] = true; + + $child_nodes = $this->getChildNodes( + $source, + $visited_source_ids + ); + + if ($child_nodes === null) { + return true; + } + + $new_sources = array_merge( + $new_sources, + $child_nodes + ); + } + + $sources = $new_sources; + } + + return false; + } + + /** + * @param array $visited_source_ids + * @return array|null + */ + private function getChildNodes( + DataFlowNode $generated_source, + array $visited_source_ids + ) : ?array { + $new_sources = []; + + if (!isset($this->forward_edges[$generated_source->id])) { + return []; + } + + foreach ($this->forward_edges[$generated_source->id] as $to_id => $path) { + $path_type = $path->type; + + if ($path->type === 'variable-use' + || $path->type === 'closure-use' + || $path->type === 'global-use' + || $path->type === 'use-inside-instance-property' + || $path->type === 'use-inside-static-property' + || $path->type === 'use-inside-call' + || $path->type === 'use-inside-conditional' + || $path->type === 'use-inside-isset' + || $path->type === 'arg' + ) { + return null; + } + + if (isset($visited_source_ids[$to_id])) { + continue; + } + + if (self::shouldIgnoreFetch($path_type, 'array', $generated_source->path_types)) { + continue; + } + + if (self::shouldIgnoreFetch($path_type, 'property', $generated_source->path_types)) { + continue; + } + + $new_destination = new DataFlowNode($to_id, $to_id, null); + $new_destination->path_types = array_merge($generated_source->path_types, [$path_type]); + + $new_sources[$to_id] = $new_destination; + } + + return $new_sources; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Composer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Composer.php new file mode 100644 index 0000000000000000000000000000000000000000..a8c20357eda3ee21ef45c7504f2b3731c5621e14 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Composer.php @@ -0,0 +1,42 @@ + */ + public $taints; + + /** @var ?self */ + public $previous; + + /** @var list */ + public $path_types = []; + + /** + * @var array> + */ + public $specialized_calls = []; + + /** + * @param array $taints + */ + public function __construct( + string $id, + string $label, + ?CodeLocation $code_location, + ?string $specialization_key = null, + array $taints = [] + ) { + $this->id = $id; + + if ($specialization_key) { + $this->unspecialized_id = $id; + $this->id .= '-' . $specialization_key; + } + + $this->label = $label; + $this->code_location = $code_location; + $this->specialization_key = $specialization_key; + $this->taints = $taints; + } + + /** + * @return static + */ + final public static function getForMethodArgument( + string $method_id, + string $cased_method_id, + int $argument_offset, + ?CodeLocation $arg_location, + ?CodeLocation $code_location = null + ): self { + $arg_id = strtolower($method_id) . '#' . ($argument_offset + 1); + + $label = $cased_method_id . '#' . ($argument_offset + 1); + + $specialization_key = null; + + if ($code_location) { + $specialization_key = strtolower($code_location->file_name) . ':' . $code_location->raw_file_start; + } + + return new static( + $arg_id, + $label, + $arg_location, + $specialization_key + ); + } + + /** + * @return static + */ + final public static function getForAssignment( + string $var_id, + CodeLocation $assignment_location + ): self { + $id = $var_id + . '-' . $assignment_location->file_name + . ':' . $assignment_location->raw_file_start + . '-' . $assignment_location->raw_file_end; + + return new static($id, $var_id, $assignment_location, null); + } + + /** + * @return static + */ + final public static function getForMethodReturn( + string $method_id, + string $cased_method_id, + ?CodeLocation $code_location, + ?CodeLocation $function_location = null + ): self { + $specialization_key = null; + + if ($function_location) { + $specialization_key = strtolower($function_location->file_name) . ':' . $function_location->raw_file_start; + } + + return new static( + \strtolower($method_id), + $cased_method_id, + $code_location, + $specialization_key + ); + } + + public function __toString(): string + { + return $this->id; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/DataFlow/Path.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/DataFlow/Path.php new file mode 100644 index 0000000000000000000000000000000000000000..f87902af31ed668dcf49dcd2bec14b8b10ccf8aa --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/DataFlow/Path.php @@ -0,0 +1,26 @@ + $unescaped_taints + * @param ?array $escaped_taints + */ + public function __construct(string $type, ?array $unescaped_taints, ?array $escaped_taints) + { + $this->type = $type; + $this->unescaped_taints = $unescaped_taints; + $this->escaped_taints = $escaped_taints; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/DataFlow/TaintSink.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/DataFlow/TaintSink.php new file mode 100644 index 0000000000000000000000000000000000000000..408f13ccd8454525596f3443a183fa103776562c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/DataFlow/TaintSink.php @@ -0,0 +1,7 @@ + $a + * @param array $b + * + * @return array{0:non-empty-list>, 1: int, 2: int, 3: array} + */ + protected static function calculateTrace( + \Closure $is_equal, + array $a, + array $b, + string $a_code, + string $b_code + ) : array { + $n = \count($a); + $m = \count($b); + $max = $n + $m; + $v = [1 => 0]; + $bc = []; + $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; + + $body_change = false; + + while ($x < $n && $y < $m && ($is_equal)($a[$x], $b[$y], $a_code, $b_code, $body_change)) { + /** @var bool */ + $bc[$x] = $body_change; + ++$x; + ++$y; + + $body_change = false; + } + + $v[$k] = $x; + if ($x >= $n && $y >= $m) { + return [$trace, $x, $y, $bc]; + } + } + } + throw new \Exception('Should not happen'); + } + + /** + * @param array> $trace + * @param array $a + * @param array $b + * @param array $bc + * + * @return list + * + * @psalm-pure + */ + protected static function extractDiff(array $trace, int $x, int $y, array $a, array $b, array $bc) : 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( + $bc[$x - 1] ? DiffElem::TYPE_KEEP_SIGNATURE : DiffElem::TYPE_KEEP, + $a[$x - 1], + $b[$y - 1] + ); + --$x; + --$y; + } + + if ($d === 0) { + break; + } + + while ($x > $prevX) { + $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $a[$x - 1], null); + --$x; + } + + while ($y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $b[$y - 1]); + --$y; + } + } + + return array_reverse($result); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/ClassStatementsDiffer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/ClassStatementsDiffer.php new file mode 100644 index 0000000000000000000000000000000000000000..a832bd092ffd1351a46900cc60b0021e825a128d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/ClassStatementsDiffer.php @@ -0,0 +1,240 @@ + $a + * @param array $b + * + * @return array{ + * 0: list, + * 1: list, + * 2: list, + * 3: array + * } + */ + public static function diff(string $name, array $a, array $b, string $a_code, string $b_code): array + { + $diff_map = []; + + [$trace, $x, $y, $bc] = self::calculateTrace( + /** + * @param string $a_code + * @param string $b_code + * @param bool $body_change + * + * @return bool + */ + function ( + PhpParser\Node\Stmt $a, + PhpParser\Node\Stmt $b, + $a_code, + $b_code, + &$body_change = false + ) use (&$diff_map): bool { + if (get_class($a) !== get_class($b)) { + return false; + } + + $a_start = (int)$a->getAttribute('startFilePos'); + $a_end = (int)$a->getAttribute('endFilePos'); + + $b_start = (int)$b->getAttribute('startFilePos'); + $b_end = (int)$b->getAttribute('endFilePos'); + + $a_comments_end = $a_start; + $b_comments_end = $b_start; + + /** @var list */ + $a_comments = $a->getComments(); + /** @var list */ + $b_comments = $b->getComments(); + + $signature_change = false; + $body_change = false; + + if ($a_comments) { + if (!$b_comments) { + $signature_change = true; + } + + $a_start = $a_comments[0]->getStartFilePos(); + } + + if ($b_comments) { + if (!$a_comments) { + $signature_change = true; + } + + $b_start = $b_comments[0]->getStartFilePos(); + } + + $a_size = $a_end - $a_start; + $b_size = $b_end - $b_start; + + if ($a_size === $b_size + && substr($a_code, $a_start, $a_size) === substr($b_code, $b_start, $b_size) + ) { + $start_diff = $b_start - $a_start; + $line_diff = $b->getLine() - $a->getLine(); + + /** @psalm-suppress MixedArrayAssignment */ + $diff_map[] = [$a_start, $a_end, $start_diff, $line_diff]; + + return true; + } + + if (!$signature_change + && substr($a_code, $a_start, $a_comments_end - $a_start) + !== substr($b_code, $b_start, $b_comments_end - $b_start) + ) { + $signature_change = true; + } + + if ($a instanceof PhpParser\Node\Stmt\ClassMethod && $b instanceof PhpParser\Node\Stmt\ClassMethod) { + if ((string) $a->name !== (string) $b->name) { + return false; + } + + if ($a->stmts) { + $first_stmt = $a->stmts[0]; + $a_stmts_start = (int) $first_stmt->getAttribute('startFilePos'); + + if ($a_stmt_comments = $first_stmt->getComments()) { + $a_stmts_start = $a_stmt_comments[0]->getStartFilePos(); + } + } else { + $a_stmts_start = $a_end; + } + + if ($b->stmts) { + $first_stmt = $b->stmts[0]; + $b_stmts_start = (int) $first_stmt->getAttribute('startFilePos'); + + if ($b_stmt_comments = $first_stmt->getComments()) { + $b_stmts_start = $b_stmt_comments[0]->getStartFilePos(); + } + } else { + $b_stmts_start = $b_end; + } + + $a_body_size = $a_end - $a_stmts_start; + $b_body_size = $b_end - $b_stmts_start; + + $body_change = $a_body_size !== $b_body_size + || substr($a_code, $a_stmts_start, $a_end - $a_stmts_start) + !== substr($b_code, $b_stmts_start, $b_end - $b_stmts_start); + + if (!$signature_change) { + $a_signature = substr($a_code, $a_start, $a_stmts_start - $a_start); + $b_signature = substr($b_code, $b_start, $b_stmts_start - $b_start); + + if ($a_signature !== $b_signature) { + $a_signature = trim($a_signature); + $b_signature = trim($b_signature); + + if (strpos($a_signature, $b_signature) === false + && strpos($b_signature, $a_signature) === false + ) { + $signature_change = true; + } + } + } + } elseif ($a instanceof PhpParser\Node\Stmt\Property && $b instanceof PhpParser\Node\Stmt\Property) { + if (count($a->props) !== 1 || count($b->props) !== 1) { + return false; + } + + if ((string) $a->props[0]->name !== (string) $b->props[0]->name || $a->flags !== $b->flags) { + return false; + } + + $body_change = substr($a_code, $a_comments_end, $a_end - $a_comments_end) + !== substr($b_code, $b_comments_end, $b_end - $b_comments_end); + } else { + $signature_change = true; + } + + if (!$signature_change && !$body_change) { + /** @psalm-suppress MixedArrayAssignment */ + $diff_map[] = [$a_start, $a_end, $b_start - $a_start, $b->getLine() - $a->getLine()]; + } + + return !$signature_change; + }, + $a, + $b, + $a_code, + $b_code + ); + + $diff = self::extractDiff($trace, $x, $y, $a, $b, $bc); + + $keep = []; + $keep_signature = []; + $add_or_delete = []; + + foreach ($diff as $diff_elem) { + if ($diff_elem->type === DiffElem::TYPE_KEEP) { + if ($diff_elem->old instanceof PhpParser\Node\Stmt\ClassMethod) { + $keep[] = strtolower($name) . '::' . strtolower((string) $diff_elem->old->name); + } elseif ($diff_elem->old instanceof PhpParser\Node\Stmt\Property) { + foreach ($diff_elem->old->props as $prop) { + $keep[] = strtolower($name) . '::$' . $prop->name; + } + } elseif ($diff_elem->old instanceof PhpParser\Node\Stmt\ClassConst) { + foreach ($diff_elem->old->consts as $const) { + $keep[] = strtolower($name) . '::' . $const->name; + } + } elseif ($diff_elem->old instanceof PhpParser\Node\Stmt\TraitUse) { + foreach ($diff_elem->old->traits as $trait) { + $keep[] = strtolower($name . '&' . (string) $trait->getAttribute('resolvedName')); + } + } + } elseif ($diff_elem->type === DiffElem::TYPE_KEEP_SIGNATURE) { + if ($diff_elem->old instanceof PhpParser\Node\Stmt\ClassMethod) { + $keep_signature[] = strtolower($name) . '::' . strtolower((string) $diff_elem->old->name); + } elseif ($diff_elem->old instanceof PhpParser\Node\Stmt\Property) { + foreach ($diff_elem->old->props as $prop) { + $keep_signature[] = strtolower($name) . '::$' . $prop->name; + } + } + } elseif ($diff_elem->type === DiffElem::TYPE_REMOVE || $diff_elem->type === DiffElem::TYPE_ADD) { + /** @psalm-suppress MixedAssignment */ + $affected_elem = $diff_elem->type === DiffElem::TYPE_REMOVE ? $diff_elem->old : $diff_elem->new; + if ($affected_elem instanceof PhpParser\Node\Stmt\ClassMethod) { + $add_or_delete[] = strtolower($name) . '::' . strtolower((string) $affected_elem->name); + } elseif ($affected_elem instanceof PhpParser\Node\Stmt\Property) { + foreach ($affected_elem->props as $prop) { + $add_or_delete[] = strtolower($name) . '::$' . $prop->name; + } + } elseif ($affected_elem instanceof PhpParser\Node\Stmt\ClassConst) { + foreach ($affected_elem->consts as $const) { + $add_or_delete[] = strtolower($name) . '::' . $const->name; + } + } elseif ($affected_elem instanceof PhpParser\Node\Stmt\TraitUse) { + foreach ($affected_elem->traits as $trait) { + $add_or_delete[] = strtolower($name . '&' . (string) $trait->getAttribute('resolvedName')); + } + } + } + } + + /** @var array $diff_map */ + return [$keep, $keep_signature, $add_or_delete, $diff_map]; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/DiffElem.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/DiffElem.php new file mode 100644 index 0000000000000000000000000000000000000000..f570111f451301cfa373e1205b90b5489d90bb9f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/DiffElem.php @@ -0,0 +1,35 @@ +type = $type; + $this->old = $old; + $this->new = $new; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/FileDiffer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/FileDiffer.php new file mode 100644 index 0000000000000000000000000000000000000000..0b8abdeb13e57fde0f862cb222168d3688855200 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/FileDiffer.php @@ -0,0 +1,314 @@ + $a + * @param list $b + * + * @return array{0:non-empty-list>, 1: int, 2: int} + * + * @psalm-pure + */ + private static function calculateTrace( + array $a, + array $b + ) : array { + $n = \count($a); + $m = \count($b); + $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 && $a[$x] === $b[$y]) { + ++$x; + ++$y; + } + + $v[$k] = $x; + if ($x >= $n && $y >= $m) { + return [$trace, $x, $y]; + } + } + } + throw new \Exception('Should not happen'); + } + + /** + * @param list> $trace + * @param list $a + * @param list $b + * + * @return list + * + * @psalm-pure + */ + private static function extractDiff(array $trace, int $x, int $y, array $a, array $b) : 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, + $a[$x - 1], + $b[$y - 1] + ); + --$x; + --$y; + } + + if ($d === 0) { + break; + } + + while ($x > $prevX) { + $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $a[$x - 1], null); + --$x; + } + + while ($y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $b[$y - 1]); + --$y; + } + } + + return array_reverse($result); + } + + /** + * @return array + * + * @psalm-pure + */ + public static function getDiff(string $a_code, string $b_code): array + { + $a = explode("\n", $a_code); + $b = explode("\n", $b_code); + [$trace, $x, $y] = self::calculateTrace($a, $b); + + $diff = self::coalesceReplacements(self::extractDiff($trace, $x, $y, $a, $b)); + + $a_offset = 0; + + $b_offset = 0; + + $last_diff_type = null; + + /** @var array{0:int, 1:int, 2:int, 3:int, 4:int, 5:string}|null */ + $last_change = null; + + $changes = []; + $i = 0; + $line_diff = 0; + + foreach ($diff as $diff_elem) { + $diff_type = $diff_elem->type; + + if ($diff_type !== $last_diff_type) { + $last_change = null; + } + + if ($diff_type === DiffElem::TYPE_REMOVE) { + /** @var string $diff_elem->old */ + $diff_text = $diff_elem->old . "\n"; + + $text_length = strlen($diff_text); + + --$line_diff; + + if ($last_change === null) { + ++$i; + $last_change = [ + $a_offset, + $a_offset + $text_length, + $b_offset, + $b_offset, + $line_diff, + '', + ]; + $changes[$i - 1] = $last_change; + } else { + $last_change[1] += $text_length; + $last_change[4] = $line_diff; + $changes[$i - 1] = $last_change; + } + + $a_offset += $text_length; + } elseif ($diff_type === DiffElem::TYPE_ADD) { + /** @var string $diff_elem->new */ + $diff_text = $diff_elem->new . "\n"; + + $text_length = strlen($diff_text); + + ++$line_diff; + + if ($last_change === null) { + ++$i; + $last_change = [ + $a_offset, + $a_offset, + $b_offset, + $b_offset + $text_length, + $line_diff, + $diff_text, + ]; + $changes[$i - 1] = $last_change; + } else { + $last_change[3] += $text_length; + $last_change[4] = $line_diff; + $last_change[5] .= $diff_text; + + $changes[$i - 1] = $last_change; + } + + $b_offset += $text_length; + } elseif ($diff_type === DiffElem::TYPE_REPLACE) { + /** @var string $diff_elem->old */ + $old_diff_text = $diff_elem->old . "\n"; + + /** @var string $diff_elem->new */ + $new_diff_text = $diff_elem->new . "\n"; + + $old_text_length = strlen($old_diff_text); + $new_text_length = strlen($new_diff_text); + + $max_same_count = min($old_text_length, $new_text_length); + + for ($j = 0; $j < $max_same_count; ++$j) { + if ($old_diff_text[$j] !== $new_diff_text[$j]) { + break; + } + + ++$a_offset; + ++$b_offset; + --$old_text_length; + --$new_text_length; + } + + $new_diff_text = substr($new_diff_text, $j); + + if ($last_change === null || $j) { + ++$i; + $last_change = [ + $a_offset, + $a_offset + $old_text_length, + $b_offset, + $b_offset + $new_text_length, + $line_diff, + $new_diff_text, + ]; + $changes[$i - 1] = $last_change; + } else { + $last_change[1] += $old_text_length; + $last_change[3] += $new_text_length; + $last_change[5] .= $new_diff_text; + $changes[$i - 1] = $last_change; + } + + $a_offset += $old_text_length; + $b_offset += $new_text_length; + } else { + /** @psalm-suppress MixedArgument */ + $same_text_length = strlen($diff_elem->new) + 1; + + $a_offset += $same_text_length; + $b_offset += $same_text_length; + } + + $last_diff_type = $diff_elem->type; + } + + return $changes; + } + + /** + * Coalesce equal-length sequences of remove+add into a replace operation. + * + * @param DiffElem[] $diff + * + * @return list + * + * @psalm-pure + */ + private static 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/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/FileStatementsDiffer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/FileStatementsDiffer.php new file mode 100644 index 0000000000000000000000000000000000000000..af09760e48711c777072cc3546eb98e26c06c58e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/FileStatementsDiffer.php @@ -0,0 +1,159 @@ + $a + * @param list $b + * + * @return array{ + * 0: list, + * 1: list, + * 2: list, + * 3: list + * } + */ + public static function diff(array $a, array $b, string $a_code, string $b_code): array + { + [$trace, $x, $y, $bc] = self::calculateTrace( + /** + * @param string $a_code + * @param string $b_code + * + * @return bool + */ + function ( + PhpParser\Node\Stmt $a, + PhpParser\Node\Stmt $b, + $a_code, + $b_code, + bool &$body_change = false + ): bool { + if (get_class($a) !== get_class($b)) { + return false; + } + + if (($a instanceof PhpParser\Node\Stmt\Namespace_ && $b instanceof PhpParser\Node\Stmt\Namespace_) + || ($a instanceof PhpParser\Node\Stmt\Class_ && $b instanceof PhpParser\Node\Stmt\Class_) + || ($a instanceof PhpParser\Node\Stmt\Interface_ && $b instanceof PhpParser\Node\Stmt\Interface_) + || ($a instanceof PhpParser\Node\Stmt\Trait_ && $b instanceof PhpParser\Node\Stmt\Trait_) + ) { + return (string)$a->name === (string)$b->name; + } + + if (($a instanceof PhpParser\Node\Stmt\Use_ + && $b instanceof PhpParser\Node\Stmt\Use_) + || ($a instanceof PhpParser\Node\Stmt\GroupUse + && $b instanceof PhpParser\Node\Stmt\GroupUse) + ) { + $a_start = (int)$a->getAttribute('startFilePos'); + $a_end = (int)$a->getAttribute('endFilePos'); + + $b_start = (int)$b->getAttribute('startFilePos'); + $b_end = (int)$b->getAttribute('endFilePos'); + + $a_size = $a_end - $a_start; + $b_size = $b_end - $b_start; + + if (substr($a_code, $a_start, $a_size) === substr($b_code, $b_start, $b_size)) { + return true; + } + } + + return false; + }, + $a, + $b, + $a_code, + $b_code + ); + + $diff = self::extractDiff($trace, $x, $y, $a, $b, $bc); + + $keep = []; + $keep_signature = []; + $add_or_delete = []; + $diff_map = []; + + foreach ($diff as $diff_elem) { + if ($diff_elem->type === DiffElem::TYPE_KEEP) { + if ($diff_elem->old instanceof PhpParser\Node\Stmt\Namespace_ + && $diff_elem->new instanceof PhpParser\Node\Stmt\Namespace_ + ) { + $namespace_keep = NamespaceStatementsDiffer::diff( + (string) $diff_elem->old->name, + $diff_elem->old->stmts, + $diff_elem->new->stmts, + $a_code, + $b_code + ); + + $keep = array_merge($keep, $namespace_keep[0]); + $keep_signature = array_merge($keep_signature, $namespace_keep[1]); + $add_or_delete = array_merge($add_or_delete, $namespace_keep[2]); + $diff_map = array_merge($diff_map, $namespace_keep[3]); + } elseif (($diff_elem->old instanceof PhpParser\Node\Stmt\Class_ + && $diff_elem->new instanceof PhpParser\Node\Stmt\Class_) + || ($diff_elem->old instanceof PhpParser\Node\Stmt\Interface_ + && $diff_elem->new instanceof PhpParser\Node\Stmt\Interface_) + || ($diff_elem->old instanceof PhpParser\Node\Stmt\Trait_ + && $diff_elem->new instanceof PhpParser\Node\Stmt\Trait_) + ) { + $class_keep = ClassStatementsDiffer::diff( + (string) $diff_elem->old->name, + $diff_elem->old->stmts, + $diff_elem->new->stmts, + $a_code, + $b_code + ); + + $keep = array_merge($keep, $class_keep[0]); + $keep_signature = array_merge($keep_signature, $class_keep[1]); + $add_or_delete = array_merge($add_or_delete, $class_keep[2]); + $diff_map = array_merge($diff_map, $class_keep[3]); + } + } elseif ($diff_elem->type === DiffElem::TYPE_REMOVE) { + if ($diff_elem->old instanceof PhpParser\Node\Stmt\Use_ + || $diff_elem->old instanceof PhpParser\Node\Stmt\GroupUse + ) { + foreach ($diff_elem->old->uses as $use) { + if ($use->alias) { + $add_or_delete[] = 'use:' . (string) $use->alias; + } else { + $name_parts = $use->name->parts; + + $add_or_delete[] = 'use:' . end($name_parts); + } + } + } + } elseif ($diff_elem->type === DiffElem::TYPE_ADD) { + if ($diff_elem->new instanceof PhpParser\Node\Stmt\Use_ + || $diff_elem->new instanceof PhpParser\Node\Stmt\GroupUse + ) { + foreach ($diff_elem->new->uses as $use) { + if ($use->alias) { + $add_or_delete[] = 'use:' . (string) $use->alias; + } else { + $name_parts = $use->name->parts; + + $add_or_delete[] = 'use:' . end($name_parts); + } + } + } + } + } + + return [$keep, $keep_signature, $add_or_delete, $diff_map]; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php new file mode 100644 index 0000000000000000000000000000000000000000..b5c32120a62b2dd38d5876d2f53b0440f8543c37 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php @@ -0,0 +1,145 @@ + $a + * @param array $b + * + * @return array{ + * 0: list, + * 1: list, + * 2: list, + * 3: list + * } + */ + public static function diff(string $name, array $a, array $b, string $a_code, string $b_code): array + { + [$trace, $x, $y, $bc] = self::calculateTrace( + /** + * @param string $a_code + * @param string $b_code + * + * @return bool + */ + function ( + PhpParser\Node\Stmt $a, + PhpParser\Node\Stmt $b, + $a_code, + $b_code, + bool &$body_change = false + ): bool { + if (get_class($a) !== get_class($b)) { + return false; + } + + if (($a instanceof PhpParser\Node\Stmt\Class_ && $b instanceof PhpParser\Node\Stmt\Class_) + || ($a instanceof PhpParser\Node\Stmt\Interface_ && $b instanceof PhpParser\Node\Stmt\Interface_) + || ($a instanceof PhpParser\Node\Stmt\Trait_ && $b instanceof PhpParser\Node\Stmt\Trait_) + ) { + // @todo add check for comments comparison + + return (string)$a->name === (string)$b->name; + } + + if (($a instanceof PhpParser\Node\Stmt\Use_ + && $b instanceof PhpParser\Node\Stmt\Use_) + || ($a instanceof PhpParser\Node\Stmt\GroupUse + && $b instanceof PhpParser\Node\Stmt\GroupUse) + ) { + $a_start = (int)$a->getAttribute('startFilePos'); + $a_end = (int)$a->getAttribute('endFilePos'); + + $b_start = (int)$b->getAttribute('startFilePos'); + $b_end = (int)$b->getAttribute('endFilePos'); + + $a_size = $a_end - $a_start; + $b_size = $b_end - $b_start; + + if (substr($a_code, $a_start, $a_size) === substr($b_code, $b_start, $b_size)) { + return true; + } + } + + return false; + }, + $a, + $b, + $a_code, + $b_code + ); + + $diff = self::extractDiff($trace, $x, $y, $a, $b, $bc); + + $keep = []; + $keep_signature = []; + $add_or_delete = []; + $diff_map = []; + + foreach ($diff as $diff_elem) { + if ($diff_elem->type === DiffElem::TYPE_KEEP) { + if (($diff_elem->old instanceof PhpParser\Node\Stmt\Class_ + && $diff_elem->new instanceof PhpParser\Node\Stmt\Class_) + || ($diff_elem->old instanceof PhpParser\Node\Stmt\Interface_ + && $diff_elem->new instanceof PhpParser\Node\Stmt\Interface_) + || ($diff_elem->old instanceof PhpParser\Node\Stmt\Trait_ + && $diff_elem->new instanceof PhpParser\Node\Stmt\Trait_) + ) { + $class_keep = ClassStatementsDiffer::diff( + ($name ? $name . '\\' : '') . $diff_elem->old->name, + $diff_elem->old->stmts, + $diff_elem->new->stmts, + $a_code, + $b_code + ); + + $keep = array_merge($keep, $class_keep[0]); + $keep_signature = array_merge($keep_signature, $class_keep[1]); + $add_or_delete = array_merge($add_or_delete, $class_keep[2]); + $diff_map = array_merge($diff_map, $class_keep[3]); + } + } elseif ($diff_elem->type === DiffElem::TYPE_REMOVE) { + if ($diff_elem->old instanceof PhpParser\Node\Stmt\Use_ + || $diff_elem->old instanceof PhpParser\Node\Stmt\GroupUse + ) { + foreach ($diff_elem->old->uses as $use) { + if ($use->alias) { + $add_or_delete[] = 'use:' . (string) $use->alias; + } else { + $name_parts = $use->name->parts; + + $add_or_delete[] = 'use:' . end($name_parts); + } + } + } + } elseif ($diff_elem->type === DiffElem::TYPE_ADD) { + if ($diff_elem->new instanceof PhpParser\Node\Stmt\Use_ + || $diff_elem->new instanceof PhpParser\Node\Stmt\GroupUse + ) { + foreach ($diff_elem->new->uses as $use) { + if ($use->alias) { + $add_or_delete[] = 'use:' . (string) $use->alias; + } else { + $name_parts = $use->name->parts; + + $add_or_delete[] = 'use:' . end($name_parts); + } + } + } + } + } + + return [$keep, $keep_signature, $add_or_delete, $diff_map]; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/BuildInfoCollector.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/BuildInfoCollector.php new file mode 100644 index 0000000000000000000000000000000000000000..1f79988cc861986671ee6a008ac217635b31dbe2 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/BuildInfoCollector.php @@ -0,0 +1,315 @@ + + */ +class BuildInfoCollector +{ + /** + * Environment variables. + * + * Overwritten through collection process. + * + * @var array + */ + protected $env; + + /** + * Read environment variables. + * + * @var array + */ + protected $readEnv = []; + + public function __construct(array $env) + { + $this->env = $env; + } + + // API + + /** + * Collect environment variables. + */ + public function collect() : array + { + $this->readEnv = []; + + $this + ->fillTravisCi() + ->fillCircleCi() + ->fillAppVeyor() + ->fillJenkins() + ->fillScrutinizer() + ->fillGithubActions(); + + return $this->readEnv; + } + + // internal method + + /** + * Fill Travis CI environment variables. + * + * "TRAVIS", "TRAVIS_JOB_ID" must be set. + * + * @return $this + * + * @psalm-suppress PossiblyUndefinedStringArrayOffset + */ + protected function fillTravisCi() : self + { + if (isset($this->env['TRAVIS']) && $this->env['TRAVIS'] && isset($this->env['TRAVIS_JOB_ID'])) { + $this->readEnv['CI_JOB_ID'] = $this->env['TRAVIS_JOB_ID']; + $this->env['CI_NAME'] = 'travis-ci'; + + // backup + $this->readEnv['TRAVIS'] = $this->env['TRAVIS']; + $this->readEnv['TRAVIS_JOB_ID'] = $this->env['TRAVIS_JOB_ID']; + $this->readEnv['CI_NAME'] = $this->env['CI_NAME']; + $this->readEnv['TRAVIS_TAG'] = $this->env['TRAVIS_TAG'] ?? ''; + + $repo_slug = (string) $this->env['TRAVIS_REPO_SLUG']; + + if ($repo_slug) { + $slug_parts = explode('/', $repo_slug); + $this->readEnv['CI_REPO_OWNER'] = $slug_parts[0]; + $this->readEnv['CI_REPO_NAME'] = $slug_parts[1]; + } + + $pr_slug = (string) ($this->env['TRAVIS_PULL_REQUEST_SLUG'] ?? ''); + + if ($pr_slug) { + $slug_parts = explode('/', $pr_slug); + + $this->readEnv['CI_PR_REPO_OWNER'] = $slug_parts[0]; + $this->readEnv['CI_PR_REPO_NAME'] = $slug_parts[1]; + } + + $this->readEnv['CI_PR_NUMBER'] = $this->env['TRAVIS_PULL_REQUEST']; + $this->readEnv['CI_BRANCH'] = $this->env['TRAVIS_BRANCH']; + } + + return $this; + } + + /** + * Fill CircleCI environment variables. + * + * "CIRCLECI", "CIRCLE_BUILD_NUM" must be set. + * + * @return $this + */ + protected function fillCircleCi() : self + { + if (isset($this->env['CIRCLECI']) && $this->env['CIRCLECI'] && isset($this->env['CIRCLE_BUILD_NUM'])) { + $this->env['CI_BUILD_NUMBER'] = $this->env['CIRCLE_BUILD_NUM']; + $this->env['CI_NAME'] = 'circleci'; + + // backup + $this->readEnv['CIRCLECI'] = $this->env['CIRCLECI']; + $this->readEnv['CIRCLE_BUILD_NUM'] = $this->env['CIRCLE_BUILD_NUM']; + $this->readEnv['CI_NAME'] = $this->env['CI_NAME']; + + $this->readEnv['CI_PR_REPO_OWNER'] = $this->env['CIRCLE_PR_USERNAME'] ?? null; + $this->readEnv['CI_PR_REPO_NAME'] = $this->env['CIRCLE_PR_REPONAME'] ?? null; + + $this->readEnv['CI_REPO_OWNER'] = $this->env['CIRCLE_PROJECT_USERNAME'] ?? null; + $this->readEnv['CI_REPO_NAME'] = $this->env['CIRCLE_PROJECT_REPONAME'] ?? null; + + $this->readEnv['CI_PR_NUMBER'] = $this->env['CIRCLE_PR_NUMBER'] ?? null; + + $this->readEnv['CI_BRANCH'] = $this->env['CIRCLE_BRANCH'] ?? null; + } + + return $this; + } + + /** + * Fill AppVeyor environment variables. + * + * "APPVEYOR", "APPVEYOR_BUILD_NUMBER" must be set. + * + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * + * @return $this + */ + protected function fillAppVeyor() : self + { + if (isset($this->env['APPVEYOR']) && $this->env['APPVEYOR'] && isset($this->env['APPVEYOR_BUILD_NUMBER'])) { + $this->readEnv['CI_BUILD_NUMBER'] = $this->env['APPVEYOR_BUILD_NUMBER']; + $this->readEnv['CI_JOB_ID'] = $this->env['APPVEYOR_JOB_NUMBER']; + $this->readEnv['CI_BRANCH'] = $this->env['APPVEYOR_REPO_BRANCH']; + $this->readEnv['CI_PR_NUMBER'] = $this->env['APPVEYOR_PULL_REQUEST_NUMBER'] ?? ''; + $this->env['CI_NAME'] = 'AppVeyor'; + + // backup + $this->readEnv['APPVEYOR'] = $this->env['APPVEYOR']; + $this->readEnv['APPVEYOR_BUILD_NUMBER'] = $this->env['APPVEYOR_BUILD_NUMBER']; + $this->readEnv['APPVEYOR_JOB_NUMBER'] = $this->env['APPVEYOR_JOB_NUMBER']; + $this->readEnv['APPVEYOR_REPO_BRANCH'] = $this->env['APPVEYOR_REPO_BRANCH']; + $this->readEnv['CI_NAME'] = $this->env['CI_NAME']; + + $repo_slug = (string) ($this->env['APPVEYOR_REPO_NAME'] ?? ''); + + if ($repo_slug) { + $slug_parts = explode('/', $repo_slug); + + $this->readEnv['CI_REPO_OWNER'] = $slug_parts[0]; + $this->readEnv['CI_REPO_NAME'] = $slug_parts[1]; + } + + $pr_slug = (string) ($this->env['APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME'] ?? ''); + + if ($pr_slug) { + $slug_parts = explode('/', $pr_slug); + + $this->readEnv['CI_PR_REPO_OWNER'] = $slug_parts[0]; + $this->readEnv['CI_PR_REPO_NAME'] = $slug_parts[1]; + } + + $this->readEnv['CI_BRANCH'] = $this->env['APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH'] + ?? $this->env['APPVEYOR_REPO_BRANCH']; + } + + return $this; + } + + /** + * Fill Jenkins environment variables. + * + * "JENKINS_URL", "BUILD_NUMBER" must be set. + * + * @return $this + */ + protected function fillJenkins() : self + { + if (isset($this->env['JENKINS_URL']) && isset($this->env['BUILD_NUMBER'])) { + $this->readEnv['CI_BUILD_NUMBER'] = $this->env['BUILD_NUMBER']; + $this->readEnv['CI_BUILD_URL'] = $this->env['JENKINS_URL']; + $this->env['CI_NAME'] = 'jenkins'; + + // backup + $this->readEnv['BUILD_NUMBER'] = $this->env['BUILD_NUMBER']; + $this->readEnv['JENKINS_URL'] = $this->env['JENKINS_URL']; + $this->readEnv['CI_NAME'] = $this->env['CI_NAME']; + } + + return $this; + } + + /** + * Fill Scrutinizer environment variables. + * + * "JENKINS_URL", "BUILD_NUMBER" must be set. + * + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * + * @return $this + */ + protected function fillScrutinizer() : self + { + if (isset($this->env['SCRUTINIZER']) && $this->env['SCRUTINIZER']) { + $this->readEnv['CI_JOB_ID'] = $this->env['SCRUTINIZER_INSPECTION_UUID']; + $this->readEnv['CI_BRANCH'] = $this->env['SCRUTINIZER_BRANCH']; + $this->readEnv['CI_PR_NUMBER'] = $this->env['SCRUTINIZER_PR_NUMBER'] ?? ''; + + // backup + $this->readEnv['CI_NAME'] = 'Scrutinizer'; + + $repo_slug = (string) ($this->env['SCRUTINIZER_PROJECT'] ?? ''); + + if ($repo_slug) { + $slug_parts = explode('/', $repo_slug); + + if ($this->readEnv['CI_PR_NUMBER']) { + $this->readEnv['CI_PR_REPO_OWNER'] = $slug_parts[1]; + $this->readEnv['CI_PR_REPO_NAME'] = $slug_parts[2]; + } else { + $this->readEnv['CI_REPO_OWNER'] = $slug_parts[1]; + $this->readEnv['CI_REPO_NAME'] = $slug_parts[2]; + } + } + } + + return $this; + } + + /** + * Fill Github Actions environment variables. + * + * @return $this + * @psalm-suppress PossiblyUndefinedStringArrayOffset + */ + protected function fillGithubActions(): BuildInfoCollector + { + if (isset($this->env['GITHUB_ACTIONS'])) { + $this->env['CI_NAME'] = 'github-actions'; + $this->env['CI_JOB_ID'] = $this->env['GITHUB_ACTIONS']; + + $githubRef = (string) $this->env['GITHUB_REF']; + if (\strpos($githubRef, 'refs/heads/') !== false) { + $githubRef = \str_replace('refs/heads/', '', $githubRef); + } elseif (\strpos($githubRef, 'refs/tags/') !== false) { + $githubRef = \str_replace('refs/tags/', '', $githubRef); + } + + $this->env['CI_BRANCH'] = $githubRef; + + $this->readEnv['GITHUB_ACTIONS'] = $this->env['GITHUB_ACTIONS']; + $this->readEnv['GITHUB_REF'] = $this->env['GITHUB_REF']; + $this->readEnv['CI_NAME'] = $this->env['CI_NAME']; + $this->readEnv['CI_BRANCH'] = $this->env['CI_BRANCH']; + + $slug_parts = explode('/', (string) $this->env['GITHUB_REPOSITORY']); + + $this->readEnv['CI_REPO_OWNER'] = $slug_parts[0]; + $this->readEnv['CI_REPO_NAME'] = $slug_parts[1]; + + if (isset($this->env['GITHUB_EVENT_PATH'])) { + $event_json = \file_get_contents((string) $this->env['GITHUB_EVENT_PATH']); + /** @var array */ + $event_data = \json_decode($event_json, true); + + if (isset($event_data['head_commit'])) { + /** + * @var array{ + * id: string, + * author: array{name: string, email: string}, + * committer: array{name: string, email: string}, + * message: string, + * timestamp: string + * } + */ + $head_commit_data = $event_data['head_commit']; + $gitinfo = new GitInfo( + $githubRef, + (new CommitInfo()) + ->setId($head_commit_data['id']) + ->setAuthorName($head_commit_data['author']['name']) + ->setAuthorEmail($head_commit_data['author']['email']) + ->setCommitterName($head_commit_data['committer']['name']) + ->setCommitterEmail($head_commit_data['committer']['email']) + ->setMessage($head_commit_data['message']) + ->setDate(\strtotime($head_commit_data['timestamp'])), + [] + ); + + $this->readEnv['git'] = $gitinfo->toArray(); + } + + if ($this->env['GITHUB_EVENT_PATH'] === 'pull_request') { + $this->readEnv['CI_PR_NUMBER'] = $event_data['number']; + } + } + } + return $this; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php new file mode 100644 index 0000000000000000000000000000000000000000..b336c6123b4c99ca647b36c39bb2b175324fbe21 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php @@ -0,0 +1,139 @@ + + */ +class GitInfoCollector +{ + /** + * Git command. + * + * @var SystemCommandExecutor + */ + protected $executor; + + /** + * Constructor. + */ + public function __construct() + { + $this->executor = new SystemCommandExecutor(); + } + + // API + + /** + * Collect git repository info. + */ + public function collect() : GitInfo + { + $branch = $this->collectBranch(); + $commit = $this->collectCommit(); + $remotes = $this->collectRemotes(); + + return new GitInfo($branch, $commit, $remotes); + } + + /** + * Collect branch name. + * + * @throws \RuntimeException + */ + protected function collectBranch() : string + { + $branchesResult = $this->executor->execute('git branch'); + + foreach ($branchesResult as $result) { + if (strpos($result, '* ') === 0) { + $exploded = explode('* ', $result, 2); + + return $exploded[1]; + } + } + + throw new \RuntimeException(); + } + + /** + * Collect commit info. + * + * @throws \RuntimeException + */ + protected function collectCommit() : CommitInfo + { + $commitResult = $this->executor->execute('git log -1 --pretty=format:%H%n%aN%n%ae%n%cN%n%ce%n%s%n%at'); + + if (count($commitResult) !== 7 || array_keys($commitResult) !== range(0, 6)) { + throw new \RuntimeException(); + } + + $commit = new CommitInfo(); + + return $commit + ->setId(trim($commitResult[0])) + ->setAuthorName(trim($commitResult[1])) + ->setAuthorEmail(trim($commitResult[2])) + ->setCommitterName(trim($commitResult[3])) + ->setCommitterEmail(trim($commitResult[4])) + ->setMessage($commitResult[5]) + ->setDate((int) $commitResult[6]); + } + + /** + * Collect remotes info. + * + * @throws \RuntimeException + * + * @return list + */ + protected function collectRemotes(): array + { + $remotesResult = $this->executor->execute('git remote -v'); + + if (count($remotesResult) === 0) { + throw new \RuntimeException(); + } + + // parse command result + $results = []; + + foreach ($remotesResult as $result) { + if (strpos($result, ' ') !== false) { + [$remote] = explode(' ', $result, 2); + + $results[] = $remote; + } + } + + // filter + $results = array_unique($results); + + // create Remote instances + $remotes = []; + + foreach ($results as $result) { + if (strpos($result, "\t") !== false) { + [$name, $url] = explode("\t", $result, 2); + + $remote = new RemoteInfo(); + $remotes[] = $remote->setName(trim($name))->setUrl(trim($url)); + } + } + + return $remotes; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/SystemCommandExecutor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/SystemCommandExecutor.php new file mode 100644 index 0000000000000000000000000000000000000000..0354900dd20d60d1ef6ac13080c3f380becf784a --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/ExecutionEnvironment/SystemCommandExecutor.php @@ -0,0 +1,34 @@ + + * @author Dariusz Rumiński + * + * @internal + */ +final class SystemCommandExecutor +{ + /** + * Execute command. + * + * + * @throws \RuntimeException + * + * @return string[] + */ + public function execute(string $command) : array + { + exec($command, $result, $returnValue); + + if ($returnValue === 0) { + /** @var string[] */ + return $result; + } + + throw new \RuntimeException(sprintf('Failed to execute command: %s', $command), $returnValue); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/ClassDocblockManipulator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/ClassDocblockManipulator.php new file mode 100644 index 0000000000000000000000000000000000000000..5dcc2cc1245b519bf19182f118a76d862920402d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/ClassDocblockManipulator.php @@ -0,0 +1,138 @@ +> + */ + private static $manipulators = []; + + /** @var Class_ */ + private $stmt; + + /** @var int */ + private $docblock_start; + + /** @var int */ + private $docblock_end; + + /** @var bool */ + private $immutable = false; + + /** @var string */ + private $indentation; + + public static function getForClass( + ProjectAnalyzer $project_analyzer, + string $file_path, + Class_ $stmt + ) : self { + if (isset(self::$manipulators[$file_path][$stmt->getLine()])) { + return self::$manipulators[$file_path][$stmt->getLine()]; + } + + $manipulator + = self::$manipulators[$file_path][$stmt->getLine()] + = new self($project_analyzer, $stmt, $file_path); + + return $manipulator; + } + + private function __construct( + ProjectAnalyzer $project_analyzer, + Class_ $stmt, + string $file_path + ) { + $this->stmt = $stmt; + $docblock = $stmt->getDocComment(); + $this->docblock_start = $docblock ? $docblock->getStartFilePos() : (int)$stmt->getAttribute('startFilePos'); + $this->docblock_end = (int)$stmt->getAttribute('startFilePos'); + + $codebase = $project_analyzer->getCodebase(); + + $file_contents = $codebase->getFileContents($file_path); + + $preceding_newline_pos = (int) strrpos($file_contents, "\n", $this->docblock_end - strlen($file_contents)); + + $first_line = substr($file_contents, $preceding_newline_pos + 1, $this->docblock_end - $preceding_newline_pos); + + $this->indentation = str_replace(ltrim($first_line), '', $first_line); + } + + public function makeImmutable() : void + { + $this->immutable = true; + } + + /** + * Gets a new docblock given the existing docblock, if one exists, and the updated return types + * and/or parameters + * + */ + private function getDocblock(): string + { + $docblock = $this->stmt->getDocComment(); + + if ($docblock) { + $parsed_docblock = DocComment::parsePreservingLength($docblock); + } else { + $parsed_docblock = new \Psalm\Internal\Scanner\ParsedDocblock('', []); + } + + $modified_docblock = false; + + if ($this->immutable) { + $modified_docblock = true; + $parsed_docblock->tags['psalm-immutable'] = ['']; + } + + if (!$modified_docblock) { + return (string)$docblock . "\n" . $this->indentation; + } + + return $parsed_docblock->render($this->indentation); + } + + /** + * @return array + */ + public static function getManipulationsForFile(string $file_path): array + { + if (!isset(self::$manipulators[$file_path])) { + return []; + } + + $file_manipulations = []; + + foreach (self::$manipulators[$file_path] as $manipulator) { + if ($manipulator->immutable) { + $file_manipulations[$manipulator->docblock_start] = new FileManipulation( + $manipulator->docblock_start, + $manipulator->docblock_end, + $manipulator->getDocblock() + ); + } + } + + return $file_manipulations; + } + + public static function clearCache(): void + { + self::$manipulators = []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/CodeMigration.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/CodeMigration.php new file mode 100644 index 0000000000000000000000000000000000000000..e4b9611f9b22fe4240ffb8c7cbe0ecac27478747 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/CodeMigration.php @@ -0,0 +1,37 @@ +source_file_path = $source_file_path; + $this->source_start = $source_start; + $this->source_end = $source_end; + $this->destination_file_path = $destination_file_path; + $this->destination_start = $destination_start; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/FileManipulationBuffer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/FileManipulationBuffer.php new file mode 100644 index 0000000000000000000000000000000000000000..1c2a42ac8424cf271013d3020f83c9a9ebb77d46 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/FileManipulationBuffer.php @@ -0,0 +1,246 @@ + */ + private static $file_manipulations = []; + + /** @var CodeMigration[] */ + private static $code_migrations = []; + + /** + * @param FileManipulation[] $file_manipulations + * + */ + public static function add(string $file_path, array $file_manipulations): void + { + if (!isset(self::$file_manipulations[$file_path])) { + self::$file_manipulations[$file_path] = []; + } + + foreach ($file_manipulations as $file_manipulation) { + self::$file_manipulations[$file_path][$file_manipulation->getKey()] = $file_manipulation; + } + } + + /** @param CodeMigration[] $code_migrations */ + public static function addCodeMigrations(array $code_migrations) : void + { + self::$code_migrations = array_merge(self::$code_migrations, $code_migrations); + } + + /** + * @return array{int, int} + */ + private static function getCodeOffsets( + string $source_file_path, + int $source_start, + int $source_end + ) : array { + if (!isset(self::$file_manipulations[$source_file_path])) { + return [0, 0]; + } + + $start_offset = 0; + $middle_offset = 0; + + foreach (self::$file_manipulations[$source_file_path] as $fm) { + $offset = strlen($fm->insertion_text) - $fm->end + $fm->start; + + if ($fm->end < $source_start) { + $start_offset += $offset; + $middle_offset += $offset; + } elseif ($fm->start > $source_start + && $fm->end < $source_end + ) { + $middle_offset += $offset; + } + } + + return [$start_offset, $middle_offset]; + } + + public static function addForCodeLocation( + CodeLocation $code_location, + string $replacement_text, + bool $swallow_newlines = false + ): void { + $bounds = $code_location->getSnippetBounds(); + + if ($swallow_newlines) { + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + + $codebase = $project_analyzer->getCodebase(); + + $file_contents = $codebase->getFileContents($code_location->file_path); + + if (($file_contents[$bounds[0] - 1] ?? null) === "\n" + && ($file_contents[$bounds[0] - 2] ?? null) === "\n" + ) { + $bounds[0] -= 2; + } + } + + self::add( + $code_location->file_path, + [ + new FileManipulation( + $bounds[0], + $bounds[1], + $replacement_text + ), + ] + ); + } + + public static function addVarAnnotationToRemove(CodeLocation\DocblockTypeLocation $code_location): void + { + $bounds = $code_location->getSelectionBounds(); + + $project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance(); + + $codebase = $project_analyzer->getCodebase(); + + $file_contents = $codebase->getFileContents($code_location->file_path); + + $comment_start = strrpos($file_contents, '/**', $bounds[0] - strlen($file_contents)); + + if ($comment_start === false) { + return; + } + + $comment_end = \strpos($file_contents, '*/', $bounds[1]); + + if ($comment_end === false) { + return; + } + + $comment_end += 2; + + $comment_text = substr($file_contents, $comment_start, $comment_end - $comment_start); + + $var_type_comment_start = $bounds[0] - $comment_start; + $var_type_comment_end = $bounds[1] - $comment_start; + + $var_start = strrpos($comment_text, '@var', $var_type_comment_start - strlen($comment_text)); + $var_end = \strpos($comment_text, "\n", $var_type_comment_end); + + if ($var_start && $var_end) { + $var_start = strrpos($comment_text, "\n", $var_start - strlen($comment_text)) ?: $var_start; + $comment_text = substr_replace($comment_text, '', $var_start, $var_end - $var_start); + if (preg_match('@^/\*\*\n(\s*\*\s*\n)*\s*\*?\*/$@', $comment_text)) { + $comment_text = ''; + } + } else { + $comment_text = ''; + } + + self::add( + $code_location->file_path, + [ + new FileManipulation( + $comment_start, + $comment_end, + $comment_text, + false, + $comment_text === '' + ), + ] + ); + } + + /** + * @return FileManipulation[] + */ + public static function getManipulationsForFile(string $file_path): array + { + if (!isset(self::$file_manipulations[$file_path])) { + return []; + } + + return self::$file_manipulations[$file_path]; + } + + /** + * @param string $file_path + * + * @return array + */ + public static function getMigrationManipulations(FileProvider $file_provider): array + { + $code_migration_manipulations = []; + + foreach (self::$code_migrations as $code_migration) { + [$start_offset, $middle_offset] = self::getCodeOffsets( + $code_migration->source_file_path, + $code_migration->source_start, + $code_migration->source_end + ); + + if (!isset($code_migration_manipulations[$code_migration->source_file_path])) { + $code_migration_manipulations[$code_migration->source_file_path] = []; + } + + if (!isset($code_migration_manipulations[$code_migration->destination_file_path])) { + $code_migration_manipulations[$code_migration->destination_file_path] = []; + } + + $delete_file_manipulation = new FileManipulation( + $code_migration->source_start + $start_offset, + $code_migration->source_end + $middle_offset, + '' + ); + + $code_migration_manipulations[$code_migration->source_file_path][] = $delete_file_manipulation; + + [$destination_start_offset] = self::getCodeOffsets( + $code_migration->destination_file_path, + $code_migration->destination_start, + $code_migration->destination_start + ); + + $manipulation = new FileManipulation( + $code_migration->destination_start + $destination_start_offset, + $code_migration->destination_start + $destination_start_offset, + "\n" . substr( + $file_provider->getContents($code_migration->source_file_path), + $delete_file_manipulation->start, + $delete_file_manipulation->end - $delete_file_manipulation->start + ) . "\n" + ); + + $code_migration_manipulations[$code_migration->destination_file_path][$manipulation->getKey()] + = $manipulation; + } + + return $code_migration_manipulations; + } + + /** + * @return array + */ + public static function getAll(): array + { + return self::$file_manipulations; + } + + public static function clearCache(): void + { + self::$file_manipulations = []; + self::$code_migrations = []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php new file mode 100644 index 0000000000000000000000000000000000000000..e3a338566ebfefd4accb1db177a8e47fcd892e28 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php @@ -0,0 +1,501 @@ +> + */ + private static $manipulators = []; + + /** @var Closure|Function_|ClassMethod|ArrowFunction */ + private $stmt; + + /** @var int */ + private $docblock_start; + + /** @var int */ + private $docblock_end; + + /** @var int */ + private $return_typehint_area_start; + + /** @var null|int */ + private $return_typehint_colon_start; + + /** @var null|int */ + private $return_typehint_start; + + /** @var null|int */ + private $return_typehint_end; + + /** @var null|string */ + private $new_php_return_type; + + /** @var bool */ + private $return_type_is_php_compatible = false; + + /** @var null|string */ + private $new_phpdoc_return_type; + + /** @var null|string */ + private $new_psalm_return_type; + + /** @var array */ + private $new_php_param_types = []; + + /** @var array */ + private $new_phpdoc_param_types = []; + + /** @var array */ + private $new_psalm_param_types = []; + + /** @var string */ + private $indentation; + + /** @var string|null */ + private $return_type_description; + + /** @var array */ + private $param_offsets = []; + + /** @var array */ + private $param_typehint_offsets = []; + + /** @var bool */ + private $is_pure = false; + + /** + * @param Closure|Function_|ClassMethod|ArrowFunction $stmt + */ + public static function getForFunction( + ProjectAnalyzer $project_analyzer, + string $file_path, + FunctionLike $stmt + ): FunctionDocblockManipulator { + if (isset(self::$manipulators[$file_path][$stmt->getLine()])) { + return self::$manipulators[$file_path][$stmt->getLine()]; + } + + $manipulator + = self::$manipulators[$file_path][$stmt->getLine()] + = new self($file_path, $stmt, $project_analyzer); + + return $manipulator; + } + + /** + * @param Closure|Function_|ClassMethod|ArrowFunction $stmt + */ + private function __construct(string $file_path, FunctionLike $stmt, ProjectAnalyzer $project_analyzer) + { + $this->stmt = $stmt; + $docblock = $stmt->getDocComment(); + $this->docblock_start = $docblock ? $docblock->getStartFilePos() : (int)$stmt->getAttribute('startFilePos'); + $this->docblock_end = $function_start = (int)$stmt->getAttribute('startFilePos'); + $function_end = (int)$stmt->getAttribute('endFilePos'); + + foreach ($stmt->params as $param) { + if ($param->var instanceof PhpParser\Node\Expr\Variable + && \is_string($param->var->name) + ) { + $this->param_offsets[$param->var->name] = (int) $param->getAttribute('startFilePos'); + + if ($param->type) { + $this->param_typehint_offsets[$param->var->name] = [ + (int) $param->type->getAttribute('startFilePos'), + (int) $param->type->getAttribute('endFilePos') + ]; + } + } + } + + $codebase = $project_analyzer->getCodebase(); + + $file_contents = $codebase->getFileContents($file_path); + + $last_arg_position = $stmt->params + ? (int) $stmt->params[count($stmt->params) - 1]->getAttribute('endFilePos') + 1 + : null; + + if ($stmt instanceof Closure && $stmt->uses) { + $last_arg_position = (int) $stmt->uses[count($stmt->uses) - 1]->getAttribute('endFilePos') + 1; + } + + $end_bracket_position = (int) strpos($file_contents, ')', $last_arg_position ?: $function_start); + + $this->return_typehint_area_start = $end_bracket_position + 1; + + $function_code = substr($file_contents, $function_start, $function_end); + + $function_code_after_bracket = substr($function_code, $end_bracket_position + 1 - $function_start); + + // do a little parsing here + $chars = str_split($function_code_after_bracket); + + $in_single_line_comment = $in_multi_line_comment = false; + + for ($i = 0, $iMax = count($chars); $i < $iMax; ++$i) { + $char = $chars[$i]; + + switch ($char) { + case "\n": + $in_single_line_comment = false; + continue 2; + + case ':': + if ($in_multi_line_comment || $in_single_line_comment) { + continue 2; + } + + $this->return_typehint_colon_start = $i + $end_bracket_position + 1; + + continue 2; + + case '/': + if ($in_multi_line_comment || $in_single_line_comment) { + continue 2; + } + + if ($chars[$i + 1] === '*') { + $in_multi_line_comment = true; + ++$i; + } + + if ($chars[$i + 1] === '/') { + $in_single_line_comment = true; + ++$i; + } + + continue 2; + + case '*': + if ($in_single_line_comment) { + continue 2; + } + + if ($chars[$i + 1] === '/') { + $in_multi_line_comment = false; + ++$i; + } + + continue 2; + + case '{': + if ($in_multi_line_comment || $in_single_line_comment) { + continue 2; + } + + break 2; + + case '?': + if ($in_multi_line_comment || $in_single_line_comment) { + continue 2; + } + + $this->return_typehint_start = $i + $end_bracket_position + 1; + break; + } + + if ($in_multi_line_comment || $in_single_line_comment) { + continue; + } + + if ($chars[$i] === '\\' || preg_match('/\w/', $char)) { + if ($this->return_typehint_start === null) { + $this->return_typehint_start = $i + $end_bracket_position + 1; + } + + if ($chars[$i + 1] !== '\\' && !preg_match('/[\w]/', $chars[$i + 1])) { + $this->return_typehint_end = $i + $end_bracket_position + 2; + break; + } + } + } + + $preceding_newline_pos = strrpos($file_contents, "\n", $this->docblock_end - strlen($file_contents)); + + if ($preceding_newline_pos === false) { + $this->indentation = ''; + + return; + } + + $first_line = substr($file_contents, $preceding_newline_pos + 1, $this->docblock_end - $preceding_newline_pos); + + $this->indentation = str_replace(ltrim($first_line), '', $first_line); + } + + /** + * Sets the new return type + * + */ + public function setReturnType( + ?string $php_type, + string $new_type, + string $phpdoc_type, + bool $is_php_compatible, + ?string $description + ): void { + $new_type = str_replace(['', ''], '', $new_type); + + $this->new_php_return_type = $php_type; + $this->new_phpdoc_return_type = $phpdoc_type; + $this->new_psalm_return_type = $new_type; + $this->return_type_is_php_compatible = $is_php_compatible; + $this->return_type_description = $description; + } + + /** + * Sets a new param type + * + * @param bool $is_php_compatible + * + */ + public function setParamType( + string $param_name, + ?string $php_type, + string $new_type, + string $phpdoc_type + ): void { + $new_type = str_replace(['', '', ''], '', $new_type); + + if ($php_type) { + $this->new_php_param_types[$param_name] = $php_type; + } + + if ($php_type !== $new_type) { + $this->new_phpdoc_param_types[$param_name] = $phpdoc_type; + $this->new_psalm_param_types[$param_name] = $new_type; + } + } + + /** + * Gets a new docblock given the existing docblock, if one exists, and the updated return types + * and/or parameters + * + */ + private function getDocblock(): string + { + $docblock = $this->stmt->getDocComment(); + + if ($docblock) { + $parsed_docblock = DocComment::parsePreservingLength($docblock); + } else { + $parsed_docblock = new \Psalm\Internal\Scanner\ParsedDocblock('', []); + } + + $modified_docblock = false; + + foreach ($this->new_phpdoc_param_types as $param_name => $phpdoc_type) { + $found_in_params = false; + $new_param_block = $phpdoc_type . ' ' . '$' . $param_name; + + if (isset($parsed_docblock->tags['param'])) { + foreach ($parsed_docblock->tags['param'] as &$param_block) { + $doc_parts = CommentAnalyzer::splitDocLine($param_block); + + if (($doc_parts[1] ?? null) === '$' . $param_name) { + if ($param_block !== $new_param_block) { + $modified_docblock = true; + } + + $param_block = $new_param_block; + $found_in_params = true; + break; + } + } + } + + if (!$found_in_params) { + $modified_docblock = true; + $parsed_docblock->tags['param'][] = $new_param_block; + } + } + + $old_phpdoc_return_type = null; + if (isset($parsed_docblock->tags['return'])) { + $old_phpdoc_return_type = reset($parsed_docblock->tags['return']); + } + + if ($this->is_pure) { + $modified_docblock = true; + $parsed_docblock->tags['psalm-pure'] = ['']; + } + + if ($this->new_phpdoc_return_type + && $this->new_phpdoc_return_type !== $old_phpdoc_return_type + ) { + $modified_docblock = true; + $parsed_docblock->tags['return'] = [ + $this->new_phpdoc_return_type + . ($this->return_type_description ? (' ' . $this->return_type_description) : ''), + ]; + } + + $old_psalm_return_type = null; + if (isset($parsed_docblock->tags['psalm-return'])) { + $old_psalm_return_type = reset($parsed_docblock->tags['psalm-return']); + } + + if ($this->new_psalm_return_type + && $this->new_phpdoc_return_type !== $this->new_psalm_return_type + && $this->new_psalm_return_type !== $old_psalm_return_type + ) { + $modified_docblock = true; + $parsed_docblock->tags['psalm-return'] = [$this->new_psalm_return_type]; + } + + if (!$parsed_docblock->tags && !$parsed_docblock->description) { + return ''; + } + + if (!$modified_docblock) { + return (string)$docblock . "\n" . $this->indentation; + } + + return $parsed_docblock->render($this->indentation); + } + + /** + * @return array + */ + public static function getManipulationsForFile(string $file_path): array + { + if (!isset(self::$manipulators[$file_path])) { + return []; + } + + $file_manipulations = []; + + foreach (self::$manipulators[$file_path] as $manipulator) { + if ($manipulator->new_php_return_type) { + if ($manipulator->return_typehint_start && $manipulator->return_typehint_end) { + $file_manipulations[$manipulator->return_typehint_start] = new FileManipulation( + $manipulator->return_typehint_start, + $manipulator->return_typehint_end, + $manipulator->new_php_return_type + ); + } else { + $file_manipulations[$manipulator->return_typehint_area_start] = new FileManipulation( + $manipulator->return_typehint_area_start, + $manipulator->return_typehint_area_start, + ': ' . $manipulator->new_php_return_type + ); + } + } elseif ($manipulator->new_php_return_type === '' + && $manipulator->return_typehint_colon_start + && $manipulator->new_phpdoc_return_type + && $manipulator->return_typehint_start + && $manipulator->return_typehint_end + ) { + $file_manipulations[$manipulator->return_typehint_start] = new FileManipulation( + $manipulator->return_typehint_colon_start, + $manipulator->return_typehint_end, + '' + ); + } + + if (!$manipulator->new_php_return_type + || !$manipulator->return_type_is_php_compatible + || $manipulator->docblock_start !== $manipulator->docblock_end + || $manipulator->is_pure + ) { + $file_manipulations[$manipulator->docblock_start] = new FileManipulation( + $manipulator->docblock_start, + $manipulator->docblock_end, + $manipulator->getDocblock() + ); + } + + foreach ($manipulator->new_php_param_types as $param_name => $new_php_param_type) { + if (!isset($manipulator->param_offsets[$param_name])) { + continue; + } + + $param_offset = $manipulator->param_offsets[$param_name]; + + $typehint_offsets = $manipulator->param_typehint_offsets[$param_name] ?? null; + + if ($new_php_param_type) { + if ($typehint_offsets) { + $file_manipulations[$typehint_offsets[0]] = new FileManipulation( + $typehint_offsets[0], + $typehint_offsets[1], + $new_php_param_type + ); + } else { + $file_manipulations[$param_offset] = new FileManipulation( + $param_offset, + $param_offset, + $new_php_param_type . ' ' + ); + } + } elseif ($new_php_param_type === '' + && $typehint_offsets + ) { + $file_manipulations[$typehint_offsets[0]] = new FileManipulation( + $typehint_offsets[0], + $param_offset, + '' + ); + } + } + } + + return $file_manipulations; + } + + public function makePure() : void + { + $this->is_pure = true; + } + + public static function clearCache(): void + { + self::$manipulators = []; + } + + /** + * @param array> $manipulators + */ + public static function addManipulators(array $manipulators) : void + { + self::$manipulators = array_merge($manipulators, self::$manipulators); + } + + /** + * @return array> + */ + public static function getManipulators(): array + { + return self::$manipulators; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php new file mode 100644 index 0000000000000000000000000000000000000000..6aeac6c06470343442e06d5d378611a69ce80dd2 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php @@ -0,0 +1,268 @@ +> + */ + private static $manipulators = []; + + /** @var Property */ + private $stmt; + + /** @var int */ + private $docblock_start; + + /** @var int */ + private $docblock_end; + + /** @var null|int */ + private $typehint_start; + + /** @var int */ + private $typehint_area_start; + + /** @var null|int */ + private $typehint_end; + + /** @var null|string */ + private $new_php_type; + + /** @var bool */ + private $type_is_php_compatible = false; + + /** @var null|string */ + private $new_phpdoc_type; + + /** @var null|string */ + private $new_psalm_type; + + /** @var string */ + private $indentation; + + /** @var bool */ + private $add_newline = false; + + /** @var string|null */ + private $type_description; + + public static function getForProperty( + ProjectAnalyzer $project_analyzer, + string $file_path, + Property $stmt + ) : self { + if (isset(self::$manipulators[$file_path][$stmt->getLine()])) { + return self::$manipulators[$file_path][$stmt->getLine()]; + } + + $manipulator + = self::$manipulators[$file_path][$stmt->getLine()] + = new self($project_analyzer, $stmt, $file_path); + + return $manipulator; + } + + private function __construct( + ProjectAnalyzer $project_analyzer, + Property $stmt, + string $file_path + ) { + $this->stmt = $stmt; + $docblock = $stmt->getDocComment(); + $this->docblock_start = $docblock ? $docblock->getStartFilePos() : (int)$stmt->getAttribute('startFilePos'); + $this->docblock_end = (int)$stmt->getAttribute('startFilePos'); + + $codebase = $project_analyzer->getCodebase(); + + $file_contents = $codebase->getFileContents($file_path); + + if (count($stmt->props) > 1) { + throw new \UnexpectedValueException('Cannot replace multiple inline properties in ' . $file_path); + } + + $prop = $stmt->props[0]; + + if ($stmt->type) { + $this->typehint_start = (int)$stmt->type->getAttribute('startFilePos'); + $this->typehint_end = (int)$stmt->type->getAttribute('endFilePos'); + } + + $this->typehint_area_start = (int)$prop->getAttribute('startFilePos') - 1; + + $preceding_newline_pos = strrpos($file_contents, "\n", $this->docblock_end - strlen($file_contents)); + + if ($preceding_newline_pos === false) { + $this->indentation = ''; + + return; + } + + if (!$docblock) { + $preceding_semicolon_pos = strrpos($file_contents, ";", $preceding_newline_pos - strlen($file_contents)); + + if ($preceding_semicolon_pos) { + $preceding_space = substr( + $file_contents, + $preceding_semicolon_pos + 1, + $preceding_newline_pos - $preceding_semicolon_pos - 1 + ); + + if (!\substr_count($preceding_space, "\n")) { + $this->add_newline = true; + } + } + } + + $first_line = substr($file_contents, $preceding_newline_pos + 1, $this->docblock_end - $preceding_newline_pos); + + $this->indentation = str_replace(ltrim($first_line), '', $first_line); + } + + public function setType( + ?string $php_type, + string $new_type, + string $phpdoc_type, + bool $is_php_compatible, + ?string $description = null + ) : void { + $new_type = str_replace(['', '', ''], '', $new_type); + + $this->new_php_type = $php_type; + $this->new_phpdoc_type = $phpdoc_type; + $this->new_psalm_type = $new_type; + $this->type_is_php_compatible = $is_php_compatible; + $this->type_description = $description; + } + + /** + * Gets a new docblock given the existing docblock, if one exists, and the updated return types + * and/or parameters + * + */ + private function getDocblock(): string + { + $docblock = $this->stmt->getDocComment(); + + if ($docblock) { + $parsed_docblock = DocComment::parsePreservingLength($docblock); + } else { + $parsed_docblock = new \Psalm\Internal\Scanner\ParsedDocblock('', []); + } + + $modified_docblock = false; + + $old_phpdoc_type = null; + if (isset($parsed_docblock->tags['var'])) { + $old_phpdoc_type = array_shift($parsed_docblock->tags['var']); + } + + if ($this->new_phpdoc_type + && $this->new_phpdoc_type !== $old_phpdoc_type + ) { + $modified_docblock = true; + $parsed_docblock->tags['var'] = [ + $this->new_phpdoc_type + . ($this->type_description ? (' ' . $this->type_description) : ''), + ]; + } + + $old_psalm_type = null; + if (isset($parsed_docblock->tags['psalm-var'])) { + $old_psalm_type = array_shift($parsed_docblock->tags['psalm-var']); + } + + if ($this->new_psalm_type + && $this->new_phpdoc_type !== $this->new_psalm_type + && $this->new_psalm_type !== $old_psalm_type + ) { + $modified_docblock = true; + $parsed_docblock->tags['psalm-var'] = [$this->new_psalm_type]; + } + + if (!$parsed_docblock->tags && !$parsed_docblock->description) { + return ''; + } + + if (!$modified_docblock) { + return (string)$docblock . "\n" . $this->indentation; + } + + return $parsed_docblock->render($this->indentation); + } + + /** + * @return array + */ + public static function getManipulationsForFile(string $file_path): array + { + if (!isset(self::$manipulators[$file_path])) { + return []; + } + + $file_manipulations = []; + + foreach (self::$manipulators[$file_path] as $manipulator) { + if ($manipulator->new_php_type) { + if ($manipulator->typehint_start && $manipulator->typehint_end) { + $file_manipulations[$manipulator->typehint_start] = new FileManipulation( + $manipulator->typehint_start, + $manipulator->typehint_end, + $manipulator->new_php_type + ); + } else { + $file_manipulations[$manipulator->typehint_area_start] = new FileManipulation( + $manipulator->typehint_area_start, + $manipulator->typehint_area_start, + ' ' . $manipulator->new_php_type + ); + } + } elseif ($manipulator->new_php_type === '' + && $manipulator->new_phpdoc_type + && $manipulator->typehint_start + && $manipulator->typehint_end + ) { + $file_manipulations[$manipulator->typehint_start] = new FileManipulation( + $manipulator->typehint_start, + $manipulator->typehint_end, + '' + ); + } + + if (!$manipulator->new_php_type + || !$manipulator->type_is_php_compatible + || $manipulator->docblock_start !== $manipulator->docblock_end + ) { + $file_manipulations[$manipulator->docblock_start] = new FileManipulation( + $manipulator->docblock_start + - ($manipulator->add_newline ? strlen($manipulator->indentation) : 0), + $manipulator->docblock_end, + ($manipulator->add_newline ? "\n" . $manipulator->indentation : '') + . $manipulator->getDocblock() + ); + } + } + + return $file_manipulations; + } + + public static function clearCache(): void + { + self::$manipulators = []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/ForkMessage.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/ForkMessage.php new file mode 100644 index 0000000000000000000000000000000000000000..e94cafc81542a159732e0f8399de0442170bd433 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/ForkMessage.php @@ -0,0 +1,6 @@ +data = $data; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php new file mode 100644 index 0000000000000000000000000000000000000000..8625aec222b73763d4496b7a122b1ab59c355acd --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php @@ -0,0 +1,16 @@ +message = $message; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php new file mode 100644 index 0000000000000000000000000000000000000000..8d406d4db3960c18192b9cb4491e2c1c9fb86fae --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php @@ -0,0 +1,19 @@ +data = $data; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php new file mode 100644 index 0000000000000000000000000000000000000000..2fbff4290dc6ed6d293172a95dd9694055dded14 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php @@ -0,0 +1,436 @@ +> $process_task_data_iterator + * An array of task data items to be divided up among the + * workers. The size of this is the number of forked processes. + * @param \Closure $startup_closure + * A closure to execute upon starting a child + * @param \Closure(int, mixed):mixed $task_closure + * A method to execute on each task data. + * This closure must return an array (to be gathered). + * @param \Closure():mixed $shutdown_closure + * A closure to execute upon shutting down a child + * @param ?\Closure(mixed $data):void $task_done_closure + * A closure to execute when a task is done + * + * @psalm-suppress MixedAssignment + */ + public function __construct( + array $process_task_data_iterator, + \Closure $startup_closure, + \Closure $task_closure, + \Closure $shutdown_closure, + ?\Closure $task_done_closure = null + ) { + $pool_size = count($process_task_data_iterator); + $this->task_done_closure = $task_done_closure; + + \assert( + $pool_size > 1, + 'The pool size must be >= 2 to use the fork pool.' + ); + + if (!extension_loaded('pcntl') || !extension_loaded('posix')) { + echo + 'The pcntl & posix extensions must be loaded in order for Psalm to be able to use multiple processes.' + . PHP_EOL; + exit(1); + } + + $disabled_functions = array_map('trim', explode(',', ini_get('disable_functions'))); + if (in_array('pcntl_fork', $disabled_functions)) { + echo "pcntl_fork() is disabled by php configuration (disable_functions directive).\n" + . "Please enable it or run Psalm single-threaded with --threads=1 cli switch.\n"; + exit(1); + } + + if (ini_get('pcre.jit') === '1' + && \PHP_OS === 'Darwin' + && version_compare(PHP_VERSION, '7.3.0') >= 0 + && version_compare(PHP_VERSION, '7.4.0') < 0 + ) { + die( + self::MAC_PCRE_MESSAGE . PHP_EOL + ); + } + + // We'll keep track of if this is the parent process + // so that we can tell who will be doing the waiting + $is_parent = false; + + $sockets = []; + + // Fork as many times as requested to get the given + // pool size + for ($proc_id = 0; $proc_id < $pool_size; ++$proc_id) { + // Create an IPC socket pair. + $sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + if (!$sockets) { + error_log('unable to create stream socket pair'); + exit(self::EXIT_FAILURE); + } + + // Fork + if (($pid = pcntl_fork()) < 0) { + error_log(posix_strerror(posix_get_last_error())); + exit(self::EXIT_FAILURE); + } + + // Parent + if ($pid > 0) { + $is_parent = true; + $this->child_pid_list[] = $pid; + $this->read_streams[] = self::streamForParent($sockets); + continue; + } + + // Child + if ($pid === 0) { + $is_parent = false; + break; + } + } + + // If we're the parent, return + if ($is_parent) { + return; + } + + // Get the write stream for the child. + $write_stream = self::streamForChild($sockets); + + // Execute anything the children wanted to execute upon + // starting up + $startup_closure(); + + // Get the work for this process + $task_data_iterator = array_values($process_task_data_iterator)[$proc_id]; + + $task_done_buffer = ''; + + try { + foreach ($task_data_iterator as $i => $task_data) { + $task_result = $task_closure($i, $task_data); + + $task_done_message = new ForkTaskDoneMessage($task_result); + $serialized_message = $task_done_buffer . base64_encode(serialize($task_done_message)) . "\n"; + + if (strlen($serialized_message) > 200) { + $bytes_written = @fwrite($write_stream, $serialized_message); + + if (strlen($serialized_message) !== $bytes_written) { + $task_done_buffer = substr($serialized_message, $bytes_written); + } else { + $task_done_buffer = ''; + } + } else { + $task_done_buffer = $serialized_message; + } + } + + // Execute each child's shutdown closure before + // exiting the process + $results = $shutdown_closure(); + + // Serialize this child's produced results and send them to the parent. + $process_done_message = new ForkProcessDoneMessage($results ?: []); + } catch (\Throwable $t) { + // This can happen when developing Psalm from source without running `composer update`, + // or because of rare bugs in Psalm. + /** @psalm-suppress MixedArgument on Windows, for some reason */ + $process_done_message = new ForkProcessErrorMessage( + $t->getMessage() . "\nStack trace in the forked worker:\n" . $t->getTraceAsString() + ); + } + + $serialized_message = $task_done_buffer . base64_encode(serialize($process_done_message)) . "\n"; + + $bytes_to_write = strlen($serialized_message); + $bytes_written = 0; + + while ($bytes_written < $bytes_to_write) { + // attempt to write the remaining unsent part + $bytes_written += @fwrite($write_stream, substr($serialized_message, $bytes_written)); + + if ($bytes_written < $bytes_to_write) { + // wait a bit + usleep(500000); + } + } + + fclose($write_stream); + + // Children exit after completing their work + exit(self::EXIT_SUCCESS); + } + + /** + * Prepare the socket pair to be used in a parent process and + * return the stream the parent will use to read results. + * + * @param resource[] $sockets the socket pair for IPC + * + * @return resource + */ + private static function streamForParent(array $sockets) + { + [$for_read, $for_write] = $sockets; + + // The parent will not use the write channel, so it + // must be closed to prevent deadlock. + fclose($for_write); + + // stream_select will be used to read multiple streams, so these + // must be set to non-blocking mode. + if (!stream_set_blocking($for_read, false)) { + error_log('unable to set read stream to non-blocking'); + exit(self::EXIT_FAILURE); + } + + return $for_read; + } + + /** + * Prepare the socket pair to be used in a child process and return + * the stream the child will use to write results. + * + * @param resource[] $sockets the socket pair for IPC + * + * @return resource + */ + private static function streamForChild(array $sockets) + { + [$for_read, $for_write] = $sockets; + + // The while will not use the read channel, so it must + // be closed to prevent deadlock. + fclose($for_read); + + return $for_write; + } + + /** + * Read the results that each child process has serialized on their write streams. + * The results are returned in an array, one for each worker. The order of the results + * is not maintained. + * + * + * @psalm-suppress MixedAssignment + * + * @return list + */ + private function readResultsFromChildren(): array + { + // Create an array of all active streams, indexed by + // resource id. + $streams = []; + foreach ($this->read_streams as $stream) { + $streams[intval($stream)] = $stream; + } + + // Create an array for the content received on each stream, + // indexed by resource id. + $content = array_fill_keys(array_keys($streams), ''); + + $terminationMessages = []; + + // Read the data off of all the stream. + while (count($streams) > 0) { + $needs_read = array_values($streams); + $needs_write = null; + $needs_except = null; + + // Wait for data on at least one stream. + $num = @stream_select($needs_read, $needs_write, $needs_except, null /* no timeout */); + if ($num === false) { + $err = error_get_last(); + + // stream_select returns false when the `select` system call is interrupted by an incoming signal + if (isset($err['message']) && stripos($err['message'], 'interrupted system call') === false) { + error_log('unable to select on read stream'); + exit(self::EXIT_FAILURE); + } + + continue; + } + + // For each stream that was ready, read the content. + foreach ($needs_read as $file) { + $buffer = fread($file, 1024); + if ($buffer !== false) { + $content[intval($file)] .= $buffer; + } + + if (strpos($buffer, "\n") !== false) { + $serialized_messages = explode("\n", $content[intval($file)]); + $content[intval($file)] = array_pop($serialized_messages); + + foreach ($serialized_messages as $serialized_message) { + $message = unserialize(base64_decode($serialized_message, true)); + + if ($message instanceof ForkProcessDoneMessage) { + $terminationMessages[] = $message->data; + } elseif ($message instanceof ForkTaskDoneMessage) { + if ($this->task_done_closure !== null) { + ($this->task_done_closure)($message->data); + } + } elseif ($message instanceof ForkProcessErrorMessage) { + throw new \Exception($message->message); + } else { + error_log('Child should return ForkMessage - response type=' . gettype($message)); + $this->did_have_error = true; + } + } + } + + // If the stream has closed, stop trying to select on it. + if (feof($file)) { + if ($content[intval($file)] !== '') { + error_log('Child did not send full message before closing the connection'); + $this->did_have_error = true; + } + + fclose($file); + unset($streams[intval($file)]); + } + } + } + + return array_values($terminationMessages); + } + + /** + * Wait for all child processes to complete + * + * @return list + */ + public function wait(): array + { + // Read all the streams from child processes into an array. + $content = $this->readResultsFromChildren(); + + // Wait for all children to return + foreach ($this->child_pid_list as $child_pid) { + $process_lookup = posix_kill($child_pid, 0); + + $status = 0; + + if ($process_lookup) { + /** + * @psalm-suppress UndefinedConstant - does not exist on windows + * @psalm-suppress MixedArgument + */ + posix_kill($child_pid, SIGALRM); + + if (pcntl_waitpid($child_pid, $status) < 0) { + error_log(posix_strerror(posix_get_last_error())); + } + } + + // Check to see if the child died a graceful death + if (pcntl_wifsignaled($status)) { + $return_code = pcntl_wexitstatus($status); + $term_sig = pcntl_wtermsig($status); + + /** + * @psalm-suppress UndefinedConstant - does not exist on windows + */ + if ($term_sig !== SIGALRM) { + $this->did_have_error = true; + error_log("Child terminated with return code $return_code and signal $term_sig"); + } + } + } + + return $content; + } + + /** + * Returns true if this had an error, e.g. due to memory limits or due to a child process crashing. + * + */ + public function didHaveError(): bool + { + return $this->did_have_error; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/PsalmRestarter.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/PsalmRestarter.php new file mode 100644 index 0000000000000000000000000000000000000000..31530ccb0cd2da045a9c48b4ebfee5e2ff27323f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Fork/PsalmRestarter.php @@ -0,0 +1,63 @@ +disabledExtensions[] = $disabledExtension; + } + + /** + * @param mixed $isLoaded + */ + protected function requiresRestart($isLoaded): bool + { + $this->required = (bool) array_filter( + $this->disabledExtensions, + function (string $extension): bool { + return extension_loaded($extension); + } + ); + + return $isLoaded || $this->required; + } + + /** + * @param mixed $command + */ + protected function restart($command): void + { + if ($this->required && $this->tmpIni) { + $regex = '/^\s*(extension\s*=.*(' . implode('|', $this->disabledExtensions) . ').*)$/mi'; + $content = file_get_contents($this->tmpIni); + + $content = preg_replace($regex, ';$1', $content); + + file_put_contents($this->tmpIni, $content); + } + + /** @psalm-suppress MixedArgument */ + parent::restart($command); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/IncludeCollector.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/IncludeCollector.php new file mode 100644 index 0000000000000000000000000000000000000000..6cd24779ebb9f5902fff4e7c1d05452dca4aa114 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/IncludeCollector.php @@ -0,0 +1,55 @@ + */ + private $included_files = []; + + /** + * @template T + * @param callable():T $f + * @return T + */ + public function runAndCollect(callable $f) + { + $before = get_included_files(); + $ret = $f(); + $after = get_included_files(); + + $included = array_diff($after, $before); + + $this->included_files = array_values(array_unique(array_merge($this->included_files, $included))); + + return $ret; + } + + /** @return list */ + public function getIncludedFiles(): array + { + return $this->included_files; + } + + /** @return list */ + public function getFilteredIncludedFiles(): array + { + return array_values(preg_grep('@^phar://@', $this->getIncludedFiles(), PREG_GREP_INVERT)); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Json/Json.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Json/Json.php new file mode 100644 index 0000000000000000000000000000000000000000..b62130a0f8c7e324a24ae1feebce2bb61efa2560 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Json/Json.php @@ -0,0 +1,42 @@ +handler = $handler; + $this->mapper = $mapper; + } + + /** + * Diagnostics notification are sent from the server to the client to signal results of validation runs. + * + * @param Diagnostic[] $diagnostics + * + * @return Promise + */ + public function publishDiagnostics(string $uri, array $diagnostics): Promise + { + return $this->handler->notify('textDocument/publishDiagnostics', [ + 'uri' => $uri, + 'diagnostics' => $diagnostics, + ]); + } + + /** + * The content request is sent from a server to a client + * to request the current content of a text document identified by the URI + * + * @param TextDocumentIdentifier $textDocument The document to get the content for + * + * @return Promise The document's current content + */ + public function xcontent(TextDocumentIdentifier $textDocument): Promise + { + return call( + /** + * @return \Generator, object, TextDocumentItem> + */ + function () use ($textDocument) { + /** @var Promise */ + $promise = $this->handler->request( + 'textDocument/xcontent', + ['textDocument' => $textDocument] + ); + + $result = yield $promise; + + /** @var TextDocumentItem */ + return $this->mapper->map($result, new TextDocumentItem); + } + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ClientHandler.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ClientHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..c5accedbd483a7ea45c5263c70dd6432363576ce --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ClientHandler.php @@ -0,0 +1,107 @@ +protocolReader = $protocolReader; + $this->protocolWriter = $protocolWriter; + $this->idGenerator = new IdGenerator; + } + + /** + * Sends a request to the client and returns a promise that is resolved with the result or rejected with the error + * + * @param string $method The method to call + * @param array|object $params The method parameters + * + * @return Promise Resolved with the result of the request or rejected with an error + */ + public function request(string $method, $params): Promise + { + $id = $this->idGenerator->generate(); + + return call( + /** + * @return \Generator> + */ + function () use ($id, $method, $params): \Generator { + yield $this->protocolWriter->write( + new Message( + new AdvancedJsonRpc\Request($id, $method, (object) $params) + ) + ); + + $deferred = new Deferred(); + + $listener = + function (Message $msg) use ($id, $deferred, &$listener): void { + error_log('request handler'); + /** + * @psalm-suppress UndefinedPropertyFetch + * @psalm-suppress MixedArgument + */ + if ($msg->body + && AdvancedJsonRpc\Response::isResponse($msg->body) + && $msg->body->id === $id + ) { + // Received a response + $this->protocolReader->removeListener('message', $listener); + if (AdvancedJsonRpc\SuccessResponse::isSuccessResponse($msg->body)) { + $deferred->resolve($msg->body->result); + } else { + $deferred->fail($msg->body->error); + } + } + }; + $this->protocolReader->on('message', $listener); + + return $deferred->promise(); + } + ); + } + + /** + * Sends a notification to the client + * + * @param string $method The method to call + * @param array|object $params The method parameters + * + * @return Promise Will be resolved as soon as the notification has been sent + */ + public function notify(string $method, $params): Promise + { + /** @var Promise */ + return $this->protocolWriter->write( + new Message( + new AdvancedJsonRpc\Notification($method, (object)$params) + ) + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/EmitterInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/EmitterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..0354453482f1fd581eb355e46a8be68de857de77 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/EmitterInterface.php @@ -0,0 +1,68 @@ + $arguments + */ + public function emit( + string $eventName, + array $arguments = [], + ?callable $continueCallBack = null + ) : bool; + + /** + * Returns the list of listeners for an event. + * + * The list is returned as an array, and the list of events are sorted by + * their priority. + * + * @return callable[] + */ + public function listeners(string $eventName) : array; + + /** + * Removes a specific listener from an event. + * + * If the listener could not be found, this method will return false. If it + * was removed it will return true. + */ + public function removeListener(string $eventName, callable $listener) : bool; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/EmitterTrait.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/EmitterTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..3f9b4ae9d7563561a40cc64451e7dfe8319fe442 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/EmitterTrait.php @@ -0,0 +1,153 @@ + + */ + protected $listeners = []; + + /** + * Subscribe to an event. + */ + public function on(string $eventName, callable $callBack, int $priority = 100): void + { + if (!isset($this->listeners[$eventName])) { + $this->listeners[$eventName] = [ + true, // If there's only one item, it's sorted + [$priority], + [$callBack], + ]; + } else { + $this->listeners[$eventName][0] = false; // marked as unsorted + $this->listeners[$eventName][1][] = $priority; + $this->listeners[$eventName][2][] = $callBack; + } + } + + /** + * Emits an event. + * + * This method will return true if 0 or more listeners were successfully + * handled. false is returned if one of the events broke the event chain. + * + * If the continueCallBack is specified, this callback will be called every + * time before the next event handler is called. + * + * If the continueCallback returns false, event propagation stops. This + * allows you to use the eventEmitter as a means for listeners to implement + * functionality in your application, and break the event loop as soon as + * some condition is fulfilled. + * + * Note that returning false from an event subscriber breaks propagation + * and returns false, but if the continue-callback stops propagation, this + * is still considered a 'successful' operation and returns true. + * + * Lastly, if there are 5 event handlers for an event. The continueCallback + * will be called at most 4 times. + * + * @param list $arguments + */ + public function emit( + string $eventName, + array $arguments = [], + ?callable $continueCallBack = null + ) : bool { + if ($continueCallBack === null) { + foreach ($this->listeners($eventName) as $listener) { + /** @psalm-suppress MixedAssignment */ + $result = \call_user_func_array($listener, $arguments); + if ($result === false) { + return false; + } + } + } else { + $listeners = $this->listeners($eventName); + $counter = \count($listeners); + + foreach ($listeners as $listener) { + --$counter; + /** @psalm-suppress MixedAssignment */ + $result = \call_user_func_array($listener, $arguments); + if ($result === false) { + return false; + } + + if ($counter > 0) { + if (!$continueCallBack()) { + break; + } + } + } + } + + return true; + } + + /** + * Returns the list of listeners for an event. + * + * The list is returned as an array, and the list of events are sorted by + * their priority. + * + * @return callable[] + */ + public function listeners(string $eventName) : array + { + if (!isset($this->listeners[$eventName])) { + return []; + } + + // The list is not sorted + if (!$this->listeners[$eventName][0]) { + // Sorting + \array_multisort($this->listeners[$eventName][1], SORT_NUMERIC, $this->listeners[$eventName][2]); + + // Marking the listeners as sorted + $this->listeners[$eventName][0] = true; + } + + return $this->listeners[$eventName][2]; + } + + /** + * Removes a specific listener from an event. + * + * If the listener could not be found, this method will return false. If it + * was removed it will return true. + */ + public function removeListener(string $eventName, callable $listener) : bool + { + if (!isset($this->listeners[$eventName])) { + return false; + } + foreach ($this->listeners[$eventName][2] as $index => $check) { + if ($check === $listener) { + unset($this->listeners[$eventName][1][$index], $this->listeners[$eventName][2][$index]); + + return true; + } + } + + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/IdGenerator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/IdGenerator.php new file mode 100644 index 0000000000000000000000000000000000000000..cc2040fd793bdf69698d145e57ea00cf1a08122f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/IdGenerator.php @@ -0,0 +1,23 @@ +counter++; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/LanguageClient.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/LanguageClient.php new file mode 100644 index 0000000000000000000000000000000000000000..5667ebd1f41933f2e60cb1a3e39bba2c34f5d760 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/LanguageClient.php @@ -0,0 +1,64 @@ +handler = new ClientHandler($reader, $writer); + $mapper = new JsonMapper; + + $this->textDocument = new Client\TextDocument($this->handler, $mapper); + } + + /** + * Send a log message to the client. + * + * @param string $message The message to send to the client. + * @psalm-param 1|2|3|4 $type + * @param int $type The log type: + * - 1 = Error + * - 2 = Warning + * - 3 = Info + * - 4 = Log + * + * @return Promise + */ + public function logMessage(string $message, int $type = 4, string $method = 'window/logMessage'): Promise + { + // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage + + if ($type < 1 || $type > 4) { + $type = 4; + } + + return $this->handler->notify( + $method, + [ + 'type' => $type, + 'message' => $message + ] + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/LanguageServer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/LanguageServer.php new file mode 100644 index 0000000000000000000000000000000000000000..bd1bc2039d99fac805867e67794af111593bf7d1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/LanguageServer.php @@ -0,0 +1,525 @@ + + */ + protected $onsave_paths_to_analyze = []; + + /** + * @var array + */ + protected $onchange_paths_to_analyze = []; + + public function __construct( + ProtocolReader $reader, + ProtocolWriter $writer, + ProjectAnalyzer $project_analyzer + ) { + parent::__construct($this, '/'); + $this->project_analyzer = $project_analyzer; + + $this->protocolWriter = $writer; + + $this->protocolReader = $reader; + $this->protocolReader->on( + 'close', + function (): void { + $this->shutdown(); + $this->exit(); + } + ); + $this->protocolReader->on( + 'message', + asyncCoroutine( + /** + * @return \Generator + */ + function (Message $msg): \Generator { + if (!$msg->body) { + return; + } + + // Ignore responses, this is the handler for requests and notifications + if (AdvancedJsonRpc\Response::isResponse($msg->body)) { + return; + } + + /** @psalm-suppress UndefinedPropertyFetch */ + if ($msg->body->method === 'textDocument/signatureHelp') { + $this->doAnalysis(); + } + + $result = null; + $error = null; + try { + // Invoke the method handler to get a result + /** + * @var Promise + * @psalm-suppress UndefinedDocblockClass + */ + $dispatched = $this->dispatch($msg->body); + /** @psalm-suppress MixedAssignment */ + $result = yield $dispatched; + } catch (AdvancedJsonRpc\Error $e) { + // If a ResponseError is thrown, send it back in the Response + $error = $e; + } catch (Throwable $e) { + // If an unexpected error occurred, send back an INTERNAL_ERROR error response + $error = new AdvancedJsonRpc\Error( + (string) $e, + AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR, + null, + $e + ); + } + // Only send a Response for a Request + // Notifications do not send Responses + /** + * @psalm-suppress UndefinedPropertyFetch + * @psalm-suppress MixedArgument + */ + if (AdvancedJsonRpc\Request::isRequest($msg->body)) { + if ($error !== null) { + $responseBody = new AdvancedJsonRpc\ErrorResponse($msg->body->id, $error); + } else { + $responseBody = new AdvancedJsonRpc\SuccessResponse($msg->body->id, $result); + } + yield $this->protocolWriter->write(new Message($responseBody)); + } + } + ) + ); + + $this->protocolReader->on( + 'readMessageGroup', + function (): void { + $this->doAnalysis(); + } + ); + + $this->client = new LanguageClient($reader, $writer); + + $this->verboseLog("Language server has started."); + } + + /** + * The initialize request is sent as the first request from the client to the server. + * + * @param ClientCapabilities $capabilities The capabilities provided by the client (editor) + * @param string|null $rootPath The rootPath of the workspace. Is null if no folder is open. + * @param int|null $processId The process Id of the parent process that started the server. + * Is null if the process has not been started by another process. If the parent process is + * not alive then the server should exit (see exit notification) its process. + * @psalm-return Promise + * @psalm-suppress PossiblyUnusedMethod + */ + public function initialize( + ClientCapabilities $capabilities, + ?string $rootPath = null, + ?int $processId = null + ): Promise { + return call( + /** @return \Generator */ + function () use ($capabilities, $rootPath, $processId) { + $this->verboseLog("Initializing..."); + $this->clientStatus('initializing'); + + // Eventually, this might block on something. Leave it as a generator. + /** @psalm-suppress TypeDoesNotContainType */ + if (false) { + yield true; + } + + $this->verboseLog("Initializing: Getting code base..."); + $this->clientStatus('initializing', 'getting code base'); + $codebase = $this->project_analyzer->getCodebase(); + + $this->verboseLog("Initializing: Scanning files..."); + $this->clientStatus('initializing', 'scanning files'); + $codebase->scanFiles($this->project_analyzer->threads); + + $this->verboseLog("Initializing: Registering stub files..."); + $this->clientStatus('initializing', 'registering stub files'); + $codebase->config->visitStubFiles($codebase, null); + + if ($this->textDocument === null) { + $this->textDocument = new TextDocument( + $this, + $codebase, + $this->project_analyzer->onchange_line_limit + ); + } + + $serverCapabilities = new ServerCapabilities(); + + $textDocumentSyncOptions = new TextDocumentSyncOptions(); + + if ($this->project_analyzer->onchange_line_limit === 0) { + $textDocumentSyncOptions->change = TextDocumentSyncKind::NONE; + } else { + $textDocumentSyncOptions->change = TextDocumentSyncKind::FULL; + } + + $serverCapabilities->textDocumentSync = $textDocumentSyncOptions; + + // Support "Find all symbols" + $serverCapabilities->documentSymbolProvider = false; + // Support "Find all symbols in workspace" + $serverCapabilities->workspaceSymbolProvider = false; + // Support "Go to definition" + $serverCapabilities->definitionProvider = true; + // Support "Find all references" + $serverCapabilities->referencesProvider = false; + // Support "Hover" + $serverCapabilities->hoverProvider = true; + // Support "Completion" + + if ($this->project_analyzer->provide_completion) { + $serverCapabilities->completionProvider = new CompletionOptions(); + $serverCapabilities->completionProvider->resolveProvider = false; + $serverCapabilities->completionProvider->triggerCharacters = ['$', '>', ':']; + } + + $serverCapabilities->signatureHelpProvider = new SignatureHelpOptions(['(', ',']); + + // Support global references + $serverCapabilities->xworkspaceReferencesProvider = false; + $serverCapabilities->xdefinitionProvider = false; + $serverCapabilities->dependenciesProvider = false; + + $this->verboseLog("Initializing: Complete."); + $this->clientStatus('initialized'); + return new InitializeResult($serverCapabilities); + } + ); + } + + /** + * @psalm-suppress PossiblyUnusedMethod + * + */ + public function initialized(): void + { + $this->clientStatus('running'); + } + + public function queueTemporaryFileAnalysis(string $file_path, string $uri): void + { + $this->onchange_paths_to_analyze[$file_path] = $uri; + } + + public function queueFileAnalysis(string $file_path, string $uri): void + { + $this->onsave_paths_to_analyze[$file_path] = $uri; + } + + public function doAnalysis(): void + { + $this->clientStatus('analyzing'); + + try { + $codebase = $this->project_analyzer->getCodebase(); + + $all_files_to_analyze = $this->onchange_paths_to_analyze + $this->onsave_paths_to_analyze; + + if (!$all_files_to_analyze) { + return; + } + + if ($this->onsave_paths_to_analyze) { + $codebase->reloadFiles($this->project_analyzer, array_keys($this->onsave_paths_to_analyze)); + } + + if ($this->onchange_paths_to_analyze) { + $codebase->reloadFiles($this->project_analyzer, array_keys($this->onchange_paths_to_analyze)); + } + + $all_file_paths_to_analyze = array_keys($all_files_to_analyze); + $codebase->analyzer->addFilesToAnalyze( + array_combine($all_file_paths_to_analyze, $all_file_paths_to_analyze) + ); + $codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false); + + $this->emitIssues($all_files_to_analyze); + + $this->onchange_paths_to_analyze = []; + $this->onsave_paths_to_analyze = []; + } finally { + // we are done, so set the status back to running + $this->clientStatus('running'); + } + } + + /** + * @param array $uris + * + */ + public function emitIssues(array $uris): void + { + $data = \Psalm\IssueBuffer::clear(); + + foreach ($uris as $file_path => $uri) { + $diagnostics = array_map( + function (IssueData $issue_data) use ($file_path) : Diagnostic { + //$check_name = $issue->check_name; + $description = $issue_data->message; + $severity = $issue_data->severity; + + $start_line = max($issue_data->line_from, 1); + $end_line = $issue_data->line_to; + $start_column = $issue_data->column_from; + $end_column = $issue_data->column_to; + // Language server has 0 based lines and columns, phan has 1-based lines and columns. + $range = new Range( + new Position($start_line - 1, $start_column - 1), + new Position($end_line - 1, $end_column - 1) + ); + switch ($severity) { + case \Psalm\Config::REPORT_INFO: + $diagnostic_severity = DiagnosticSeverity::WARNING; + break; + case \Psalm\Config::REPORT_ERROR: + default: + $diagnostic_severity = DiagnosticSeverity::ERROR; + break; + } + $diagnostic = new Diagnostic( + $description, + $range, + null, + $diagnostic_severity, + 'Psalm' + ); + + //$code = 'PS' . \str_pad((string) $issue_data->shortcode, 3, "0", \STR_PAD_LEFT); + $code = $issue_data->link; + + if ($this->project_analyzer->language_server_use_extended_diagnostic_codes) { + // Added in VSCode 1.43.0 and will be part of the LSP 3.16.0 standard. + // Since this new functionality is not backwards compatible, we use a + // configuration option so the end user must opt in to it using the cli argument. + // https://github.com/microsoft/vscode/blob/1.43.0/src/vs/vscode.d.ts#L4688-L4699 + + /** @psalm-suppress InvalidPropertyAssignmentValue */ + $diagnostic->code = [ + "value" => $code, + "target" => $issue_data->link, + ]; + } else { + // the Diagnostic constructor only takes `int` for the code, but the property can be + // `int` or `string`, so we set the property directly because we want to use a `string` + $diagnostic->code = $code; + } + + return $diagnostic; + }, + $data[$file_path] ?? [] + ); + + $this->client->textDocument->publishDiagnostics($uri, $diagnostics); + } + } + + /** + * The shutdown request is sent from the client to the server. It asks the server to shut down, + * but to not exit (otherwise the response might not be delivered correctly to the client). + * There is a separate exit notification that asks the server to exit. + * + * @psalm-return Promise + */ + public function shutdown(): Promise + { + $this->clientStatus('closing'); + $this->verboseLog("Shutting down..."); + $codebase = $this->project_analyzer->getCodebase(); + $scanned_files = $codebase->scanner->getScannedFiles(); + $codebase->file_reference_provider->updateReferenceCache( + $codebase, + $scanned_files + ); + $this->clientStatus('closed'); + return new Success(null); + } + + /** + * A notification to ask the server to exit its process. + * + */ + public function exit(): void + { + exit(0); + } + + + /** + * Send log message to the client + * + * @param string $message The log message to send to the client. + * @psalm-param 1|2|3|4 $type + * @param int $type The log type: + * - 1 = Error + * - 2 = Warning + * - 3 = Info + * - 4 = Log + */ + private function verboseLog(string $message, int $type = 4): Promise + { + if ($this->project_analyzer->language_server_verbose) { + try { + return $this->client->logMessage( + '[Psalm ' .PSALM_VERSION. ' - PHP Language Server] ' . $message, + $type + ); + } catch (\Throwable $err) { + // do nothing + } + } + return new Success(null); + } + + /** + * Send status message to client. This is the same as sending a log message, + * except this is meant for parsing by the client to present status updates in a UI. + * + * @param string $status The log message to send to the client. Should not contain colons `:`. + * @param string|null $additional_info This is additional info that the client + * can use as part of the display message. + */ + private function clientStatus(string $status, ?string $additional_info = null): Promise + { + try { + // here we send a notification to the client using the telemetry notification method + return $this->client->logMessage( + $status . (!empty($additional_info) ? ': ' . $additional_info : ''), + 3, + 'telemetry/event' + ); + } catch (\Throwable $err) { + return new Success(null); + } + } + + /** + * Transforms an absolute file path into a URI as used by the language server protocol. + * + * @psalm-pure + */ + public static function pathToUri(string $filepath): string + { + $filepath = trim(str_replace('\\', '/', $filepath), '/'); + $parts = explode('/', $filepath); + // Don't %-encode the colon after a Windows drive letter + $first = array_shift($parts); + if (substr($first, -1) !== ':') { + $first = rawurlencode($first); + } + $parts = array_map('rawurlencode', $parts); + array_unshift($parts, $first); + $filepath = implode('/', $parts); + + return 'file:///' . $filepath; + } + + /** + * Transforms URI into file path + * + * + */ + public static function uriToPath(string $uri): string + { + $fragments = parse_url($uri); + if ($fragments === false + || !isset($fragments['scheme']) + || $fragments['scheme'] !== 'file' + || !isset($fragments['path']) + ) { + throw new \InvalidArgumentException("Not a valid file URI: $uri"); + } + + $filepath = urldecode((string) $fragments['path']); + + if (strpos($filepath, ':') !== false) { + if ($filepath[0] === '/') { + $filepath = substr($filepath, 1); + } + $filepath = str_replace('/', '\\', $filepath); + } + + $realpath = \realpath($filepath); + if ($realpath !== false) { + return $realpath; + } + + return $filepath; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/Message.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/Message.php new file mode 100644 index 0000000000000000000000000000000000000000..ae6ac62e3a148c48742997f424fe0d225b123b17 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/Message.php @@ -0,0 +1,70 @@ +body = MessageBody::parse(array_pop($parts)); + foreach ($parts as $line) { + if ($line) { + $pair = explode(': ', $line); + $obj->headers[$pair[0]] = $pair[1]; + } + } + + return $obj; + } + + /** + * @param string[] $headers + */ + public function __construct(?MessageBody $body = null, array $headers = []) + { + $this->body = $body; + if (!isset($headers['Content-Type'])) { + $headers['Content-Type'] = 'application/vscode-jsonrpc; charset=utf8'; + } + $this->headers = $headers; + } + + public function __toString(): string + { + $body = (string)$this->body; + $contentLength = strlen($body); + $this->headers['Content-Length'] = (string) $contentLength; + $headers = ''; + foreach ($this->headers as $name => $value) { + $headers .= "$name: $value\r\n"; + } + + return $headers . "\r\n" . $body; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolReader.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolReader.php new file mode 100644 index 0000000000000000000000000000000000000000..3f64c40eceea5df1bae2e9db195f5d5b16354a05 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolReader.php @@ -0,0 +1,13 @@ +, ?string, void> + * @psalm-suppress MixedReturnTypeCoercion + * @psalm-suppress MixedArgument in old Amp versions + * @psalm-suppress MixedAssignment in old Amp versions + */ + function () use ($input) : \Generator { + while ($this->is_accepting_new_requests) { + $read_promise = $input->read(); + + $chunk = yield $read_promise; + + if ($chunk === null) { + break; + } + + if ($this->readMessages($chunk) > 0) { + $this->emit('readMessageGroup'); + } + } + + $this->emitClose(); + } + ); + + $this->on( + 'close', + static function () use ($input): void { + $input->close(); + } + ); + } + + private function readMessages(string $buffer) : int + { + $emitted_messages = 0; + $i = 0; + while (($buffer[$i] ?? '') !== '') { + $this->buffer .= $buffer[$i++]; + switch ($this->parsing_mode) { + case self::PARSE_HEADERS: + if ($this->buffer === "\r\n") { + $this->parsing_mode = self::PARSE_BODY; + $this->content_length = (int) ($this->headers['Content-Length'] ?? 0); + $this->buffer = ''; + } elseif (substr($this->buffer, -2) === "\r\n") { + $parts = explode(':', $this->buffer); + $this->headers[$parts[0]] = trim($parts[1]); + $this->buffer = ''; + } + break; + case self::PARSE_BODY: + if (strlen($this->buffer) === $this->content_length) { + if (!$this->is_accepting_new_requests) { + // If we fork, don't read any bytes in the input buffer from the worker process. + $this->emitClose(); + + return $emitted_messages; + } + // MessageBody::parse can throw an Error, maybe log an error? + try { + $msg = new Message(MessageBody::parse($this->buffer), $this->headers); + } catch (Exception $_) { + $msg = null; + } + if ($msg) { + ++$emitted_messages; + $this->emit('message', [$msg]); + /** @psalm-suppress DocblockTypeContradiction */ + if (!$this->is_accepting_new_requests) { + // If we fork, don't read any bytes in the input buffer from the worker process. + $this->emitClose(); + + return $emitted_messages; + } + } + $this->parsing_mode = self::PARSE_HEADERS; + $this->headers = []; + $this->buffer = ''; + } + break; + } + } + + return $emitted_messages; + } + + private function emitClose(): void + { + if ($this->did_emit_close) { + return; + } + $this->did_emit_close = true; + $this->emit('close'); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolStreamWriter.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolStreamWriter.php new file mode 100644 index 0000000000000000000000000000000000000000..a59f62fa52d909d15efdfa722034643ed2b5d1ed --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolStreamWriter.php @@ -0,0 +1,33 @@ +output = new ResourceOutputStream($output); + } + + /** + * {@inheritdoc} + */ + public function write(Message $msg): Promise + { + return $this->output->write((string)$msg); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolWriter.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolWriter.php new file mode 100644 index 0000000000000000000000000000000000000000..d84079cc6b1472c294774b062e13b4eda1988088 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/LanguageServer/ProtocolWriter.php @@ -0,0 +1,16 @@ +server = $server; + $this->codebase = $codebase; + $this->onchange_line_limit = $onchange_line_limit; + } + + /** + * The document open notification is sent from the client to the server to signal newly opened text documents. The + * document's truth is now managed by the client and the server must not try to read the document's truth using the + * document's uri. + * + * @param \LanguageServerProtocol\TextDocumentItem $textDocument the document that was opened + */ + public function didOpen(TextDocumentItem $textDocument): void + { + $file_path = LanguageServer::uriToPath($textDocument->uri); + + if (!$this->codebase->config->isInProjectDirs($file_path)) { + error_log($file_path . ' is not in project'); + + return; + } + + $this->codebase->file_provider->openFile($file_path); + + $this->server->queueFileAnalysis($file_path, $textDocument->uri); + } + + public function didSave(TextDocumentItem $textDocument): void + { + $file_path = LanguageServer::uriToPath($textDocument->uri); + + if (!$this->codebase->config->isInProjectDirs($file_path)) { + return; + } + + // reopen file + $this->codebase->removeTemporaryFileChanges($file_path); + $this->codebase->file_provider->setOpenContents($file_path, $textDocument->text); + + $this->server->queueFileAnalysis($file_path, $textDocument->uri); + } + + /** + * The document change notification is sent from the client to the server to signal changes to a text document. + * + * @param \LanguageServerProtocol\VersionedTextDocumentIdentifier $textDocument + * @param \LanguageServerProtocol\TextDocumentContentChangeEvent[] $contentChanges + */ + public function didChange(VersionedTextDocumentIdentifier $textDocument, array $contentChanges): void + { + $file_path = \Psalm\Internal\LanguageServer\LanguageServer::uriToPath($textDocument->uri); + + if (!$this->codebase->config->isInProjectDirs($file_path)) { + return; + } + + if ($this->onchange_line_limit === 0) { + return; + } + + if (count($contentChanges) === 1 && $contentChanges[0]->range === null) { + $new_content = $contentChanges[0]->text; + } else { + throw new \UnexpectedValueException('Not expecting partial diff'); + } + + if ($this->onchange_line_limit !== null) { + if (substr_count($new_content, "\n") > $this->onchange_line_limit) { + return; + } + } + + $this->codebase->addTemporaryFileChanges($file_path, $new_content); + $this->server->queueTemporaryFileAnalysis($file_path, $textDocument->uri); + } + + /** + * The document close notification is sent from the client to the server when the document got closed in the client. + * The document's truth now exists where the document's uri points to (e.g. if the document's uri is a file uri the + * truth now exists on disk). + * + * @param \LanguageServerProtocol\TextDocumentIdentifier $textDocument The document that was closed + * + */ + public function didClose(TextDocumentIdentifier $textDocument): void + { + $file_path = LanguageServer::uriToPath($textDocument->uri); + + $this->codebase->file_provider->closeFile($file_path); + $this->server->client->textDocument->publishDiagnostics($textDocument->uri, []); + } + + /** + * The goto definition request is sent from the client to the server to resolve the definition location of a symbol + * at a given text document position. + * + * @param TextDocumentIdentifier $textDocument The text document + * @param Position $position The position inside the text document + * @psalm-return Promise|Promise + */ + public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise + { + $file_path = LanguageServer::uriToPath($textDocument->uri); + + try { + $reference_location = $this->codebase->getReferenceAtPosition($file_path, $position); + } catch (\Psalm\Exception\UnanalyzedFileException $e) { + $this->codebase->file_provider->openFile($file_path); + $this->server->queueFileAnalysis($file_path, $textDocument->uri); + + return new Success(null); + } + + if ($reference_location === null) { + return new Success(null); + } + + [$reference] = $reference_location; + + $code_location = $this->codebase->getSymbolLocation($file_path, $reference); + + if (!$code_location) { + return new Success(null); + } + + return new Success( + new Location( + LanguageServer::pathToUri($code_location->file_path), + new Range( + new Position($code_location->getLineNumber() - 1, $code_location->getColumn() - 1), + new Position($code_location->getEndLineNumber() - 1, $code_location->getEndColumn() - 1) + ) + ) + ); + } + + /** + * The hover request is sent from the client to the server to request + * hover information at a given text document position. + * + * @param TextDocumentIdentifier $textDocument The text document + * @param Position $position The position inside the text document + * @psalm-return Promise|Promise + */ + public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise + { + $file_path = LanguageServer::uriToPath($textDocument->uri); + + try { + $reference_location = $this->codebase->getReferenceAtPosition($file_path, $position); + } catch (\Psalm\Exception\UnanalyzedFileException $e) { + $this->codebase->file_provider->openFile($file_path); + $this->server->queueFileAnalysis($file_path, $textDocument->uri); + + return new Success(null); + } + + if ($reference_location === null) { + return new Success(null); + } + + [$reference, $range] = $reference_location; + + $symbol_information = $this->codebase->getSymbolInformation($file_path, $reference); + + if ($symbol_information === null) { + return new Success(null); + } + + $contents = []; + $contents[] = new MarkedString('php', $symbol_information); + + return new Success(new Hover($contents, $range)); + } + + /** + * The Completion request is sent from the client to the server to compute completion items at a given cursor + * position. Completion items are presented in the IntelliSense user interface. If computing full completion items + * is expensive, servers can additionally provide a handler for the completion item resolve request + * ('completionItem/resolve'). This request is sent when a completion item is selected in the user interface. A + * typically use case is for example: the 'textDocument/completion' request doesn't fill in the documentation + * property for returned completion items since it is expensive to compute. When the item is selected in the user + * interface then a 'completionItem/resolve' request is sent with the selected completion item as a param. The + * returned completion item should have the documentation property filled in. + * + * @param TextDocumentIdentifier The text document + * @param Position $position The position + * @psalm-return Promise>|Promise + */ + public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise + { + $this->server->doAnalysis(); + + $file_path = LanguageServer::uriToPath($textDocument->uri); + if (!$this->codebase->config->isInProjectDirs($file_path)) { + return new Success([]); + } + + try { + $completion_data = $this->codebase->getCompletionDataAtPosition($file_path, $position); + } catch (\Psalm\Exception\UnanalyzedFileException $e) { + $this->codebase->file_provider->openFile($file_path); + $this->server->queueFileAnalysis($file_path, $textDocument->uri); + + return new Success([]); + } + + if (!$completion_data) { + error_log('completion not found at ' . $position->line . ':' . $position->character); + + return new Success([]); + } + + [$recent_type, $gap, $offset] = $completion_data; + + if ($gap === '->' || $gap === '::') { + $completion_items = $this->codebase->getCompletionItemsForClassishThing($recent_type, $gap); + } else { + $completion_items = $this->codebase->getCompletionItemsForPartialSymbol($recent_type, $offset, $file_path); + } + + return new Success(new CompletionList($completion_items, false)); + } + + public function signatureHelp(TextDocumentIdentifier $textDocument, Position $position): Promise + { + $file_path = LanguageServer::uriToPath($textDocument->uri); + + try { + $argument_location = $this->codebase->getFunctionArgumentAtPosition($file_path, $position); + } catch (\Psalm\Exception\UnanalyzedFileException $e) { + $this->codebase->file_provider->openFile($file_path); + $this->server->queueFileAnalysis($file_path, $textDocument->uri); + + return new Success(new \LanguageServerProtocol\SignatureHelp()); + } + + if ($argument_location === null) { + return new Success(new \LanguageServerProtocol\SignatureHelp()); + } + + $signature_information = $this->codebase->getSignatureInformation($argument_location[0]); + + if (!$signature_information) { + return new Success(new \LanguageServerProtocol\SignatureHelp()); + } + + return new Success(new \LanguageServerProtocol\SignatureHelp([ + $signature_information, + ], 0, $argument_location[1])); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/MethodIdentifier.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/MethodIdentifier.php new file mode 100644 index 0000000000000000000000000000000000000000..6c73f9b527b83c5bea355002a0e5da363e85ed6d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/MethodIdentifier.php @@ -0,0 +1,62 @@ +fq_class_name = $fq_class_name; + $this->method_name = $method_name; + } + + /** + * Takes any valid reference to a method id and converts + * it into a MethodIdentifier + * + * @param string|MethodIdentifier $method_id + * + * @psalm-pure + */ + public static function wrap($method_id): self + { + return \is_string($method_id) ? static::fromMethodIdReference($method_id) : $method_id; + } + + /** + * @psalm-pure + */ + public static function isValidMethodIdReference(string $method_id): bool + { + return \strpos($method_id, '::') !== false; + } + + /** + * @psalm-pure + */ + public static function fromMethodIdReference(string $method_id): self + { + if (!static::isValidMethodIdReference($method_id)) { + throw new \InvalidArgumentException('Invalid method id reference provided: ' . $method_id); + } + // remove trailing backslash if it exists + $method_id = \preg_replace('/^\\\\/', '', $method_id); + $method_id_parts = \explode('::', $method_id); + return new self($method_id_parts[0], \strtolower($method_id_parts[1])); + } + + /** @return non-empty-string */ + public function __toString(): string + { + return $this->fq_class_name . '::' . $this->method_name; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpTraverser/CustomTraverser.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpTraverser/CustomTraverser.php new file mode 100644 index 0000000000000000000000000000000000000000..a220dbdeab69eda9886d7b5eee799a63c751c00c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpTraverser/CustomTraverser.php @@ -0,0 +1,169 @@ +stopTraversal = false; + } + + /** + * Recursively traverse a node. + * + * @param Node $node node to traverse + * + * @return Node Result of traversal (may be original node or new one) + */ + protected function traverseNode(Node $node) : Node + { + foreach ($node->getSubNodeNames() as $name) { + $subNode = &$node->$name; + + if (\is_array($subNode)) { + $subNode = $this->traverseArray($subNode); + if ($this->stopTraversal) { + break; + } + } elseif ($subNode instanceof Node) { + $traverseChildren = true; + foreach ($this->visitors as $visitor) { + $return = $visitor->enterNode($subNode, $traverseChildren); + if (null !== $return) { + if ($return instanceof Node) { + $subNode = $return; + } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $subNode = $this->traverseNode($subNode); + if ($this->stopTraversal) { + break; + } + } + + foreach ($this->visitors as $visitor) { + $return = $visitor->leaveNode($subNode); + if (null !== $return) { + if ($return instanceof Node) { + $subNode = $return; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } 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) + ); + } + } + } + } + } + + return $node; + } + + /** + * 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; + foreach ($this->visitors as $visitor) { + $return = $visitor->enterNode($node, $traverseChildren); + if (null !== $return) { + if ($return instanceof Node) { + $node = $return; + } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $node = $this->traverseNode($node); + if ($this->stopTraversal) { + break; + } + } + + foreach ($this->visitors as $visitor) { + $return = $visitor->leaveNode($node); + if (null !== $return) { + if ($return instanceof Node) { + $node = $return; + } elseif (\is_array($return)) { + $doNodes[] = [$i, $return]; + break; + } elseif (self::REMOVE_NODE === $return) { + $doNodes[] = [$i, []]; + break; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (false === $return) { + throw new \LogicException( + 'bool(false) return from leaveNode() no longer supported. ' . + 'Return NodeTraverser::REMOVE_NODE instead' + ); + } 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; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/AssignmentMapVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/AssignmentMapVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..018e461a754b8e3721926cb4b765a46c4e309e9b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/AssignmentMapVisitor.php @@ -0,0 +1,79 @@ +> + */ + protected $assignment_map = []; + + /** + * @var string|null + */ + protected $this_class_name; + + public function __construct(?string $this_class_name) + { + $this->this_class_name = $this_class_name; + } + + public function enterNode(PhpParser\Node $node): ?int + { + if ($node instanceof PhpParser\Node\Expr\Assign) { + $left_var_id = ExpressionIdentifier::getRootVarId($node->var, $this->this_class_name); + $right_var_id = ExpressionIdentifier::getRootVarId($node->expr, $this->this_class_name); + + if ($left_var_id) { + $this->assignment_map[$left_var_id][$right_var_id ?: 'isset'] = true; + } + + return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } elseif ($node instanceof PhpParser\Node\Expr\PostInc + || $node instanceof PhpParser\Node\Expr\PostDec + || $node instanceof PhpParser\Node\Expr\PreInc + || $node instanceof PhpParser\Node\Expr\PreDec + || $node instanceof PhpParser\Node\Expr\AssignOp + ) { + $var_id = ExpressionIdentifier::getRootVarId($node->var, $this->this_class_name); + + if ($var_id) { + $this->assignment_map[$var_id][$var_id] = true; + } + + return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } elseif ($node instanceof PhpParser\Node\Expr\FuncCall) { + foreach ($node->args as $arg) { + $arg_var_id = ExpressionIdentifier::getRootVarId($arg->value, $this->this_class_name); + + if ($arg_var_id) { + $this->assignment_map[$arg_var_id][$arg_var_id] = true; + } + } + } elseif ($node instanceof PhpParser\Node\Stmt\Unset_) { + foreach ($node->vars as $arg) { + $arg_var_id = ExpressionIdentifier::getRootVarId($arg, $this->this_class_name); + + if ($arg_var_id) { + $this->assignment_map[$arg_var_id][$arg_var_id] = true; + } + } + } + + return null; + } + + /** + * @return array> + */ + public function getAssignmentMap(): array + { + return $this->assignment_map; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..857e050cb10f254c53e59538092c561e8a2c79a5 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php @@ -0,0 +1,82 @@ + + */ + protected $non_trivial_expr = []; + + private function checkNonTrivialExpr(PhpParser\Node\Expr $node): bool + { + /** + * @psalm-suppress UndefinedClass + * @psalm-suppress TypeDoesNotContainType + */ + if ($node instanceof PhpParser\Node\Expr\ArrayDimFetch + || $node instanceof PhpParser\Node\Expr\Closure + || $node instanceof PhpParser\Node\Expr\ClosureUse + || $node instanceof PhpParser\Node\Expr\Eval_ + || $node instanceof PhpParser\Node\Expr\Exit_ + || $node instanceof PhpParser\Node\Expr\Include_ + || $node instanceof PhpParser\Node\Expr\FuncCall + || $node instanceof PhpParser\Node\Expr\MethodCall + || $node instanceof PhpParser\Node\Expr\ArrowFunction + || $node instanceof PhpParser\Node\Expr\ShellExec + || $node instanceof PhpParser\Node\Expr\StaticCall + || $node instanceof PhpParser\Node\Expr\Yield_ + || $node instanceof PhpParser\Node\Expr\YieldFrom + || $node instanceof PhpParser\Node\Expr\New_ + || $node instanceof PhpParser\Node\Expr\Cast\String_ + ) { + if (($node instanceof PhpParser\Node\Expr\FuncCall + || $node instanceof PhpParser\Node\Expr\MethodCall + || $node instanceof PhpParser\Node\Expr\StaticCall) + && isset($node->pure) + ) { + return false; + } + + if ($node instanceof PhpParser\Node\Expr\New_ && isset($node->external_mutation_free)) { + return false; + } + + return true; + } else { + return false; + } + } + + public function enterNode(PhpParser\Node $node): ?int + { + if ($node instanceof PhpParser\Node\Expr) { + // Check for Non-Trivial Expression first + if ($this->checkNonTrivialExpr($node)) { + $this->non_trivial_expr[] = $node; + return PhpParser\NodeTraverser::STOP_TRAVERSAL; + } elseif ($node instanceof PhpParser\Node\Expr\ClassConstFetch + || $node instanceof PhpParser\Node\Expr\ConstFetch + || $node instanceof PhpParser\Node\Expr\Error + || $node instanceof PhpParser\Node\Expr\PropertyFetch + || $node instanceof PhpParser\Node\Expr\StaticPropertyFetch + ) { + return PhpParser\NodeTraverser::STOP_TRAVERSAL; + } + } + return null; + } + + /** + * @return array + */ + public function getNonTrivialExpr(): array + { + return $this->non_trivial_expr; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/CloningVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/CloningVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..915ebec701c18b5c61075885dbd0d673a2cb9a0b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/CloningVisitor.php @@ -0,0 +1,36 @@ +getComments()) { + $node->setAttribute( + 'comments', + array_map( + /** + * @return \PhpParser\Comment + */ + function (\PhpParser\Comment $c): \PhpParser\Comment { + return clone $c; + }, + $cs + ) + ); + } + + return $node; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..0b5c844776509f0381620281e796751534d9ff62 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php @@ -0,0 +1,35 @@ +type_provider = $old_type_provider; + } + + /** + * @return Node\Expr + */ + public function enterNode(Node $node): Node + { + /** @var \PhpParser\Node\Expr $node */ + $origNode = $node; + + $node = clone $node; + + $node_type = $this->type_provider->getType($origNode); + + if ($node_type) { + $this->type_provider->setType($node, clone $node_type); + } + + return $node; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/NodeCleanerVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/NodeCleanerVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..0b7768a08ad1a97b99a95f69e3938023003146d5 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/NodeCleanerVisitor.php @@ -0,0 +1,26 @@ +type_provider = $type_provider; + } + + public function enterNode(PhpParser\Node $node): ?int + { + if ($node instanceof PhpParser\Node\Expr) { + $this->type_provider->clearNodeOfTypeAndAssertions($node); + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/NodeCounterVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/NodeCounterVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..1e86a9be8ebc22062b045a26fa950472ef96673d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/NodeCounterVisitor.php @@ -0,0 +1,21 @@ +count++; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/OffsetShifterVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/OffsetShifterVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..8a729d692fdc67b32b8cc2107ff31c217c3edc11 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/OffsetShifterVisitor.php @@ -0,0 +1,62 @@ +file_offset = $offset; + $this->line_offset = $line_offset; + } + + /** + * @return null|int + */ + public function enterNode(PhpParser\Node $node) + { + /** @var array{startFilePos: int, endFilePos: int, startLine: int} */ + $attrs = $node->getAttributes(); + + if ($cs = $node->getComments()) { + $new_comments = []; + + foreach ($cs as $c) { + if ($c instanceof PhpParser\Comment\Doc) { + $new_comments[] = new PhpParser\Comment\Doc( + $c->getText(), + $c->getStartLine() + $this->line_offset, + $c->getStartFilePos() + $this->file_offset + ); + } else { + $new_comments[] = new PhpParser\Comment( + $c->getText(), + $c->getStartLine() + $this->line_offset, + $c->getStartFilePos() + $this->file_offset + ); + } + } + + $node->setAttribute('comments', $new_comments); + } + + /** + * @psalm-suppress MixedOperand + */ + $node->setAttribute('startFilePos', $attrs['startFilePos'] + $this->file_offset); + /** @psalm-suppress MixedOperand */ + $node->setAttribute('endFilePos', $attrs['endFilePos'] + $this->file_offset); + /** @psalm-suppress MixedOperand */ + $node->setAttribute('startLine', $attrs['startLine'] + $this->line_offset); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ParamReplacementVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ParamReplacementVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..1809535f379fcf9fd86ceeff3b94df5cea1c73d1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ParamReplacementVisitor.php @@ -0,0 +1,112 @@ + */ + private $replacements = []; + + /** @var bool */ + private $new_name_replaced = false; + + /** @var bool */ + private $new_new_name_used = false; + + public function __construct(string $old_name, string $new_name) + { + $this->old_name = $old_name; + $this->new_name = $new_name; + } + + public function enterNode(PhpParser\Node $node): ?int + { + if ($node instanceof PhpParser\Node\Expr\Variable) { + if ($node->name === $this->old_name) { + $this->replacements[] = new FileManipulation( + (int) $node->getAttribute('startFilePos') + 1, + (int) $node->getAttribute('endFilePos') + 1, + $this->new_name + ); + } elseif ($node->name === $this->new_name) { + if ($this->new_new_name_used) { + $this->replacements = []; + return PhpParser\NodeTraverser::STOP_TRAVERSAL; + } + + $this->replacements[] = new FileManipulation( + (int) $node->getAttribute('startFilePos') + 1, + (int) $node->getAttribute('endFilePos') + 1, + $this->new_name . '_new' + ); + + $this->new_name_replaced = true; + } elseif ($node->name === $this->new_name . '_new') { + if ($this->new_name_replaced) { + $this->replacements = []; + return PhpParser\NodeTraverser::STOP_TRAVERSAL; + } + + $this->new_new_name_used = true; + } + } elseif ($node instanceof PhpParser\Node\Stmt\ClassMethod + && ($docblock = $node->getDocComment()) + ) { + $parsed_docblock = \Psalm\Internal\Scanner\DocblockParser::parse($docblock->getText()); + + $replaced = false; + + foreach ($parsed_docblock->tags as $tag_name => $tags) { + foreach ($tags as $i => $tag) { + if ($tag_name === 'param' + || $tag_name === 'psalm-param' + || $tag_name === 'phpstan-param' + || $tag_name === 'phan-param' + ) { + $parts = \Psalm\Internal\Analyzer\CommentAnalyzer::splitDocLine($tag); + + if (($parts[1] ?? '') === '$' . $this->old_name) { + $parsed_docblock->tags[$tag_name][$i] = \str_replace( + '$' . $this->old_name, + '$' . $this->new_name, + $tag + ); + $replaced = true; + } + } + } + } + + if ($replaced) { + $this->replacements[] = new FileManipulation( + $docblock->getStartFilePos(), + $docblock->getStartFilePos() + \strlen($docblock->getText()), + \rtrim($parsed_docblock->render($parsed_docblock->first_line_padding)), + false, + false + ); + } + } + + return null; + } + + /** + * @return list + */ + public function getReplacements(): array + { + return $this->replacements; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..e2920ece65793b5746a3a10827db7d756ccd0a00 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php @@ -0,0 +1,350 @@ + */ + private $offset_map; + + /** @var bool */ + private $must_rescan = false; + + /** @var int */ + private $non_method_changes; + + /** @var string */ + private $a_file_contents; + + /** @var string */ + private $b_file_contents; + + /** @var int */ + private $a_file_contents_length; + + /** @var PhpParser\Parser */ + private $parser; + + /** @var PhpParser\ErrorHandler\Collecting */ + private $error_handler; + + /** @param array $offset_map */ + public function __construct( + PhpParser\Parser $parser, + PhpParser\ErrorHandler\Collecting $error_handler, + array $offset_map, + string $a_file_contents, + string $b_file_contents + ) { + $this->parser = $parser; + $this->error_handler = $error_handler; + $this->offset_map = $offset_map; + $this->a_file_contents = $a_file_contents; + $this->a_file_contents_length = strlen($a_file_contents); + $this->b_file_contents = $b_file_contents; + $this->non_method_changes = count($offset_map); + } + + /** + * @param bool $traverseChildren + * + * @return null|int|PhpParser\Node + */ + public function enterNode(PhpParser\Node $node, &$traverseChildren = true) + { + /** @var array{startFilePos: int, endFilePos: int, startLine: int} */ + $attrs = $node->getAttributes(); + + if ($cs = $node->getComments()) { + $stmt_start_pos = $cs[0]->getStartFilePos(); + } else { + $stmt_start_pos = $attrs['startFilePos']; + } + + $stmt_end_pos = $attrs['endFilePos']; + + $start_offset = 0; + $end_offset = 0; + + $line_offset = 0; + + foreach ($this->offset_map as [$a_s, $a_e, $b_s, $b_e, $line_diff]) { + if ($a_s > $stmt_end_pos) { + break; + } + + $end_offset = $b_e - $a_e; + + if ($a_s < $stmt_start_pos) { + $start_offset = $b_s - $a_s; + } + + if ($a_e < $stmt_start_pos) { + $start_offset = $end_offset; + + $line_offset = $line_diff; + + continue; + } + + if ($node instanceof PhpParser\Node\Stmt\ClassMethod + || $node instanceof PhpParser\Node\Stmt\Namespace_ + || $node instanceof PhpParser\Node\Stmt\ClassLike + ) { + if ($node instanceof PhpParser\Node\Stmt\ClassMethod) { + if ($a_s >= $stmt_start_pos && $a_e <= $stmt_end_pos) { + foreach ($this->offset_map as [$a_s2, $a_e2, $b_s2, $b_e2]) { + if ($a_s2 > $stmt_end_pos) { + break; + } + + // we have a diff that goes outside the bounds that we care about + if ($a_e2 > $stmt_end_pos) { + $this->must_rescan = true; + + return PhpParser\NodeTraverser::STOP_TRAVERSAL; + } + + $end_offset = $b_e2 - $a_e2; + + if ($a_s2 < $stmt_start_pos) { + $start_offset = $b_s2 - $a_s2; + } + + if ($a_e2 < $stmt_start_pos) { + $start_offset = $end_offset; + + $line_offset = $line_diff; + + continue; + } + + if ($a_s2 >= $stmt_start_pos && $a_e2 <= $stmt_end_pos) { + --$this->non_method_changes; + } + } + + $stmt_start_pos += $start_offset; + $stmt_end_pos += $end_offset; + + $current_line = substr_count(substr($this->b_file_contents, 0, $stmt_start_pos), "\n"); + + $method_contents = substr( + $this->b_file_contents, + $stmt_start_pos, + $stmt_end_pos - $stmt_start_pos + 1 + ); + + if (!$method_contents) { + $this->must_rescan = true; + + return PhpParser\NodeTraverser::STOP_TRAVERSAL; + } + + $error_handler = new \PhpParser\ErrorHandler\Collecting(); + + $fake_class = 'parser->parse( + $fake_class, + $error_handler + ) ?: []; + + if (!$replacement_stmts + || !$replacement_stmts[0] instanceof PhpParser\Node\Stmt\ClassLike + || count($replacement_stmts[0]->stmts) !== 1 + ) { + $hacky_class_fix = self::balanceBrackets($fake_class); + + if ($replacement_stmts + && $replacement_stmts[0] instanceof PhpParser\Node\Stmt\ClassLike + && count($replacement_stmts[0]->stmts) !== 1 + ) { + $this->must_rescan = true; + + return PhpParser\NodeTraverser::STOP_TRAVERSAL; + } + + // changes "): {" to ") {" + $hacky_class_fix = preg_replace('/(\)[\s]*):([\s]*\{)/', '$1 $2', $hacky_class_fix); + + // allows autocompletion + $hacky_class_fix = preg_replace('/(->|::)(\n\s*if\s*\()/', '~;$2', $hacky_class_fix); + + if ($hacky_class_fix !== $fake_class) { + $replacement_stmts = $this->parser->parse( + $hacky_class_fix, + $error_handler + ) ?: []; + } + + if (!$replacement_stmts + || !$replacement_stmts[0] instanceof PhpParser\Node\Stmt\ClassLike + || count($replacement_stmts[0]->stmts) > 1 + ) { + $this->must_rescan = true; + + return PhpParser\NodeTraverser::STOP_TRAVERSAL; + } + } + + $replacement_stmts = $replacement_stmts[0]->stmts; + + $renumbering_traverser = new PhpParser\NodeTraverser; + $position_shifter = new \Psalm\Internal\PhpVisitor\OffsetShifterVisitor( + $stmt_start_pos - 15, + $current_line + ); + $renumbering_traverser->addVisitor($position_shifter); + $replacement_stmts = $renumbering_traverser->traverse($replacement_stmts); + + if ($error_handler->hasErrors()) { + foreach ($error_handler->getErrors() as $error) { + if ($error->hasColumnInfo()) { + /** @var array{startFilePos: int, endFilePos: int} */ + $error_attrs = $error->getAttributes(); + /** @psalm-suppress MixedOperand */ + $error = new PhpParser\Error( + $error->getRawMessage(), + [ + 'startFilePos' => $stmt_start_pos + $error_attrs['startFilePos'] - 15, + 'endFilePos' => $stmt_start_pos + $error_attrs['endFilePos'] - 15, + 'startLine' => $error->getStartLine() + $current_line + $line_offset, + ] + ); + } + + $this->error_handler->handleError($error); + } + } + + $error_handler->clearErrors(); + + $traverseChildren = false; + + return reset($replacement_stmts); + } + + $this->must_rescan = true; + + return PhpParser\NodeTraverser::STOP_TRAVERSAL; + } + + if ($node->stmts) { + /** @var int */ + $stmt_inner_start_pos = $node->stmts[0]->getAttribute('startFilePos'); + + /** @var int */ + $stmt_inner_end_pos = $node->stmts[count($node->stmts) - 1]->getAttribute('endFilePos'); + + if ($node instanceof PhpParser\Node\Stmt\ClassLike) { + /** @psalm-suppress PossiblyFalseOperand */ + $stmt_inner_start_pos = strrpos( + $this->a_file_contents, + '{', + $stmt_inner_start_pos - $this->a_file_contents_length + ) + 1; + + if ($stmt_inner_end_pos < $this->a_file_contents_length) { + $stmt_inner_end_pos = strpos($this->a_file_contents, '}', $stmt_inner_end_pos + 1); + } + } + + if ($a_s > $stmt_inner_start_pos && $a_e < $stmt_inner_end_pos) { + continue; + } + } + } + + $this->must_rescan = true; + + return PhpParser\NodeTraverser::STOP_TRAVERSAL; + } + + if ($start_offset !== 0 || $end_offset !== 0 || $line_offset !== 0) { + if ($start_offset !== 0) { + if ($cs) { + $new_comments = []; + + foreach ($cs as $c) { + if ($c instanceof PhpParser\Comment\Doc) { + $new_comments[] = new PhpParser\Comment\Doc( + $c->getText(), + $c->getStartLine() + $line_offset, + $c->getStartFilePos() + $start_offset + ); + } else { + $new_comments[] = new PhpParser\Comment( + $c->getText(), + $c->getStartLine() + $line_offset, + $c->getStartFilePos() + $start_offset + ); + } + } + + $node->setAttribute('comments', $new_comments); + + /** @psalm-suppress MixedOperand */ + $node->setAttribute('startFilePos', $attrs['startFilePos'] + $start_offset); + } else { + $node->setAttribute('startFilePos', $stmt_start_pos + $start_offset); + } + } + + if ($end_offset !== 0) { + $node->setAttribute('endFilePos', $stmt_end_pos + $end_offset); + } + + if ($line_offset !== 0) { + /** @psalm-suppress MixedOperand */ + $node->setAttribute('startLine', $attrs['startLine'] + $line_offset); + } + + return $node; + } + + return null; + } + + public function mustRescan() : bool + { + return $this->must_rescan || $this->non_method_changes; + } + + /** + * @psalm-pure + */ + private static function balanceBrackets(string $fake_class) : string + { + $tokens = \token_get_all($fake_class); + + $brace_count = 0; + + foreach ($tokens as $token) { + if ($token === '{') { + ++$brace_count; + } elseif ($token === '}') { + --$brace_count; + } + } + + if ($brace_count > 0) { + $fake_class .= \str_repeat('}', $brace_count); + } + + return $fake_class; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..6ea58b0a5f5778d5552da651252c684e6727e4ef --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php @@ -0,0 +1,4154 @@ +> */ + private $class_template_types = []; + + /** @var array> */ + private $function_template_types = []; + + /** @var FunctionLikeStorage[] */ + private $functionlike_storages = []; + + /** @var FileStorage */ + private $file_storage; + + /** @var ClassLikeStorage[] */ + private $classlike_storages = []; + + /** @var class-string<\Psalm\Plugin\Hook\AfterClassLikeVisitInterface>[] */ + private $after_classlike_check_plugins; + + /** @var int */ + private $php_major_version; + + /** @var int */ + private $php_minor_version; + + /** @var PhpParser\Node\Name|null */ + private $namespace_name; + + /** @var PhpParser\Node\Expr|null */ + private $exists_cond_expr; + + /** + * @var ?int + */ + private $skip_if_descendants = null; + + /** + * @var array + */ + private $type_aliases = []; + + /** + * @var array + */ + private $classlike_type_aliases = []; + + /** + * @var array + */ + private $bad_classes = []; + + public function __construct( + Codebase $codebase, + FileStorage $file_storage, + FileScanner $file_scanner + ) { + $this->codebase = $codebase; + $this->file_scanner = $file_scanner; + $this->file_path = $file_scanner->file_path; + $this->scan_deep = $file_scanner->will_analyze; + $this->config = $codebase->config; + $this->file_storage = $file_storage; + $this->aliases = $this->file_storage->aliases = new Aliases(); + $this->after_classlike_check_plugins = $this->config->after_visit_classlikes; + $this->php_major_version = $codebase->php_major_version; + $this->php_minor_version = $codebase->php_minor_version; + } + + public function enterNode(PhpParser\Node $node): ?int + { + foreach ($node->getComments() as $comment) { + if ($comment instanceof PhpParser\Comment\Doc && !$node instanceof PhpParser\Node\Stmt\ClassLike) { + $self_fqcln = $node instanceof PhpParser\Node\Stmt\ClassLike + && $node->name !== null + ? ($this->aliases->namespace ? $this->aliases->namespace . '\\' : '') . $node->name->name + : null; + + try { + $type_aliases = CommentAnalyzer::getTypeAliasesFromComment( + $comment, + $this->aliases, + $this->type_aliases, + $self_fqcln + ); + + foreach ($type_aliases as $type_alias) { + // finds issues, if there are any + TypeParser::parseTokens($type_alias->replacement_tokens); + } + + $this->type_aliases += $type_aliases; + } catch (DocblockParseException $e) { + $this->file_storage->docblock_issues[] = new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($this->file_scanner, $node, null, true) + ); + } catch (TypeParseTreeException $e) { + $this->file_storage->docblock_issues[] = new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($this->file_scanner, $node, null, true) + ); + } + } + } + + if ($node instanceof PhpParser\Node\Stmt\Namespace_) { + $this->file_storage->aliases = $this->aliases; + + $this->namespace_name = $node->name; + + $this->aliases = new Aliases( + $node->name ? implode('\\', $node->name->parts) : '', + $this->aliases->uses, + $this->aliases->functions, + $this->aliases->constants, + $this->aliases->uses_flipped, + $this->aliases->functions_flipped, + $this->aliases->constants_flipped + ); + + $this->file_storage->namespace_aliases[(int) $node->getAttribute('startFilePos')] = $this->aliases; + + if ($node->stmts) { + $this->aliases->namespace_first_stmt_start = (int) $node->stmts[0]->getAttribute('startFilePos'); + } + } elseif ($node instanceof PhpParser\Node\Stmt\Use_) { + foreach ($node->uses as $use) { + $use_path = implode('\\', $use->name->parts); + + $use_alias = $use->alias ? $use->alias->name : $use->name->getLast(); + + switch ($use->type !== PhpParser\Node\Stmt\Use_::TYPE_UNKNOWN ? $use->type : $node->type) { + case PhpParser\Node\Stmt\Use_::TYPE_FUNCTION: + $this->aliases->functions[strtolower($use_alias)] = $use_path; + $this->aliases->functions_flipped[strtolower($use_path)] = $use_alias; + break; + + case PhpParser\Node\Stmt\Use_::TYPE_CONSTANT: + $this->aliases->constants[$use_alias] = $use_path; + $this->aliases->constants_flipped[$use_path] = $use_alias; + break; + + case PhpParser\Node\Stmt\Use_::TYPE_NORMAL: + $this->aliases->uses[strtolower($use_alias)] = $use_path; + $this->aliases->uses_flipped[strtolower($use_path)] = $use_alias; + break; + } + } + + if (!$this->aliases->uses_start) { + $this->aliases->uses_start = (int) $node->getAttribute('startFilePos'); + } + + $this->aliases->uses_end = (int) $node->getAttribute('endFilePos') + 1; + } elseif ($node instanceof PhpParser\Node\Stmt\GroupUse) { + $use_prefix = implode('\\', $node->prefix->parts); + + foreach ($node->uses as $use) { + $use_path = $use_prefix . '\\' . implode('\\', $use->name->parts); + $use_alias = $use->alias ? $use->alias->name : $use->name->getLast(); + + switch ($use->type !== PhpParser\Node\Stmt\Use_::TYPE_UNKNOWN ? $use->type : $node->type) { + case PhpParser\Node\Stmt\Use_::TYPE_FUNCTION: + $this->aliases->functions[strtolower($use_alias)] = $use_path; + $this->aliases->functions_flipped[strtolower($use_path)] = $use_alias; + break; + + case PhpParser\Node\Stmt\Use_::TYPE_CONSTANT: + $this->aliases->constants[$use_alias] = $use_path; + $this->aliases->constants_flipped[$use_path] = $use_alias; + break; + + case PhpParser\Node\Stmt\Use_::TYPE_NORMAL: + $this->aliases->uses[strtolower($use_alias)] = $use_path; + $this->aliases->uses_flipped[strtolower($use_path)] = $use_alias; + break; + } + } + + if (!$this->aliases->uses_start) { + $this->aliases->uses_start = (int) $node->getAttribute('startFilePos'); + } + + $this->aliases->uses_end = (int) $node->getAttribute('endFilePos') + 1; + } elseif ($node instanceof PhpParser\Node\Stmt\ClassLike) { + if ($this->skip_if_descendants) { + return null; + } + + if ($this->registerClassLike($node) === false) { + $this->bad_classes[\spl_object_id($node)] = true; + return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + } elseif (($node instanceof PhpParser\Node\Expr\New_ + || $node instanceof PhpParser\Node\Expr\Instanceof_ + || $node instanceof PhpParser\Node\Expr\StaticPropertyFetch + || $node instanceof PhpParser\Node\Expr\ClassConstFetch + || $node instanceof PhpParser\Node\Expr\StaticCall) + && $node->class instanceof PhpParser\Node\Name + ) { + $fq_classlike_name = ClassLikeAnalyzer::getFQCLNFromNameObject($node->class, $this->aliases); + + if (!in_array(strtolower($fq_classlike_name), ['self', 'static', 'parent'], true)) { + $this->codebase->scanner->queueClassLikeForScanning( + $fq_classlike_name, + false, + !($node instanceof PhpParser\Node\Expr\ClassConstFetch) + || !($node->name instanceof PhpParser\Node\Identifier) + || strtolower($node->name->name) !== 'class' + ); + $this->file_storage->referenced_classlikes[strtolower($fq_classlike_name)] = $fq_classlike_name; + } + } elseif ($node instanceof PhpParser\Node\Stmt\TryCatch) { + foreach ($node->catches as $catch) { + foreach ($catch->types as $catch_type) { + $catch_fqcln = ClassLikeAnalyzer::getFQCLNFromNameObject($catch_type, $this->aliases); + + if (!in_array(strtolower($catch_fqcln), ['self', 'static', 'parent'], true)) { + $this->codebase->scanner->queueClassLikeForScanning($catch_fqcln); + $this->file_storage->referenced_classlikes[strtolower($catch_fqcln)] = $catch_fqcln; + } + } + } + } elseif ($node instanceof PhpParser\Node\FunctionLike) { + if ($node instanceof PhpParser\Node\Stmt\Function_ + || $node instanceof PhpParser\Node\Stmt\ClassMethod + ) { + if ($this->skip_if_descendants) { + return null; + } + } + + $this->registerFunctionLike($node); + + if ($node instanceof PhpParser\Node\Expr\Closure + || $node instanceof PhpParser\Node\Expr\ArrowFunction + ) { + $this->codebase->scanner->queueClassLikeForScanning('Closure'); + } + + if (!$this->scan_deep) { + return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + } elseif ($node instanceof PhpParser\Node\Stmt\Global_) { + $function_like_storage = end($this->functionlike_storages); + + if ($function_like_storage) { + foreach ($node->vars as $var) { + if ($var instanceof PhpParser\Node\Expr\Variable) { + if (is_string($var->name) && $var->name !== 'argv' && $var->name !== 'argc') { + $var_id = '$' . $var->name; + + $function_like_storage->global_variables[$var_id] = true; + } + } + } + } + } elseif ($node instanceof PhpParser\Node\Expr\FuncCall && $node->name instanceof PhpParser\Node\Name) { + $function_id = implode('\\', $node->name->parts); + if (InternalCallMapHandler::inCallMap($function_id)) { + $this->registerClassMapFunctionCall($function_id, $node); + } + } elseif ($node instanceof PhpParser\Node\Stmt\TraitUse) { + if ($this->skip_if_descendants) { + return null; + } + + if (!$this->classlike_storages) { + throw new \LogicException('$this->classlike_storages should not be empty'); + } + + $storage = $this->classlike_storages[count($this->classlike_storages) - 1]; + + $method_map = $storage->trait_alias_map ?: []; + $visibility_map = $storage->trait_visibility_map ?: []; + $final_map = $storage->trait_final_map ?: []; + + foreach ($node->adaptations as $adaptation) { + if ($adaptation instanceof PhpParser\Node\Stmt\TraitUseAdaptation\Alias) { + $old_name = strtolower($adaptation->method->name); + $new_name = $old_name; + + if ($adaptation->newName) { + $new_name = strtolower($adaptation->newName->name); + + if ($new_name !== $old_name) { + $method_map[$new_name] = $old_name; + } + } + + if ($adaptation->newModifier) { + switch ($adaptation->newModifier) { + case 1: + $visibility_map[$new_name] = ClassLikeAnalyzer::VISIBILITY_PUBLIC; + break; + + case 2: + $visibility_map[$new_name] = ClassLikeAnalyzer::VISIBILITY_PROTECTED; + break; + + case 4: + $visibility_map[$new_name] = ClassLikeAnalyzer::VISIBILITY_PRIVATE; + break; + + case 32: + $final_map[$new_name] = true; + break; + } + } + } + } + + $storage->trait_alias_map = $method_map; + $storage->trait_visibility_map = $visibility_map; + $storage->trait_final_map = $final_map; + + foreach ($node->traits as $trait) { + $trait_fqcln = ClassLikeAnalyzer::getFQCLNFromNameObject($trait, $this->aliases); + $this->codebase->scanner->queueClassLikeForScanning($trait_fqcln, $this->scan_deep); + $storage->used_traits[strtolower($trait_fqcln)] = $trait_fqcln; + $this->file_storage->required_classes[strtolower($trait_fqcln)] = $trait_fqcln; + } + + if ($node_comment = $node->getDocComment()) { + $comments = DocComment::parsePreservingLength($node_comment); + + if (isset($comments->combined_tags['use'])) { + foreach ($comments->combined_tags['use'] as $template_line) { + $this->useTemplatedType( + $storage, + $node, + trim(preg_replace('@^[ \t]*\*@m', '', $template_line)) + ); + } + } + + if (isset($comments->tags['template-extends']) + || isset($comments->tags['extends']) + || isset($comments->tags['template-implements']) + || isset($comments->tags['implements']) + ) { + $storage->docblock_issues[] = new InvalidDocblock( + 'You must use @use or @template-use to parameterize traits', + new CodeLocation($this->file_scanner, $node, null, true) + ); + } + } + } elseif ($node instanceof PhpParser\Node\Expr\Include_) { + $this->visitInclude($node); + } elseif ($node instanceof PhpParser\Node\Expr\Assign + || $node instanceof PhpParser\Node\Expr\AssignOp + || $node instanceof PhpParser\Node\Expr\AssignRef + || $node instanceof PhpParser\Node\Stmt\For_ + || $node instanceof PhpParser\Node\Stmt\Foreach_ + || $node instanceof PhpParser\Node\Stmt\While_ + || $node instanceof PhpParser\Node\Stmt\Do_ + || $node instanceof PhpParser\Node\Stmt\Echo_ + ) { + if ($doc_comment = $node->getDocComment()) { + $var_comments = []; + + try { + $var_comments = CommentAnalyzer::getTypeFromComment( + $doc_comment, + $this->file_scanner, + $this->aliases, + null, + $this->type_aliases + ); + } catch (DocblockParseException $e) { + // do nothing + } + + foreach ($var_comments as $var_comment) { + if (!$var_comment->type) { + continue; + } + + $var_type = $var_comment->type; + $var_type->queueClassLikesForScanning($this->codebase, $this->file_storage); + } + } + + if ($node instanceof PhpParser\Node\Expr\Assign + || $node instanceof PhpParser\Node\Expr\AssignOp + || $node instanceof PhpParser\Node\Expr\AssignRef + ) { + if ($node->var instanceof PhpParser\Node\Expr\PropertyFetch + && $node->var->var instanceof PhpParser\Node\Expr\Variable + && $node->var->var->name === 'this' + && $node->var->name instanceof PhpParser\Node\Identifier + ) { + $functionlike_storage = end($this->functionlike_storages); + + if ($functionlike_storage instanceof MethodStorage) { + $functionlike_storage->this_property_mutations[$node->var->name->name] = true; + } + } + } + } elseif ($node instanceof PhpParser\Node\Stmt\Const_) { + foreach ($node->consts as $const) { + $const_type = SimpleTypeInferer::infer( + $this->codebase, + new \Psalm\Internal\Provider\NodeDataProvider(), + $const->value, + $this->aliases + ) ?: Type::getMixed(); + + $fq_const_name = Type::getFQCLNFromString($const->name->name, $this->aliases); + + if ($this->codebase->register_stub_files || $this->codebase->register_autoload_files) { + $this->codebase->addGlobalConstantType($fq_const_name, $const_type); + } + + $this->file_storage->constants[$fq_const_name] = $const_type; + $this->file_storage->declaring_constants[$fq_const_name] = $this->file_path; + } + } elseif ($node instanceof PhpParser\Node\Stmt\If_ && !$this->skip_if_descendants) { + if (!$this->fq_classlike_names && !$this->functionlike_storages) { + $this->exists_cond_expr = $node->cond; + + if ($this->enterConditional($this->exists_cond_expr) === false) { + // the else node should terminate the agreement + $this->skip_if_descendants = $node->else ? $node->else->getLine() : $node->getLine(); + } + } + } elseif ($node instanceof PhpParser\Node\Stmt\Else_) { + if ($this->skip_if_descendants === $node->getLine()) { + $this->skip_if_descendants = null; + $this->exists_cond_expr = null; + } elseif (!$this->skip_if_descendants) { + if ($this->exists_cond_expr && $this->enterConditional($this->exists_cond_expr) === true) { + $this->skip_if_descendants = $node->getLine(); + } + } + } elseif ($node instanceof PhpParser\Node\Expr\Yield_ || $node instanceof PhpParser\Node\Expr\YieldFrom) { + $function_like_storage = end($this->functionlike_storages); + + if ($function_like_storage) { + $function_like_storage->has_yield = true; + } + } elseif ($node instanceof PhpParser\Node\Expr\Cast\Object_) { + $this->codebase->scanner->queueClassLikeForScanning('stdClass', false, false); + $this->file_storage->referenced_classlikes['stdclass'] = 'stdClass'; + } + + return null; + } + + /** + * @return null + */ + public function leaveNode(PhpParser\Node $node) + { + if ($node instanceof PhpParser\Node\Stmt\Namespace_) { + if (!$this->file_storage->aliases) { + throw new \UnexpectedValueException('File storage liases should not be null'); + } + + $this->aliases = $this->file_storage->aliases; + + if ($this->codebase->register_stub_files + && $node->name + && $node->name->parts === ['PHPSTORM_META'] + ) { + foreach ($node->stmts as $meta_stmt) { + if ($meta_stmt instanceof PhpParser\Node\Stmt\Expression + && $meta_stmt->expr instanceof PhpParser\Node\Expr\FuncCall + && $meta_stmt->expr->name instanceof PhpParser\Node\Name + && $meta_stmt->expr->name->parts === ['override'] + && count($meta_stmt->expr->args) > 1 + ) { + PhpStormMetaScanner::handleOverride($meta_stmt->expr->args, $this->codebase); + } + } + } + } elseif ($node instanceof PhpParser\Node\Stmt\ClassLike) { + if ($this->skip_if_descendants) { + return null; + } + + if (isset($this->bad_classes[\spl_object_id($node)])) { + return null; + } + + if (!$this->fq_classlike_names) { + throw new \LogicException('$this->fq_classlike_names should not be empty'); + } + + $fq_classlike_name = array_pop($this->fq_classlike_names); + + if (!$this->classlike_storages) { + throw new \UnexpectedValueException('$this->classlike_storages cannot be empty'); + } + + $classlike_storage = array_pop($this->classlike_storages); + + if (PropertyMap::inPropertyMap($fq_classlike_name)) { + $mapped_properties = PropertyMap::getPropertyMap()[strtolower($fq_classlike_name)]; + + foreach ($mapped_properties as $property_name => $public_mapped_property) { + $property_type = Type::parseString($public_mapped_property); + + $property_type->queueClassLikesForScanning($this->codebase, $this->file_storage); + + if (!isset($classlike_storage->properties[$property_name])) { + $classlike_storage->properties[$property_name] = new PropertyStorage(); + } + + $classlike_storage->properties[$property_name]->type = $property_type; + + $property_id = $fq_classlike_name . '::$' . $property_name; + + $classlike_storage->declaring_property_ids[$property_name] = $fq_classlike_name; + $classlike_storage->appearing_property_ids[$property_name] = $property_id; + } + } + + $converted_aliases = \array_map( + function (TypeAlias\InlineTypeAlias $t): ?TypeAlias\ClassTypeAlias { + try { + $union = TypeParser::parseTokens( + $t->replacement_tokens, + null, + [], + $this->type_aliases + ); + + $union->setFromDocblock(); + + return new TypeAlias\ClassTypeAlias( + \array_values($union->getAtomicTypes()) + ); + } catch (\Exception $e) { + return null; + } + }, + $this->classlike_type_aliases + ); + + foreach ($converted_aliases as $key => $type) { + if (!$type) { + $classlike_storage->docblock_issues[] = new InvalidDocblock( + '@psalm-type ' . $key . ' contains invalid references', + new CodeLocation($this->file_scanner, $node, null, true) + ); + } + } + + $classlike_storage->type_aliases = \array_filter($converted_aliases); + + $this->classlike_type_aliases = []; + + if ($classlike_storage->has_visitor_issues) { + $this->file_storage->has_visitor_issues = true; + } + + if ($node->name) { + $this->class_template_types = []; + } + + if ($this->after_classlike_check_plugins) { + $file_manipulations = []; + + foreach ($this->after_classlike_check_plugins as $plugin_fq_class_name) { + $plugin_fq_class_name::afterClassLikeVisit( + $node, + $classlike_storage, + $this, + $this->codebase, + $file_manipulations + ); + } + } + + if (!$this->file_storage->has_visitor_issues) { + $this->codebase->cacheClassLikeStorage($classlike_storage, $this->file_path); + } + } elseif ($node instanceof PhpParser\Node\FunctionLike) { + if ($node instanceof PhpParser\Node\Stmt\Function_ + || $node instanceof PhpParser\Node\Stmt\ClassMethod + ) { + $this->function_template_types = []; + } + + if ($this->skip_if_descendants) { + return null; + } + + if (!$this->functionlike_storages) { + if ($this->file_storage->has_visitor_issues) { + return null; + } + + throw new \UnexpectedValueException( + 'There should be function storages for line ' . $this->file_path . ':' . $node->getLine() + ); + } + + $functionlike_storage = array_pop($this->functionlike_storages); + + if ($functionlike_storage->docblock_issues + && (strpos($this->file_path, 'CoreGenericFunctions.phpstub') + || strpos($this->file_path, 'CoreGenericClasses.phpstub')) + ) { + throw new \UnexpectedValueException('Error with core stub file docblocks'); + } + + if ($functionlike_storage->has_visitor_issues) { + $this->file_storage->has_visitor_issues = true; + } + } elseif ($node instanceof PhpParser\Node\Stmt\If_ && $node->getLine() === $this->skip_if_descendants) { + $this->exists_cond_expr = null; + $this->skip_if_descendants = null; + } elseif ($node instanceof PhpParser\Node\Stmt\Else_ && $node->getLine() === $this->skip_if_descendants) { + $this->exists_cond_expr = null; + $this->skip_if_descendants = null; + } + + return null; + } + + private function enterConditional(PhpParser\Node\Expr $expr) : ?bool + { + if ($expr instanceof PhpParser\Node\Expr\BooleanNot) { + $enter_negated = $this->enterConditional($expr->expr); + + return $enter_negated === null ? null : !$enter_negated; + } + + if ($expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd) { + $enter_conditional_left = $this->enterConditional($expr->left); + $enter_conditional_right = $this->enterConditional($expr->right); + + return $enter_conditional_left !== false && $enter_conditional_right !== false; + } + + if ($expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) { + $enter_conditional_left = $this->enterConditional($expr->left); + $enter_conditional_right = $this->enterConditional($expr->right); + + return $enter_conditional_left !== false || $enter_conditional_right !== false; + } + + if (!$expr instanceof PhpParser\Node\Expr\FuncCall) { + return null; + } + + return $this->functionEvaluatesToTrue($expr); + } + + private function functionEvaluatesToTrue(PhpParser\Node\Expr\FuncCall $function) : ?bool + { + if (!$function->name instanceof PhpParser\Node\Name) { + return null; + } + + if ($function->name->parts === ['function_exists'] + && isset($function->args[0]) + && $function->args[0]->value instanceof PhpParser\Node\Scalar\String_ + && function_exists($function->args[0]->value->value) + ) { + $reflection_function = new \ReflectionFunction($function->args[0]->value->value); + + if ($reflection_function->isInternal()) { + return true; + } + + return false; + } + + if ($function->name->parts === ['class_exists'] + && isset($function->args[0]) + ) { + $string_value = null; + + if ($function->args[0]->value instanceof PhpParser\Node\Scalar\String_) { + $string_value = $function->args[0]->value->value; + } elseif ($function->args[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch + && $function->args[0]->value->class instanceof PhpParser\Node\Name + && $function->args[0]->value->name instanceof PhpParser\Node\Identifier + && strtolower($function->args[0]->value->name->name) === 'class' + ) { + $string_value = (string) $function->args[0]->value->class->getAttribute('resolvedName'); + } + + if ($string_value && class_exists($string_value)) { + $reflection_class = new \ReflectionClass($string_value); + + if ($reflection_class->getFileName() !== $this->file_path) { + $this->codebase->scanner->queueClassLikeForScanning( + $string_value + ); + + return true; + } + } + + return false; + } + + if ($function->name->parts === ['interface_exists'] + && isset($function->args[0]) + ) { + $string_value = null; + + if ($function->args[0]->value instanceof PhpParser\Node\Scalar\String_) { + $string_value = $function->args[0]->value->value; + } elseif ($function->args[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch + && $function->args[0]->value->class instanceof PhpParser\Node\Name + && $function->args[0]->value->name instanceof PhpParser\Node\Identifier + && strtolower($function->args[0]->value->name->name) === 'class' + ) { + $string_value = (string) $function->args[0]->value->class->getAttribute('resolvedName'); + } + + if ($string_value && interface_exists($string_value)) { + $reflection_class = new \ReflectionClass($string_value); + + if ($reflection_class->getFileName() !== $this->file_path) { + $this->codebase->scanner->queueClassLikeForScanning( + $string_value + ); + + return true; + } + } + + return false; + } + + return null; + } + + private function registerClassMapFunctionCall( + string $function_id, + PhpParser\Node\Expr\FuncCall $node + ): void { + $callables = InternalCallMapHandler::getCallablesFromCallMap($function_id); + + if ($callables) { + foreach ($callables as $callable) { + assert($callable->params !== null); + + foreach ($callable->params as $function_param) { + if ($function_param->type) { + $function_param->type->queueClassLikesForScanning( + $this->codebase, + $this->file_storage + ); + } + } + + if ($callable->return_type && !$callable->return_type->hasMixed()) { + $callable->return_type->queueClassLikesForScanning($this->codebase, $this->file_storage); + } + } + } + + if ($function_id === 'define') { + $first_arg_value = isset($node->args[0]) ? $node->args[0]->value : null; + $second_arg_value = isset($node->args[1]) ? $node->args[1]->value : null; + if ($first_arg_value && $second_arg_value) { + $type_provider = new \Psalm\Internal\Provider\NodeDataProvider(); + $const_name = ConstFetchAnalyzer::getConstName( + $first_arg_value, + $type_provider, + $this->codebase, + $this->aliases + ); + + if ($const_name !== null) { + $const_type = SimpleTypeInferer::infer( + $this->codebase, + $type_provider, + $second_arg_value, + $this->aliases + ) ?: Type::getMixed(); + + if ($this->functionlike_storages && !$this->config->hoist_constants) { + $functionlike_storage = + $this->functionlike_storages[count($this->functionlike_storages) - 1]; + $functionlike_storage->defined_constants[$const_name] = $const_type; + } else { + $this->file_storage->constants[$const_name] = $const_type; + $this->file_storage->declaring_constants[$const_name] = $this->file_path; + } + + if (($this->codebase->register_stub_files || $this->codebase->register_autoload_files) + && !\defined($const_name) + ) { + $this->codebase->addGlobalConstantType($const_name, $const_type); + } + } + } + } + + $mapping_function_ids = []; + + if (($function_id === 'array_map' && isset($node->args[0])) + || ($function_id === 'array_filter' && isset($node->args[1])) + ) { + $node_arg_value = $function_id === 'array_map' ? $node->args[0]->value : $node->args[1]->value; + + if ($node_arg_value instanceof PhpParser\Node\Scalar\String_ + || $node_arg_value instanceof PhpParser\Node\Expr\Array_ + || $node_arg_value instanceof PhpParser\Node\Expr\BinaryOp\Concat + ) { + $mapping_function_ids = CallAnalyzer::getFunctionIdsFromCallableArg( + $this->file_scanner, + $node_arg_value + ); + } + + foreach ($mapping_function_ids as $potential_method_id) { + if (strpos($potential_method_id, '::') === false) { + continue; + } + + [$callable_fqcln] = explode('::', $potential_method_id); + + if (!in_array(strtolower($callable_fqcln), ['self', 'parent', 'static'], true)) { + $this->codebase->scanner->queueClassLikeForScanning( + $callable_fqcln + ); + } + } + } + + if ($function_id === 'func_get_arg' + || $function_id === 'func_get_args' + || $function_id === 'func_num_args' + ) { + $function_like_storage = end($this->functionlike_storages); + + if ($function_like_storage) { + $function_like_storage->variadic = true; + } + } + + if ($function_id === 'is_a' || $function_id === 'is_subclass_of') { + $second_arg = $node->args[1]->value ?? null; + + if ($second_arg instanceof PhpParser\Node\Scalar\String_) { + $this->codebase->scanner->queueClassLikeForScanning( + $second_arg->value + ); + } + } + + if ($function_id === 'class_alias' && !$this->skip_if_descendants) { + $first_arg = $node->args[0]->value ?? null; + $second_arg = $node->args[1]->value ?? null; + + if ($first_arg instanceof PhpParser\Node\Scalar\String_) { + $first_arg_value = $first_arg->value; + } elseif ($first_arg instanceof PhpParser\Node\Expr\ClassConstFetch + && $first_arg->class instanceof PhpParser\Node\Name + && $first_arg->name instanceof PhpParser\Node\Identifier + && strtolower($first_arg->name->name) === 'class' + ) { + /** @var string */ + $first_arg_value = $first_arg->class->getAttribute('resolvedName'); + } else { + $first_arg_value = null; + } + + if ($second_arg instanceof PhpParser\Node\Scalar\String_) { + $second_arg_value = $second_arg->value; + } elseif ($second_arg instanceof PhpParser\Node\Expr\ClassConstFetch + && $second_arg->class instanceof PhpParser\Node\Name + && $second_arg->name instanceof PhpParser\Node\Identifier + && strtolower($second_arg->name->name) === 'class' + ) { + /** @var string */ + $second_arg_value = $second_arg->class->getAttribute('resolvedName'); + } else { + $second_arg_value = null; + } + + if ($first_arg_value !== null && $second_arg_value !== null) { + if ($first_arg_value[0] === '\\') { + $first_arg_value = substr($first_arg_value, 1); + } + + if ($second_arg_value[0] === '\\') { + $second_arg_value = substr($second_arg_value, 1); + } + + $second_arg_value = strtolower($second_arg_value); + + $this->codebase->classlikes->addClassAlias( + $first_arg_value, + $second_arg_value + ); + + $this->file_storage->classlike_aliases[$second_arg_value] = $first_arg_value; + } + } + } + + /** + * @return false|null + */ + private function registerClassLike(PhpParser\Node\Stmt\ClassLike $node): ?bool + { + $class_location = new CodeLocation($this->file_scanner, $node); + $name_location = null; + + $storage = null; + + $class_name = null; + + $is_classlike_overridden = false; + + if ($node->name === null) { + if (!$node instanceof PhpParser\Node\Stmt\Class_) { + throw new \LogicException('Anonymous classes are always classes'); + } + + $fq_classlike_name = ClassAnalyzer::getAnonymousClassName($node, $this->file_path); + } else { + $name_location = new CodeLocation($this->file_scanner, $node->name); + + $fq_classlike_name = + ($this->aliases->namespace ? $this->aliases->namespace . '\\' : '') . $node->name->name; + + $fq_classlike_name_lc = strtolower($fq_classlike_name); + + $class_name = $node->name->name; + + if ($this->codebase->classlike_storage_provider->has($fq_classlike_name_lc)) { + $duplicate_storage = $this->codebase->classlike_storage_provider->get($fq_classlike_name_lc); + + if (!$this->codebase->register_stub_files) { + if (!$duplicate_storage->stmt_location + || $duplicate_storage->stmt_location->file_path !== $this->file_path + || $class_location->getHash() !== $duplicate_storage->stmt_location->getHash() + ) { + if (IssueBuffer::accepts( + new DuplicateClass( + 'Class ' . $fq_classlike_name . ' has already been defined' + . ($duplicate_storage->location + ? ' in ' . $duplicate_storage->location->file_path + : ''), + $name_location + ) + )) { + } + + $this->file_storage->has_visitor_issues = true; + + $duplicate_storage->has_visitor_issues = true; + + return false; + } + } elseif (!$duplicate_storage->location + || $duplicate_storage->location->file_path !== $this->file_path + || $class_location->getHash() !== $duplicate_storage->location->getHash() + ) { + $is_classlike_overridden = true; + // we're overwriting some methods + $storage = $duplicate_storage; + $this->codebase->classlike_storage_provider->makeNew(strtolower($fq_classlike_name)); + $storage->populated = false; + $storage->class_implements = []; // we do this because reflection reports + $storage->parent_interfaces = []; + $storage->stubbed = true; + $storage->aliases = $this->aliases; + + foreach ($storage->dependent_classlikes as $dependent_name_lc => $_) { + $dependent_storage = $this->codebase->classlike_storage_provider->get($dependent_name_lc); + $dependent_storage->populated = false; + $this->codebase->classlike_storage_provider->makeNew($dependent_name_lc); + } + } + } + } + + $fq_classlike_name_lc = strtolower($fq_classlike_name); + + $this->file_storage->classlikes_in_file[$fq_classlike_name_lc] = $fq_classlike_name; + + $this->fq_classlike_names[] = $fq_classlike_name; + + if (!$storage) { + $storage = $this->codebase->classlike_storage_provider->create($fq_classlike_name); + } + + if ($class_name + && isset($this->aliases->uses[strtolower($class_name)]) + && $this->aliases->uses[strtolower($class_name)] !== $fq_classlike_name + ) { + IssueBuffer::add( + new \Psalm\Issue\ParseError( + 'Class name ' . $class_name . ' clashes with a use statement alias', + $name_location ?: $class_location + ) + ); + + $storage->has_visitor_issues = true; + $this->file_storage->has_visitor_issues = true; + } + + $storage->stmt_location = $class_location; + $storage->location = $name_location; + if ($this->namespace_name) { + $storage->namespace_name_location = new CodeLocation($this->file_scanner, $this->namespace_name); + } + $storage->user_defined = !$this->codebase->register_stub_files; + $storage->stubbed = $this->codebase->register_stub_files; + $storage->aliases = $this->aliases; + + + $this->classlike_storages[] = $storage; + + if ($node instanceof PhpParser\Node\Stmt\Class_) { + $storage->abstract = $node->isAbstract(); + $storage->final = $node->isFinal(); + + $this->codebase->classlikes->addFullyQualifiedClassName($fq_classlike_name, $this->file_path); + + if ($node->extends) { + $parent_fqcln = ClassLikeAnalyzer::getFQCLNFromNameObject($node->extends, $this->aliases); + $parent_fqcln = $this->codebase->classlikes->getUnAliasedName($parent_fqcln); + $this->codebase->scanner->queueClassLikeForScanning( + $parent_fqcln, + $this->scan_deep + ); + $parent_fqcln_lc = strtolower($parent_fqcln); + $storage->parent_class = $parent_fqcln; + $storage->parent_classes[$parent_fqcln_lc] = $parent_fqcln; + $this->file_storage->required_classes[strtolower($parent_fqcln)] = $parent_fqcln; + } + + foreach ($node->implements as $interface) { + $interface_fqcln = ClassLikeAnalyzer::getFQCLNFromNameObject($interface, $this->aliases); + $this->codebase->scanner->queueClassLikeForScanning($interface_fqcln); + $storage->class_implements[strtolower($interface_fqcln)] = $interface_fqcln; + $storage->direct_class_interfaces[strtolower($interface_fqcln)] = $interface_fqcln; + $this->file_storage->required_interfaces[strtolower($interface_fqcln)] = $interface_fqcln; + } + } elseif ($node instanceof PhpParser\Node\Stmt\Interface_) { + $storage->is_interface = true; + $this->codebase->classlikes->addFullyQualifiedInterfaceName($fq_classlike_name, $this->file_path); + + foreach ($node->extends as $interface) { + $interface_fqcln = ClassLikeAnalyzer::getFQCLNFromNameObject($interface, $this->aliases); + $interface_fqcln = $this->codebase->classlikes->getUnAliasedName($interface_fqcln); + $this->codebase->scanner->queueClassLikeForScanning($interface_fqcln); + $storage->parent_interfaces[strtolower($interface_fqcln)] = $interface_fqcln; + $storage->direct_interface_parents[strtolower($interface_fqcln)] = $interface_fqcln; + $this->file_storage->required_interfaces[strtolower($interface_fqcln)] = $interface_fqcln; + } + } elseif ($node instanceof PhpParser\Node\Stmt\Trait_) { + $storage->is_trait = true; + $this->file_storage->has_trait = true; + $this->codebase->classlikes->addFullyQualifiedTraitName($fq_classlike_name, $this->file_path); + } + + $doc_comment = $node->getDocComment(); + if ($doc_comment) { + $docblock_info = null; + try { + $docblock_info = CommentAnalyzer::extractClassLikeDocblockInfo( + $node, + $doc_comment, + $this->aliases + ); + } catch (DocblockParseException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage() . ' in docblock for ' . implode('.', $this->fq_classlike_names), + $name_location ?: $class_location + ); + } + + if ($docblock_info) { + if ($docblock_info->stub_override && !$is_classlike_overridden) { + throw new InvalidClasslikeOverrideException( + 'Class/interface/trait ' . $fq_classlike_name . ' is marked as stub override,' + . ' but no original counterpart found' + ); + } + + if ($docblock_info->templates) { + $storage->template_types = []; + + \usort( + $docblock_info->templates, + function (array $l, array $r) : int { + return $l[4] > $r[4] ? 1 : -1; + } + ); + + foreach ($docblock_info->templates as $i => $template_map) { + $template_name = $template_map[0]; + + if ($template_map[1] !== null && $template_map[2] !== null) { + if (trim($template_map[2])) { + try { + $template_type = TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $template_map[2], + $this->aliases, + $storage->template_types, + $this->type_aliases + ), + null, + $storage->template_types, + $this->type_aliases + ); + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage() . ' in docblock for ' + . implode('.', $this->fq_classlike_names), + $name_location ?: $class_location + ); + + continue; + } + + $storage->template_types[$template_name] = [ + $fq_classlike_name => [$template_type], + ]; + } else { + $storage->docblock_issues[] = new InvalidDocblock( + 'Template missing as type', + $name_location ?: $class_location + ); + } + } else { + /** @psalm-suppress PropertyTypeCoercion due to a Psalm bug */ + $storage->template_types[$template_name][$fq_classlike_name] = [Type::getMixed()]; + } + + $storage->template_covariants[$i] = $template_map[3]; + } + + $this->class_template_types = $storage->template_types; + } + + foreach ($docblock_info->template_extends as $extended_class_name) { + $this->extendTemplatedType($storage, $node, $extended_class_name); + } + + foreach ($docblock_info->template_implements as $implemented_class_name) { + $this->implementTemplatedType($storage, $node, $implemented_class_name); + } + + if ($docblock_info->yield) { + try { + $yield_type_tokens = TypeTokenizer::getFullyQualifiedTokens( + $docblock_info->yield, + $this->aliases, + $storage->template_types, + $this->type_aliases + ); + + $yield_type = TypeParser::parseTokens( + $yield_type_tokens, + null, + $storage->template_types ?: [], + $this->type_aliases + ); + $yield_type->setFromDocblock(); + $yield_type->queueClassLikesForScanning( + $this->codebase, + $this->file_storage, + $storage->template_types ?: [] + ); + + $storage->yield = $yield_type; + } catch (TypeParseTreeException $e) { + // do nothing + } + } + + if ($docblock_info->extension_requirement !== null) { + $storage->extension_requirement = (string) TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $docblock_info->extension_requirement, + $this->aliases, + $this->class_template_types, + $this->type_aliases + ), + null, + $this->class_template_types, + $this->type_aliases + ); + } + + foreach ($docblock_info->implementation_requirements as $implementation_requirement) { + $storage->implementation_requirements[] = (string) TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $implementation_requirement, + $this->aliases, + $this->class_template_types, + $this->type_aliases + ), + null, + $this->class_template_types, + $this->type_aliases + ); + } + + $storage->sealed_properties = $docblock_info->sealed_properties; + $storage->sealed_methods = $docblock_info->sealed_methods; + + if ($docblock_info->properties) { + foreach ($docblock_info->properties as $property) { + $pseudo_property_type_tokens = TypeTokenizer::getFullyQualifiedTokens( + $property['type'], + $this->aliases, + $this->class_template_types, + $this->type_aliases + ); + + try { + $pseudo_property_type = TypeParser::parseTokens( + $pseudo_property_type_tokens, + null, + $this->class_template_types, + $this->type_aliases + ); + $pseudo_property_type->setFromDocblock(); + $pseudo_property_type->queueClassLikesForScanning( + $this->codebase, + $this->file_storage, + $storage->template_types ?: [] + ); + + if ($property['tag'] !== 'property-read' && $property['tag'] !== 'psalm-property-read') { + $storage->pseudo_property_set_types[$property['name']] = $pseudo_property_type; + } + + if ($property['tag'] !== 'property-write' && $property['tag'] !== 'psalm-property-write') { + $storage->pseudo_property_get_types[$property['name']] = $pseudo_property_type; + } + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage() . ' in docblock for ' . implode('.', $this->fq_classlike_names), + $name_location ?: $class_location + ); + } + } + + $storage->sealed_properties = true; + } + + foreach ($docblock_info->methods as $method) { + /** @var MethodStorage */ + $pseudo_method_storage = $this->registerFunctionLike($method, true); + + if ($pseudo_method_storage->is_static) { + $storage->pseudo_static_methods[strtolower($method->name->name)] = $pseudo_method_storage; + } else { + $storage->pseudo_methods[strtolower($method->name->name)] = $pseudo_method_storage; + } + + $storage->sealed_methods = true; + } + + foreach ($docblock_info->imported_types as $import_type_entry) { + $imported_type_data = $import_type_entry['parts']; + $location = new DocblockTypeLocation( + $this->file_scanner, + $import_type_entry['start_offset'], + $import_type_entry['end_offset'], + $import_type_entry['line_number'] + ); + // There are two valid forms: + // @psalm-import Thing from Something + // @psalm-import Thing from Something as Alias + // but there could be leftovers after that + if (count($imported_type_data) < 3) { + $storage->docblock_issues[] = new InvalidTypeImport( + 'Invalid import in docblock for ' . implode('.', $this->fq_classlike_names) + . ', expecting " from ",' + . ' got "' . implode(' ', $imported_type_data) . '" instead.', + $location + ); + continue; + } + + if ($imported_type_data[1] === 'from' + && !empty($imported_type_data[0]) + && !empty($imported_type_data[2]) + ) { + $type_alias_name = $as_alias_name = $imported_type_data[0]; + $declaring_classlike_name = $imported_type_data[2]; + } else { + $storage->docblock_issues[] = new InvalidTypeImport( + 'Invalid import in docblock for ' . implode('.', $this->fq_classlike_names) + . ', expecting " from ", got "' + . implode( + ' ', + [$imported_type_data[0], $imported_type_data[1], $imported_type_data[2]] + ) . '" instead.', + $location + ); + continue; + } + + if (count($imported_type_data) >= 4 && $imported_type_data[3] === 'as') { + // long form + if (empty($imported_type_data[4])) { + $storage->docblock_issues[] = new InvalidTypeImport( + 'Invalid import in docblock for ' . implode('.', $this->fq_classlike_names) + . ', expecting "as ", got "' + . $imported_type_data[3] . ' ' . ($imported_type_data[4] ?? '') . '" instead.', + $location + ); + continue; + } + + $as_alias_name = $imported_type_data[4]; + } + + $declaring_fq_classlike_name = Type::getFQCLNFromString( + $declaring_classlike_name, + $this->aliases + ); + + $this->codebase->scanner->queueClassLikeForScanning($declaring_fq_classlike_name); + $this->file_storage->referenced_classlikes[strtolower($declaring_fq_classlike_name)] + = $declaring_fq_classlike_name; + + $this->type_aliases[$as_alias_name] = new TypeAlias\LinkableTypeAlias( + $declaring_fq_classlike_name, + $type_alias_name, + $import_type_entry['line_number'], + $import_type_entry['start_offset'], + $import_type_entry['end_offset'] + ); + } + + $storage->deprecated = $docblock_info->deprecated; + + if ($docblock_info->internal + && !$docblock_info->psalm_internal + && $this->aliases->namespace + ) { + $storage->internal = explode('\\', $this->aliases->namespace)[0]; + } else { + $storage->internal = $docblock_info->psalm_internal ?? ''; + } + + $storage->final = $storage->final || $docblock_info->final; + + $storage->preserve_constructor_signature = $docblock_info->consistent_constructor; + + if ($storage->preserve_constructor_signature) { + $has_constructor = false; + + foreach ($node->stmts as $stmt) { + if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod + && $stmt->name->name === '__construct' + ) { + $has_constructor = true; + break; + } + } + + if (!$has_constructor) { + self::registerEmptyConstructor($storage); + } + } + + foreach ($docblock_info->mixins as $key => $mixin) { + $mixin_type = TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $mixin, + $this->aliases, + $this->class_template_types, + $this->type_aliases, + $fq_classlike_name + ), + null, + $this->class_template_types, + $this->type_aliases + ); + + $mixin_type->queueClassLikesForScanning( + $this->codebase, + $this->file_storage, + $storage->template_types ?: [] + ); + + $mixin_type->setFromDocblock(); + + if ($mixin_type->isSingle()) { + $mixin_type = \array_values($mixin_type->getAtomicTypes())[0]; + + if ($mixin_type instanceof Type\Atomic\TNamedObject) { + $storage->namedMixins[] = $mixin_type; + } + + if ($mixin_type instanceof Type\Atomic\TTemplateParam) { + $storage->templatedMixins[] = $mixin_type; + } + } + + if ($key === 0) { + $storage->mixin_declaring_fqcln = $storage->name; + + // backwards compatibility + if ($mixin_type instanceof Type\Atomic\TNamedObject + || $mixin_type instanceof Type\Atomic\TTemplateParam) { + /** @psalm-suppress DeprecatedProperty **/ + $storage->mixin = $mixin_type; + } + } + } + + $storage->mutation_free = $docblock_info->mutation_free; + $storage->external_mutation_free = $docblock_info->external_mutation_free; + $storage->specialize_instance = $docblock_info->taint_specialize; + + $storage->override_property_visibility = $docblock_info->override_property_visibility; + $storage->override_method_visibility = $docblock_info->override_method_visibility; + + $storage->suppressed_issues = $docblock_info->suppressed_issues; + } + } + + foreach ($node->getComments() as $comment) { + if (!$comment instanceof PhpParser\Comment\Doc) { + continue; + } + + try { + $type_aliases = CommentAnalyzer::getTypeAliasesFromComment( + $comment, + $this->aliases, + $this->type_aliases, + $fq_classlike_name + ); + + foreach ($type_aliases as $type_alias) { + // finds issues, if there are any + TypeParser::parseTokens($type_alias->replacement_tokens); + } + + $this->type_aliases += $type_aliases; + + if ($type_aliases) { + $this->classlike_type_aliases = $type_aliases; + } + } catch (DocblockParseException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($this->file_scanner, $node, null, true) + ); + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + (string)$e->getMessage(), + new CodeLocation($this->file_scanner, $node, null, true) + ); + } + } + + foreach ($node->stmts as $node_stmt) { + if ($node_stmt instanceof PhpParser\Node\Stmt\ClassConst) { + $this->visitClassConstDeclaration($node_stmt, $storage, $fq_classlike_name); + } + } + + foreach ($node->stmts as $node_stmt) { + if ($node_stmt instanceof PhpParser\Node\Stmt\Property) { + $this->visitPropertyDeclaration($node_stmt, $this->config, $storage, $fq_classlike_name); + } + } + + return null; + } + + private function extendTemplatedType( + ClassLikeStorage $storage, + PhpParser\Node\Stmt\ClassLike $node, + string $extended_class_name + ): void { + if (trim($extended_class_name) === '') { + $storage->docblock_issues[] = new InvalidDocblock( + 'Extended class cannot be empty in docblock for ' . implode('.', $this->fq_classlike_names), + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + try { + $extended_union_type = TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $extended_class_name, + $this->aliases, + $this->class_template_types, + $this->type_aliases + ), + null, + $this->class_template_types, + $this->type_aliases + ); + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage() . ' in docblock for ' . implode('.', $this->fq_classlike_names), + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + if (!$extended_union_type->isSingle()) { + $storage->docblock_issues[] = new InvalidDocblock( + '@template-extends cannot be a union type', + new CodeLocation($this->file_scanner, $node, null, true) + ); + } + + $extended_union_type->setFromDocblock(); + + $extended_union_type->queueClassLikesForScanning( + $this->codebase, + $this->file_storage, + $storage->template_types ?: [] + ); + + foreach ($extended_union_type->getAtomicTypes() as $atomic_type) { + if (!$atomic_type instanceof Type\Atomic\TGenericObject) { + $storage->docblock_issues[] = new InvalidDocblock( + '@template-extends has invalid class ' . $atomic_type->getId(), + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + $generic_class_lc = strtolower($atomic_type->value); + + if (!isset($storage->parent_classes[$generic_class_lc]) + && !isset($storage->parent_interfaces[$generic_class_lc]) + ) { + $storage->docblock_issues[] = new InvalidDocblock( + '@template-extends must include the name of an extended class,' + . ' got ' . $atomic_type->getId(), + new CodeLocation($this->file_scanner, $node, null, true) + ); + } + + $extended_type_parameters = []; + + $storage->template_type_extends_count = count($atomic_type->type_params); + + foreach ($atomic_type->type_params as $type_param) { + $extended_type_parameters[] = $type_param; + } + + $storage->template_type_extends[$atomic_type->value] = $extended_type_parameters; + } + } + + private function implementTemplatedType( + ClassLikeStorage $storage, + PhpParser\Node\Stmt\ClassLike $node, + string $implemented_class_name + ): void { + if (trim($implemented_class_name) === '') { + $storage->docblock_issues[] = new InvalidDocblock( + 'Extended class cannot be empty in docblock for ' . implode('.', $this->fq_classlike_names), + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + try { + $implemented_union_type = TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $implemented_class_name, + $this->aliases, + $this->class_template_types, + $this->type_aliases + ), + null, + $this->class_template_types, + $this->type_aliases + ); + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage() . ' in docblock for ' . implode('.', $this->fq_classlike_names), + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + if (!$implemented_union_type->isSingle()) { + $storage->docblock_issues[] = new InvalidDocblock( + '@template-implements cannot be a union type', + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + $implemented_union_type->setFromDocblock(); + + $implemented_union_type->queueClassLikesForScanning( + $this->codebase, + $this->file_storage, + $storage->template_types ?: [] + ); + + foreach ($implemented_union_type->getAtomicTypes() as $atomic_type) { + if (!$atomic_type instanceof Type\Atomic\TGenericObject) { + $storage->docblock_issues[] = new InvalidDocblock( + '@template-implements has invalid class ' . $atomic_type->getId(), + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + $generic_class_lc = strtolower($atomic_type->value); + + if (!isset($storage->class_implements[$generic_class_lc])) { + $storage->docblock_issues[] = new InvalidDocblock( + '@template-implements must include the name of an implemented class,' + . ' got ' . $atomic_type->getId(), + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + $implemented_type_parameters = []; + + $storage->template_type_implements_count[$generic_class_lc] = count($atomic_type->type_params); + + foreach ($atomic_type->type_params as $type_param) { + $implemented_type_parameters[] = $type_param; + } + + $storage->template_type_extends[$atomic_type->value] = $implemented_type_parameters; + } + } + + private function useTemplatedType( + ClassLikeStorage $storage, + PhpParser\Node\Stmt\TraitUse $node, + string $used_class_name + ): void { + if (trim($used_class_name) === '') { + $storage->docblock_issues[] = new InvalidDocblock( + 'Extended class cannot be empty in docblock for ' . implode('.', $this->fq_classlike_names), + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + try { + $used_union_type = TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $used_class_name, + $this->aliases, + $this->class_template_types, + $this->type_aliases + ), + null, + $this->class_template_types, + $this->type_aliases + ); + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage() . ' in docblock for ' . implode('.', $this->fq_classlike_names), + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + if (!$used_union_type->isSingle()) { + $storage->docblock_issues[] = new InvalidDocblock( + '@template-use cannot be a union type', + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + $used_union_type->setFromDocblock(); + + $used_union_type->queueClassLikesForScanning( + $this->codebase, + $this->file_storage, + $storage->template_types ?: [] + ); + + foreach ($used_union_type->getAtomicTypes() as $atomic_type) { + if (!$atomic_type instanceof Type\Atomic\TGenericObject) { + $storage->docblock_issues[] = new InvalidDocblock( + '@template-use has invalid class ' . $atomic_type->getId(), + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + $generic_class_lc = strtolower($atomic_type->value); + + if (!isset($storage->used_traits[$generic_class_lc])) { + $storage->docblock_issues[] = new InvalidDocblock( + '@template-use must include the name of an used class,' + . ' got ' . $atomic_type->getId(), + new CodeLocation($this->file_scanner, $node, null, true) + ); + + return; + } + + $used_type_parameters = []; + + $storage->template_type_uses_count[$generic_class_lc] = count($atomic_type->type_params); + + foreach ($atomic_type->type_params as $type_param) { + $used_type_parameters[] = $type_param; + } + + $storage->template_type_extends[$atomic_type->value] = $used_type_parameters; + } + } + + private static function registerEmptyConstructor(ClassLikeStorage $class_storage) : void + { + $method_name_lc = '__construct'; + + if (isset($class_storage->methods[$method_name_lc])) { + return; + } + + $storage = $class_storage->methods['__construct'] = new MethodStorage(); + + $storage->cased_name = '__construct'; + $storage->defining_fqcln = $class_storage->name; + + $storage->mutation_free = $storage->external_mutation_free = true; + $storage->mutation_free_inferred = true; + + $class_storage->declaring_method_ids['__construct'] = new \Psalm\Internal\MethodIdentifier( + $class_storage->name, + '__construct' + ); + + $class_storage->inheritable_method_ids['__construct'] + = $class_storage->declaring_method_ids['__construct']; + $class_storage->appearing_method_ids['__construct'] + = $class_storage->declaring_method_ids['__construct']; + $class_storage->overridden_method_ids['__construct'] = []; + + $storage->visibility = ClassLikeAnalyzer::VISIBILITY_PUBLIC; + } + + /** + * @param bool $fake_method in the case of @method annotations we do something a little strange + * + * @return FunctionStorage|MethodStorage|false + */ + private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, bool $fake_method = false) + { + $class_storage = null; + $fq_classlike_name = null; + $is_functionlike_override = false; + + $method_name_lc = null; + + if ($fake_method && $stmt instanceof PhpParser\Node\Stmt\ClassMethod) { + $cased_function_id = '@method ' . $stmt->name->name; + + $storage = new MethodStorage(); + $storage->defining_fqcln = ''; + $storage->is_static = $stmt->isStatic(); + $class_storage = $this->classlike_storages[count($this->classlike_storages) - 1]; + $storage->final = $class_storage->final; + } elseif ($stmt instanceof PhpParser\Node\Stmt\Function_) { + $cased_function_id = + ($this->aliases->namespace ? $this->aliases->namespace . '\\' : '') . $stmt->name->name; + $function_id = strtolower($cased_function_id); + + $storage = new FunctionStorage(); + + if ($this->codebase->register_stub_files || $this->codebase->register_autoload_files) { + if (isset($this->file_storage->functions[$function_id]) + && ($this->codebase->register_stub_files + || !$this->codebase->functions->hasStubbedFunction($function_id)) + ) { + $this->codebase->functions->addGlobalFunction( + $function_id, + $this->file_storage->functions[$function_id] + ); + + $storage = $this->file_storage->functions[$function_id]; + $this->functionlike_storages[] = $storage; + + return $storage; + } + } else { + if (isset($this->file_storage->functions[$function_id])) { + $duplicate_function_storage = $this->file_storage->functions[$function_id]; + + if ($duplicate_function_storage->location + && $duplicate_function_storage->location->getLineNumber() === $stmt->getLine() + ) { + $storage = $this->file_storage->functions[$function_id]; + $this->functionlike_storages[] = $storage; + + return $storage; + } + + if (IssueBuffer::accepts( + new DuplicateFunction( + 'Method ' . $function_id . ' has already been defined' + . ($duplicate_function_storage->location + ? ' in ' . $duplicate_function_storage->location->file_path + : ''), + new CodeLocation($this->file_scanner, $stmt, null, true) + ) + )) { + // fall through + } + + $this->file_storage->has_visitor_issues = true; + + $duplicate_function_storage->has_visitor_issues = true; + + $storage = $this->file_storage->functions[$function_id]; + $this->functionlike_storages[] = $storage; + + return $storage; + } + + if (isset($this->config->getPredefinedFunctions()[$function_id])) { + /** @psalm-suppress ArgumentTypeCoercion */ + $reflection_function = new \ReflectionFunction($function_id); + + if ($reflection_function->getFileName() !== $this->file_path) { + if (IssueBuffer::accepts( + new DuplicateFunction( + 'Method ' . $function_id . ' has already been defined as a core function', + new CodeLocation($this->file_scanner, $stmt, null, true) + ) + )) { + // fall through + } + } + } + } + + if ($this->codebase->register_stub_files + || ($this->codebase->register_autoload_files + && !$this->codebase->functions->hasStubbedFunction($function_id)) + ) { + $this->codebase->functions->addGlobalFunction($function_id, $storage); + } + + $this->file_storage->functions[$function_id] = $storage; + $this->file_storage->declaring_function_ids[$function_id] = strtolower($this->file_path); + } elseif ($stmt instanceof PhpParser\Node\Stmt\ClassMethod) { + if (!$this->fq_classlike_names) { + throw new \LogicException('$this->fq_classlike_names should not be null'); + } + + $fq_classlike_name = $this->fq_classlike_names[count($this->fq_classlike_names) - 1]; + + $method_name_lc = strtolower($stmt->name->name); + + $function_id = $fq_classlike_name . '::' . $method_name_lc; + $cased_function_id = $fq_classlike_name . '::' . $stmt->name->name; + + if (!$this->classlike_storages) { + throw new \UnexpectedValueException('$class_storages cannot be empty for ' . $function_id); + } + + $class_storage = $this->classlike_storages[count($this->classlike_storages) - 1]; + + $storage = null; + + if (isset($class_storage->methods[$method_name_lc])) { + if (!$this->codebase->register_stub_files) { + $duplicate_method_storage = $class_storage->methods[$method_name_lc]; + + if (IssueBuffer::accepts( + new DuplicateMethod( + 'Method ' . $function_id . ' has already been defined' + . ($duplicate_method_storage->location + ? ' in ' . $duplicate_method_storage->location->file_path + : ''), + new CodeLocation($this->file_scanner, $stmt, null, true) + ) + )) { + // fall through + } + + $this->file_storage->has_visitor_issues = true; + + $duplicate_method_storage->has_visitor_issues = true; + + return false; + } else { + $is_functionlike_override = true; + } + + $storage = $class_storage->methods[$method_name_lc]; + } + + if (!$storage) { + $storage = $class_storage->methods[$method_name_lc] = new MethodStorage(); + } + + $storage->defining_fqcln = $fq_classlike_name; + + $class_name_parts = explode('\\', $fq_classlike_name); + $class_name = array_pop($class_name_parts); + + if ($method_name_lc === strtolower($class_name) + && !isset($class_storage->methods['__construct']) + && strpos($fq_classlike_name, '\\') === false + && $this->codebase->php_major_version < 8 + ) { + $this->codebase->methods->setDeclaringMethodId( + $fq_classlike_name, + '__construct', + $fq_classlike_name, + $method_name_lc + ); + + $this->codebase->methods->setAppearingMethodId( + $fq_classlike_name, + '__construct', + $fq_classlike_name, + $method_name_lc + ); + } + + $method_id = new \Psalm\Internal\MethodIdentifier( + $fq_classlike_name, + $method_name_lc + ); + + $class_storage->declaring_method_ids[$method_name_lc] + = $class_storage->appearing_method_ids[$method_name_lc] + = $method_id; + + if (!$stmt->isPrivate() || $method_name_lc === '__construct' || $class_storage->is_trait) { + $class_storage->inheritable_method_ids[$method_name_lc] = $method_id; + } + + if (!isset($class_storage->overridden_method_ids[$method_name_lc])) { + $class_storage->overridden_method_ids[$method_name_lc] = []; + } + + $storage->is_static = $stmt->isStatic(); + $storage->abstract = $stmt->isAbstract(); + + $storage->final = $class_storage->final || $stmt->isFinal(); + + if ($storage->final && $method_name_lc === '__construct') { + // a bit of a hack, but makes sure that `new static` works for these classes + $class_storage->preserve_constructor_signature = true; + } + + if ($stmt->isPrivate()) { + $storage->visibility = ClassLikeAnalyzer::VISIBILITY_PRIVATE; + } elseif ($stmt->isProtected()) { + $storage->visibility = ClassLikeAnalyzer::VISIBILITY_PROTECTED; + } else { + $storage->visibility = ClassLikeAnalyzer::VISIBILITY_PUBLIC; + } + } elseif ($stmt instanceof PhpParser\Node\Expr\Closure + || $stmt instanceof PhpParser\Node\Expr\ArrowFunction + ) { + $function_id = $cased_function_id = strtolower($this->file_path) + . ':' . $stmt->getLine() + . ':' . (int) $stmt->getAttribute('startFilePos') . ':-:closure'; + + $storage = $this->file_storage->functions[$function_id] = new FunctionStorage(); + + if ($stmt instanceof PhpParser\Node\Expr\Closure) { + foreach ($stmt->uses as $closure_use) { + if ($closure_use->byRef && \is_string($closure_use->var->name)) { + $storage->byref_uses[$closure_use->var->name] = true; + } + } + } + } else { + throw new \UnexpectedValueException('Unrecognized functionlike'); + } + + $this->functionlike_storages[] = $storage; + + if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod) { + $storage->cased_name = $stmt->name->name; + } elseif ($stmt instanceof PhpParser\Node\Stmt\Function_) { + $storage->cased_name = + ($this->aliases->namespace ? $this->aliases->namespace . '\\' : '') . $stmt->name->name; + } + + if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod || $stmt instanceof PhpParser\Node\Stmt\Function_) { + $storage->location = new CodeLocation($this->file_scanner, $stmt->name, null, true); + } else { + $storage->location = new CodeLocation($this->file_scanner, $stmt, null, true); + } + + $storage->stmt_location = new CodeLocation($this->file_scanner, $stmt); + + $required_param_count = 0; + $i = 0; + $has_optional_param = false; + + $existing_params = []; + $storage->params = []; + + foreach ($stmt->getParams() as $param) { + if ($param->var instanceof PhpParser\Node\Expr\Error) { + $storage->docblock_issues[] = new InvalidDocblock( + 'Param' . ($i + 1) . ' of ' . $cased_function_id . ' has invalid syntax', + new CodeLocation($this->file_scanner, $param, null, true) + ); + + ++$i; + + continue; + } + + $param_storage = $this->getTranslatedFunctionParam($param, $stmt, $fake_method, $fq_classlike_name); + + if ($param_storage->name === 'haystack' + && (strpos($this->file_path, 'CoreGenericFunctions.phpstub') + || strpos($this->file_path, 'CoreGenericClasses.phpstub')) + ) { + $param_storage->expect_variable = true; + } + + if (isset($existing_params['$' . $param_storage->name])) { + $storage->docblock_issues[] = new DuplicateParam( + 'Duplicate param $' . $param_storage->name . ' in docblock for ' . $cased_function_id, + new CodeLocation($this->file_scanner, $param, null, true) + ); + + ++$i; + + continue; + } + + $existing_params['$' . $param_storage->name] = $i; + $storage->param_lookup[$param_storage->name] = !!$param->type; + $storage->params[] = $param_storage; + + if (!$param_storage->is_optional && !$param_storage->is_variadic) { + $required_param_count = $i + 1; + + if (!$param->variadic + && $has_optional_param + ) { + foreach ($storage->params as $param) { + $param->is_optional = false; + } + } + } else { + $has_optional_param = true; + } + + ++$i; + } + + $storage->required_param_count = $required_param_count; + + if ($stmt instanceof PhpParser\Node\Stmt\Function_ + || $stmt instanceof PhpParser\Node\Stmt\ClassMethod + ) { + if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod + && $storage instanceof MethodStorage + && $class_storage + && !$class_storage->mutation_free + && $stmt->stmts + && count($stmt->stmts) === 1 + && !count($stmt->params) + && $stmt->stmts[0] instanceof PhpParser\Node\Stmt\Return_ + && $stmt->stmts[0]->expr instanceof PhpParser\Node\Expr\PropertyFetch + && $stmt->stmts[0]->expr->var instanceof PhpParser\Node\Expr\Variable + && $stmt->stmts[0]->expr->var->name === 'this' + && $stmt->stmts[0]->expr->name instanceof PhpParser\Node\Identifier + ) { + $property_name = $stmt->stmts[0]->expr->name->name; + + if (isset($class_storage->properties[$property_name]) + && $class_storage->properties[$property_name]->type + ) { + $storage->mutation_free = true; + $storage->external_mutation_free = true; + $storage->mutation_free_inferred = !$stmt->isFinal() && !$class_storage->final; + + $class_storage->properties[$property_name]->getter_method = strtolower($stmt->name->name); + } + } elseif (strpos($stmt->name->name, 'assert') === 0 + && $stmt->stmts + ) { + $var_assertions = []; + + foreach ($stmt->stmts as $function_stmt) { + if ($function_stmt instanceof PhpParser\Node\Stmt\If_) { + $final_actions = \Psalm\Internal\Analyzer\ScopeAnalyzer::getControlActions( + $function_stmt->stmts, + null, + $this->config->exit_functions, + [], + false + ); + + if ($final_actions !== [\Psalm\Internal\Analyzer\ScopeAnalyzer::ACTION_END]) { + $var_assertions = []; + break; + } + + $cond_id = \spl_object_id($function_stmt->cond); + + $if_clauses = \Psalm\Type\Algebra::getFormula( + $cond_id, + $cond_id, + $function_stmt->cond, + $this->fq_classlike_names + ? $this->fq_classlike_names[count($this->fq_classlike_names) - 1] + : null, + $this->file_scanner, + null + ); + + $negated_formula = \Psalm\Type\Algebra::negateFormula($if_clauses); + + $rules = \Psalm\Type\Algebra::getTruthsFromFormula($negated_formula); + + if (!$rules) { + $var_assertions = []; + break; + } + + foreach ($rules as $var_id => $rule) { + foreach ($rule as $rule_part) { + if (count($rule_part) > 1) { + continue 2; + } + } + + if (isset($existing_params[$var_id])) { + $param_offset = $existing_params[$var_id]; + + $var_assertions[] = new \Psalm\Storage\Assertion( + $param_offset, + $rule + ); + } elseif (strpos($var_id, '$this->') === 0) { + $var_assertions[] = new \Psalm\Storage\Assertion( + $var_id, + $rule + ); + } + } + } else { + $var_assertions = []; + break; + } + } + + $storage->assertions = $var_assertions; + } + } + + if (!$this->scan_deep + && ($stmt instanceof PhpParser\Node\Stmt\Function_ + || $stmt instanceof PhpParser\Node\Stmt\ClassMethod + || $stmt instanceof PhpParser\Node\Expr\Closure) + && $stmt->stmts + ) { + // pick up func_get_args that would otherwise be missed + foreach ($stmt->stmts as $function_stmt) { + if ($function_stmt instanceof PhpParser\Node\Stmt\Expression + && $function_stmt->expr instanceof PhpParser\Node\Expr\Assign + && $function_stmt->expr->expr instanceof PhpParser\Node\Expr\FuncCall + && $function_stmt->expr->expr->name instanceof PhpParser\Node\Name + ) { + $function_id = implode('\\', $function_stmt->expr->expr->name->parts); + + if ($function_id === 'func_get_arg' + || $function_id === 'func_get_args' + || $function_id === 'func_num_args' + ) { + $storage->variadic = true; + } + } elseif ($function_stmt instanceof PhpParser\Node\Stmt\If_ + && $function_stmt->cond instanceof PhpParser\Node\Expr\BinaryOp + && $function_stmt->cond->left instanceof PhpParser\Node\Expr\BinaryOp\Equal + && $function_stmt->cond->left->left instanceof PhpParser\Node\Expr\FuncCall + && $function_stmt->cond->left->left->name instanceof PhpParser\Node\Name + ) { + $function_id = implode('\\', $function_stmt->cond->left->left->name->parts); + + if ($function_id === 'func_get_arg' + || $function_id === 'func_get_args' + || $function_id === 'func_num_args' + ) { + $storage->variadic = true; + } + } + } + } + + $parser_return_type = $stmt->getReturnType(); + + if ($parser_return_type) { + $original_type = $parser_return_type; + + $storage->return_type = $this->getTypeFromTypeHint($parser_return_type); + + $storage->return_type_location = new CodeLocation( + $this->file_scanner, + $original_type + ); + + if ($stmt->returnsByRef()) { + $storage->return_type->by_ref = true; + } + + $storage->signature_return_type = $storage->return_type; + $storage->signature_return_type_location = $storage->return_type_location; + } + + if ($stmt->returnsByRef()) { + $storage->returns_by_ref = true; + } + + $doc_comment = $stmt->getDocComment(); + + + if ($class_storage + && !$class_storage->is_trait + && strlen($class_storage->internal) > strlen($storage->internal) + ) { + $storage->internal = $class_storage->internal; + } + + if ($doc_comment) { + try { + $docblock_info = CommentAnalyzer::extractFunctionDocblockInfo($doc_comment); + } catch (IncorrectDocblockException $e) { + $storage->docblock_issues[] = new MissingDocblockType( + $e->getMessage() . ' in docblock for ' . $cased_function_id, + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + + $docblock_info = null; + } catch (DocblockParseException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage() . ' in docblock for ' . $cased_function_id, + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + + $docblock_info = null; + } + + if ($docblock_info) { + $this->handleFunctionLikeDocblock( + $stmt, + $storage, + $docblock_info, + $class_storage, + $is_functionlike_override, + $fake_method, + $cased_function_id + ); + } + } + + if ($class_storage && $method_name_lc === '__construct') { + foreach ($stmt->getParams() as $param) { + if (!$param->flags || !$param->var instanceof PhpParser\Node\Expr\Variable) { + continue; + } + + $param_storage = null; + + foreach ($storage->params as $param_storage) { + if ($param_storage->name === $param->var->name) { + break; + } + } + + if (!$param_storage) { + continue; + } + + $property_storage = $class_storage->properties[$param_storage->name] = new PropertyStorage(); + $property_storage->is_static = false; + $property_storage->type = $param_storage->type; + $property_storage->signature_type = $param_storage->signature_type; + $property_storage->signature_type_location = $param_storage->signature_type_location; + $property_storage->type_location = $param_storage->type_location; + $property_storage->location = $param_storage->location; + $property_storage->stmt_location = new CodeLocation($this->file_scanner, $param); + $property_storage->has_default = $param->default ? true : false; + + $property_id = $fq_classlike_name . '::$' . $param_storage->name; + + switch ($param->flags) { + case \PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC: + $property_storage->visibility = ClassLikeAnalyzer::VISIBILITY_PUBLIC; + $class_storage->inheritable_property_ids[$param_storage->name] = $property_id; + break; + + case \PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED: + $property_storage->visibility = ClassLikeAnalyzer::VISIBILITY_PROTECTED; + $class_storage->inheritable_property_ids[$param_storage->name] = $property_id; + break; + + case \PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE: + $property_storage->visibility = ClassLikeAnalyzer::VISIBILITY_PRIVATE; + break; + } + + $fq_classlike_name = $this->fq_classlike_names[count($this->fq_classlike_names) - 1]; + + $property_id = $fq_classlike_name . '::$' . $param_storage->name; + + $class_storage->declaring_property_ids[$param_storage->name] = $fq_classlike_name; + $class_storage->appearing_property_ids[$param_storage->name] = $property_id; + $class_storage->initialized_properties[$param_storage->name] = true; + } + } + + if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod + && $stmt->name->name === '__construct' + && $class_storage + && $storage instanceof MethodStorage + && $storage->params + && $this->config->infer_property_types_from_constructor + ) { + $this->inferPropertyTypeFromConstructor($stmt, $storage, $class_storage); + } + + return $storage; + } + + private function handleFunctionLikeDocblock( + PhpParser\Node\FunctionLike $stmt, + FunctionLikeStorage $storage, + \Psalm\Internal\Scanner\FunctionDocblockComment $docblock_info, + ?ClassLikeStorage $class_storage, + bool $is_functionlike_override, + bool $fake_method, + string $cased_function_id + ) : void { + if ($docblock_info->mutation_free) { + $storage->mutation_free = true; + + if ($storage instanceof MethodStorage) { + $storage->external_mutation_free = true; + $storage->mutation_free_inferred = false; + } + } + + if ($storage instanceof MethodStorage && $docblock_info->external_mutation_free) { + $storage->external_mutation_free = true; + } + + if ($docblock_info->deprecated) { + $storage->deprecated = true; + } + + if ($docblock_info->internal + && !$docblock_info->psalm_internal + && $this->aliases->namespace + ) { + $storage->internal = explode('\\', $this->aliases->namespace)[0]; + } elseif (!$class_storage + || ($docblock_info->psalm_internal + && strlen($docblock_info->psalm_internal) > strlen($class_storage->internal) + ) + ) { + $storage->internal = $docblock_info->psalm_internal ?? ''; + } + + if (($storage->internal || ($class_storage && $class_storage->internal)) + && !$this->config->allow_internal_named_arg_calls + ) { + $storage->allow_named_arg_calls = false; + } elseif ($docblock_info->no_named_args) { + $storage->allow_named_arg_calls = false; + } + + if ($docblock_info->variadic) { + $storage->variadic = true; + } + + if ($docblock_info->pure) { + $storage->pure = true; + $storage->specialize_call = true; + $storage->mutation_free = true; + if ($storage instanceof MethodStorage) { + $storage->external_mutation_free = true; + } + } + + if ($docblock_info->specialize_call) { + $storage->specialize_call = true; + } + + if ($docblock_info->ignore_nullable_return && $storage->return_type) { + $storage->return_type->ignore_nullable_issues = true; + } + + if ($docblock_info->ignore_falsable_return && $storage->return_type) { + $storage->return_type->ignore_falsable_issues = true; + } + + if ($docblock_info->stub_override && !$is_functionlike_override) { + throw new InvalidMethodOverrideException( + 'Method ' . $cased_function_id . ' is marked as stub override,' + . ' but no original counterpart found' + ); + } + + $storage->suppressed_issues = $docblock_info->suppressed_issues; + + foreach ($docblock_info->throws as [$throw, $offset, $line]) { + $throw_location = new CodeLocation\DocblockTypeLocation( + $this->file_scanner, + $offset, + $offset + \strlen($throw), + $line + ); + + $class_names = \array_filter(\array_map('trim', explode('|', $throw))); + + foreach ($class_names as $throw_class) { + if ($throw_class !== 'self' && $throw_class !== 'static' && $throw_class !== 'parent') { + $exception_fqcln = Type::getFQCLNFromString( + $throw_class, + $this->aliases + ); + } else { + $exception_fqcln = $throw_class; + } + + $this->codebase->scanner->queueClassLikeForScanning($exception_fqcln); + $this->file_storage->referenced_classlikes[strtolower($exception_fqcln)] = $exception_fqcln; + $storage->throws[$exception_fqcln] = true; + $storage->throw_locations[$exception_fqcln] = $throw_location; + } + } + + if (!$this->config->use_docblock_types) { + return; + } + + if ($storage instanceof MethodStorage && $docblock_info->inheritdoc) { + $storage->inheritdoc = true; + } + + $template_types = $class_storage && $class_storage->template_types ? $class_storage->template_types : null; + + if ($docblock_info->templates) { + $storage->template_types = []; + + foreach ($docblock_info->templates as $i => $template_map) { + $template_name = $template_map[0]; + + if ($template_map[1] !== null && $template_map[2] !== null) { + if (trim($template_map[2])) { + try { + $template_type = TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $template_map[2], + $this->aliases, + $storage->template_types + ($template_types ?: []), + $this->type_aliases + ), + null, + $storage->template_types + ($template_types ?: []), + $this->type_aliases + ); + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + 'Template ' . $template_name . ' has invalid as type - ' . $e->getMessage(), + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + + $template_type = Type::getMixed(); + } + } else { + $storage->docblock_issues[] = new InvalidDocblock( + 'Template ' . $template_name . ' missing as type', + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + + $template_type = Type::getMixed(); + } + } else { + $template_type = Type::getMixed(); + } + + if (isset($template_types[$template_name])) { + $storage->docblock_issues[] = new InvalidDocblock( + 'Duplicate template param ' . $template_name . ' in docblock for ' + . $cased_function_id, + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + } else { + $storage->template_types[$template_name] = [ + 'fn-' . strtolower($cased_function_id) => [$template_type], + ]; + } + + $storage->template_covariants[$i] = $template_map[3]; + } + + $template_types = array_merge($template_types ?: [], $storage->template_types); + + $this->function_template_types = $template_types; + } + + if ($docblock_info->assertions) { + $storage->assertions = []; + + foreach ($docblock_info->assertions as $assertion) { + $assertion_type_parts = $this->getAssertionParts( + $storage, + $assertion['type'], + $stmt, + $class_storage && !$class_storage->is_trait ? $class_storage->name : null + ); + + if (!$assertion_type_parts) { + continue; + } + + foreach ($storage->params as $i => $param) { + if ($param->name === $assertion['param_name']) { + $storage->assertions[] = new \Psalm\Storage\Assertion( + $i, + [$assertion_type_parts] + ); + continue 2; + } + } + + $storage->assertions[] = new \Psalm\Storage\Assertion( + '$' . $assertion['param_name'], + [$assertion_type_parts] + ); + } + } + + if ($docblock_info->if_true_assertions) { + $storage->if_true_assertions = []; + + foreach ($docblock_info->if_true_assertions as $assertion) { + $assertion_type_parts = $this->getAssertionParts( + $storage, + $assertion['type'], + $stmt, + $class_storage && !$class_storage->is_trait ? $class_storage->name : null + ); + + if (!$assertion_type_parts) { + continue; + } + + foreach ($storage->params as $i => $param) { + if ($param->name === $assertion['param_name']) { + $storage->if_true_assertions[] = new \Psalm\Storage\Assertion( + $i, + [$assertion_type_parts] + ); + continue 2; + } + } + + $storage->if_true_assertions[] = new \Psalm\Storage\Assertion( + '$' . $assertion['param_name'], + [$assertion_type_parts] + ); + } + } + + if ($docblock_info->if_false_assertions) { + $storage->if_false_assertions = []; + + foreach ($docblock_info->if_false_assertions as $assertion) { + $assertion_type_parts = $this->getAssertionParts( + $storage, + $assertion['type'], + $stmt, + $class_storage && !$class_storage->is_trait ? $class_storage->name : null + ); + + if (!$assertion_type_parts) { + continue; + } + + foreach ($storage->params as $i => $param) { + if ($param->name === $assertion['param_name']) { + $storage->if_false_assertions[] = new \Psalm\Storage\Assertion( + $i, + [$assertion_type_parts] + ); + continue 2; + } + } + + $storage->if_false_assertions[] = new \Psalm\Storage\Assertion( + '$' . $assertion['param_name'], + [$assertion_type_parts] + ); + } + } + + foreach ($docblock_info->globals as $global) { + try { + $storage->global_types[$global['name']] = TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $global['type'], + $this->aliases, + null, + $this->type_aliases + ), + null + ); + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage() . ' in docblock for ' . $cased_function_id, + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + + continue; + } + } + + if ($docblock_info->params) { + $this->improveParamsFromDocblock( + $storage, + $docblock_info->params, + $stmt, + $fake_method, + $class_storage && !$class_storage->is_trait ? $class_storage->name : null + ); + } + + if ($storage instanceof MethodStorage) { + $storage->has_docblock_param_types = (bool) array_filter( + $storage->params, + function (FunctionLikeParameter $p): bool { + return $p->type !== null && $p->has_docblock_type; + } + ); + } + + $class_template_types = $this->class_template_types; + + foreach ($docblock_info->params_out as $docblock_param_out) { + $param_name = substr($docblock_param_out['name'], 1); + + try { + $out_type = TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $docblock_param_out['type'], + $this->aliases, + $this->function_template_types + $class_template_types, + $this->type_aliases + ), + null, + $this->function_template_types + $class_template_types, + $this->type_aliases + ); + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage() . ' in docblock for ' . $cased_function_id, + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + + + + continue; + } + + $out_type->queueClassLikesForScanning( + $this->codebase, + $this->file_storage, + $storage->template_types ?: [] + ); + + foreach ($storage->params as $param_storage) { + if ($param_storage->name === $param_name) { + $param_storage->out_type = $out_type; + } + } + } + + if ($docblock_info->self_out + && $storage instanceof MethodStorage) { + $out_type = TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $docblock_info->self_out['type'], + $this->aliases, + $this->function_template_types + $class_template_types, + $this->type_aliases + ), + null, + $this->function_template_types + $class_template_types, + $this->type_aliases + ); + $storage->self_out_type = $out_type; + } + + foreach ($docblock_info->taint_sink_params as $taint_sink_param) { + $param_name = substr($taint_sink_param['name'], 1); + + foreach ($storage->params as $param_storage) { + if ($param_storage->name === $param_name) { + $param_storage->sinks[] = $taint_sink_param['taint']; + } + } + } + + foreach ($docblock_info->taint_source_types as $taint_source_type) { + if ($taint_source_type === 'input') { + $storage->taint_source_types = array_merge( + $storage->taint_source_types, + \Psalm\Type\TaintKindGroup::ALL_INPUT + ); + } else { + $storage->taint_source_types[] = $taint_source_type; + } + } + + $storage->added_taints = $docblock_info->added_taints; + $storage->removed_taints = $docblock_info->removed_taints; + + if ($docblock_info->flows) { + foreach ($docblock_info->flows as $flow) { + $path_type = 'arg'; + + $fancy_path_regex = '/-\(([a-z\-]+)\)->/'; + + if (preg_match($fancy_path_regex, $flow, $matches)) { + if (isset($matches[1])) { + $path_type = $matches[1]; + } + + $flow = preg_replace($fancy_path_regex, '->', $flow); + } + + $flow_parts = explode('->', $flow); + + if (isset($flow_parts[1]) && trim($flow_parts[1]) === 'return') { + $source_param_string = trim($flow_parts[0]); + + if ($source_param_string[0] === '(' && substr($source_param_string, -1) === ')') { + $source_params = preg_split('/, ?/', substr($source_param_string, 1, -1)); + + foreach ($source_params as $source_param) { + $source_param = substr($source_param, 1); + + foreach ($storage->params as $i => $param_storage) { + if ($param_storage->name === $source_param) { + $storage->return_source_params[$i] = $path_type; + } + } + } + } + } + } + } + + foreach ($docblock_info->assert_untainted_params as $untainted_assert_param) { + $param_name = substr($untainted_assert_param['name'], 1); + + foreach ($storage->params as $param_storage) { + if ($param_storage->name === $param_name) { + $param_storage->assert_untainted = true; + } + } + } + + if ($docblock_info->template_typeofs) { + foreach ($docblock_info->template_typeofs as $template_typeof) { + foreach ($storage->params as $param) { + if ($param->name === $template_typeof['param_name']) { + $param_type_nullable = $param->type && $param->type->isNullable(); + + $template_type = null; + $template_class = null; + + if (isset($template_types[$template_typeof['template_type']])) { + foreach ($template_types[$template_typeof['template_type']] as $class => $map) { + $template_type = $map[0]; + $template_class = $class; + } + } + + $template_atomic_type = null; + + if ($template_type) { + foreach ($template_type->getAtomicTypes() as $tat) { + if ($tat instanceof Type\Atomic\TNamedObject) { + $template_atomic_type = $tat; + } + } + } + + $param->type = new Type\Union([ + new Type\Atomic\TTemplateParamClass( + $template_typeof['template_type'], + $template_type && !$template_type->isMixed() + ? (string)$template_type + : 'object', + $template_atomic_type, + $template_class ?: 'fn-' . strtolower($cased_function_id) + ), + ]); + + if ($param_type_nullable) { + $param->type->addType(new Type\Atomic\TNull); + } + + break; + } + } + } + } + + if ($docblock_info->return_type) { + $docblock_return_type = $docblock_info->return_type; + + if (!$fake_method + && $docblock_info->return_type_line_number + && $docblock_info->return_type_start + && $docblock_info->return_type_end + ) { + $storage->return_type_location = new CodeLocation\DocblockTypeLocation( + $this->file_scanner, + $docblock_info->return_type_start, + $docblock_info->return_type_end, + $docblock_info->return_type_line_number + ); + } else { + $storage->return_type_location = new CodeLocation( + $this->file_scanner, + $stmt, + null, + false, + !$fake_method + ? CodeLocation::FUNCTION_PHPDOC_RETURN_TYPE + : CodeLocation::FUNCTION_PHPDOC_METHOD, + $docblock_info->return_type + ); + } + + try { + $fixed_type_tokens = TypeTokenizer::getFullyQualifiedTokens( + $docblock_return_type, + $this->aliases, + $this->function_template_types + $class_template_types, + $this->type_aliases, + $class_storage && !$class_storage->is_trait ? $class_storage->name : null + ); + + $param_type_mapping = []; + + // This checks for param references in the return type tokens + // If found, the param is replaced with a generated template param + foreach ($fixed_type_tokens as $i => $type_token) { + $token_body = $type_token[0]; + $template_function_id = 'fn-' . strtolower($cased_function_id); + + if ($token_body[0] === '$') { + foreach ($storage->params as $j => $param_storage) { + if ('$' . $param_storage->name === $token_body) { + if (!isset($param_type_mapping[$token_body])) { + $template_name = 'TGeneratedFromParam' . $j; + + $template_as_type = $param_storage->type + ? clone $param_storage->type + : Type::getMixed(); + + $storage->template_types[$template_name] = [ + $template_function_id => [ + $template_as_type + ], + ]; + + $this->function_template_types[$template_name] + = $storage->template_types[$template_name]; + + $param_type_mapping[$token_body] = $template_name; + + $param_storage->type = new Type\Union([ + new Type\Atomic\TTemplateParam( + $template_name, + $template_as_type, + $template_function_id + ) + ]); + } + + // spaces are allowed before $foo in get(string $foo) magic method + // definitions, but we want to remove them in this instance + if (isset($fixed_type_tokens[$i - 1]) + && $fixed_type_tokens[$i - 1][0][0] === ' ' + ) { + unset($fixed_type_tokens[$i - 1]); + } + + $fixed_type_tokens[$i][0] = $param_type_mapping[$token_body]; + + continue 2; + } + } + } + + if ($token_body === 'func_num_args()') { + $template_name = 'TFunctionArgCount'; + + $storage->template_types[$template_name] = [ + $template_function_id => [ + Type::getInt() + ], + ]; + + $this->function_template_types[$template_name] + = $storage->template_types[$template_name]; + + $fixed_type_tokens[$i][0] = $template_name; + } + } + + $storage->return_type = TypeParser::parseTokens( + \array_values($fixed_type_tokens), + null, + $this->function_template_types + $class_template_types, + $this->type_aliases + ); + + $storage->return_type->setFromDocblock(); + + if ($storage->signature_return_type) { + $all_typehint_types_match = true; + $signature_return_atomic_types = $storage->signature_return_type->getAtomicTypes(); + + foreach ($storage->return_type->getAtomicTypes() as $key => $type) { + if (isset($signature_return_atomic_types[$key])) { + $type->from_docblock = false; + } else { + $all_typehint_types_match = false; + } + } + + if ($all_typehint_types_match) { + $storage->return_type->from_docblock = false; + } + + if ($storage->signature_return_type->isNullable() + && !$storage->return_type->isNullable() + && !$storage->return_type->hasTemplate() + && !$storage->return_type->hasConditional() + ) { + $storage->return_type->addType(new Type\Atomic\TNull()); + } + } + + $storage->return_type->queueClassLikesForScanning($this->codebase, $this->file_storage); + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage() . ' in docblock for ' . $cased_function_id, + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + } + + if ($storage->return_type && $docblock_info->ignore_nullable_return) { + $storage->return_type->ignore_nullable_issues = true; + } + + if ($storage->return_type && $docblock_info->ignore_falsable_return) { + $storage->return_type->ignore_falsable_issues = true; + } + + if ($stmt->returnsByRef() && $storage->return_type) { + $storage->return_type->by_ref = true; + } + + if ($docblock_info->return_type_line_number && !$fake_method) { + $storage->return_type_location->setCommentLine($docblock_info->return_type_line_number); + } + + $storage->return_type_description = $docblock_info->return_type_description; + } + } + + private function inferPropertyTypeFromConstructor( + PhpParser\Node\Stmt\ClassMethod $stmt, + MethodStorage $storage, + ClassLikeStorage $class_storage + ) : void { + if (!$stmt->stmts) { + return; + } + + $assigned_properties = []; + + foreach ($stmt->stmts as $function_stmt) { + if ($function_stmt instanceof PhpParser\Node\Stmt\Expression + && $function_stmt->expr instanceof PhpParser\Node\Expr\Assign + && $function_stmt->expr->var instanceof PhpParser\Node\Expr\PropertyFetch + && $function_stmt->expr->var->var instanceof PhpParser\Node\Expr\Variable + && $function_stmt->expr->var->var->name === 'this' + && $function_stmt->expr->var->name instanceof PhpParser\Node\Identifier + && ($property_name = $function_stmt->expr->var->name->name) + && isset($class_storage->properties[$property_name]) + && $function_stmt->expr->expr instanceof PhpParser\Node\Expr\Variable + && is_string($function_stmt->expr->expr->name) + && ($param_name = $function_stmt->expr->expr->name) + && isset($storage->param_lookup[$param_name]) + ) { + if ($class_storage->properties[$property_name]->type + || !$storage->param_lookup[$param_name] + ) { + continue; + } + + $param_index = \array_search($param_name, \array_keys($storage->param_lookup), true); + + if ($param_index === false || !isset($storage->params[$param_index]->type)) { + continue; + } + + $param_type = $storage->params[$param_index]->type; + + $assigned_properties[$property_name] = + $storage->params[$param_index]->is_variadic + ? new Type\Union([ + new Type\Atomic\TArray([ + Type::getInt(), + $param_type, + ]), + ]) + : $param_type; + } else { + $assigned_properties = []; + break; + } + } + + if (!$assigned_properties) { + return; + } + + $storage->external_mutation_free = true; + + foreach ($assigned_properties as $property_name => $property_type) { + $class_storage->properties[$property_name]->type = clone $property_type; + } + } + + /** + * @return non-empty-list|null + */ + private function getAssertionParts( + FunctionLikeStorage $storage, + string $assertion_type, + PhpParser\Node\FunctionLike $stmt, + ?string $self_fqcln + ) : ?array { + $prefix = ''; + + if ($assertion_type[0] === '!') { + $prefix = '!'; + $assertion_type = substr($assertion_type, 1); + } + + if ($assertion_type[0] === '~') { + $prefix .= '~'; + $assertion_type = substr($assertion_type, 1); + } + + if ($assertion_type[0] === '=') { + $prefix .= '='; + $assertion_type = substr($assertion_type, 1); + } + + $class_template_types = !$stmt instanceof PhpParser\Node\Stmt\ClassMethod || !$stmt->isStatic() + ? $this->class_template_types + : []; + + try { + $namespaced_type = TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $assertion_type, + $this->aliases, + $this->function_template_types + $class_template_types, + $this->type_aliases, + $self_fqcln, + null, + true + ) + ); + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + 'Invalid @psalm-assert union type ' . $e, + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + + return null; + } + + + if ($prefix && count($namespaced_type->getAtomicTypes()) > 1) { + $storage->docblock_issues[] = new InvalidDocblock( + 'Docblock assertions cannot contain | characters together with ' . $prefix, + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + + return null; + } + + $namespaced_type->queueClassLikesForScanning( + $this->codebase, + $this->file_storage, + $this->function_template_types + $class_template_types + ); + + $assertion_type_parts = []; + + foreach ($namespaced_type->getAtomicTypes() as $namespaced_type_part) { + if ($namespaced_type_part instanceof Type\Atomic\TAssertionFalsy + || ($namespaced_type_part instanceof Type\Atomic\TList + && $namespaced_type_part->type_param->isMixed()) + || ($namespaced_type_part instanceof Type\Atomic\TArray + && $namespaced_type_part->type_params[0]->isArrayKey() + && $namespaced_type_part->type_params[1]->isMixed()) + || ($namespaced_type_part instanceof Type\Atomic\TIterable + && $namespaced_type_part->type_params[0]->isMixed() + && $namespaced_type_part->type_params[1]->isMixed()) + ) { + $assertion_type_parts[] = $prefix . $namespaced_type_part->getAssertionString(); + } else { + $assertion_type_parts[] = $prefix . $namespaced_type_part->getId(); + } + } + + return $assertion_type_parts; + } + + public function getTranslatedFunctionParam( + PhpParser\Node\Param $param, + PhpParser\Node\FunctionLike $stmt, + bool $fake_method, + ?string $fq_classlike_name + ) : FunctionLikeParameter { + $param_type = null; + + $is_nullable = $param->default !== null && + $param->default instanceof PhpParser\Node\Expr\ConstFetch && + strtolower($param->default->name->parts[0]) === 'null'; + + $param_typehint = $param->type; + + if ($param_typehint) { + $param_type = $this->getTypeFromTypeHint($param_typehint); + + if ($is_nullable) { + $param_type->addType(new Type\Atomic\TNull); + } else { + $is_nullable = $param_type->isNullable(); + } + } + + $is_optional = $param->default !== null; + + if ($param->var instanceof PhpParser\Node\Expr\Error || !is_string($param->var->name)) { + throw new \UnexpectedValueException('Not expecting param name to be non-string'); + } + + return new FunctionLikeParameter( + $param->var->name, + $param->byRef, + $param_type, + new CodeLocation( + $this->file_scanner, + $fake_method ? $stmt : $param->var, + null, + false, + !$fake_method + ? CodeLocation::FUNCTION_PARAM_VAR + : CodeLocation::FUNCTION_PHPDOC_METHOD + ), + $param_typehint + ? new CodeLocation( + $this->file_scanner, + $fake_method ? $stmt : $param, + null, + false, + CodeLocation::FUNCTION_PARAM_TYPE + ) + : null, + $is_optional, + $is_nullable, + $param->variadic, + $param->default + ? SimpleTypeInferer::infer( + $this->codebase, + new \Psalm\Internal\Provider\NodeDataProvider(), + $param->default, + $this->aliases, + null, + null, + $fq_classlike_name + ) + : null + ); + } + + /** + * @param PhpParser\Node\Identifier|PhpParser\Node\Name|PhpParser\Node\NullableType|PhpParser\Node\UnionType $hint + */ + private function getTypeFromTypeHint(PhpParser\NodeAbstract $hint) : Type\Union + { + if ($hint instanceof PhpParser\Node\UnionType) { + $type = null; + + if (!$hint->types) { + throw new \UnexpectedValueException('bad'); + } + + foreach ($hint->types as $atomic_typehint) { + $resolved_type = $this->getTypeFromTypeHint($atomic_typehint); + + if (!$type) { + $type = $resolved_type; + } else { + $type = Type::combineUnionTypes($resolved_type, $type); + } + } + + return $type; + } + + $is_nullable = false; + + if ($hint instanceof PhpParser\Node\NullableType) { + $is_nullable = true; + $hint = $hint->type; + } + + if ($hint instanceof PhpParser\Node\Identifier) { + $type_string = $hint->name; + } elseif ($hint instanceof PhpParser\Node\Name\FullyQualified) { + $type_string = (string)$hint; + + $this->codebase->scanner->queueClassLikeForScanning($type_string); + $this->file_storage->referenced_classlikes[strtolower($type_string)] = $type_string; + } else { + $lower_hint = strtolower($hint->parts[0]); + + if ($this->classlike_storages + && ($lower_hint === 'self' || $lower_hint === 'static') + && !end($this->classlike_storages)->is_trait + ) { + $type_string = $this->fq_classlike_names[count($this->fq_classlike_names) - 1]; + + if ($lower_hint === 'static') { + $type_string .= '&static'; + } + } else { + $type_string = ClassLikeAnalyzer::getFQCLNFromNameObject($hint, $this->aliases); + } + + if (!in_array($lower_hint, ['self', 'static', 'parent'], true)) { + $this->codebase->scanner->queueClassLikeForScanning($type_string); + $this->file_storage->referenced_classlikes[strtolower($type_string)] = $type_string; + } + } + + $type = Type::parseString( + $type_string, + [$this->php_major_version, $this->php_minor_version], + [] + ); + + if ($is_nullable) { + $type->addType(new Type\Atomic\TNull); + } + + return $type; + } + + /** + * @param array $docblock_params + * + */ + private function improveParamsFromDocblock( + FunctionLikeStorage $storage, + array $docblock_params, + PhpParser\Node\FunctionLike $function, + bool $fake_method, + ?string $fq_classlike_name + ): void { + $base = $this->fq_classlike_names + ? $this->fq_classlike_names[count($this->fq_classlike_names) - 1] . '::' + : ''; + + $cased_method_id = $base . $storage->cased_name; + + $unused_docblock_params = []; + + $class_template_types = !$function instanceof PhpParser\Node\Stmt\ClassMethod || !$function->isStatic() + ? $this->class_template_types + : []; + + foreach ($docblock_params as $docblock_param) { + $param_name = $docblock_param['name']; + $docblock_param_variadic = false; + + if (substr($param_name, 0, 3) === '...') { + $docblock_param_variadic = true; + $param_name = substr($param_name, 3); + } + + $param_name = substr($param_name, 1); + + $storage_param = null; + + foreach ($storage->params as $function_signature_param) { + if ($function_signature_param->name === $param_name) { + $storage_param = $function_signature_param; + break; + } + } + + if (!$fake_method) { + $docblock_type_location = new CodeLocation\DocblockTypeLocation( + $this->file_scanner, + $docblock_param['start'], + $docblock_param['end'], + $docblock_param['line_number'] + ); + } else { + $docblock_type_location = new CodeLocation( + $this->file_scanner, + $function, + null, + false, + CodeLocation::FUNCTION_PHPDOC_METHOD, + null + ); + } + + if ($storage_param === null) { + $param_location = new CodeLocation( + $this->file_scanner, + $function, + null, + true, + CodeLocation::FUNCTION_PARAM_VAR + ); + + $param_location->setCommentLine($docblock_param['line_number']); + $unused_docblock_params[$param_name] = $param_location; + + if (!$docblock_param_variadic || $storage->params || $this->scan_deep) { + continue; + } + + $storage_param = new FunctionLikeParameter( + $param_name, + false, + null, + null, + null, + false, + false, + true, + null + ); + + $storage->params[] = $storage_param; + } + + try { + $new_param_type = TypeParser::parseTokens( + TypeTokenizer::getFullyQualifiedTokens( + $docblock_param['type'], + $this->aliases, + $this->function_template_types + $class_template_types, + $this->type_aliases, + $fq_classlike_name + ), + null, + $this->function_template_types + $class_template_types, + $this->type_aliases + ); + } catch (TypeParseTreeException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage() . ' in docblock for ' . $cased_method_id, + $docblock_type_location + ); + + continue; + } + + $storage_param->has_docblock_type = true; + $new_param_type->setFromDocblock(); + + $new_param_type->queueClassLikesForScanning( + $this->codebase, + $this->file_storage, + $storage->template_types ?: [] + ); + + if ($storage->template_types) { + foreach ($storage->template_types as $t => $type_map) { + foreach ($type_map as $obj => [$type]) { + if ($type->isMixed() && $docblock_param['type'] === 'class-string<' . $t . '>') { + $storage->template_types[$t][$obj] = [Type::getObject()]; + + if (isset($this->function_template_types[$t])) { + $this->function_template_types[$t][$obj] = $storage->template_types[$t][$obj]; + } + } + } + } + } + + if (!$docblock_param_variadic && $storage_param->is_variadic && $new_param_type->hasArray()) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var Type\Atomic\TArray|Type\Atomic\TKeyedArray|Type\Atomic\TList + */ + $array_type = $new_param_type->getAtomicTypes()['array']; + + if ($array_type instanceof Type\Atomic\TKeyedArray) { + $new_param_type = $array_type->getGenericValueType(); + } elseif ($array_type instanceof Type\Atomic\TList) { + $new_param_type = $array_type->type_param; + } else { + $new_param_type = $array_type->type_params[1]; + } + } + + $existing_param_type_nullable = $storage_param->is_nullable; + + if (!$storage_param->type || $storage_param->type->hasMixed() || $storage->template_types) { + if ($existing_param_type_nullable + && !$new_param_type->isNullable() + && !$new_param_type->hasTemplate() + ) { + $new_param_type->addType(new Type\Atomic\TNull()); + } + + if ($this->config->add_param_default_to_docblock_type + && $storage_param->default_type + && !$storage_param->default_type->hasMixed() + && (!$storage_param->type || !$storage_param->type->hasMixed()) + ) { + $new_param_type = Type::combineUnionTypes($new_param_type, $storage_param->default_type); + } + + $storage_param->type = $new_param_type; + $storage_param->type_location = $docblock_type_location; + continue; + } + + $storage_param_atomic_types = $storage_param->type->getAtomicTypes(); + + $all_typehint_types_match = true; + + foreach ($new_param_type->getAtomicTypes() as $key => $type) { + if (isset($storage_param_atomic_types[$key])) { + $type->from_docblock = false; + + if ($storage_param_atomic_types[$key] instanceof Type\Atomic\TArray + && $type instanceof Type\Atomic\TArray + && $type->type_params[0]->hasArrayKey() + ) { + $type->type_params[0]->from_docblock = false; + } + } else { + $all_typehint_types_match = false; + } + } + + if ($all_typehint_types_match) { + $new_param_type->from_docblock = false; + } + + if ($existing_param_type_nullable && !$new_param_type->isNullable()) { + $new_param_type->addType(new Type\Atomic\TNull()); + } + + $storage_param->type = $new_param_type; + $storage_param->type_location = $docblock_type_location; + } + + $params_without_docblock_type = array_filter( + $storage->params, + function (FunctionLikeParameter $p) : bool { + return !$p->has_docblock_type && (!$p->type || $p->type->hasArray()); + } + ); + + if ($params_without_docblock_type) { + $storage->unused_docblock_params = $unused_docblock_params; + } + } + + private function visitPropertyDeclaration( + PhpParser\Node\Stmt\Property $stmt, + Config $config, + ClassLikeStorage $storage, + string $fq_classlike_name + ): void { + if (!$this->fq_classlike_names) { + throw new \LogicException('$this->fq_classlike_names should not be empty'); + } + + $comment = $stmt->getDocComment(); + $var_comment = null; + + $property_is_initialized = false; + + $existing_constants = $storage->constants; + + if ($comment && $comment->getText() && ($config->use_docblock_types || $config->use_docblock_property_types)) { + if (preg_match('/[ \t\*]+@psalm-suppress[ \t]+PropertyNotSetInConstructor/', (string)$comment)) { + $property_is_initialized = true; + } + + try { + $var_comments = CommentAnalyzer::getTypeFromComment( + $comment, + $this->file_scanner, + $this->aliases, + $this->function_template_types + (!$stmt->isStatic() ? $this->class_template_types : []), + $this->type_aliases + ); + + $var_comment = array_pop($var_comments); + } catch (IncorrectDocblockException $e) { + $storage->docblock_issues[] = new MissingDocblockType( + $e->getMessage(), + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + } catch (DocblockParseException $e) { + $storage->docblock_issues[] = new InvalidDocblock( + $e->getMessage(), + new CodeLocation($this->file_scanner, $stmt, null, true) + ); + } + } + + $signature_type = null; + $signature_type_location = null; + + if ($stmt->type) { + $parser_property_type = $stmt->type; + + $signature_type = $this->getTypeFromTypeHint($stmt->type); + + $signature_type_location = new CodeLocation( + $this->file_scanner, + $parser_property_type, + null, + false, + CodeLocation::FUNCTION_RETURN_TYPE + ); + } + + $doc_var_group_type = $var_comment ? $var_comment->type : null; + + if ($doc_var_group_type) { + $doc_var_group_type->queueClassLikesForScanning($this->codebase, $this->file_storage); + $doc_var_group_type->setFromDocblock(); + } + + foreach ($stmt->props as $property) { + $doc_var_location = null; + + $property_storage = $storage->properties[$property->name->name] = new PropertyStorage(); + $property_storage->is_static = $stmt->isStatic(); + $property_storage->type = $signature_type; + $property_storage->signature_type = $signature_type; + $property_storage->signature_type_location = $signature_type_location; + $property_storage->type_location = $signature_type_location; + $property_storage->location = new CodeLocation($this->file_scanner, $property->name); + $property_storage->stmt_location = new CodeLocation($this->file_scanner, $stmt); + $property_storage->has_default = $property->default ? true : false; + $property_storage->deprecated = $var_comment ? $var_comment->deprecated : false; + $property_storage->internal = $var_comment ? $var_comment->psalm_internal ?? '' : ''; + if (! $property_storage->internal && $var_comment && $var_comment->internal) { + $property_storage->internal = NamespaceAnalyzer::getNameSpaceRoot($fq_classlike_name); + } + $property_storage->readonly = $var_comment ? $var_comment->readonly : false; + $property_storage->allow_private_mutation = $var_comment ? $var_comment->allow_private_mutation : false; + + if (!$signature_type && !$doc_var_group_type) { + if ($property->default) { + $property_storage->suggested_type = SimpleTypeInferer::infer( + $this->codebase, + new \Psalm\Internal\Provider\NodeDataProvider(), + $property->default, + $this->aliases, + null, + $existing_constants, + $fq_classlike_name + ); + } + + $property_storage->type = null; + } else { + if ($var_comment + && $var_comment->type_start + && $var_comment->type_end + && $var_comment->line_number + ) { + $doc_var_location = new CodeLocation\DocblockTypeLocation( + $this->file_scanner, + $var_comment->type_start, + $var_comment->type_end, + $var_comment->line_number + ); + } + + if ($doc_var_group_type) { + $property_storage->type = count($stmt->props) === 1 + ? $doc_var_group_type + : clone $doc_var_group_type; + } + } + + if ($property_storage->type + && $property_storage->type !== $property_storage->signature_type + ) { + if (!$property_storage->signature_type) { + $property_storage->type_location = $doc_var_location; + } + + if ($property_storage->signature_type) { + $all_typehint_types_match = true; + $signature_atomic_types = $property_storage->signature_type->getAtomicTypes(); + + foreach ($property_storage->type->getAtomicTypes() as $key => $type) { + if (isset($signature_atomic_types[$key])) { + $type->from_docblock = false; + } else { + $all_typehint_types_match = false; + } + } + + if ($all_typehint_types_match) { + $property_storage->type->from_docblock = false; + } + + if ($property_storage->signature_type->isNullable() + && !$property_storage->type->isNullable() + ) { + $property_storage->type->addType(new Type\Atomic\TNull()); + } + } + + $property_storage->type->queueClassLikesForScanning($this->codebase, $this->file_storage); + } + + if ($stmt->isPublic()) { + $property_storage->visibility = ClassLikeAnalyzer::VISIBILITY_PUBLIC; + } elseif ($stmt->isProtected()) { + $property_storage->visibility = ClassLikeAnalyzer::VISIBILITY_PROTECTED; + } elseif ($stmt->isPrivate()) { + $property_storage->visibility = ClassLikeAnalyzer::VISIBILITY_PRIVATE; + } + + $fq_classlike_name = $this->fq_classlike_names[count($this->fq_classlike_names) - 1]; + + $property_id = $fq_classlike_name . '::$' . $property->name->name; + + $storage->declaring_property_ids[$property->name->name] = $fq_classlike_name; + $storage->appearing_property_ids[$property->name->name] = $property_id; + + if ($property_is_initialized) { + $storage->initialized_properties[$property->name->name] = true; + } + + if (!$stmt->isPrivate()) { + $storage->inheritable_property_ids[$property->name->name] = $property_id; + } + } + } + + private function visitClassConstDeclaration( + PhpParser\Node\Stmt\ClassConst $stmt, + ClassLikeStorage $storage, + string $fq_classlike_name + ): void { + $existing_constants = $storage->constants; + + $comment = $stmt->getDocComment(); + $deprecated = false; + $config = $this->config; + + if ($comment && $comment->getText() && ($config->use_docblock_types || $config->use_docblock_property_types)) { + $comments = DocComment::parsePreservingLength($comment); + + if (isset($comments->tags['deprecated'])) { + $deprecated = true; + } + } + + foreach ($stmt->consts as $const) { + $const_type = SimpleTypeInferer::infer( + $this->codebase, + new \Psalm\Internal\Provider\NodeDataProvider(), + $const->value, + $this->aliases, + null, + $existing_constants, + $fq_classlike_name + ); + + $storage->constants[$const->name->name] = $constant_storage = new \Psalm\Storage\ClassConstantStorage( + $const_type, + $stmt->isProtected() + ? ClassLikeAnalyzer::VISIBILITY_PROTECTED + : ($stmt->isPrivate() + ? ClassLikeAnalyzer::VISIBILITY_PRIVATE + : ClassLikeAnalyzer::VISIBILITY_PUBLIC), + new CodeLocation( + $this->file_scanner, + $const->name + ) + ); + + $constant_storage->stmt_location = new CodeLocation( + $this->file_scanner, + $const + ); + + if ($const_type) { + $existing_constants[$const->name->name] = new \Psalm\Storage\ClassConstantStorage( + $const_type, + ClassLikeAnalyzer::VISIBILITY_PUBLIC, + null + ); + } else { + $unresolved_const_expr = self::getUnresolvedClassConstExpr( + $const->value, + $this->aliases, + $fq_classlike_name + ); + + if ($unresolved_const_expr) { + $constant_storage->unresolved_node = $unresolved_const_expr; + } else { + $constant_storage->type = Type::getMixed(); + } + } + + if ($deprecated) { + $constant_storage->deprecated = true; + } + } + } + + public static function getUnresolvedClassConstExpr( + PhpParser\Node\Expr $stmt, + Aliases $aliases, + string $fq_classlike_name + ) : ?UnresolvedConstantComponent { + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) { + $left = self::getUnresolvedClassConstExpr( + $stmt->left, + $aliases, + $fq_classlike_name + ); + + $right = self::getUnresolvedClassConstExpr( + $stmt->right, + $aliases, + $fq_classlike_name + ); + + if (!$left || !$right) { + return null; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Plus) { + return new UnresolvedConstant\UnresolvedAdditionOp($left, $right); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Minus) { + return new UnresolvedConstant\UnresolvedSubtractionOp($left, $right); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Mul) { + return new UnresolvedConstant\UnresolvedMultiplicationOp($left, $right); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Div) { + return new UnresolvedConstant\UnresolvedDivisionOp($left, $right); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) { + return new UnresolvedConstant\UnresolvedConcatOp($left, $right); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr) { + return new UnresolvedConstant\UnresolvedBitwiseOr($left, $right); + } + } + + if ($stmt instanceof PhpParser\Node\Expr\Ternary) { + $cond = self::getUnresolvedClassConstExpr( + $stmt->cond, + $aliases, + $fq_classlike_name + ); + + $if = null; + + if ($stmt->if) { + $if = self::getUnresolvedClassConstExpr( + $stmt->if, + $aliases, + $fq_classlike_name + ); + + if ($if === null) { + $if = false; + } + } + + $else = self::getUnresolvedClassConstExpr( + $stmt->else, + $aliases, + $fq_classlike_name + ); + + if ($cond && $else && $if !== false) { + return new UnresolvedConstant\UnresolvedTernary($cond, $if, $else); + } + } + + if ($stmt instanceof PhpParser\Node\Expr\ConstFetch) { + if (strtolower($stmt->name->parts[0]) === 'false') { + return new UnresolvedConstant\ScalarValue(false); + } elseif (strtolower($stmt->name->parts[0]) === 'true') { + return new UnresolvedConstant\ScalarValue(true); + } elseif (strtolower($stmt->name->parts[0]) === 'null') { + return new UnresolvedConstant\ScalarValue(null); + } elseif ($stmt->name->parts[0] === '__NAMESPACE__') { + return new UnresolvedConstant\ScalarValue($aliases->namespace); + } + + return new UnresolvedConstant\Constant( + implode('\\', $stmt->name->parts), + $stmt->name instanceof PhpParser\Node\Name\FullyQualified + ); + } + + if ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Namespace_) { + return new UnresolvedConstant\ScalarValue($aliases->namespace); + } + + if ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch && $stmt->dim) { + $left = self::getUnresolvedClassConstExpr( + $stmt->var, + $aliases, + $fq_classlike_name + ); + + $right = self::getUnresolvedClassConstExpr( + $stmt->dim, + $aliases, + $fq_classlike_name + ); + + if ($left && $right) { + return new UnresolvedConstant\ArrayOffsetFetch($left, $right); + } + } + + if ($stmt instanceof PhpParser\Node\Expr\ClassConstFetch) { + if ($stmt->class instanceof PhpParser\Node\Name + && $stmt->name instanceof PhpParser\Node\Identifier + && $fq_classlike_name + && $stmt->class->parts !== ['static'] + && $stmt->class->parts !== ['parent'] + ) { + if ($stmt->class->parts === ['self']) { + $const_fq_class_name = $fq_classlike_name; + } else { + $const_fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject( + $stmt->class, + $aliases + ); + } + + return new UnresolvedConstant\ClassConstant($const_fq_class_name, $stmt->name->name); + } + + return null; + } + + if ($stmt instanceof PhpParser\Node\Scalar\String_ + || $stmt instanceof PhpParser\Node\Scalar\LNumber + || $stmt instanceof PhpParser\Node\Scalar\DNumber + ) { + return new UnresolvedConstant\ScalarValue($stmt->value); + } + + if ($stmt instanceof PhpParser\Node\Expr\Array_) { + $items = []; + + foreach ($stmt->items as $item) { + if ($item === null) { + return null; + } + + if ($item->key) { + $item_key_type = self::getUnresolvedClassConstExpr( + $item->key, + $aliases, + $fq_classlike_name + ); + + if (!$item_key_type) { + return null; + } + } else { + $item_key_type = null; + } + + $item_value_type = self::getUnresolvedClassConstExpr( + $item->value, + $aliases, + $fq_classlike_name + ); + + if (!$item_value_type) { + return null; + } + + $items[] = new UnresolvedConstant\KeyValuePair($item_key_type, $item_value_type); + } + + return new UnresolvedConstant\ArrayValue($items); + } + + return null; + } + + public function visitInclude(PhpParser\Node\Expr\Include_ $stmt): void + { + $config = Config::getInstance(); + + if (!$config->allow_includes) { + throw new FileIncludeException( + 'File includes are not allowed per your Psalm config - check the allowFileIncludes flag.' + ); + } + + if ($stmt->expr instanceof PhpParser\Node\Scalar\String_) { + $path_to_file = $stmt->expr->value; + + // attempts to resolve using get_include_path dirs + $include_path = IncludeAnalyzer::resolveIncludePath($path_to_file, dirname($this->file_path)); + $path_to_file = $include_path ? $include_path : $path_to_file; + + if (DIRECTORY_SEPARATOR === '/') { + $is_path_relative = $path_to_file[0] !== DIRECTORY_SEPARATOR; + } else { + $is_path_relative = !preg_match('~^[A-Z]:\\\\~i', $path_to_file); + } + + if ($is_path_relative) { + $path_to_file = $config->base_dir . DIRECTORY_SEPARATOR . $path_to_file; + } + } else { + $path_to_file = IncludeAnalyzer::getPathTo( + $stmt->expr, + null, + null, + $this->file_path, + $this->config + ); + } + + if ($path_to_file) { + $path_to_file = IncludeAnalyzer::normalizeFilePath($path_to_file); + + if ($this->file_path === $path_to_file) { + return; + } + + if ($this->codebase->fileExists($path_to_file)) { + if ($this->scan_deep) { + $this->codebase->scanner->addFileToDeepScan($path_to_file); + } else { + $this->codebase->scanner->addFileToShallowScan($path_to_file); + } + + $this->file_storage->required_file_paths[strtolower($path_to_file)] = $path_to_file; + + return; + } + } + } + + public function getFilePath(): string + { + return $this->file_path; + } + + public function getFileName(): string + { + return $this->file_scanner->getFileName(); + } + + public function getRootFilePath(): string + { + return $this->file_scanner->getRootFilePath(); + } + + public function getRootFileName(): string + { + return $this->file_scanner->getRootFileName(); + } + + public function getAliases(): Aliases + { + return $this->aliases; + } + + public function afterTraverse(array $nodes): void + { + $this->file_storage->type_aliases = $this->type_aliases; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ShortClosureVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ShortClosureVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..c33b7e4cc37bc9aaf8a3a0073d5020e3999a86bd --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ShortClosureVisitor.php @@ -0,0 +1,32 @@ + + */ + protected $used_variables = []; + + public function enterNode(PhpParser\Node $node): ?int + { + if ($node instanceof PhpParser\Node\Expr\Variable && \is_string($node->name)) { + $this->used_variables['$' . $node->name] = true; + } + + return null; + } + + /** + * @return array + */ + public function getUsedVariables(): array + { + return $this->used_variables; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..bcd00334fd77198a24600c1601c461bca2cb5289 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php @@ -0,0 +1,222 @@ + $offset_map + */ + public function __construct(ErrorHandler $errorHandler, ?array $offset_map = null) + { + if ($offset_map) { + foreach ($offset_map as [, , $b_s, $b_e]) { + if ($this->start_change === null) { + $this->start_change = $b_s; + } + + $this->end_change = $b_e; + } + } + + $this->nameContext = new NameContext($errorHandler); + } + + public function beforeTraverse(array $nodes): ?array + { + $this->nameContext->startNamespace(); + + return null; + } + + public function enterNode(Node $node): ?int + { + 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); + } + } + + if ($node instanceof Stmt\ClassMethod + && $this->start_change + && $this->end_change + ) { + /** @var array{startFilePos: int, endFilePos: int} */ + $attrs = $node->getAttributes(); + + if ($cs = $node->getComments()) { + $attrs['startFilePos'] = $cs[0]->getStartFilePos(); + } + + if ($attrs['endFilePos'] < $this->start_change + || $attrs['startFilePos'] > $this->end_change + ) { + return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + } + + if ($node instanceof Stmt\ClassMethod + || $node instanceof Expr\Closure + ) { + $this->resolveSignature($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\Trait_) { + $this->resolveTrait($node); + } 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; + } + + private function addAlias(Stmt\UseUse $use, int $type, ?Name $prefix = null): void + { + // Add prefix for group uses + /** @var Name $name */ + $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 $node + */ + private function resolveSignature(PhpParser\NodeAbstract $node): void + { + foreach ($node->params as $param) { + $param->type = $this->resolveType($param->type); + } + $node->returnType = $this->resolveType($node->returnType); + } + + /** + * @param PhpParser\Node|string|null $node + * + * @return null|PhpParser\Node\Identifier|PhpParser\Node\Name|PhpParser\Node\NullableType + * @psalm-suppress InvalidReturnType + * @psalm-suppress InvalidReturnStatement + */ + private function resolveType($node): ?Node + { + if ($node instanceof Node\NullableType) { + /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */ + $node->type = $this->resolveType($node->type); + + return $node; + } + if ($node instanceof Name) { + return $this->resolveClassName($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 + { + $resolvedName = $this->nameContext->getResolvedName($name, $type); + if (null !== $resolvedName) { + $name->setAttribute('resolvedName', $resolvedName->toString()); + } + + return $name; + } + + protected function resolveClassName(Name $name): Name + { + return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL); + } + + protected function resolveTrait(Stmt\Trait_ $node): void + { + $resolvedName = Name::concat($this->nameContext->getNamespace(), (string) $node->name); + + if (null !== $resolvedName) { + $node->setAttribute('resolvedName', $resolvedName->toString()); + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/TraitFinder.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/TraitFinder.php new file mode 100644 index 0000000000000000000000000000000000000000..8321e937f6e56760daae46d258f52c21acc4ab88 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/TraitFinder.php @@ -0,0 +1,82 @@ + */ + private $matching_trait_nodes = []; + + private $fq_trait_name; + + public function __construct(string $fq_trait_name) + { + $this->fq_trait_name = $fq_trait_name; + } + + /** + * @param bool $traverseChildren + * + * @return int|null + */ + public function enterNode(PhpParser\Node $node, &$traverseChildren = true) + { + if ($node instanceof PhpParser\Node\Stmt\Trait_) { + /** @var ?string */ + $resolved_name = $node->getAttribute('resolvedName'); + + if ($resolved_name === null) { + // compare ends of names, a temporary hack because PHPParser caches + // may not have that attribute + + $fq_trait_name_parts = \explode('\\', $this->fq_trait_name); + + /** @psalm-suppress PossiblyNullPropertyFetch */ + if ($node->name->name === \end($fq_trait_name_parts)) { + $this->matching_trait_nodes[] = $node; + } + } elseif ($resolved_name === $this->fq_trait_name) { + $this->matching_trait_nodes[] = $node; + } + } + + if ($node instanceof PhpParser\Node\Stmt\ClassLike + || $node instanceof PhpParser\Node\FunctionLike + ) { + return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + return null; + } + + public function getNode() : ?PhpParser\Node\Stmt\Trait_ + { + if (!count($this->matching_trait_nodes)) { + return null; + } + + if (count($this->matching_trait_nodes) === 1 || !\trait_exists($this->fq_trait_name)) { + return $this->matching_trait_nodes[0]; + } + + try { + $reflection_trait = new \ReflectionClass($this->fq_trait_name); + } catch (\Throwable $t) { + return null; + } + + foreach ($this->matching_trait_nodes as $node) { + if ($node->getLine() === $reflection_trait->getStartLine()) { + return $node; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..2a3cbbe5cb2df57ca5a7d1ca1c3a6c098be2d900 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php @@ -0,0 +1,33 @@ +fake_type_provider = $fake_type_provider; + $this->real_type_provider = $real_type_provider; + } + + public function enterNode(Node $node): void + { + $origNode = $node; + + /** @psalm-suppress ArgumentTypeCoercion */ + $node_type = $this->fake_type_provider->getType($origNode); + + if ($node_type) { + /** @psalm-suppress ArgumentTypeCoercion */ + $this->real_type_provider->setType($origNode, clone $node_type); + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/DisableCommand.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/DisableCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..163b20948cbd7296698fceff0608b4a21951ea5b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/DisableCommand.php @@ -0,0 +1,80 @@ +plugin_list_factory = $plugin_list_factory; + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setName('disable') + ->setDescription('Disables a named plugin') + ->addArgument( + 'pluginName', + InputArgument::REQUIRED, + 'Plugin name (fully qualified class name or composer package name)' + ) + ->addUsage('vendor/plugin-package-name [-c path/to/psalm.xml]'); + $this->addUsage('\'Plugin\Class\Name\' [-c path/to/psalm.xml]'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $current_dir = (string) getcwd() . DIRECTORY_SEPARATOR; + + $config_file_path = $input->getOption('config'); + if ($config_file_path !== null && !is_string($config_file_path)) { + throw new \UnexpectedValueException('Config file path should be a string'); + } + + $plugin_list = ($this->plugin_list_factory)($current_dir, $config_file_path); + + $plugin_name = $input->getArgument('pluginName'); + + assert(is_string($plugin_name)); + + try { + $plugin_class = $plugin_list->resolvePluginClass($plugin_name); + } catch (InvalidArgumentException $e) { + $io->error('Unknown plugin class ' . $plugin_name); + + return 2; + } + + if (!$plugin_list->isEnabled($plugin_class)) { + $io->note('Plugin already disabled'); + + return 3; + } + + $plugin_list->disable($plugin_class); + $io->success('Plugin disabled'); + + return 0; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/EnableCommand.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/EnableCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..1b29d0d98831193677b0e2589b16d0a5b77d9292 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/EnableCommand.php @@ -0,0 +1,80 @@ +plugin_list_factory = $plugin_list_factory; + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setName('enable') + ->setDescription('Enables a named plugin') + ->addArgument( + 'pluginName', + InputArgument::REQUIRED, + 'Plugin name (fully qualified class name or composer package name)' + ) + ->addUsage('vendor/plugin-package-name [-c path/to/psalm.xml]'); + $this->addUsage('\'Plugin\Class\Name\' [-c path/to/psalm.xml]'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $current_dir = (string) getcwd() . DIRECTORY_SEPARATOR; + + $config_file_path = $input->getOption('config'); + if ($config_file_path !== null && !is_string($config_file_path)) { + throw new \UnexpectedValueException('Config file path should be a string'); + } + + $plugin_list = ($this->plugin_list_factory)($current_dir, $config_file_path); + + $plugin_name = $input->getArgument('pluginName'); + + assert(is_string($plugin_name)); + + try { + $plugin_class = $plugin_list->resolvePluginClass($plugin_name); + } catch (InvalidArgumentException $e) { + $io->error('Unknown plugin class ' . $plugin_name); + + return 2; + } + + if ($plugin_list->isEnabled($plugin_class)) { + $io->note('Plugin already enabled'); + + return 3; + } + + $plugin_list->enable($plugin_class); + $io->success('Plugin enabled'); + + return 0; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/ShowCommand.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/ShowCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..a231d3993ee73b1682e90d3f37c996aa2ae99620 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/Command/ShowCommand.php @@ -0,0 +1,92 @@ +plugin_list_factory = $plugin_list_factory; + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setName('show') + ->setDescription('Lists enabled and available plugins') + ->addUsage('[-c path/to/psalm.xml]'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $current_dir = (string) getcwd() . DIRECTORY_SEPARATOR; + + $config_file_path = $input->getOption('config'); + if ($config_file_path !== null && !is_string($config_file_path)) { + throw new \UnexpectedValueException('Config file path should be a string'); + } + + $plugin_list = ($this->plugin_list_factory)($current_dir, $config_file_path); + + $enabled = $plugin_list->getEnabled(); + $available = $plugin_list->getAvailable(); + + $formatRow = + /** + * @return array{0: null|string, 1: string} + */ + function (string $class, ?string $package): array { + return [$package, $class]; + }; + + $io->section('Enabled'); + if (count($enabled)) { + $io->table( + ['Package', 'Class'], + array_map( + $formatRow, + array_keys($enabled), + array_values($enabled) + ) + ); + } else { + $io->note('No plugins enabled'); + } + + $io->section('Available'); + if (count($available)) { + $io->table( + ['Package', 'Class'], + array_map( + $formatRow, + array_keys($available), + array_values($available) + ) + ); + } else { + $io->note('No plugins available'); + } + + return 0; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/ComposerLock.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/ComposerLock.php new file mode 100644 index 0000000000000000000000000000000000000000..cd48e2f88ce4e0444ce529b1a8c55d3db81878e5 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/ComposerLock.php @@ -0,0 +1,114 @@ +file_names = $file_names; + } + + /** + * @param mixed $package + * + * @psalm-assert-if-true array $package + * + * @psalm-pure + */ + public function isPlugin($package): bool + { + return is_array($package) + && isset($package['name']) + && is_string($package['name']) + && isset($package['type']) + && $package['type'] === 'psalm-plugin' + && isset($package['extra']['psalm']['pluginClass']) + && is_array($package['extra']) + && is_array($package['extra']['psalm']) + && is_string($package['extra']['psalm']['pluginClass']); + } + + /** + * @return array [packageName => pluginClass, ...] + */ + public function getPlugins(): array + { + $pluginPackages = $this->getAllPluginPackages(); + $ret = []; + foreach ($pluginPackages as $package) { + $ret[$package['name']] = $package['extra']['psalm']['pluginClass']; + } + + return $ret; + } + + private function read(string $file_name): array + { + /** @psalm-suppress MixedAssignment */ + $contents = json_decode(file_get_contents($file_name), true); + + if ($error = json_last_error()) { + throw new RuntimeException(json_last_error_msg(), $error); + } + + if (!is_array($contents)) { + throw new RuntimeException('Malformed ' . $file_name . ', expecting JSON-encoded object'); + } + + return $contents; + } + + /** + * @return list + */ + private function getAllPluginPackages(): array + { + $packages = $this->getAllPackages(); + $ret = []; + /** @psalm-suppress MixedAssignment */ + foreach ($packages as $package) { + if ($this->isPlugin($package)) { + /** @var array{type:'psalm-plugin',name:string,extra:array{psalm:array{pluginClass:string}}} */ + $ret[] = $package; + } + } + + return $ret; + } + + private function getAllPackages(): array + { + $packages = []; + foreach ($this->file_names as $file_name) { + $composer_lock_contents = $this->read($file_name); + if (!isset($composer_lock_contents['packages']) || !is_array($composer_lock_contents['packages'])) { + throw new RuntimeException('packages section is missing or not an array'); + } + if (!isset($composer_lock_contents['packages-dev']) || !is_array($composer_lock_contents['packages-dev'])) { + throw new RuntimeException('packages-dev section is missing or not an array'); + } + $packages = array_merge( + $packages, + array_merge( + $composer_lock_contents['packages'], + $composer_lock_contents['packages-dev'] + ) + ); + } + + return $packages; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/ConfigFile.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/ConfigFile.php new file mode 100644 index 0000000000000000000000000000000000000000..09ec5db84bb8e84afd6589eac71596fb54fc91dd --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/ConfigFile.php @@ -0,0 +1,144 @@ +current_dir = $current_dir; + + if ($explicit_path) { + $this->path = $explicit_path; + } else { + $path = Config::locateConfigFile($current_dir); + if (!$path) { + throw new RuntimeException('Cannot find Psalm config'); + } + $this->path = $path; + } + } + + public function getConfig(): Config + { + return Config::loadFromXMLFile($this->path, $this->current_dir); + } + + public function removePlugin(string $plugin_class): void + { + $config_xml = $this->readXml(); + /** @var \DomElement */ + $psalm_root = $config_xml->getElementsByTagName('psalm')[0]; + $plugins_elements = $psalm_root->getElementsByTagName('plugins'); + if (!$plugins_elements->length) { + // no plugins, nothing to remove + return; + } + + /** @var \DomElement */ + $plugins_element = $plugins_elements->item(0); + + $plugin_elements = $plugins_element->getElementsByTagName('pluginClass'); + + foreach ($plugin_elements as $plugin_element) { + if ($plugin_element->getAttribute('class') === $plugin_class) { + $plugins_element->removeChild($plugin_element); + break; + } + } + + if (!$plugin_elements->length) { + // avoid breaking old psalm binaries, whose schema did not allow empty plugins + $psalm_root->removeChild($plugins_element); + } + + $this->saveXml($config_xml); + } + + public function addPlugin(string $plugin_class): void + { + $config_xml = $this->readXml(); + /** @var \DomElement */ + $psalm_root = $config_xml->getElementsByTagName('psalm')->item(0); + $plugins_elements = $psalm_root->getElementsByTagName('plugins'); + if (!$plugins_elements->length) { + $plugins_element = $config_xml->createElement('plugins'); + if ($plugins_element) { + $psalm_root->appendChild($plugins_element); + } + } else { + /** @var \DomElement */ + $plugins_element = $plugins_elements->item(0); + } + + $plugin_class_element = $config_xml->createElement('pluginClass'); + if ($plugin_class_element) { + $plugin_class_element->setAttribute('xmlns', self::NS); + $plugin_class_element->setAttribute('class', $plugin_class); + if ($plugins_element) { + $plugins_element->appendChild($plugin_class_element); + } + } + + $this->saveXml($config_xml); + } + + private function readXml(): DOMDocument + { + $doc = new DOMDocument(); + + $file_contents = file_get_contents($this->path); + + if (($tag_start = strpos($file_contents, '', $tag_start + 1); + + if ($tag_end !== false) { + $this->psalm_tag_end_pos = $tag_end; + $this->psalm_header = substr($file_contents, 0, $tag_end); + } + } + + $doc->loadXML($file_contents); + + return $doc; + } + + private function saveXml(DOMDocument $config_xml): void + { + $new_file_contents = $config_xml->saveXML($config_xml); + + if (($tag_start = strpos($new_file_contents, '', $tag_start + 1); + + if ($tag_end !== false + && ($new_file_contents[$tag_end - 1] !== '/') + && $this->psalm_tag_end_pos + && $this->psalm_header + ) { + $new_file_contents = $this->psalm_header . substr($new_file_contents, $tag_end); + } + } + + file_put_contents($this->path, $new_file_contents); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/PluginList.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/PluginList.php new file mode 100644 index 0000000000000000000000000000000000000000..c642558ef38ff88d89caf5b8795c85fb27535113 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/PluginList.php @@ -0,0 +1,106 @@ + [pluginClass => packageName] */ + private $all_plugins = null; + + /** @var ?array [pluginClass => ?packageName] */ + private $enabled_plugins = null; + + public function __construct(ConfigFile $config_file, ComposerLock $composer_lock) + { + $this->config_file = $config_file; + $this->composer_lock = $composer_lock; + } + + /** + * @return array [pluginClass => ?packageName, ...] + */ + public function getEnabled(): array + { + if (!$this->enabled_plugins) { + $this->enabled_plugins = []; + foreach ($this->config_file->getConfig()->getPluginClasses() as $plugin_entry) { + $plugin_class = $plugin_entry['class']; + $this->enabled_plugins[$plugin_class] = $this->findPluginPackage($plugin_class); + } + } + + return $this->enabled_plugins; + } + + /** + * @return array [pluginCLass => ?packageName] + */ + public function getAvailable(): array + { + return array_diff_key($this->getAll(), $this->getEnabled()); + } + + /** + * @return array [pluginClass => packageName] + */ + public function getAll(): array + { + if (null === $this->all_plugins) { + $this->all_plugins = array_flip($this->composer_lock->getPlugins()); + } + + return $this->all_plugins; + } + + public function resolvePluginClass(string $class_or_package): string + { + if (false === strpos($class_or_package, '/')) { + return $class_or_package; // must be a class then + } + + // pluginClass => ?pluginPackage + $plugin_classes = $this->getAll(); + + $class = array_search($class_or_package, $plugin_classes, true); + + if (false === $class) { + throw new \InvalidArgumentException('Unknown plugin: ' . $class_or_package); + } + + return $class; + } + + public function findPluginPackage(string $class): ?string + { + // pluginClass => ?pluginPackage + $plugin_classes = $this->getAll(); + + return $plugin_classes[$class] ?? null; + } + + public function isEnabled(string $class): bool + { + return array_key_exists($class, $this->getEnabled()); + } + + public function enable(string $class): void + { + $this->config_file->addPlugin($class); + } + + public function disable(string $class): void + { + $this->config_file->removePlugin($class); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/PluginListFactory.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/PluginListFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..18a6a8039c9672dbc3e14aa0e7c9459bc95ec8c2 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/PluginManager/PluginListFactory.php @@ -0,0 +1,68 @@ +project_root = $project_root; + $this->psalm_root = $psalm_root; + } + + public function __invoke(string $current_dir, ?string $config_file_path = null): PluginList + { + $config_file = new ConfigFile($current_dir, $config_file_path); + $composer_lock = new ComposerLock($this->findLockFiles()); + + return new PluginList($config_file, $composer_lock); + } + + /** @return non-empty-array */ + private function findLockFiles(): array + { + // use cases + // 1. plugins are installed into project vendors - composer.lock is PROJECT_ROOT/composer.lock + // 2. plugins are installed into separate composer environment (either global or bamarni-bin) + // - composer.lock is PSALM_ROOT/../../../composer.lock + // 3. plugins are installed into psalm vendors - composer.lock is PSALM_ROOT/composer.lock + // 4. none of the above - use stub (empty virtual composer.lock) + + if ($this->psalm_root === $this->project_root) { + // managing plugins for psalm itself + $composer_lock_filenames = [ + Composer::getLockFilePath(rtrim($this->psalm_root, DIRECTORY_SEPARATOR)), + ]; + } else { + $composer_lock_filenames = [ + Composer::getLockFilePath(rtrim($this->project_root, DIRECTORY_SEPARATOR)), + Composer::getLockFilePath(rtrim($this->psalm_root, DIRECTORY_SEPARATOR) . '/../../..'), + Composer::getLockFilePath(rtrim($this->psalm_root, DIRECTORY_SEPARATOR)), + ]; + } + + $composer_lock_filenames = array_filter($composer_lock_filenames, 'is_readable'); + + if (empty($composer_lock_filenames)) { + $stub_composer_lock = (object)[ + 'packages' => [], + 'packages-dev' => [], + ]; + $composer_lock_filenames[] = 'data:application/json,' . urlencode(json_encode($stub_composer_lock)); + } + + return $composer_lock_filenames; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..36b030e171a336351769c46eaeda5d0b6be67b90 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageCacheProvider.php @@ -0,0 +1,165 @@ +config = $config; + + $storage_dir = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'Storage' . DIRECTORY_SEPARATOR; + + $dependent_files = [ + $storage_dir . 'FileStorage.php', + $storage_dir . 'FunctionLikeStorage.php', + $storage_dir . 'ClassLikeStorage.php', + $storage_dir . 'MethodStorage.php', + ]; + + if ($config->after_visit_classlikes) { + $dependent_files = array_merge($dependent_files, $config->plugin_paths); + } + + foreach ($dependent_files as $dependent_file_path) { + if (!file_exists($dependent_file_path)) { + throw new \UnexpectedValueException($dependent_file_path . ' must exist'); + } + + $this->modified_timestamps .= ' ' . filemtime($dependent_file_path); + } + + $this->modified_timestamps .= $this->config->hash; + } + + public function writeToCache(ClassLikeStorage $storage, ?string $file_path, ?string $file_contents): void + { + $fq_classlike_name_lc = strtolower($storage->name); + + $cache_location = $this->getCacheLocationForClass($fq_classlike_name_lc, $file_path, true); + $storage->hash = $this->getCacheHash($file_path, $file_contents); + + if ($this->config->use_igbinary) { + file_put_contents($cache_location, igbinary_serialize($storage)); + } else { + file_put_contents($cache_location, serialize($storage)); + } + } + + public function getLatestFromCache( + string $fq_classlike_name_lc, + ?string $file_path, + ?string $file_contents + ): ClassLikeStorage { + $cached_value = $this->loadFromCache($fq_classlike_name_lc, $file_path); + + if (!$cached_value) { + throw new \UnexpectedValueException($fq_classlike_name_lc . ' should be in cache'); + } + + $cache_hash = $this->getCacheHash($file_path, $file_contents); + + /** @psalm-suppress TypeDoesNotContainType */ + if (@get_class($cached_value) === '__PHP_Incomplete_Class' + || $cache_hash !== $cached_value->hash + ) { + unlink($this->getCacheLocationForClass($fq_classlike_name_lc, $file_path)); + + throw new \UnexpectedValueException($fq_classlike_name_lc . ' should not be outdated'); + } + + return $cached_value; + } + + private function getCacheHash(?string $file_path, ?string $file_contents): string + { + return sha1(($file_path ? $file_contents : '') . $this->modified_timestamps); + } + + /** + * @psalm-suppress MixedAssignment + */ + private function loadFromCache(string $fq_classlike_name_lc, ?string $file_path): ?ClassLikeStorage + { + $cache_location = $this->getCacheLocationForClass($fq_classlike_name_lc, $file_path); + + if (file_exists($cache_location)) { + if ($this->config->use_igbinary) { + $storage = igbinary_unserialize((string)file_get_contents($cache_location)); + + if ($storage instanceof ClassLikeStorage) { + return $storage; + } + + return null; + } + + $storage = unserialize((string)file_get_contents($cache_location)); + + if ($storage instanceof ClassLikeStorage) { + return $storage; + } + + return null; + } + + return null; + } + + private function getCacheLocationForClass( + string $fq_classlike_name_lc, + ?string $file_path, + bool $create_directory = false + ): string { + $root_cache_directory = $this->config->getCacheDirectory(); + + if (!$root_cache_directory) { + throw new \UnexpectedValueException('No cache directory defined'); + } + + $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::CLASS_CACHE_DIRECTORY; + + if ($create_directory && !is_dir($parser_cache_directory)) { + mkdir($parser_cache_directory, 0777, true); + } + + return $parser_cache_directory + . DIRECTORY_SEPARATOR + . sha1(($file_path ? strtolower($file_path) . ' ' : '') . $fq_classlike_name_lc) + . ($this->config->use_igbinary ? '-igbinary' : ''); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..73fbb0ebdbcd35ad1a215b90754e6a4a3d7e74e8 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php @@ -0,0 +1,134 @@ + + */ + private static $storage = []; + + /** + * @var array + */ + private static $new_storage = []; + + /** + * @var ?ClassLikeStorageCacheProvider + */ + public $cache; + + public function __construct(?ClassLikeStorageCacheProvider $cache = null) + { + $this->cache = $cache; + } + + /** + * @throws \InvalidArgumentException when class does not exist + */ + public function get(string $fq_classlike_name): ClassLikeStorage + { + $fq_classlike_name_lc = strtolower($fq_classlike_name); + if (!isset(self::$storage[$fq_classlike_name_lc])) { + throw new \InvalidArgumentException('Could not get class storage for ' . $fq_classlike_name_lc); + } + + return self::$storage[$fq_classlike_name_lc]; + } + + public function has(string $fq_classlike_name): bool + { + $fq_classlike_name_lc = strtolower($fq_classlike_name); + + return isset(self::$storage[$fq_classlike_name_lc]); + } + + public function exhume(string $fq_classlike_name, ?string $file_path, ?string $file_contents): ClassLikeStorage + { + $fq_classlike_name_lc = strtolower($fq_classlike_name); + + if (isset(self::$storage[$fq_classlike_name_lc])) { + return self::$storage[$fq_classlike_name_lc]; + } + + if (!$this->cache) { + throw new \LogicException('Cannot exhume when there’s no cache'); + } + + $cached_value = $this->cache->getLatestFromCache($fq_classlike_name_lc, $file_path, $file_contents); + + self::$storage[$fq_classlike_name_lc] = $cached_value; + self::$new_storage[$fq_classlike_name_lc] = $cached_value; + + return $cached_value; + } + + /** + * @return array + */ + public function getAll(): array + { + return self::$storage; + } + + /** + * @return array + */ + public function getNew(): array + { + return self::$new_storage; + } + + /** + * @param array $more + * + */ + public function addMore(array $more): void + { + self::$new_storage = array_merge(self::$new_storage, $more); + self::$storage = array_merge(self::$storage, $more); + } + + public function makeNew(string $fq_classlike_name_lc): void + { + self::$new_storage[$fq_classlike_name_lc] = self::$storage[$fq_classlike_name_lc]; + } + + public function create(string $fq_classlike_name): ClassLikeStorage + { + $fq_classlike_name_lc = strtolower($fq_classlike_name); + + $storage = new ClassLikeStorage($fq_classlike_name); + self::$storage[$fq_classlike_name_lc] = $storage; + self::$new_storage[$fq_classlike_name_lc] = $storage; + + return $storage; + } + + public function remove(string $fq_classlike_name): void + { + $fq_classlike_name_lc = strtolower($fq_classlike_name); + + unset(self::$storage[$fq_classlike_name_lc]); + } + + public static function deleteAll(): void + { + self::$storage = []; + self::$new_storage = []; + } + + public static function populated(): void + { + self::$new_storage = []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..84f9bf187722bb6d50f31e1d7b21bae8193b3a63 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileProvider.php @@ -0,0 +1,130 @@ + + */ + protected $temp_files = []; + + /** + * @var array + */ + protected $open_files = []; + + public function getContents(string $file_path, bool $go_to_source = false): string + { + if (!$go_to_source && isset($this->temp_files[strtolower($file_path)])) { + return $this->temp_files[strtolower($file_path)]; + } + + if (isset($this->open_files[strtolower($file_path)])) { + return $this->open_files[strtolower($file_path)]; + } + + if (!file_exists($file_path)) { + throw new \UnexpectedValueException('File ' . $file_path . ' should exist to get contents'); + } + + if (is_dir($file_path)) { + throw new \UnexpectedValueException('File ' . $file_path . ' is a directory'); + } + + return (string)file_get_contents($file_path); + } + + public function setContents(string $file_path, string $file_contents): void + { + if (isset($this->open_files[strtolower($file_path)])) { + $this->open_files[strtolower($file_path)] = $file_contents; + } + + if (isset($this->temp_files[strtolower($file_path)])) { + $this->temp_files[strtolower($file_path)] = $file_contents; + } + + file_put_contents($file_path, $file_contents); + } + + public function setOpenContents(string $file_path, string $file_contents): void + { + if (isset($this->open_files[strtolower($file_path)])) { + $this->open_files[strtolower($file_path)] = $file_contents; + } + } + + public function getModifiedTime(string $file_path): int + { + if (!file_exists($file_path)) { + throw new \UnexpectedValueException('File should exist to get modified time'); + } + + return (int)filemtime($file_path); + } + + public function addTemporaryFileChanges(string $file_path, string $new_content): void + { + $this->temp_files[strtolower($file_path)] = $new_content; + } + + public function removeTemporaryFileChanges(string $file_path): void + { + unset($this->temp_files[strtolower($file_path)]); + } + + public function openFile(string $file_path): void + { + $this->open_files[strtolower($file_path)] = $this->getContents($file_path, true); + } + + public function isOpen(string $file_path): bool + { + return isset($this->temp_files[strtolower($file_path)]) || isset($this->open_files[strtolower($file_path)]); + } + + public function closeFile(string $file_path): void + { + unset($this->temp_files[strtolower($file_path)], $this->open_files[strtolower($file_path)]); + } + + public function fileExists(string $file_path): bool + { + return file_exists($file_path); + } + + /** + * @param array $file_extensions + * + * @return list + */ + public function getFilesInDir(string $dir_path, array $file_extensions): array + { + $file_paths = []; + + /** @var \RecursiveDirectoryIterator */ + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir_path)); + $iterator->rewind(); + + while ($iterator->valid()) { + if (!$iterator->isDot()) { + $extension = $iterator->getExtension(); + if (in_array($extension, $file_extensions, true)) { + $file_paths[] = (string)$iterator->getRealPath(); + } + } + + $iterator->next(); + } + + return $file_paths; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..66497d84635fa63af96a03ded1bb0e0865d6bae8 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php @@ -0,0 +1,639 @@ +config = $config; + } + + public function hasConfigChanged() : bool + { + $has_changed = $this->config->hash !== $this->getConfigHashCache(); + $this->setConfigHashCache($this->config->hash); + return $has_changed; + } + + /** + * @psalm-suppress MixedAssignment + */ + public function getCachedFileReferences(): ?array + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return null; + } + + $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::REFERENCE_CACHE_NAME; + + if (!is_readable($reference_cache_location)) { + return null; + } + + $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + + if (!is_array($reference_cache)) { + throw new \UnexpectedValueException('The reference cache must be an array'); + } + + return $reference_cache; + } + + /** + * @psalm-suppress MixedAssignment + */ + public function getCachedClassLikeFiles(): ?array + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return null; + } + + $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASSLIKE_FILE_CACHE_NAME; + + if (!is_readable($reference_cache_location)) { + return null; + } + + $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + + if (!is_array($reference_cache)) { + throw new \UnexpectedValueException('The reference cache must be an array'); + } + + return $reference_cache; + } + + /** + * @psalm-suppress MixedAssignment + */ + public function getCachedNonMethodClassReferences(): ?array + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return null; + } + + $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::NONMETHOD_CLASS_REFERENCE_CACHE_NAME; + + if (!is_readable($reference_cache_location)) { + return null; + } + + $reference_cache = unserialize((string) file_get_contents($reference_cache_location)); + + if (!is_array($reference_cache)) { + throw new \UnexpectedValueException('The reference cache must be an array'); + } + + return $reference_cache; + } + + /** + * @psalm-suppress MixedAssignment + */ + public function getCachedMethodClassReferences(): ?array + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return null; + } + + $cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_CLASS_REFERENCE_CACHE_NAME; + + if (!is_readable($cache_location)) { + return null; + } + + $reference_cache = unserialize((string) file_get_contents($cache_location)); + + if (!is_array($reference_cache)) { + throw new \UnexpectedValueException('The reference cache must be an array'); + } + + return $reference_cache; + } + + /** + * @psalm-suppress MixedAssignment + */ + public function getCachedMethodMemberReferences(): ?array + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return null; + } + + $class_member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASS_METHOD_CACHE_NAME; + + if (!is_readable($class_member_cache_location)) { + return null; + } + + $class_member_reference_cache = unserialize((string) file_get_contents($class_member_cache_location)); + + if (!is_array($class_member_reference_cache)) { + throw new \UnexpectedValueException('The reference cache must be an array'); + } + + return $class_member_reference_cache; + } + + /** + * @psalm-suppress MixedAssignment + */ + public function getCachedMethodMissingMemberReferences(): ?array + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return null; + } + + $class_member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_MISSING_MEMBER_CACHE_NAME; + + if (!is_readable($class_member_cache_location)) { + return null; + } + + $class_member_reference_cache = unserialize((string) file_get_contents($class_member_cache_location)); + + if (!is_array($class_member_reference_cache)) { + throw new \UnexpectedValueException('The reference cache must be an array'); + } + + return $class_member_reference_cache; + } + + /** + * @psalm-suppress MixedAssignment + */ + public function getCachedFileMemberReferences(): ?array + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return null; + } + + $file_class_member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_CLASS_MEMBER_CACHE_NAME; + + if (!is_readable($file_class_member_cache_location)) { + return null; + } + + $file_class_member_reference_cache = unserialize((string) file_get_contents($file_class_member_cache_location)); + + if (!is_array($file_class_member_reference_cache)) { + throw new \UnexpectedValueException('The reference cache must be an array'); + } + + return $file_class_member_reference_cache; + } + + /** + * @psalm-suppress MixedAssignment + */ + public function getCachedFileMissingMemberReferences(): ?array + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return null; + } + + $file_class_member_cache_location + = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_MISSING_MEMBER_CACHE_NAME; + + if (!is_readable($file_class_member_cache_location)) { + return null; + } + + $file_class_member_reference_cache = unserialize((string) file_get_contents($file_class_member_cache_location)); + + if (!is_array($file_class_member_reference_cache)) { + throw new \UnexpectedValueException('The reference cache must be an array'); + } + + return $file_class_member_reference_cache; + } + + /** + * @psalm-suppress MixedAssignment + */ + public function getCachedMixedMemberNameReferences(): ?array + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return null; + } + + $cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::UNKNOWN_MEMBER_CACHE_NAME; + + if (!is_readable($cache_location)) { + return null; + } + + $cache = unserialize((string) file_get_contents($cache_location)); + + if (!is_array($cache)) { + throw new \UnexpectedValueException('The reference cache must be an array'); + } + + return $cache; + } + + /** + * @psalm-suppress MixedAssignment + */ + public function getCachedMethodParamUses(): ?array + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return null; + } + + $cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_PARAM_USE_CACHE_NAME; + + if (!is_readable($cache_location)) { + return null; + } + + $cache = unserialize((string) file_get_contents($cache_location)); + + if (!is_array($cache)) { + throw new \UnexpectedValueException('The method param use cache must be an array'); + } + + return $cache; + } + + /** + * @psalm-suppress MixedAssignment + */ + public function getCachedIssues(): ?array + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return null; + } + + $issues_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::ISSUES_CACHE_NAME; + + if (!is_readable($issues_cache_location)) { + return null; + } + + $issues_cache = unserialize((string) file_get_contents($issues_cache_location)); + + if (!is_array($issues_cache)) { + throw new \UnexpectedValueException('The reference cache must be an array'); + } + + return $issues_cache; + } + + public function setCachedFileReferences(array $file_references): void + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::REFERENCE_CACHE_NAME; + + file_put_contents($reference_cache_location, serialize($file_references)); + } + + public function setCachedClassLikeFiles(array $file_references): void + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASSLIKE_FILE_CACHE_NAME; + + file_put_contents($reference_cache_location, serialize($file_references)); + } + + public function setCachedNonMethodClassReferences(array $file_class_references): void + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::NONMETHOD_CLASS_REFERENCE_CACHE_NAME; + + file_put_contents($reference_cache_location, serialize($file_class_references)); + } + + public function setCachedMethodClassReferences(array $method_class_references): void + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $reference_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_CLASS_REFERENCE_CACHE_NAME; + + file_put_contents($reference_cache_location, serialize($method_class_references)); + } + + public function setCachedMethodMemberReferences(array $member_references): void + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CLASS_METHOD_CACHE_NAME; + + file_put_contents($member_cache_location, serialize($member_references)); + } + + public function setCachedMethodMissingMemberReferences(array $member_references): void + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_MISSING_MEMBER_CACHE_NAME; + + file_put_contents($member_cache_location, serialize($member_references)); + } + + public function setCachedFileMemberReferences(array $member_references): void + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_CLASS_MEMBER_CACHE_NAME; + + file_put_contents($member_cache_location, serialize($member_references)); + } + + public function setCachedFileMissingMemberReferences(array $member_references): void + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $member_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_MISSING_MEMBER_CACHE_NAME; + + file_put_contents($member_cache_location, serialize($member_references)); + } + + public function setCachedMixedMemberNameReferences(array $references): void + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::UNKNOWN_MEMBER_CACHE_NAME; + + file_put_contents($cache_location, serialize($references)); + } + + public function setCachedMethodParamUses(array $uses): void + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::METHOD_PARAM_USE_CACHE_NAME; + + file_put_contents($cache_location, serialize($uses)); + } + + public function setCachedIssues(array $issues): void + { + $cache_directory = $this->config->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $issues_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::ISSUES_CACHE_NAME; + + file_put_contents($issues_cache_location, serialize($issues)); + } + + /** + * @return array>|false + */ + public function getAnalyzedMethodCache() + { + $cache_directory = $this->config->getCacheDirectory(); + + $analyzed_methods_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::ANALYZED_METHODS_CACHE_NAME; + + if ($cache_directory + && file_exists($analyzed_methods_cache_location) + ) { + /** @var array> */ + return unserialize(file_get_contents($analyzed_methods_cache_location)); + } + + return false; + } + + /** + * @param array> $analyzed_methods + */ + public function setAnalyzedMethodCache(array $analyzed_methods): void + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + if ($cache_directory) { + $analyzed_methods_cache_location = $cache_directory + . DIRECTORY_SEPARATOR + . self::ANALYZED_METHODS_CACHE_NAME; + + file_put_contents( + $analyzed_methods_cache_location, + serialize($analyzed_methods) + ); + } + } + + /** + * @return array|false + */ + public function getFileMapCache() + { + $cache_directory = $this->config->getCacheDirectory(); + + $file_maps_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_MAPS_CACHE_NAME; + + if ($cache_directory + && file_exists($file_maps_cache_location) + ) { + /** + * @var array + */ + $file_maps_cache = unserialize(file_get_contents($file_maps_cache_location)); + + return $file_maps_cache; + } + + return false; + } + + /** + * @param array $file_maps + */ + public function setFileMapCache(array $file_maps): void + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + if ($cache_directory) { + $file_maps_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::FILE_MAPS_CACHE_NAME; + + file_put_contents( + $file_maps_cache_location, + serialize($file_maps) + ); + } + } + + /** + * @return array|false + */ + public function getTypeCoverage() + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + $type_coverage_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::TYPE_COVERAGE_CACHE_NAME; + + if ($cache_directory + && file_exists($type_coverage_cache_location) + ) { + /** @var array */ + $type_coverage_cache = unserialize(file_get_contents($type_coverage_cache_location)); + + return $type_coverage_cache; + } + + return false; + } + + /** + * @param array $mixed_counts + */ + public function setTypeCoverage(array $mixed_counts): void + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + if ($cache_directory) { + $type_coverage_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::TYPE_COVERAGE_CACHE_NAME; + + file_put_contents( + $type_coverage_cache_location, + serialize($mixed_counts) + ); + } + } + + /** + * @return string|false + */ + public function getConfigHashCache() + { + $cache_directory = $this->config->getCacheDirectory(); + + $config_hash_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CONFIG_HASH_CACHE_NAME; + + if ($cache_directory + && file_exists($config_hash_cache_location) + ) { + /** @var string */ + $file_maps_cache = file_get_contents($config_hash_cache_location); + + return $file_maps_cache; + } + + return false; + } + + public function setConfigHashCache(string $hash): void + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + if ($cache_directory) { + if (!file_exists($cache_directory)) { + \mkdir($cache_directory, 0777, true); + } + + $config_hash_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::CONFIG_HASH_CACHE_NAME; + + file_put_contents( + $config_hash_cache_location, + $hash + ); + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileReferenceProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileReferenceProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..1f22173137d5a34de19baa47b295ec91e27b43a4 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileReferenceProvider.php @@ -0,0 +1,1012 @@ +> + */ + private static $nonmethod_references_to_classes = []; + + /** + * A lookup table used for getting all the methods that reference a class + * + * @var array> + */ + private static $method_references_to_classes = []; + + /** + * A lookup table used for getting all the files that reference a class member + * + * @var array> + */ + private static $file_references_to_class_members = []; + + /** + * A lookup table used for getting all the files that reference a missing class member + * + * @var array> + */ + private static $file_references_to_missing_class_members = []; + + /** + * @var array> + */ + private static $files_inheriting_classes = []; + + /** + * A list of all files deleted since the last successful run + * + * @var array|null + */ + private static $deleted_files = null; + + /** + * A lookup table used for getting all the files referenced by a file + * + * @var array, i:array}> + */ + private static $file_references = []; + + /** + * @var array> + */ + private static $method_references_to_class_members = []; + + /** + * @var array> + */ + private static $method_references_to_missing_class_members = []; + + /** + * @var array> + */ + private static $references_to_mixed_member_names = []; + + /** + * @var array> + */ + private static $class_method_locations = []; + + /** + * @var array> + */ + private static $class_property_locations = []; + + /** + * @var array> + */ + private static $class_locations = []; + + /** + * @var array + */ + private static $classlike_files = []; + + /** + * @var array> + */ + private static $analyzed_methods = []; + + /** + * @var array> + */ + private static $issues = []; + + /** + * @var array + */ + private static $file_maps = []; + + /** + * @var array + */ + private static $mixed_counts = []; + + /** + * @var array>> + */ + private static $method_param_uses = []; + + /** + * @var ?FileReferenceCacheProvider + */ + public $cache; + + public function __construct(?FileReferenceCacheProvider $cache = null) + { + $this->cache = $cache; + } + + /** + * @return array + */ + public function getDeletedReferencedFiles(): array + { + if (self::$deleted_files === null) { + self::$deleted_files = array_filter( + array_keys(self::$file_references), + function (string $file_name): bool { + return !file_exists($file_name); + } + ); + } + + return self::$deleted_files; + } + + /** + * @param lowercase-string $fq_class_name_lc + */ + public function addNonMethodReferenceToClass(string $source_file, string $fq_class_name_lc): void + { + self::$nonmethod_references_to_classes[$fq_class_name_lc][$source_file] = true; + } + + /** + * @return array> + */ + public function getAllNonMethodReferencesToClasses(): array + { + return self::$nonmethod_references_to_classes; + } + + /** + * @param array> $references + * + */ + public function addNonMethodReferencesToClasses(array $references): void + { + foreach ($references as $key => $reference) { + if (isset(self::$nonmethod_references_to_classes[$key])) { + self::$nonmethod_references_to_classes[$key] = array_merge( + $reference, + self::$nonmethod_references_to_classes[$key] + ); + } else { + self::$nonmethod_references_to_classes[$key] = $reference; + } + } + } + + /** + * @param array $map + */ + public function addClassLikeFiles(array $map) : void + { + self::$classlike_files += $map; + } + + public function addFileReferenceToClassMember(string $source_file, string $referenced_member_id): void + { + self::$file_references_to_class_members[$referenced_member_id][$source_file] = true; + } + + public function addFileReferenceToMissingClassMember(string $source_file, string $referenced_member_id): void + { + self::$file_references_to_missing_class_members[$referenced_member_id][$source_file] = true; + } + + /** + * @return array> + */ + public function getAllFileReferencesToClassMembers(): array + { + return self::$file_references_to_class_members; + } + + /** + * @return array> + */ + public function getAllFileReferencesToMissingClassMembers(): array + { + return self::$file_references_to_missing_class_members; + } + + /** + * @param array> $references + * + */ + public function addFileReferencesToClassMembers(array $references): void + { + foreach ($references as $key => $reference) { + if (isset(self::$file_references_to_class_members[$key])) { + self::$file_references_to_class_members[$key] = array_merge( + $reference, + self::$file_references_to_class_members[$key] + ); + } else { + self::$file_references_to_class_members[$key] = $reference; + } + } + } + + /** + * @param array> $references + * + */ + public function addFileReferencesToMissingClassMembers(array $references): void + { + foreach ($references as $key => $reference) { + if (isset(self::$file_references_to_missing_class_members[$key])) { + self::$file_references_to_missing_class_members[$key] = array_merge( + $reference, + self::$file_references_to_missing_class_members[$key] + ); + } else { + self::$file_references_to_missing_class_members[$key] = $reference; + } + } + } + + public function addFileInheritanceToClass(string $source_file, string $fq_class_name_lc): void + { + self::$files_inheriting_classes[$fq_class_name_lc][$source_file] = true; + } + + public function addMethodParamUse(string $method_id, int $offset, string $referencing_method_id): void + { + self::$method_param_uses[$method_id][$offset][$referencing_method_id] = true; + } + + /** + * @return array + */ + private function calculateFilesReferencingFile(Codebase $codebase, string $file): array + { + $referenced_files = []; + + $file_classes = ClassLikeAnalyzer::getClassesForFile($codebase, $file); + + foreach ($file_classes as $file_class_lc => $_) { + if (isset(self::$nonmethod_references_to_classes[$file_class_lc])) { + $new_files = array_keys(self::$nonmethod_references_to_classes[$file_class_lc]); + + $referenced_files = array_merge( + $referenced_files, + $new_files + ); + } + + if (isset(self::$method_references_to_classes[$file_class_lc])) { + $new_referencing_methods = array_keys(self::$method_references_to_classes[$file_class_lc]); + + foreach ($new_referencing_methods as $new_referencing_method_id) { + $fq_class_name_lc = \explode('::', $new_referencing_method_id)[0]; + + try { + $referenced_files[] = $codebase->scanner->getClassLikeFilePath($fq_class_name_lc); + } catch (\UnexpectedValueException $e) { + if (isset(self::$classlike_files[$fq_class_name_lc])) { + $referenced_files[] = self::$classlike_files[$fq_class_name_lc]; + } + } + } + } + } + + return array_unique($referenced_files); + } + + /** + * @return array + */ + private function calculateFilesInheritingFile(Codebase $codebase, string $file): array + { + $referenced_files = []; + + $file_classes = ClassLikeAnalyzer::getClassesForFile($codebase, $file); + + foreach ($file_classes as $file_class_lc => $_) { + if (isset(self::$files_inheriting_classes[$file_class_lc])) { + $referenced_files = array_merge( + $referenced_files, + array_keys(self::$files_inheriting_classes[$file_class_lc]) + ); + } + } + + return array_unique($referenced_files); + } + + public function removeDeletedFilesFromReferences(): void + { + $deleted_files = self::getDeletedReferencedFiles(); + + if ($deleted_files) { + foreach ($deleted_files as $file) { + unset(self::$file_references[$file]); + } + + if ($this->cache) { + $this->cache->setCachedFileReferences(self::$file_references); + } + } + } + + /** + * @return array + */ + public function getFilesReferencingFile(string $file): array + { + return isset(self::$file_references[$file]['a']) ? self::$file_references[$file]['a'] : []; + } + + /** + * @return array + */ + public function getFilesInheritingFromFile(string $file): array + { + return isset(self::$file_references[$file]['i']) ? self::$file_references[$file]['i'] : []; + } + + /** + * @return array> + */ + public function getAllMethodReferencesToClassMembers(): array + { + return self::$method_references_to_class_members; + } + + /** + * @return array> + */ + public function getAllMethodReferencesToClasses(): array + { + return self::$method_references_to_classes; + } + + /** + * @return array> + */ + public function getAllMethodReferencesToMissingClassMembers(): array + { + return self::$method_references_to_missing_class_members; + } + + /** + * @return array> + */ + public function getAllReferencesToMixedMemberNames(): array + { + return self::$references_to_mixed_member_names; + } + + /** + * @return array>> + */ + public function getAllMethodParamUses(): array + { + return self::$method_param_uses; + } + + /** + * @psalm-suppress MixedPropertyTypeCoercion + */ + public function loadReferenceCache(bool $force_reload = true): bool + { + if ($this->cache && (!$this->loaded_from_cache || $force_reload)) { + $this->loaded_from_cache = true; + + $file_references = $this->cache->getCachedFileReferences(); + + if ($file_references === null) { + return false; + } + + self::$file_references = $file_references; + + $nonmethod_references_to_classes = $this->cache->getCachedNonMethodClassReferences(); + + if ($nonmethod_references_to_classes === null) { + return false; + } + + self::$nonmethod_references_to_classes = $nonmethod_references_to_classes; + + $method_references_to_classes = $this->cache->getCachedMethodClassReferences(); + + if ($method_references_to_classes === null) { + return false; + } + + self::$method_references_to_classes = $method_references_to_classes; + + $method_references_to_class_members = $this->cache->getCachedMethodMemberReferences(); + + if ($method_references_to_class_members === null) { + return false; + } + + self::$method_references_to_class_members = $method_references_to_class_members; + + $method_references_to_missing_class_members = $this->cache->getCachedMethodMissingMemberReferences(); + + if ($method_references_to_missing_class_members === null) { + return false; + } + + self::$method_references_to_missing_class_members = $method_references_to_missing_class_members; + + $file_references_to_class_members = $this->cache->getCachedFileMemberReferences(); + + if ($file_references_to_class_members === null) { + return false; + } + + self::$file_references_to_class_members = $file_references_to_class_members; + + $file_references_to_missing_class_members = $this->cache->getCachedFileMissingMemberReferences(); + + if ($file_references_to_missing_class_members === null) { + return false; + } + + self::$file_references_to_missing_class_members = $file_references_to_missing_class_members; + + $references_to_mixed_member_names = $this->cache->getCachedMixedMemberNameReferences(); + + if ($references_to_mixed_member_names === null) { + return false; + } + + self::$references_to_mixed_member_names = $references_to_mixed_member_names; + + $analyzed_methods = $this->cache->getAnalyzedMethodCache(); + + if ($analyzed_methods === false) { + return false; + } + + self::$analyzed_methods = $analyzed_methods; + + $issues = $this->cache->getCachedIssues(); + + if ($issues === null) { + return false; + } + + self::$issues = $issues; + + $method_param_uses = $this->cache->getCachedMethodParamUses(); + + if ($method_param_uses === null) { + return false; + } + + self::$method_param_uses = $method_param_uses; + + $mixed_counts = $this->cache->getTypeCoverage(); + + if ($mixed_counts === false) { + return false; + } + + self::$mixed_counts = $mixed_counts; + + $classlike_files = $this->cache->getCachedClassLikeFiles(); + + if ($classlike_files === null) { + return false; + } + + self::$classlike_files = $classlike_files; + + self::$file_maps = $this->cache->getFileMapCache() ?: []; + + return true; + } + + return false; + } + + /** + * @param array $visited_files + * + */ + public function updateReferenceCache(Codebase $codebase, array $visited_files): void + { + foreach ($visited_files as $file => $_) { + $all_file_references = array_unique( + array_merge( + isset(self::$file_references[$file]['a']) ? self::$file_references[$file]['a'] : [], + $this->calculateFilesReferencingFile($codebase, $file) + ) + ); + + $inheritance_references = array_unique( + array_merge( + isset(self::$file_references[$file]['i']) ? self::$file_references[$file]['i'] : [], + $this->calculateFilesInheritingFile($codebase, $file) + ) + ); + + self::$file_references[$file] = [ + 'a' => $all_file_references, + 'i' => $inheritance_references, + ]; + } + + if ($this->cache) { + $this->cache->setCachedFileReferences(self::$file_references); + $this->cache->setCachedMethodClassReferences(self::$method_references_to_classes); + $this->cache->setCachedNonMethodClassReferences(self::$nonmethod_references_to_classes); + $this->cache->setCachedMethodMemberReferences(self::$method_references_to_class_members); + $this->cache->setCachedFileMemberReferences(self::$file_references_to_class_members); + $this->cache->setCachedMethodMissingMemberReferences(self::$method_references_to_missing_class_members); + $this->cache->setCachedFileMissingMemberReferences(self::$file_references_to_missing_class_members); + $this->cache->setCachedMixedMemberNameReferences(self::$references_to_mixed_member_names); + $this->cache->setCachedMethodParamUses(self::$method_param_uses); + $this->cache->setCachedIssues(self::$issues); + $this->cache->setCachedClassLikeFiles(self::$classlike_files); + $this->cache->setFileMapCache(self::$file_maps); + $this->cache->setTypeCoverage(self::$mixed_counts); + $this->cache->setAnalyzedMethodCache(self::$analyzed_methods); + } + } + + /** + * @param lowercase-string $fq_class_name_lc + */ + public function addMethodReferenceToClass(string $calling_function_id, string $fq_class_name_lc): void + { + if (!isset(self::$method_references_to_classes[$fq_class_name_lc])) { + self::$method_references_to_classes[$fq_class_name_lc] = [$calling_function_id => true]; + } else { + self::$method_references_to_classes[$fq_class_name_lc][$calling_function_id] = true; + } + } + + public function addMethodReferenceToClassMember(string $calling_function_id, string $referenced_member_id): void + { + if (!isset(self::$method_references_to_class_members[$referenced_member_id])) { + self::$method_references_to_class_members[$referenced_member_id] = [$calling_function_id => true]; + } else { + self::$method_references_to_class_members[$referenced_member_id][$calling_function_id] = true; + } + } + + public function addMethodReferenceToMissingClassMember( + string $calling_function_id, + string $referenced_member_id + ): void { + if (!isset(self::$method_references_to_missing_class_members[$referenced_member_id])) { + self::$method_references_to_missing_class_members[$referenced_member_id] = [$calling_function_id => true]; + } else { + self::$method_references_to_missing_class_members[$referenced_member_id][$calling_function_id] = true; + } + } + + public function addCallingLocationForClassMethod(CodeLocation $code_location, string $referenced_member_id): void + { + if (!isset(self::$class_method_locations[$referenced_member_id])) { + self::$class_method_locations[$referenced_member_id] = [$code_location]; + } else { + self::$class_method_locations[$referenced_member_id][] = $code_location; + } + } + + public function addCallingLocationForClassProperty( + CodeLocation $code_location, + string $referenced_property_id + ): void { + if (!isset(self::$class_property_locations[$referenced_property_id])) { + self::$class_property_locations[$referenced_property_id] = [$code_location]; + } else { + self::$class_property_locations[$referenced_property_id][] = $code_location; + } + } + + public function addCallingLocationForClass(CodeLocation $code_location, string $referenced_class): void + { + if (!isset(self::$class_locations[$referenced_class])) { + self::$class_locations[$referenced_class] = [$code_location]; + } else { + self::$class_locations[$referenced_class][] = $code_location; + } + } + + public function isClassMethodReferenced(string $method_id) : bool + { + return !empty(self::$file_references_to_class_members[$method_id]) + || !empty(self::$method_references_to_class_members[$method_id]); + } + + public function isClassPropertyReferenced(string $property_id) : bool + { + return !empty(self::$file_references_to_class_members[$property_id]) + || !empty(self::$method_references_to_class_members[$property_id]); + } + + public function isClassReferenced(string $fq_class_name_lc) : bool + { + return isset(self::$method_references_to_classes[$fq_class_name_lc]) + || isset(self::$nonmethod_references_to_classes[$fq_class_name_lc]); + } + + public function isMethodParamUsed(string $method_id, int $offset) : bool + { + return !empty(self::$method_param_uses[$method_id][$offset]); + } + + /** + * @param array> $references + * + */ + public function setNonMethodReferencesToClasses(array $references): void + { + self::$nonmethod_references_to_classes = $references; + } + + /** + * @return array> + */ + public function getAllClassMethodLocations() : array + { + return self::$class_method_locations; + } + + /** + * @return array> + */ + public function getAllClassPropertyLocations() : array + { + return self::$class_property_locations; + } + + /** + * @return array> + */ + public function getAllClassLocations() : array + { + return self::$class_locations; + } + + /** + * @return array + */ + public function getClassMethodLocations(string $method_id) : array + { + return self::$class_method_locations[$method_id] ?? []; + } + + /** + * @return array + */ + public function getClassPropertyLocations(string $property_id) : array + { + return self::$class_property_locations[$property_id] ?? []; + } + + /** + * @return array + */ + public function getClassLocations(string $fq_class_name_lc) : array + { + return self::$class_locations[$fq_class_name_lc] ?? []; + } + + /** + * @param array> $references + * + */ + public function addMethodReferencesToClassMembers(array $references): void + { + foreach ($references as $key => $reference) { + if (isset(self::$method_references_to_class_members[$key])) { + self::$method_references_to_class_members[$key] = array_merge( + $reference, + self::$method_references_to_class_members[$key] + ); + } else { + self::$method_references_to_class_members[$key] = $reference; + } + } + } + + /** + * @param array> $references + * + */ + public function addMethodReferencesToClasses(array $references): void + { + foreach ($references as $key => $reference) { + if (isset(self::$method_references_to_classes[$key])) { + self::$method_references_to_classes[$key] = array_merge( + $reference, + self::$method_references_to_classes[$key] + ); + } else { + self::$method_references_to_classes[$key] = $reference; + } + } + } + + /** + * @param array> $references + * + */ + public function addMethodReferencesToMissingClassMembers(array $references): void + { + foreach ($references as $key => $reference) { + if (isset(self::$method_references_to_missing_class_members[$key])) { + self::$method_references_to_missing_class_members[$key] = array_merge( + $reference, + self::$method_references_to_missing_class_members[$key] + ); + } else { + self::$method_references_to_missing_class_members[$key] = $reference; + } + } + } + + /** + * @param array>> $references + * + */ + public function addMethodParamUses(array $references): void + { + foreach ($references as $method_id => $method_param_uses) { + if (isset(self::$method_param_uses[$method_id])) { + foreach ($method_param_uses as $offset => $reference_map) { + if (isset(self::$method_param_uses[$method_id][$offset])) { + self::$method_param_uses[$method_id][$offset] = array_merge( + self::$method_param_uses[$method_id][$offset], + $reference_map + ); + } else { + self::$method_param_uses[$method_id][$offset] = $reference_map; + } + } + } else { + self::$method_param_uses[$method_id] = $method_param_uses; + } + } + } + + /** + * @param array> $references + * + */ + public function setCallingMethodReferencesToClasses(array $references): void + { + self::$method_references_to_classes = $references; + } + + /** + * @param array> $references + * + */ + public function setCallingMethodReferencesToClassMembers(array $references): void + { + self::$method_references_to_class_members = $references; + } + + /** + * @param array> $references + * + */ + public function setCallingMethodReferencesToMissingClassMembers(array $references): void + { + self::$method_references_to_missing_class_members = $references; + } + + /** + * @param array> $references + * + */ + public function setFileReferencesToClassMembers(array $references): void + { + self::$file_references_to_class_members = $references; + } + + /** + * @param array> $references + * + */ + public function setFileReferencesToMissingClassMembers(array $references): void + { + self::$file_references_to_missing_class_members = $references; + } + + /** + * @param array> $references + * + */ + public function setReferencesToMixedMemberNames(array $references): void + { + self::$references_to_mixed_member_names = $references; + } + + /** + * @param array>> $references + * + */ + public function setMethodParamUses(array $references): void + { + self::$method_param_uses = $references; + } + + /** + * @param array> $references + * + */ + public function addClassMethodLocations(array $references): void + { + foreach ($references as $referenced_member_id => $locations) { + if (isset(self::$class_method_locations[$referenced_member_id])) { + self::$class_method_locations[$referenced_member_id] = array_merge( + self::$class_method_locations[$referenced_member_id], + $locations + ); + } else { + self::$class_method_locations[$referenced_member_id] = $locations; + } + } + } + + /** + * @param array> $references + * + */ + public function addClassPropertyLocations(array $references): void + { + foreach ($references as $referenced_member_id => $locations) { + if (isset(self::$class_property_locations[$referenced_member_id])) { + self::$class_property_locations[$referenced_member_id] = array_merge( + self::$class_property_locations[$referenced_member_id], + $locations + ); + } else { + self::$class_property_locations[$referenced_member_id] = $locations; + } + } + } + + /** + * @param array> $references + * + */ + public function addClassLocations(array $references): void + { + foreach ($references as $referenced_member_id => $locations) { + if (isset(self::$class_locations[$referenced_member_id])) { + self::$class_locations[$referenced_member_id] = array_merge( + self::$class_locations[$referenced_member_id], + $locations + ); + } else { + self::$class_locations[$referenced_member_id] = $locations; + } + } + } + + /** + * @return array> + */ + public function getExistingIssues() : array + { + return self::$issues; + } + + public function clearExistingIssuesForFile(string $file_path): void + { + unset(self::$issues[$file_path]); + } + + public function clearExistingFileMapsForFile(string $file_path): void + { + unset(self::$file_maps[$file_path]); + } + + public function addIssue(string $file_path, IssueData $issue): void + { + // don’t save parse errors ever, as they're not responsive to AST diffing + if ($issue->type === 'ParseError') { + return; + } + + if (!isset(self::$issues[$file_path])) { + self::$issues[$file_path] = [$issue]; + } else { + self::$issues[$file_path][] = $issue; + } + } + + /** + * @param array> $analyzed_methods + * + */ + public function setAnalyzedMethods(array $analyzed_methods): void + { + self::$analyzed_methods = $analyzed_methods; + } + + /** + * @param array $file_maps + */ + public function setFileMaps(array $file_maps) : void + { + self::$file_maps = $file_maps; + } + + /** + * @return array + */ + public function getTypeCoverage(): array + { + return self::$mixed_counts; + } + + /** + * @param array $mixed_counts + * + */ + public function setTypeCoverage(array $mixed_counts): void + { + self::$mixed_counts = array_merge(self::$mixed_counts, $mixed_counts); + } + + /** + * @return array> + */ + public function getAnalyzedMethods(): array + { + return self::$analyzed_methods; + } + + /** + * @return array + */ + public function getFileMaps(): array + { + return self::$file_maps; + } + + public static function clearCache(): void + { + self::$files_inheriting_classes = []; + self::$deleted_files = null; + self::$file_references = []; + self::$file_references_to_class_members = []; + self::$method_references_to_class_members = []; + self::$method_references_to_classes = []; + self::$nonmethod_references_to_classes = []; + self::$file_references_to_missing_class_members = []; + self::$method_references_to_missing_class_members = []; + self::$references_to_mixed_member_names = []; + self::$class_method_locations = []; + self::$class_property_locations = []; + self::$class_locations = []; + self::$analyzed_methods = []; + self::$issues = []; + self::$file_maps = []; + self::$method_param_uses = []; + self::$classlike_files = []; + self::$mixed_counts = []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageCacheProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageCacheProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..cd561ca4b7f074378110434afd4f67bc4c088f7f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageCacheProvider.php @@ -0,0 +1,169 @@ +config = $config; + + $storage_dir = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'Storage' . DIRECTORY_SEPARATOR; + + $dependent_files = [ + $storage_dir . 'FileStorage.php', + $storage_dir . 'FunctionLikeStorage.php', + $storage_dir . 'ClassLikeStorage.php', + $storage_dir . 'MethodStorage.php', + $storage_dir . 'FunctionLikeParameter.php', + ]; + + if ($config->after_visit_classlikes) { + $dependent_files = array_merge($dependent_files, $config->plugin_paths); + } + + foreach ($dependent_files as $dependent_file_path) { + if (!file_exists($dependent_file_path)) { + throw new \UnexpectedValueException($dependent_file_path . ' must exist'); + } + + $this->modified_timestamps .= ' ' . filemtime($dependent_file_path); + } + + $this->modified_timestamps .= $this->config->hash; + } + + public function writeToCache(FileStorage $storage, string $file_contents): void + { + $file_path = strtolower($storage->file_path); + $cache_location = $this->getCacheLocationForPath($file_path, true); + $storage->hash = $this->getCacheHash($file_path, $file_contents); + + if ($this->config->use_igbinary) { + file_put_contents($cache_location, igbinary_serialize($storage)); + } else { + file_put_contents($cache_location, serialize($storage)); + } + } + + public function getLatestFromCache(string $file_path, string $file_contents): ?FileStorage + { + $file_path = strtolower($file_path); + $cached_value = $this->loadFromCache($file_path); + + if (!$cached_value) { + return null; + } + + $cache_hash = $this->getCacheHash($file_path, $file_contents); + + /** @psalm-suppress TypeDoesNotContainType */ + if (@get_class($cached_value) === '__PHP_Incomplete_Class' + || $cache_hash !== $cached_value->hash + ) { + $this->removeCacheForFile($file_path); + + return null; + } + + return $cached_value; + } + + public function removeCacheForFile(string $file_path): void + { + $cache_path = $this->getCacheLocationForPath($file_path); + + if (file_exists($cache_path)) { + unlink($cache_path); + } + } + + private function getCacheHash(string $file_path, string $file_contents): string + { + return sha1(strtolower($file_path) . ' ' . $file_contents . $this->modified_timestamps); + } + + /** + * @psalm-suppress MixedAssignment + */ + private function loadFromCache(string $file_path): ?FileStorage + { + $cache_location = $this->getCacheLocationForPath($file_path); + + if (file_exists($cache_location)) { + if ($this->config->use_igbinary) { + $storage = igbinary_unserialize((string)file_get_contents($cache_location)); + + if ($storage instanceof FileStorage) { + return $storage; + } + + return null; + } + + $storage = unserialize((string)file_get_contents($cache_location)); + + if ($storage instanceof FileStorage) { + return $storage; + } + + return null; + } + + return null; + } + + private function getCacheLocationForPath(string $file_path, bool $create_directory = false): string + { + $root_cache_directory = $this->config->getCacheDirectory(); + + if (!$root_cache_directory) { + throw new \UnexpectedValueException('No cache directory defined'); + } + + $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_STORAGE_CACHE_DIRECTORY; + + if ($create_directory && !is_dir($parser_cache_directory)) { + mkdir($parser_cache_directory, 0777, true); + } + + return $parser_cache_directory + . DIRECTORY_SEPARATOR + . sha1($file_path) + . ($this->config->use_igbinary ? '-igbinary' : ''); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..9ac09718ae1b68d96428c460db5a33142b260555 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageProvider.php @@ -0,0 +1,129 @@ + + */ + private static $storage = []; + + /** + * A list of data useful to analyse new files + * Storing this statically is much faster (at least in PHP 7.2.1) + * + * @var array + */ + private static $new_storage = []; + + /** + * @var ?FileStorageCacheProvider + */ + public $cache; + + public function __construct(?FileStorageCacheProvider $cache = null) + { + $this->cache = $cache; + } + + public function get(string $file_path): FileStorage + { + $file_path = strtolower($file_path); + + if (!isset(self::$storage[$file_path])) { + throw new \InvalidArgumentException('Could not get file storage for ' . $file_path); + } + + return self::$storage[$file_path]; + } + + public function remove(string $file_path): void + { + unset(self::$storage[strtolower($file_path)]); + } + + public function has(string $file_path, ?string $file_contents = null): bool + { + $file_path = strtolower($file_path); + + if (isset(self::$storage[$file_path])) { + return true; + } + + if ($file_contents === null) { + return false; + } + + if (!$this->cache) { + return false; + } + + $cached_value = $this->cache->getLatestFromCache($file_path, $file_contents); + + if (!$cached_value) { + return false; + } + + self::$storage[$file_path] = $cached_value; + self::$new_storage[$file_path] = $cached_value; + + return true; + } + + /** + * @return array + */ + public function getAll(): array + { + return self::$storage; + } + + /** + * @return array + */ + public function getNew(): array + { + return self::$new_storage; + } + + /** + * @param array $more + * + */ + public function addMore(array $more): void + { + self::$new_storage = array_merge(self::$new_storage, $more); + self::$storage = array_merge(self::$storage, $more); + } + + public function create(string $file_path): FileStorage + { + $file_path_lc = strtolower($file_path); + + $storage = new FileStorage($file_path); + self::$storage[$file_path_lc] = $storage; + self::$new_storage[$file_path_lc] = $storage; + + return $storage; + } + + public static function deleteAll(): void + { + self::$storage = []; + } + + public static function populated(): void + { + self::$new_storage = []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FunctionExistenceProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FunctionExistenceProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..050c46ffd1390e5288c02322623972dfb69b9ff6 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FunctionExistenceProvider.php @@ -0,0 +1,79 @@ + + * > + */ + private static $handlers = []; + + public function __construct() + { + self::$handlers = []; + } + + /** + * @param class-string $class + * + */ + public function registerClass(string $class): void + { + $callable = \Closure::fromCallable([$class, 'doesFunctionExist']); + + foreach ($class::getFunctionIds() as $function_id) { + $this->registerClosure($function_id, $callable); + } + } + + /** + * /** + * @param \Closure( + * StatementsSource, + * string + * ) : ?bool $c + * + */ + public function registerClosure(string $function_id, \Closure $c): void + { + self::$handlers[$function_id][] = $c; + } + + public function has(string $function_id) : bool + { + return isset(self::$handlers[strtolower($function_id)]); + } + + /** + * @param array $call_args + * + */ + public function doesFunctionExist( + StatementsSource $statements_source, + string $function_id + ): ?bool { + foreach (self::$handlers[strtolower($function_id)] as $function_handler) { + $function_exists = $function_handler( + $statements_source, + $function_id + ); + + if ($function_exists !== null) { + return $function_exists; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FunctionParamsProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FunctionParamsProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..7c24735c06fc3c435540050440ff0a4fbda1d49e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FunctionParamsProvider.php @@ -0,0 +1,93 @@ +, + * ?Context=, + * ?CodeLocation= + * ) : ?array> + * > + */ + private static $handlers = []; + + public function __construct() + { + self::$handlers = []; + } + + /** + * @param class-string $class + * + */ + public function registerClass(string $class): void + { + $callable = \Closure::fromCallable([$class, 'getFunctionParams']); + + foreach ($class::getFunctionIds() as $function_id) { + $this->registerClosure($function_id, $callable); + } + } + + /** + * @param \Closure( + * StatementsSource, + * string, + * array, + * ?Context=, + * ?CodeLocation= + * ) : ?array $c + * + */ + public function registerClosure(string $fq_classlike_name, \Closure $c): void + { + self::$handlers[strtolower($fq_classlike_name)][] = $c; + } + + public function has(string $fq_classlike_name) : bool + { + return isset(self::$handlers[strtolower($fq_classlike_name)]); + } + + /** + * @param array $call_args + * + * @return ?array + */ + public function getFunctionParams( + StatementsSource $statements_source, + string $function_id, + array $call_args, + ?Context $context = null, + ?CodeLocation $code_location = null + ): ?array { + foreach (self::$handlers[strtolower($function_id)] as $class_handler) { + $result = $class_handler( + $statements_source, + $function_id, + $call_args, + $context, + $code_location + ); + + if ($result) { + return $result; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..f92660e2d0a16152570ef9366515fb0efda32e51 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php @@ -0,0 +1,122 @@ +, + * Context, + * CodeLocation + * ) : ?Type\Union> + * > + */ + private static $handlers = []; + + public function __construct() + { + self::$handlers = []; + + $this->registerClass(ReturnTypeProvider\ArrayChunkReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayColumnReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayFilterReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayMapReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayMergeReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayPadReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayPointerAdjustmentReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayPopReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayRandReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayReduceReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArraySliceReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayReverseReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayUniqueReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayValuesReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ArrayFillReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\FilterVarReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\IteratorToArrayReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ParseUrlReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\StrReplaceReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\VersionCompareReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\MktimeReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ExplodeReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\GetObjectVarsReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\GetClassMethodsReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\FirstArgStringReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\HexdecReturnTypeProvider::class); + } + + /** + * @param class-string $class + * + */ + public function registerClass(string $class): void + { + $callable = \Closure::fromCallable([$class, 'getFunctionReturnType']); + + foreach ($class::getFunctionIds() as $function_id) { + $this->registerClosure($function_id, $callable); + } + } + + /** + * /** + * @param \Closure( + * StatementsSource, + * non-empty-string, + * array, + * Context, + * CodeLocation + * ) : ?Type\Union $c + * + */ + public function registerClosure(string $function_id, \Closure $c): void + { + self::$handlers[$function_id][] = $c; + } + + public function has(string $function_id) : bool + { + return isset(self::$handlers[strtolower($function_id)]); + } + + /** + * @param non-empty-string $function_id + * @param array $call_args + * + */ + public function getReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ): ?Type\Union { + foreach (self::$handlers[strtolower($function_id)] as $function_handler) { + $return_type = $function_handler( + $statements_source, + $function_id, + $call_args, + $context, + $code_location + ); + + if ($return_type) { + return $return_type; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodExistenceProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodExistenceProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..4686be0c90e3768ba57cabbb00ae0dd8dcaf89f1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodExistenceProvider.php @@ -0,0 +1,88 @@ + + * > + */ + private static $handlers = []; + + public function __construct() + { + self::$handlers = []; + } + + /** + * @param class-string $class + * + */ + public function registerClass(string $class): void + { + $callable = \Closure::fromCallable([$class, 'doesMethodExist']); + + foreach ($class::getClassLikeNames() as $fq_classlike_name) { + $this->registerClosure($fq_classlike_name, $callable); + } + } + + /** + * /** + * @param \Closure( + * string, + * string, + * ?StatementsSource=, + * ?CodeLocation + * ) : ?bool $c + * + */ + public function registerClosure(string $fq_classlike_name, \Closure $c): void + { + self::$handlers[strtolower($fq_classlike_name)][] = $c; + } + + public function has(string $fq_classlike_name) : bool + { + return isset(self::$handlers[strtolower($fq_classlike_name)]); + } + + /** + * @param array $call_args + * + */ + public function doesMethodExist( + string $fq_classlike_name, + string $method_name_lowercase, + ?StatementsSource $source = null, + ?CodeLocation $code_location = null + ): ?bool { + foreach (self::$handlers[strtolower($fq_classlike_name)] as $method_handler) { + $method_exists = $method_handler( + $fq_classlike_name, + $method_name_lowercase, + $source, + $code_location + ); + + if ($method_exists !== null) { + return $method_exists; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodParamsProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodParamsProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..e8d54d5c4349e58ec3fb805fee0f52f72bc74c2a --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodParamsProvider.php @@ -0,0 +1,99 @@ +=, + * ?StatementsSource=, + * ?Context=, + * ?CodeLocation= + * ) : ?array> + * > + */ + private static $handlers = []; + + public function __construct() + { + self::$handlers = []; + + $this->registerClass(ReturnTypeProvider\PdoStatementSetFetchMode::class); + } + + /** + * @param class-string $class + * + */ + public function registerClass(string $class): void + { + $callable = \Closure::fromCallable([$class, 'getMethodParams']); + + foreach ($class::getClassLikeNames() as $fq_classlike_name) { + $this->registerClosure($fq_classlike_name, $callable); + } + } + + /** + * @param \Closure( + * string, + * string, + * ?array=, + * ?StatementsSource=, + * ?Context=, + * ?CodeLocation= + * ) : ?array $c + * + */ + public function registerClosure(string $fq_classlike_name, \Closure $c): void + { + self::$handlers[strtolower($fq_classlike_name)][] = $c; + } + + public function has(string $fq_classlike_name) : bool + { + return isset(self::$handlers[strtolower($fq_classlike_name)]); + } + + /** + * @param ?array $call_args + * + * @return ?array + */ + public function getMethodParams( + string $fq_classlike_name, + string $method_name_lowercase, + ?array $call_args = null, + ?StatementsSource $statements_source = null, + ?Context $context = null, + ?CodeLocation $code_location = null + ): ?array { + foreach (self::$handlers[strtolower($fq_classlike_name)] as $class_handler) { + $result = $class_handler( + $fq_classlike_name, + $method_name_lowercase, + $call_args, + $statements_source, + $context, + $code_location + ); + + if ($result !== null) { + return $result; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..b7cbd19532cb7c199867bef7dc8fbe9f1fc00566 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php @@ -0,0 +1,115 @@ +, + * Context, + * CodeLocation, + * ?array=, + * ?string=, + * ?lowercase-string= + * ) : ?Type\Union> + * > + */ + private static $handlers = []; + + public function __construct() + { + self::$handlers = []; + + $this->registerClass(ReturnTypeProvider\DomNodeAppendChild::class); + $this->registerClass(ReturnTypeProvider\SimpleXmlElementAsXml::class); + $this->registerClass(ReturnTypeProvider\PdoStatementReturnTypeProvider::class); + $this->registerClass(ReturnTypeProvider\ClosureFromCallableReturnTypeProvider::class); + } + + /** + * @param class-string $class + * + */ + public function registerClass(string $class): void + { + $callable = \Closure::fromCallable([$class, 'getMethodReturnType']); + + foreach ($class::getClassLikeNames() as $fq_classlike_name) { + $this->registerClosure($fq_classlike_name, $callable); + } + } + + /** + * @param \Closure( + * StatementsSource, + * string, + * lowercase-string, + * array, + * Context, + * CodeLocation, + * ?array=, + * ?string=, + * ?lowercase-string= + * ) : ?Type\Union $c + * + */ + public function registerClosure(string $fq_classlike_name, \Closure $c): void + { + self::$handlers[strtolower($fq_classlike_name)][] = $c; + } + + public function has(string $fq_classlike_name) : bool + { + return isset(self::$handlers[strtolower($fq_classlike_name)]); + } + + /** + * @param array $call_args + * @param ?array $template_type_parameters + * + */ + public function getReturnType( + StatementsSource $statements_source, + string $fq_classlike_name, + string $method_name, + array $call_args, + Context $context, + CodeLocation $code_location, + ?array $template_type_parameters = null, + ?string $called_fq_classlike_name = null, + ?string $called_method_name = null + ): ?Type\Union { + foreach (self::$handlers[strtolower($fq_classlike_name)] as $class_handler) { + $result = $class_handler( + $statements_source, + $fq_classlike_name, + strtolower($method_name), + $call_args, + $context, + $code_location, + $template_type_parameters, + $called_fq_classlike_name, + $called_method_name ? strtolower($called_method_name) : null + ); + + if ($result) { + return $result; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodVisibilityProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodVisibilityProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..cdf5a4d642831b03cf382a42fb2a86c16ad247f8 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/MethodVisibilityProvider.php @@ -0,0 +1,93 @@ + + * > + */ + private static $handlers = []; + + public function __construct() + { + self::$handlers = []; + } + + /** + * @param class-string $class + * + */ + public function registerClass(string $class): void + { + $callable = \Closure::fromCallable([$class, 'isMethodVisible']); + + foreach ($class::getClassLikeNames() as $fq_classlike_name) { + $this->registerClosure($fq_classlike_name, $callable); + } + } + + /** + * /** + * @param \Closure( + * StatementsSource, + * string, + * string, + * Context, + * ?CodeLocation + * ) : ?bool $c + * + */ + public function registerClosure(string $fq_classlike_name, \Closure $c): void + { + self::$handlers[strtolower($fq_classlike_name)][] = $c; + } + + public function has(string $fq_classlike_name) : bool + { + return isset(self::$handlers[strtolower($fq_classlike_name)]); + } + + /** + * @param array $call_args + * + */ + public function isMethodVisible( + StatementsSource $source, + string $fq_classlike_name, + string $method_name, + Context $context, + ?CodeLocation $code_location = null + ): ?bool { + foreach (self::$handlers[strtolower($fq_classlike_name)] as $method_handler) { + $method_visible = $method_handler( + $source, + $fq_classlike_name, + $method_name, + $context, + $code_location + ); + + if ($method_visible !== null) { + return $method_visible; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/NodeDataProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/NodeDataProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..aad64fc05acb45b3a39633c595f649b0fd05109b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/NodeDataProvider.php @@ -0,0 +1,125 @@ + */ + private $node_types; + + /** @var SplObjectStorage>>|null> */ + private $node_assertions; + + /** @var SplObjectStorage> */ + private $node_if_true_assertions; + + /** @var SplObjectStorage> */ + private $node_if_false_assertions; + + /** @var bool */ + public $cache_assertions = true; + + public function __construct() + { + /** @psalm-suppress PropertyTypeCoercion */ + $this->node_types = new SplObjectStorage(); + /** @psalm-suppress PropertyTypeCoercion */ + $this->node_assertions = new SplObjectStorage(); + /** @psalm-suppress PropertyTypeCoercion */ + $this->node_if_true_assertions = new SplObjectStorage(); + /** @psalm-suppress PropertyTypeCoercion */ + $this->node_if_false_assertions = new SplObjectStorage(); + } + + /** + * @param PhpParser\Node\Expr|PhpParser\Node\Name|PhpParser\Node\Stmt\Return_ $node + */ + public function setType(PhpParser\NodeAbstract $node, Union $type) : void + { + $this->node_types[$node] = $type; + } + + /** + * @param PhpParser\Node\Expr|PhpParser\Node\Name|PhpParser\Node\Stmt\Return_ $node + */ + public function getType(PhpParser\NodeAbstract $node) : ?Union + { + return $this->node_types[$node] ?? null; + } + + /** + * @param array>>|null $assertions + */ + public function setAssertions(PhpParser\Node\Expr $node, ?array $assertions) : void + { + if (!$this->cache_assertions) { + return; + } + + $this->node_assertions[$node] = $assertions; + } + + /** + * @return array>>|null + */ + public function getAssertions(PhpParser\Node\Expr $node) : ?array + { + if (!$this->cache_assertions) { + return null; + } + + return $this->node_assertions[$node] ?? null; + } + + /** + * @param PhpParser\Node\Expr\FuncCall|PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $node + * @param array $assertions + */ + public function setIfTrueAssertions(PhpParser\Node\Expr $node, array $assertions) : void + { + $this->node_if_true_assertions[$node] = $assertions; + } + + /** + * @param PhpParser\Node\Expr\FuncCall|PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $node + * @return array|null + */ + public function getIfTrueAssertions(PhpParser\Node\Expr $node) : ?array + { + return $this->node_if_true_assertions[$node] ?? null; + } + + /** + * @param PhpParser\Node\Expr\FuncCall|PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $node + * @param array $assertions + */ + public function setIfFalseAssertions(PhpParser\Node\Expr $node, array $assertions) : void + { + $this->node_if_false_assertions[$node] = $assertions; + } + + /** + * @param PhpParser\Node\Expr\FuncCall|PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $node + * @return array|null + */ + public function getIfFalseAssertions(PhpParser\Node\Expr $node) : ?array + { + return $this->node_if_false_assertions[$node] ?? null; + } + + public function isPureCompatible(PhpParser\Node\Expr $node) : bool + { + $node_type = self::getType($node); + + return ($node_type && $node_type->reference_free) || isset($node->pure); + } + + public function clearNodeOfTypeAndAssertions(PhpParser\Node\Expr $node) : void + { + unset($this->node_types[$node], $this->node_assertions[$node]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ParserCacheProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ParserCacheProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..931631ff9b5031b2b5e910f11ad491b8578b941f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -0,0 +1,406 @@ +|null + */ + private $existing_file_content_hashes = null; + + /** + * A map of recently-added filename hashes to contents hashes + * + * @var array + */ + private $new_file_content_hashes = []; + + /** + * @var bool + */ + private $use_file_cache; + + /** @var bool */ + private $use_igbinary; + + public function __construct(Config $config, bool $use_file_cache = true) + { + $this->use_igbinary = $config->use_igbinary; + $this->use_file_cache = $use_file_cache; + } + + /** + * @return list|null + * + * @psalm-suppress UndefinedFunction + */ + public function loadStatementsFromCache( + string $file_path, + int $file_modified_time, + string $file_content_hash + ): ?array { + $root_cache_directory = Config::getInstance()->getCacheDirectory(); + + if (!$root_cache_directory) { + return null; + } + + $file_cache_key = $this->getParserCacheKey( + $file_path + ); + + $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY; + + $file_content_hashes = $this->new_file_content_hashes + $this->getExistingFileContentHashes(); + + $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; + + if (isset($file_content_hashes[$file_cache_key]) + && $file_content_hash === $file_content_hashes[$file_cache_key] + && is_readable($cache_location) + && filemtime($cache_location) > $file_modified_time + ) { + if ($this->use_igbinary) { + /** @var list<\PhpParser\Node\Stmt> */ + $stmts = igbinary_unserialize((string)file_get_contents($cache_location)); + } else { + /** @var list<\PhpParser\Node\Stmt> */ + $stmts = unserialize((string)file_get_contents($cache_location)); + } + + return $stmts; + } + + return null; + } + + /** + * @return list|null + * + * @psalm-suppress UndefinedFunction + */ + public function loadExistingStatementsFromCache(string $file_path): ?array + { + $root_cache_directory = Config::getInstance()->getCacheDirectory(); + + if (!$root_cache_directory) { + return null; + } + + $file_cache_key = $this->getParserCacheKey( + $file_path + ); + + $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY; + + $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; + + if (is_readable($cache_location)) { + if ($this->use_igbinary) { + /** @var list<\PhpParser\Node\Stmt> */ + return igbinary_unserialize((string)file_get_contents($cache_location)) ?: null; + } + + /** @var list<\PhpParser\Node\Stmt> */ + return unserialize((string)file_get_contents($cache_location)) ?: null; + } + + return null; + } + + public function loadExistingFileContentsFromCache(string $file_path): ?string + { + if (!$this->use_file_cache) { + return null; + } + + $root_cache_directory = Config::getInstance()->getCacheDirectory(); + + if (!$root_cache_directory) { + return null; + } + + $file_cache_key = $this->getParserCacheKey( + $file_path + ); + + $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_CONTENTS_CACHE_DIRECTORY; + + $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; + + if (is_readable($cache_location)) { + return file_get_contents($cache_location); + } + + return null; + } + + /** + * @return array + */ + private function getExistingFileContentHashes(): array + { + $config = Config::getInstance(); + $root_cache_directory = $config->getCacheDirectory(); + + if ($this->existing_file_content_hashes === null) { + $file_hashes_path = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_HASHES; + + if ($root_cache_directory && is_readable($file_hashes_path)) { + $hashes_encoded = (string) file_get_contents($file_hashes_path); + + if (!$hashes_encoded) { + error_log('Unexpected value when loading from file content hashes'); + $this->existing_file_content_hashes = []; + + return []; + } + + /** @psalm-suppress MixedAssignment */ + $hashes_decoded = json_decode($hashes_encoded, true); + + if (!is_array($hashes_decoded)) { + error_log('Unexpected value ' . gettype($hashes_decoded)); + $this->existing_file_content_hashes = []; + + return []; + } + + /** @var array $hashes_decoded */ + $this->existing_file_content_hashes = $hashes_decoded; + } else { + $this->existing_file_content_hashes = []; + } + } + + return $this->existing_file_content_hashes; + } + + /** + * @param list $stmts + * + * + * @psalm-suppress UndefinedFunction + */ + public function saveStatementsToCache( + string $file_path, + string $file_content_hash, + array $stmts, + bool $touch_only + ): void { + $root_cache_directory = Config::getInstance()->getCacheDirectory(); + + if (!$root_cache_directory) { + return; + } + + $file_cache_key = $this->getParserCacheKey( + $file_path + ); + + $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY; + + $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; + + if ($touch_only) { + touch($cache_location); + } else { + if (!is_dir($parser_cache_directory)) { + mkdir($parser_cache_directory, 0777, true); + } + + if ($this->use_igbinary) { + file_put_contents($cache_location, igbinary_serialize($stmts)); + } else { + file_put_contents($cache_location, serialize($stmts)); + } + + $this->new_file_content_hashes[$file_cache_key] = $file_content_hash; + } + } + + /** + * @return array + */ + public function getNewFileContentHashes(): array + { + return $this->new_file_content_hashes; + } + + /** + * @param array $file_content_hashes + * + */ + public function addNewFileContentHashes(array $file_content_hashes): void + { + $this->new_file_content_hashes = $file_content_hashes + $this->new_file_content_hashes; + } + + public function saveFileContentHashes(): void + { + $root_cache_directory = Config::getInstance()->getCacheDirectory(); + + if (!$root_cache_directory) { + return; + } + + $file_content_hashes = $this->new_file_content_hashes + $this->getExistingFileContentHashes(); + + $file_hashes_path = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_HASHES; + + file_put_contents( + $file_hashes_path, + json_encode($file_content_hashes) + ); + } + + public function cacheFileContents(string $file_path, string $file_contents): void + { + if (!$this->use_file_cache) { + return; + } + + $root_cache_directory = Config::getInstance()->getCacheDirectory(); + + if (!$root_cache_directory) { + return; + } + + $file_cache_key = $this->getParserCacheKey( + $file_path + ); + + $parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::FILE_CONTENTS_CACHE_DIRECTORY; + + $cache_location = $parser_cache_directory . DIRECTORY_SEPARATOR . $file_cache_key; + + if (!is_dir($parser_cache_directory)) { + mkdir($parser_cache_directory, 0777, true); + } + + file_put_contents($cache_location, $file_contents); + } + + public function deleteOldParserCaches(float $time_before): int + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + if ($cache_directory) { + return 0; + } + + $removed_count = 0; + + $cache_directory .= DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY; + + if (is_dir($cache_directory)) { + $directory_files = scandir($cache_directory, SCANDIR_SORT_NONE); + + foreach ($directory_files as $directory_file) { + $full_path = $cache_directory . DIRECTORY_SEPARATOR . $directory_file; + + if ($directory_file[0] === '.') { + continue; + } + + if (filemtime($full_path) < $time_before && is_writable($full_path)) { + unlink($full_path); + ++$removed_count; + } + } + } + + return $removed_count; + } + + public function processSuccessfulRun(): void + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $cache_directory .= DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY; + + if (is_dir($cache_directory)) { + $directory_files = scandir($cache_directory, SCANDIR_SORT_NONE); + + foreach ($directory_files as $directory_file) { + $full_path = $cache_directory . DIRECTORY_SEPARATOR . $directory_file; + + if ($directory_file[0] === '.') { + continue; + } + + touch($full_path); + } + } + } + + /** + * @param array $file_names + */ + public function touchParserCaches(array $file_names, int $min_time): void + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $cache_directory .= DIRECTORY_SEPARATOR . self::PARSER_CACHE_DIRECTORY; + + if (is_dir($cache_directory)) { + foreach ($file_names as $file_name) { + $hash_file_name = $cache_directory . DIRECTORY_SEPARATOR . $this->getParserCacheKey($file_name); + + if (file_exists($hash_file_name)) { + if (filemtime($hash_file_name) < $min_time) { + touch($hash_file_name, $min_time); + } + } + } + } + } + + private function getParserCacheKey(string $file_name): string + { + return md5($file_name) . ($this->use_igbinary ? '-igbinary' : '') . '-r'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ProjectCacheProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ProjectCacheProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..cae6172e26715a8c2f917f6dda20c00cfb2ad178 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ProjectCacheProvider.php @@ -0,0 +1,125 @@ +composer_lock_location = $composer_lock_location; + } + + public function canDiffFiles(): bool + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + return $cache_directory && file_exists($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME); + } + + public function processSuccessfulRun(float $start_time): void + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + if (!$cache_directory) { + return; + } + + $run_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME; + + \touch($run_cache_location, (int)$start_time); + } + + public function getLastRun(): int + { + if ($this->last_run === null) { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + if (file_exists($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME)) { + $this->last_run = \filemtime($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME); + } else { + $this->last_run = 0; + } + } + + return $this->last_run; + } + + public function hasLockfileChanged() : bool + { + if (!file_exists($this->composer_lock_location)) { + return true; + } + + $lockfile_contents = file_get_contents($this->composer_lock_location); + + if (!$lockfile_contents) { + return true; + } + + $sha1 = \sha1($lockfile_contents); + + $changed = $sha1 !== $this->getComposerLockHash(); + + $this->composer_lock_hash = $sha1; + + return $changed; + } + + public function updateComposerLockHash() : void + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + if (!$cache_directory || !$this->composer_lock_hash) { + return; + } + + if (!file_exists($cache_directory)) { + \mkdir($cache_directory, 0777, true); + } + + $lock_hash_location = $cache_directory . DIRECTORY_SEPARATOR . self::COMPOSER_LOCK_HASH; + + file_put_contents($lock_hash_location, $this->composer_lock_hash); + } + + protected function getComposerLockHash() : string + { + if ($this->composer_lock_hash === null) { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + $lock_hash_location = $cache_directory . DIRECTORY_SEPARATOR . self::COMPOSER_LOCK_HASH; + + if (file_exists($lock_hash_location)) { + $this->composer_lock_hash = file_get_contents($lock_hash_location) ?: ''; + } else { + $this->composer_lock_hash = ''; + } + } + + return $this->composer_lock_hash; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/PropertyExistenceProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/PropertyExistenceProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..6eb23fc2fd0649a6028735ed70792103ce81490f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/PropertyExistenceProvider.php @@ -0,0 +1,96 @@ + + * > + */ + private static $handlers = []; + + public function __construct() + { + self::$handlers = []; + } + + /** + * @param class-string $class + * + */ + public function registerClass(string $class): void + { + $callable = \Closure::fromCallable([$class, 'doesPropertyExist']); + + foreach ($class::getClassLikeNames() as $fq_classlike_name) { + $this->registerClosure($fq_classlike_name, $callable); + } + } + + /** + * @param \Closure( + * string, + * string, + * bool, + * ?StatementsSource=, + * ?Context=, + * ?CodeLocation= + * ) : ?bool $c + * + */ + public function registerClosure(string $fq_classlike_name, \Closure $c): void + { + self::$handlers[strtolower($fq_classlike_name)][] = $c; + } + + public function has(string $fq_classlike_name) : bool + { + return isset(self::$handlers[strtolower($fq_classlike_name)]); + } + + /** + * @param array $call_args + * + */ + public function doesPropertyExist( + string $fq_classlike_name, + string $property_name, + bool $read_mode, + ?StatementsSource $source = null, + ?Context $context = null, + ?CodeLocation $code_location = null + ): ?bool { + foreach (self::$handlers[strtolower($fq_classlike_name)] as $property_handler) { + $property_exists = $property_handler( + $fq_classlike_name, + $property_name, + $read_mode, + $source, + $context, + $code_location + ); + + if ($property_exists !== null) { + return $property_exists; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/PropertyTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/PropertyTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..3f82426ec519707ffd04db77ae233c4b54dcb600 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/PropertyTypeProvider.php @@ -0,0 +1,93 @@ + + * > + */ + private static $handlers = []; + + public function __construct() + { + self::$handlers = []; + } + + /** + * @param class-string $class + * + */ + public function registerClass(string $class): void + { + $callable = \Closure::fromCallable([$class, 'getPropertyType']); + + foreach ($class::getClassLikeNames() as $fq_classlike_name) { + $this->registerClosure($fq_classlike_name, $callable); + } + } + + /** + * /** + * @param \Closure( + * string, + * string, + * bool, + * ?StatementsSource=, + * ?Context= + * ) : ?Type\Union $c + * + */ + public function registerClosure(string $fq_classlike_name, \Closure $c): void + { + self::$handlers[strtolower($fq_classlike_name)][] = $c; + } + + public function has(string $fq_classlike_name) : bool + { + return isset(self::$handlers[strtolower($fq_classlike_name)]); + } + + /** + * @param array $call_args + * + */ + public function getPropertyType( + string $fq_classlike_name, + string $property_name, + bool $read_mode, + ?StatementsSource $source = null, + ?Context $context = null + ): ?Type\Union { + foreach (self::$handlers[strtolower($fq_classlike_name)] as $property_handler) { + $property_type = $property_handler( + $fq_classlike_name, + $property_name, + $read_mode, + $source, + $context + ); + + if ($property_type !== null) { + return $property_type; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..981683cc54c52072c823a6afd99bace0b691df87 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php @@ -0,0 +1,97 @@ + + * > + */ + private static $handlers = []; + + public function __construct() + { + self::$handlers = []; + } + + /** + * @param class-string $class + * + */ + public function registerClass(string $class): void + { + $callable = \Closure::fromCallable([$class, 'isPropertyVisible']); + + foreach ($class::getClassLikeNames() as $fq_classlike_name) { + $this->registerClosure($fq_classlike_name, $callable); + } + } + + /** + * /** + * @param \Closure( + * StatementsSource, + * string, + * string, + * bool, + * Context, + * CodeLocation + * ) : ?bool $c + * + */ + public function registerClosure(string $fq_classlike_name, \Closure $c): void + { + self::$handlers[strtolower($fq_classlike_name)][] = $c; + } + + public function has(string $fq_classlike_name) : bool + { + return isset(self::$handlers[strtolower($fq_classlike_name)]); + } + + /** + * @param array $call_args + * + */ + public function isPropertyVisible( + StatementsSource $source, + string $fq_classlike_name, + string $property_name, + bool $read_mode, + Context $context, + CodeLocation $code_location + ): ?bool { + foreach (self::$handlers[strtolower($fq_classlike_name)] as $property_handler) { + $property_visible = $property_handler( + $source, + $fq_classlike_name, + $property_name, + $read_mode, + $context, + $code_location + ); + + if ($property_visible !== null) { + return $property_visible; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/Providers.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/Providers.php new file mode 100644 index 0000000000000000000000000000000000000000..eba376d9b72f373a762252902996db397044b9d3 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/Providers.php @@ -0,0 +1,65 @@ +file_provider = $file_provider; + $this->parser_cache_provider = $parser_cache_provider; + $this->project_cache_provider = $project_cache_provider; + + $this->file_storage_provider = new FileStorageProvider($file_storage_cache_provider); + $this->classlike_storage_provider = new ClassLikeStorageProvider($classlike_storage_cache_provider); + $this->statements_provider = new StatementsProvider( + $file_provider, + $parser_cache_provider, + $file_storage_cache_provider + ); + $this->file_reference_provider = new FileReferenceProvider($file_reference_cache_provider); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayChunkReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayChunkReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..2a744928ea90315bfed7bb4d22b4ea9bfca78706 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayChunkReturnTypeProvider.php @@ -0,0 +1,49 @@ += 2 + && ($array_arg_type = $statements_source->getNodeTypeProvider()->getType($call_args[0]->value)) + && $array_arg_type->isSingle() + && $array_arg_type->hasArray() + && ($array_type = ArrayType::infer($array_arg_type->getAtomicTypes()['array'])) + ) { + $preserve_keys = isset($call_args[2]) + && ($preserve_keys_arg_type = $statements_source->getNodeTypeProvider()->getType($call_args[2]->value)) + && (string) $preserve_keys_arg_type !== 'false'; + + return new Type\Union([ + new Type\Atomic\TList( + new Type\Union([ + $preserve_keys + ? new Type\Atomic\TNonEmptyArray([$array_type->key, $array_type->value]) + : new Type\Atomic\TNonEmptyList($array_type->value) + ]) + ) + ]); + } + + return new Type\Union([new Type\Atomic\TList(Type::getArray())]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..f08b505ab46d24cce18d7f6d013a68c03a87fc1b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php @@ -0,0 +1,118 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $row_shape = null; + $input_array_not_empty = false; + + // calculate row shape + if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value)) + && $first_arg_type->isSingle() + && $first_arg_type->hasArray() + ) { + $input_array = $first_arg_type->getAtomicTypes()['array']; + if ($input_array instanceof Type\Atomic\TKeyedArray) { + $row_type = $input_array->getGenericArrayType()->type_params[1]; + if ($row_type->isSingle() && $row_type->hasArray()) { + $row_shape = $row_type->getAtomicTypes()['array']; + } + } elseif ($input_array instanceof Type\Atomic\TArray) { + $row_type = $input_array->type_params[1]; + if ($row_type->isSingle() && $row_type->hasArray()) { + $row_shape = $row_type->getAtomicTypes()['array']; + } + } elseif ($input_array instanceof Type\Atomic\TList) { + $row_type = $input_array->type_param; + if ($row_type->isSingle() && $row_type->hasArray()) { + $row_shape = $row_type->getAtomicTypes()['array']; + } + } + + $input_array_not_empty = $input_array instanceof Type\Atomic\TNonEmptyList || + $input_array instanceof Type\Atomic\TNonEmptyArray || + $input_array instanceof Type\Atomic\TKeyedArray; + } + + $value_column_name = null; + // calculate value column name + if (($second_arg_type = $statements_source->node_data->getType($call_args[1]->value))) { + if ($second_arg_type->isSingleIntLiteral()) { + $value_column_name = $second_arg_type->getSingleIntLiteral()->value; + } elseif ($second_arg_type->isSingleStringLiteral()) { + $value_column_name = $second_arg_type->getSingleStringLiteral()->value; + } + } + + $key_column_name = null; + $third_arg_type = null; + // calculate key column name + if (isset($call_args[2])) { + $third_arg_type = $statements_source->node_data->getType($call_args[2]->value); + + if ($third_arg_type) { + if ($third_arg_type->isSingleIntLiteral()) { + $key_column_name = $third_arg_type->getSingleIntLiteral()->value; + } elseif ($third_arg_type->isSingleStringLiteral()) { + $key_column_name = $third_arg_type->getSingleStringLiteral()->value; + } + } + } + + $result_key_type = Type::getArrayKey(); + $result_element_type = null; + $have_at_least_one_res = false; + // calculate results + if ($row_shape instanceof Type\Atomic\TKeyedArray) { + if ((null !== $value_column_name) && isset($row_shape->properties[$value_column_name])) { + if ($input_array_not_empty) { + $have_at_least_one_res = true; + } + $result_element_type = $row_shape->properties[$value_column_name]; + } else { + $result_element_type = Type::getMixed(); + } + + if ((null !== $key_column_name) && isset($row_shape->properties[$key_column_name])) { + $result_key_type = $row_shape->properties[$key_column_name]; + } + } + + if (isset($call_args[2]) && (string)$third_arg_type !== 'null') { + $type = $have_at_least_one_res ? + new Type\Atomic\TNonEmptyArray([$result_key_type, $result_element_type ?? Type::getMixed()]) + : new Type\Atomic\TArray([$result_key_type, $result_element_type ?? Type::getMixed()]); + } else { + $type = $have_at_least_one_res ? + new Type\Atomic\TNonEmptyList($result_element_type ?? Type::getMixed()) + : new Type\Atomic\TList($result_element_type ?? Type::getMixed()); + } + + return new Type\Union([$type]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..4b281f2b1f433723ed891705762d20e4ce4b7b3e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php @@ -0,0 +1,61 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $first_arg_type = isset($call_args[0]) ? $statements_source->node_data->getType($call_args[0]->value) : null; + $third_arg_type = isset($call_args[2]) ? $statements_source->node_data->getType($call_args[2]->value) : null; + + if ($third_arg_type) { + if ($first_arg_type + && $first_arg_type->isSingleIntLiteral() + && $first_arg_type->getSingleIntLiteral()->value === 0 + ) { + return new Type\Union([ + new Type\Atomic\TNonEmptyList( + clone $third_arg_type + ) + ]); + } + + return new Type\Union([ + new Type\Atomic\TArray([ + Type::getInt(), + clone $third_arg_type + ]) + ]); + } + + return new Type\Union([ + new Type\Atomic\TArray([ + Type::getInt(), + Type::getMixed() + ]) + ]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..bf3a255e900a74e440ef51890396f27df5d508d4 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php @@ -0,0 +1,294 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof StatementsAnalyzer) { + return Type::getMixed(); + } + + $array_arg = isset($call_args[0]->value) ? $call_args[0]->value : null; + + $first_arg_array = $array_arg + && ($first_arg_type = $statements_source->node_data->getType($array_arg)) + && $first_arg_type->hasType('array') + && ($array_atomic_type = $first_arg_type->getAtomicTypes()['array']) + && ($array_atomic_type instanceof Type\Atomic\TArray + || $array_atomic_type instanceof Type\Atomic\TKeyedArray + || $array_atomic_type instanceof Type\Atomic\TList) + ? $array_atomic_type + : null; + + if (!$first_arg_array) { + return Type::getArray(); + } + + if ($first_arg_array instanceof Type\Atomic\TArray) { + $inner_type = $first_arg_array->type_params[1]; + $key_type = clone $first_arg_array->type_params[0]; + } elseif ($first_arg_array instanceof Type\Atomic\TList) { + $inner_type = $first_arg_array->type_param; + $key_type = Type::getInt(); + } else { + $inner_type = $first_arg_array->getGenericValueType(); + $key_type = $first_arg_array->getGenericKeyType(); + + if (!isset($call_args[1]) && !$first_arg_array->previous_value_type) { + $had_one = count($first_arg_array->properties) === 1; + + $first_arg_array = clone $first_arg_array; + + $new_properties = \array_filter( + array_map( + function ($keyed_type) use ($statements_source, $context) { + $prev_keyed_type = $keyed_type; + + $keyed_type = \Psalm\Internal\Type\AssertionReconciler::reconcile( + '!falsy', + clone $keyed_type, + '', + $statements_source, + $context->inside_loop, + [], + null, + $statements_source->getSuppressedIssues() + ); + + $keyed_type->possibly_undefined = ($prev_keyed_type->hasInt() + && !$prev_keyed_type->hasLiteralInt()) + || $prev_keyed_type->hasFloat() + || $prev_keyed_type->getId() !== $keyed_type->getId(); + + return $keyed_type; + }, + $first_arg_array->properties + ), + function ($keyed_type) { + return !$keyed_type->isEmpty(); + } + ); + + if (!$new_properties) { + return Type::getEmptyArray(); + } + + $first_arg_array->properties = $new_properties; + + $first_arg_array->is_list = $first_arg_array->is_list && $had_one; + $first_arg_array->sealed = false; + + return new Type\Union([$first_arg_array]); + } + } + + if (!isset($call_args[1])) { + $inner_type = \Psalm\Internal\Type\AssertionReconciler::reconcile( + '!falsy', + clone $inner_type, + '', + $statements_source, + $context->inside_loop, + [], + null, + $statements_source->getSuppressedIssues() + ); + + if ($first_arg_array instanceof Type\Atomic\TKeyedArray + && $first_arg_array->is_list + && $key_type->isSingleIntLiteral() + && $key_type->getSingleIntLiteral()->value === 0 + ) { + return new Type\Union([ + new Type\Atomic\TList( + $inner_type + ), + ]); + } + + if ($key_type->getLiteralStrings()) { + $key_type->addType(new Type\Atomic\TString); + } + + if ($key_type->getLiteralInts()) { + $key_type->addType(new Type\Atomic\TInt); + } + + if (!$inner_type->getAtomicTypes()) { + return Type::getEmptyArray(); + } + + return new Type\Union([ + new Type\Atomic\TArray([ + $key_type, + $inner_type, + ]), + ]); + } + + if (!isset($call_args[2])) { + $function_call_arg = $call_args[1]; + + if ($function_call_arg->value instanceof PhpParser\Node\Scalar\String_ + || $function_call_arg->value instanceof PhpParser\Node\Expr\Array_ + || $function_call_arg->value instanceof PhpParser\Node\Expr\BinaryOp\Concat + ) { + $mapping_function_ids = CallAnalyzer::getFunctionIdsFromCallableArg( + $statements_source, + $function_call_arg->value + ); + + if ($array_arg && $mapping_function_ids) { + $assertions = []; + + ArrayMapReturnTypeProvider::getReturnTypeFromMappingIds( + $statements_source, + $mapping_function_ids, + $context, + $function_call_arg, + \array_slice($call_args, 0, 1), + $assertions + ); + + $array_var_id = ExpressionIdentifier::getArrayVarId( + $array_arg, + null, + $statements_source + ); + + if (isset($assertions[$array_var_id . '[$__fake_offset_var__]'])) { + $changed_var_ids = []; + + $assertions = ['$inner_type' => $assertions[$array_var_id . '[$__fake_offset_var__]']]; + + $reconciled_types = Reconciler::reconcileKeyedTypes( + $assertions, + $assertions, + ['$inner_type' => $inner_type], + $changed_var_ids, + ['$inner_type' => true], + $statements_source, + $statements_source->getTemplateTypeMap() ?: [], + false, + new CodeLocation($statements_source, $function_call_arg->value) + ); + + if (isset($reconciled_types['$inner_type'])) { + $inner_type = $reconciled_types['$inner_type']; + } + } + } + } elseif (($function_call_arg->value instanceof PhpParser\Node\Expr\Closure + || $function_call_arg->value instanceof PhpParser\Node\Expr\ArrowFunction) + && ($second_arg_type = $statements_source->node_data->getType($function_call_arg->value)) + && ($closure_types = $second_arg_type->getClosureTypes()) + ) { + $closure_atomic_type = \reset($closure_types); + $closure_return_type = $closure_atomic_type->return_type ?: Type::getMixed(); + + if ($closure_return_type->isVoid()) { + IssueBuffer::accepts( + new InvalidReturnType( + 'No return type could be found in the closure passed to array_filter', + $code_location + ), + $statements_source->getSuppressedIssues() + ); + + return Type::getArray(); + } + + if (count($function_call_arg->value->getStmts()) === 1 && count($function_call_arg->value->params)) { + $first_param = $function_call_arg->value->params[0]; + $stmt = $function_call_arg->value->getStmts()[0]; + + if ($first_param->variadic === false + && $first_param->var instanceof PhpParser\Node\Expr\Variable + && is_string($first_param->var->name) + && $stmt instanceof PhpParser\Node\Stmt\Return_ + && $stmt->expr + ) { + $codebase = $statements_source->getCodebase(); + + $assertions = AssertionFinder::scrapeAssertions( + $stmt->expr, + null, + $statements_source, + $codebase + ); + + if (isset($assertions['$' . $first_param->var->name])) { + $changed_var_ids = []; + + $assertions = ['$inner_type' => $assertions['$' . $first_param->var->name]]; + + $reconciled_types = Reconciler::reconcileKeyedTypes( + $assertions, + $assertions, + ['$inner_type' => $inner_type], + $changed_var_ids, + ['$inner_type' => true], + $statements_source, + $statements_source->getTemplateTypeMap() ?: [], + false, + new CodeLocation($statements_source, $stmt) + ); + + if (isset($reconciled_types['$inner_type'])) { + $inner_type = $reconciled_types['$inner_type']; + } + } + } + } + } + + return new Type\Union([ + new Type\Atomic\TArray([ + $key_type, + $inner_type, + ]), + ]); + } + + if (!$inner_type->getAtomicTypes()) { + return Type::getEmptyArray(); + } + + return new Type\Union([ + new Type\Atomic\TArray([ + $key_type, + $inner_type, + ]), + ]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..7788bea6b24fc56f2d2dc9cd6e6d1d16ff952ede --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php @@ -0,0 +1,452 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $function_call_arg = $call_args[0] ?? null; + + $function_call_type = $function_call_arg + ? $statements_source->node_data->getType($function_call_arg->value) + : null; + + if ($function_call_type && $function_call_type->isNull()) { + \array_shift($call_args); + + $array_arg_types = []; + + foreach ($call_args as $call_arg) { + $call_arg_type = $statements_source->node_data->getType($call_arg->value); + + if ($call_arg_type) { + $array_arg_types[] = clone $call_arg_type; + } else { + $array_arg_types[] = Type::getMixed(); + break; + } + } + + if ($array_arg_types) { + return new Type\Union([new Type\Atomic\TKeyedArray($array_arg_types)]); + } + + return Type::getArray(); + } + + $array_arg = $call_args[1] ?? null; + + if (!$array_arg) { + return Type::getArray(); + } + + $array_arg_atomic_type = null; + $array_arg_type = null; + + if ($array_arg_union_type = $statements_source->node_data->getType($array_arg->value)) { + $arg_types = $array_arg_union_type->getAtomicTypes(); + + if (isset($arg_types['array'])) { + $array_arg_atomic_type = $arg_types['array']; + $array_arg_type = ArrayType::infer($array_arg_atomic_type); + } + } + + $generic_key_type = null; + $mapping_return_type = null; + + if ($function_call_arg && $function_call_type) { + if (count($call_args) === 2) { + $generic_key_type = $array_arg_type->key ?? Type::getArrayKey(); + } else { + $generic_key_type = Type::getInt(); + } + + if ($closure_types = $function_call_type->getClosureTypes()) { + $closure_atomic_type = \reset($closure_types); + + $closure_return_type = $closure_atomic_type->return_type ?: Type::getMixed(); + + if ($closure_return_type->isVoid()) { + $closure_return_type = Type::getNull(); + } + + $mapping_return_type = clone $closure_return_type; + } elseif ($function_call_arg->value instanceof PhpParser\Node\Scalar\String_ + || $function_call_arg->value instanceof PhpParser\Node\Expr\Array_ + || $function_call_arg->value instanceof PhpParser\Node\Expr\BinaryOp\Concat + ) { + $mapping_function_ids = CallAnalyzer::getFunctionIdsFromCallableArg( + $statements_source, + $function_call_arg->value + ); + + if ($mapping_function_ids) { + $mapping_return_type = self::getReturnTypeFromMappingIds( + $statements_source, + $mapping_function_ids, + $context, + $function_call_arg, + \array_slice($call_args, 1) + ); + } + + if ($function_call_arg->value instanceof PhpParser\Node\Expr\Array_ + && isset($function_call_arg->value->items[0]) + && isset($function_call_arg->value->items[1]) + && $function_call_arg->value->items[1]->value instanceof PhpParser\Node\Scalar\String_ + && $function_call_arg->value->items[0]->value instanceof PhpParser\Node\Expr\Variable + && ($variable_type + = $statements_source->node_data->getType($function_call_arg->value->items[0]->value)) + ) { + $fake_method_call = null; + + foreach ($variable_type->getAtomicTypes() as $variable_atomic_type) { + if ($variable_atomic_type instanceof Type\Atomic\TTemplateParam + || $variable_atomic_type instanceof Type\Atomic\TTemplateParamClass + ) { + $fake_method_call = new PhpParser\Node\Expr\StaticCall( + $function_call_arg->value->items[0]->value, + $function_call_arg->value->items[1]->value->value, + [] + ); + } elseif ($variable_atomic_type instanceof Type\Atomic\TTemplateParamClass) { + $fake_method_call = new PhpParser\Node\Expr\StaticCall( + $function_call_arg->value->items[0]->value, + $function_call_arg->value->items[1]->value->value, + [] + ); + } + } + + if ($fake_method_call) { + $fake_method_return_type = self::executeFakeCall( + $statements_source, + $fake_method_call, + $context + ); + + if ($fake_method_return_type) { + $mapping_return_type = $fake_method_return_type; + } + } + } + } + } + + if ($mapping_return_type && $generic_key_type) { + if ($array_arg_atomic_type instanceof Type\Atomic\TKeyedArray && count($call_args) === 2) { + $atomic_type = new Type\Atomic\TKeyedArray( + array_map( + /** + * @return Type\Union + */ + function (Type\Union $_) use ($mapping_return_type): Type\Union { + return clone $mapping_return_type; + }, + $array_arg_atomic_type->properties + ) + ); + $atomic_type->is_list = $array_arg_atomic_type->is_list; + $atomic_type->sealed = $array_arg_atomic_type->sealed; + + return new Type\Union([$atomic_type]); + } + + if ($array_arg_atomic_type instanceof Type\Atomic\TList + || count($call_args) !== 2 + ) { + if ($array_arg_atomic_type instanceof Type\Atomic\TNonEmptyList) { + return new Type\Union([ + new Type\Atomic\TNonEmptyList( + $mapping_return_type + ), + ]); + } + + return new Type\Union([ + new Type\Atomic\TList( + $mapping_return_type + ), + ]); + } + + if ($array_arg_atomic_type instanceof Type\Atomic\TNonEmptyArray) { + return new Type\Union([ + new Type\Atomic\TNonEmptyArray([ + $generic_key_type, + $mapping_return_type, + ]), + ]); + } + + return new Type\Union([ + new Type\Atomic\TArray([ + $generic_key_type, + $mapping_return_type, + ]) + ]); + } + + return count($call_args) === 2 && !($array_arg_type->is_list ?? false) + ? new Type\Union([ + new Type\Atomic\TArray([ + $array_arg_type->key ?? Type::getArrayKey(), + Type::getMixed(), + ]) + ]) + : Type::getList(); + } + + /** + * @param-out array>>|null $assertions + */ + private static function executeFakeCall( + \Psalm\Internal\Analyzer\StatementsAnalyzer $statements_analyzer, + PhpParser\Node\Expr $fake_call, + Context $context, + ?array &$assertions = null + ) : ?Type\Union { + $old_data_provider = $statements_analyzer->node_data; + + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + if (!in_array('MixedArrayOffset', $suppressed_issues, true)) { + $statements_analyzer->addSuppressedIssues(['MixedArrayOffset']); + } + + $was_inside_call = $context->inside_call; + + $context->inside_call = true; + + if ($fake_call instanceof PhpParser\Node\Expr\StaticCall) { + \Psalm\Internal\Analyzer\Statements\Expression\Call\StaticCallAnalyzer::analyze( + $statements_analyzer, + $fake_call, + $context + ); + } elseif ($fake_call instanceof PhpParser\Node\Expr\MethodCall) { + \Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_call, + $context + ); + } elseif ($fake_call instanceof PhpParser\Node\Expr\FuncCall) { + \Psalm\Internal\Analyzer\Statements\Expression\Call\FunctionCallAnalyzer::analyze( + $statements_analyzer, + $fake_call, + $context + ); + } else { + throw new \UnexpectedValueException('UnrecognizedCall'); + } + + $codebase = $statements_analyzer->getCodebase(); + + if ($assertions !== null) { + $assertions = AssertionFinder::scrapeAssertions( + $fake_call, + null, + $statements_analyzer, + $codebase + ); + } + + $context->inside_call = $was_inside_call; + + if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['PossiblyInvalidMethodCall']); + } + + if (!in_array('MixedArrayOffset', $suppressed_issues, true)) { + $statements_analyzer->removeSuppressedIssues(['MixedArrayOffset']); + } + + $return_type = $statements_analyzer->node_data->getType($fake_call) ?: null; + + $statements_analyzer->node_data = $old_data_provider; + + return $return_type; + } + + /** + * @param non-empty-array $mapping_function_ids + * @param array $array_args + * @param-out array>>|null $assertions + */ + public static function getReturnTypeFromMappingIds( + \Psalm\Internal\Analyzer\StatementsAnalyzer $statements_source, + array $mapping_function_ids, + Context $context, + PhpParser\Node\Arg $function_call_arg, + array $array_args, + ?array &$assertions = null + ) : Type\Union { + $mapping_return_type = null; + + $codebase = $statements_source->getCodebase(); + + foreach ($mapping_function_ids as $mapping_function_id) { + $mapping_function_id_parts = explode('&', $mapping_function_id); + + foreach ($mapping_function_id_parts as $mapping_function_id_part) { + $fake_args = []; + + foreach ($array_args as $array_arg) { + $fake_args[] = new PhpParser\Node\Arg( + new PhpParser\Node\Expr\ArrayDimFetch( + $array_arg->value, + new PhpParser\Node\Expr\Variable( + '__fake_offset_var__', + $array_arg->value->getAttributes() + ), + $array_arg->value->getAttributes() + ), + false, + false, + $array_arg->getAttributes() + ); + } + + if (strpos($mapping_function_id_part, '::') !== false) { + $is_instance = false; + + if ($mapping_function_id_part[0] === '$') { + $mapping_function_id_part = \substr($mapping_function_id_part, 1); + $is_instance = true; + } + + $method_id_parts = explode('::', $mapping_function_id_part); + [$callable_fq_class_name, $callable_method_name] = $method_id_parts; + + if ($is_instance) { + $fake_method_call = new PhpParser\Node\Expr\MethodCall( + new PhpParser\Node\Expr\Variable( + '__fake_method_call_var__', + $function_call_arg->getAttributes() + ), + new PhpParser\Node\Identifier( + $callable_method_name, + $function_call_arg->getAttributes() + ), + $fake_args, + $function_call_arg->getAttributes() + ); + + $context->vars_in_scope['$__fake_offset_var__'] = Type::getMixed(); + $context->vars_in_scope['$__fake_method_call_var__'] = new Type\Union([ + new Type\Atomic\TNamedObject($callable_fq_class_name) + ]); + + $fake_method_return_type = self::executeFakeCall( + $statements_source, + $fake_method_call, + $context, + $assertions + ); + + unset($context->vars_in_scope['$__fake_offset_var__']); + unset($context->vars_in_scope['$__method_call_var__']); + } else { + $fake_method_call = new PhpParser\Node\Expr\StaticCall( + new PhpParser\Node\Name\FullyQualified( + $callable_fq_class_name, + $function_call_arg->getAttributes() + ), + new PhpParser\Node\Identifier( + $callable_method_name, + $function_call_arg->getAttributes() + ), + $fake_args, + $function_call_arg->getAttributes() + ); + + $context->vars_in_scope['$__fake_offset_var__'] = Type::getMixed(); + + $fake_method_return_type = self::executeFakeCall( + $statements_source, + $fake_method_call, + $context, + $assertions + ); + + unset($context->vars_in_scope['$__fake_offset_var__']); + } + + $function_id_return_type = $fake_method_return_type ?: Type::getMixed(); + } else { + $fake_function_call = new PhpParser\Node\Expr\FuncCall( + new PhpParser\Node\Name\FullyQualified( + $mapping_function_id_part, + $function_call_arg->getAttributes() + ), + $fake_args, + $function_call_arg->getAttributes() + ); + + $context->vars_in_scope['$__fake_offset_var__'] = Type::getMixed(); + + $fake_function_return_type = self::executeFakeCall( + $statements_source, + $fake_function_call, + $context, + $assertions + ); + + unset($context->vars_in_scope['$__fake_offset_var__']); + + $function_id_return_type = $fake_function_return_type ?: Type::getMixed(); + } + } + + if (!$mapping_return_type) { + $mapping_return_type = $function_id_return_type; + } else { + $mapping_return_type = Type::combineUnionTypes( + $function_id_return_type, + $mapping_return_type, + $codebase + ); + } + } + + return $mapping_return_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..ca99787ee29cae490559b3112cff0645e4fbbe17 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php @@ -0,0 +1,241 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $inner_value_types = []; + $inner_key_types = []; + + $codebase = $statements_source->getCodebase(); + + $generic_properties = []; + $all_keyed_arrays = true; + $all_int_offsets = true; + $all_nonempty_lists = true; + $any_nonempty = false; + + foreach ($call_args as $call_arg) { + if (!($call_arg_type = $statements_source->node_data->getType($call_arg->value))) { + return Type::getArray(); + } + + foreach ($call_arg_type->getAtomicTypes() as $type_part) { + if ($call_arg->unpack) { + if (!$type_part instanceof Type\Atomic\TArray) { + if ($type_part instanceof Type\Atomic\TKeyedArray) { + $type_part_value_type = $type_part->getGenericValueType(); + } elseif ($type_part instanceof Type\Atomic\TList) { + $type_part_value_type = $type_part->type_param; + } else { + return Type::getArray(); + } + } else { + $type_part_value_type = $type_part->type_params[1]; + } + + $unpacked_type_parts = []; + + foreach ($type_part_value_type->getAtomicTypes() as $value_type_part) { + $unpacked_type_parts[] = $value_type_part; + } + } else { + $unpacked_type_parts = [$type_part]; + } + + foreach ($unpacked_type_parts as $unpacked_type_part) { + if (!$unpacked_type_part instanceof Type\Atomic\TArray) { + if (($unpacked_type_part instanceof Type\Atomic\TFalse + && $call_arg_type->ignore_falsable_issues) + || ($unpacked_type_part instanceof Type\Atomic\TNull + && $call_arg_type->ignore_nullable_issues) + ) { + continue; + } + + if ($unpacked_type_part instanceof Type\Atomic\TKeyedArray) { + foreach ($unpacked_type_part->properties as $key => $type) { + if (!\is_string($key)) { + $generic_properties[] = $type; + continue; + } + + if (!isset($generic_properties[$key]) || !$type->possibly_undefined) { + $generic_properties[$key] = $type; + } else { + $was_possibly_undefined = $generic_properties[$key]->possibly_undefined; + + $generic_properties[$key] = Type::combineUnionTypes( + $generic_properties[$key], + $type, + $codebase + ); + + $generic_properties[$key]->possibly_undefined = $was_possibly_undefined; + } + } + + if (!$unpacked_type_part->is_list) { + $all_nonempty_lists = false; + } + + if ($unpacked_type_part->sealed) { + $any_nonempty = true; + } + + continue; + } + + if ($unpacked_type_part instanceof Type\Atomic\TList) { + $all_keyed_arrays = false; + + if (!$unpacked_type_part instanceof Type\Atomic\TNonEmptyList) { + $all_nonempty_lists = false; + } else { + $any_nonempty = true; + } + } else { + if ($unpacked_type_part instanceof Type\Atomic\TMixed + && $unpacked_type_part->from_loop_isset + ) { + $unpacked_type_part = new Type\Atomic\TArray([ + Type::getArrayKey(), + Type::getMixed(true), + ]); + } else { + return Type::getArray(); + } + } + } else { + if (!$unpacked_type_part->type_params[0]->isEmpty()) { + foreach ($generic_properties as $key => $keyed_type) { + $generic_properties[$key] = Type::combineUnionTypes( + $keyed_type, + $unpacked_type_part->type_params[1], + $codebase + ); + } + + $all_keyed_arrays = false; + $all_nonempty_lists = false; + } + } + + if ($unpacked_type_part instanceof Type\Atomic\TArray) { + if ($unpacked_type_part->type_params[1]->isEmpty()) { + continue; + } + + if (!$unpacked_type_part->type_params[0]->isInt()) { + $all_int_offsets = false; + } + + if ($unpacked_type_part instanceof Type\Atomic\TNonEmptyArray) { + $any_nonempty = true; + } + } + + $inner_key_types = array_merge( + $inner_key_types, + $unpacked_type_part instanceof Type\Atomic\TList + ? [new Type\Atomic\TInt()] + : array_values($unpacked_type_part->type_params[0]->getAtomicTypes()) + ); + $inner_value_types = array_merge( + $inner_value_types, + $unpacked_type_part instanceof Type\Atomic\TList + ? array_values($unpacked_type_part->type_param->getAtomicTypes()) + : array_values($unpacked_type_part->type_params[1]->getAtomicTypes()) + ); + } + } + } + + $inner_key_type = null; + $inner_value_type = null; + + if ($inner_key_types) { + $inner_key_type = TypeCombination::combineTypes($inner_key_types, $codebase, true); + } + + if ($inner_value_types) { + $inner_value_type = TypeCombination::combineTypes($inner_value_types, $codebase, true); + } + + if ($generic_properties) { + $objectlike = new Type\Atomic\TKeyedArray($generic_properties); + + if ($all_nonempty_lists || $all_int_offsets) { + $objectlike->is_list = true; + } + + if (!$all_keyed_arrays) { + $objectlike->previous_key_type = $inner_key_type; + $objectlike->previous_value_type = $inner_value_type; + } + + return new Type\Union([$objectlike]); + } + + if ($inner_value_type) { + if ($all_int_offsets) { + if ($any_nonempty) { + return new Type\Union([ + new Type\Atomic\TNonEmptyList($inner_value_type), + ]); + } + + return new Type\Union([ + new Type\Atomic\TList($inner_value_type), + ]); + } + + $inner_key_type = $inner_key_type ?: Type::getArrayKey(); + + if ($any_nonempty) { + return new Type\Union([ + new Type\Atomic\TNonEmptyArray([ + $inner_key_type, + $inner_value_type, + ]), + ]); + } + + return new Type\Union([ + new Type\Atomic\TArray([ + $inner_key_type, + $inner_value_type, + ]), + ]); + } + + return Type::getArray(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPadReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPadReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..daa7f6ee3ab10aac9554bf1043fe61a8351bdafa --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPadReturnTypeProvider.php @@ -0,0 +1,61 @@ +getNodeTypeProvider(); + + if (count($call_args) >= 3 + && ($array_arg_type = $type_provider->getType($call_args[0]->value)) + && ($size_arg_type = $type_provider->getType($call_args[1]->value)) + && ($value_arg_type = $type_provider->getType($call_args[2]->value)) + && $array_arg_type->isSingle() + && $array_arg_type->hasArray() + && ($array_type = ArrayType::infer($array_arg_type->getAtomicTypes()['array'])) + ) { + $codebase = $statements_source->getCodebase(); + $key_type = Type::combineUnionTypes($array_type->key, Type::getInt(), $codebase); + $value_type = Type::combineUnionTypes($array_type->value, $value_arg_type, $codebase); + $can_return_empty = ( + !$size_arg_type->isSingleIntLiteral() + || $size_arg_type->getSingleIntLiteral()->value === 0 + ); + + return new Type\Union([ + $array_type->is_list + ? ( + $can_return_empty + ? new Type\Atomic\TList($value_type) + : new Type\Atomic\TNonEmptyList($value_type) + ) + : ( + $can_return_empty + ? new Type\Atomic\TArray([$key_type, $value_type]) + : new Type\Atomic\TNonEmptyArray([$key_type, $value_type]) + ) + ]); + } + + return Type::getArray(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..3cc36e2da06a96a8ead8963df521ef84500d9856 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php @@ -0,0 +1,72 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $first_arg = isset($call_args[0]->value) ? $call_args[0]->value : null; + + $first_arg_array = $first_arg + && ($first_arg_type = $statements_source->node_data->getType($first_arg)) + && $first_arg_type->hasType('array') + && ($array_atomic_type = $first_arg_type->getAtomicTypes()['array']) + && ($array_atomic_type instanceof Type\Atomic\TArray + || $array_atomic_type instanceof Type\Atomic\TKeyedArray + || $array_atomic_type instanceof Type\Atomic\TList) + ? $array_atomic_type + : null; + + if (!$first_arg_array) { + return Type::getMixed(); + } + + if ($first_arg_array instanceof Type\Atomic\TArray) { + $value_type = clone $first_arg_array->type_params[1]; + $definitely_has_items = $first_arg_array instanceof Type\Atomic\TNonEmptyArray; + } elseif ($first_arg_array instanceof Type\Atomic\TList) { + $value_type = clone $first_arg_array->type_param; + $definitely_has_items = $first_arg_array instanceof Type\Atomic\TNonEmptyList; + } else { + $value_type = $first_arg_array->getGenericValueType(); + $definitely_has_items = $first_arg_array->getGenericArrayType() instanceof Type\Atomic\TNonEmptyArray; + } + + if ($value_type->isEmpty()) { + $value_type = Type::getFalse(); + } elseif (($function_id !== 'reset' && $function_id !== 'end') || !$definitely_has_items) { + $value_type->addType(new Type\Atomic\TFalse); + + $codebase = $statements_source->getCodebase(); + + if ($codebase->config->ignore_internal_falsable_issues) { + $value_type->ignore_falsable_issues = true; + } + } + + return $value_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..b785e05b91196818fe67686f76ead8a6407d1548 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php @@ -0,0 +1,91 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $first_arg = isset($call_args[0]->value) ? $call_args[0]->value : null; + + $first_arg_array = $first_arg + && ($first_arg_type = $statements_source->node_data->getType($first_arg)) + && $first_arg_type->hasType('array') + && !$first_arg_type->hasMixed() + && ($array_atomic_type = $first_arg_type->getAtomicTypes()['array']) + && ($array_atomic_type instanceof Type\Atomic\TArray + || $array_atomic_type instanceof Type\Atomic\TKeyedArray + || $array_atomic_type instanceof Type\Atomic\TList) + ? $array_atomic_type + : null; + + if (!$first_arg_array) { + return Type::getMixed(); + } + + $nullable = false; + + if ($first_arg_array instanceof Type\Atomic\TArray) { + $value_type = clone $first_arg_array->type_params[1]; + + if ($value_type->isEmpty()) { + return Type::getNull(); + } + + if (!$first_arg_array instanceof Type\Atomic\TNonEmptyArray) { + $nullable = true; + } + } elseif ($first_arg_array instanceof Type\Atomic\TList) { + $value_type = clone $first_arg_array->type_param; + + if (!$first_arg_array instanceof Type\Atomic\TNonEmptyList) { + $nullable = true; + } + } else { + // special case where we know the type of the first element + if ($function_id === 'array_shift' && $first_arg_array->is_list && isset($first_arg_array->properties[0])) { + $value_type = clone $first_arg_array->properties[0]; + } else { + $value_type = $first_arg_array->getGenericValueType(); + + if (!$first_arg_array->sealed && !$first_arg_array->previous_value_type) { + $nullable = true; + } + } + } + + if ($nullable) { + $value_type->addType(new Type\Atomic\TNull); + + $codebase = $statements_source->getCodebase(); + + if ($codebase->config->ignore_internal_nullable_issues) { + $value_type->ignore_nullable_issues = true; + } + } + + return $value_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..d61e8d8f7a2ef1646b46aa4d312139be87ec461f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php @@ -0,0 +1,74 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $first_arg = isset($call_args[0]->value) ? $call_args[0]->value : null; + $second_arg = isset($call_args[1]->value) ? $call_args[1]->value : null; + + $first_arg_array = $first_arg + && ($first_arg_type = $statements_source->node_data->getType($first_arg)) + && $first_arg_type->hasType('array') + && ($array_atomic_type = $first_arg_type->getAtomicTypes()['array']) + && ($array_atomic_type instanceof Type\Atomic\TArray + || $array_atomic_type instanceof Type\Atomic\TKeyedArray + || $array_atomic_type instanceof Type\Atomic\TList) + ? $array_atomic_type + : null; + + if (!$first_arg_array) { + return Type::getMixed(); + } + + if ($first_arg_array instanceof Type\Atomic\TArray) { + $key_type = clone $first_arg_array->type_params[0]; + } elseif ($first_arg_array instanceof Type\Atomic\TList) { + $key_type = Type::getInt(); + } else { + $key_type = $first_arg_array->getGenericKeyType(); + } + + if (!$second_arg + || ($second_arg instanceof PhpParser\Node\Scalar\LNumber && $second_arg->value === 1) + ) { + return $key_type; + } + + $arr_type = new Type\Union([ + new Type\Atomic\TList( + $key_type + ), + ]); + + if ($second_arg instanceof PhpParser\Node\Scalar\LNumber) { + return $arr_type; + } + + return Type::combineUnionTypes($key_type, $arr_type); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..822dda2ab01f504f2ba0f91d84249bc4b47184a4 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php @@ -0,0 +1,295 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if (!isset($call_args[0]) || !isset($call_args[1])) { + return Type::getMixed(); + } + + $codebase = $statements_source->getCodebase(); + + $array_arg = $call_args[0]->value; + $function_call_arg = $call_args[1]->value; + + $array_arg_type = $statements_source->node_data->getType($array_arg); + $function_call_arg_type = $statements_source->node_data->getType($function_call_arg); + + if (!$array_arg_type || !$function_call_arg_type) { + return Type::getMixed(); + } + + $array_arg_types = $array_arg_type->getAtomicTypes(); + + $array_arg_atomic_type = null; + + if (isset($array_arg_types['array']) + && ($array_arg_types['array'] instanceof Type\Atomic\TArray + || $array_arg_types['array'] instanceof Type\Atomic\TKeyedArray + || $array_arg_types['array'] instanceof Type\Atomic\TList) + ) { + $array_arg_atomic_type = $array_arg_types['array']; + + if ($array_arg_atomic_type instanceof Type\Atomic\TKeyedArray) { + $array_arg_atomic_type = $array_arg_atomic_type->getGenericArrayType(); + } elseif ($array_arg_atomic_type instanceof Type\Atomic\TList) { + $array_arg_atomic_type = new Type\Atomic\TArray([ + Type::getInt(), + clone $array_arg_atomic_type->type_param + ]); + } + } + + if (!isset($call_args[2])) { + $reduce_return_type = Type::getNull(); + $reduce_return_type->ignore_nullable_issues = true; + } else { + $reduce_return_type = $statements_source->node_data->getType($call_args[2]->value); + + if (!$reduce_return_type) { + return Type::getMixed(); + } + + if ($reduce_return_type->hasMixed()) { + return Type::getMixed(); + } + } + + $initial_type = $reduce_return_type; + + if ($closure_types = $function_call_arg_type->getClosureTypes()) { + $closure_atomic_type = \reset($closure_types); + + $closure_return_type = $closure_atomic_type->return_type ?: Type::getMixed(); + + if ($closure_return_type->isVoid()) { + $closure_return_type = Type::getNull(); + } + + $reduce_return_type = Type::combineUnionTypes($closure_return_type, $reduce_return_type); + + if ($closure_atomic_type->params !== null) { + if (count($closure_atomic_type->params) < 2) { + if (IssueBuffer::accepts( + new InvalidArgument( + 'The closure passed to array_reduce needs two params', + new CodeLocation($statements_source, $function_call_arg) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + + return Type::getMixed(); + } + + [$carry_param, $item_param] = $closure_atomic_type->params; + + if ($carry_param->type + && ( + !UnionTypeComparator::isContainedBy( + $codebase, + $initial_type, + $carry_param->type + ) + || ( + !$reduce_return_type->hasMixed() + && !UnionTypeComparator::isContainedBy( + $codebase, + $reduce_return_type, + $carry_param->type + ) + ) + ) + ) { + if (IssueBuffer::accepts( + new InvalidArgument( + 'The first param of the closure passed to array_reduce must take ' + . $reduce_return_type . ' but only accepts ' . $carry_param->type, + $carry_param->type_location + ?: new CodeLocation($statements_source, $function_call_arg) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + + return Type::getMixed(); + } + + if ($item_param->type + && $array_arg_atomic_type + && !$array_arg_atomic_type->type_params[1]->hasMixed() + && !UnionTypeComparator::isContainedBy( + $codebase, + $array_arg_atomic_type->type_params[1], + $item_param->type + ) + ) { + if (IssueBuffer::accepts( + new InvalidArgument( + 'The second param of the closure passed to array_reduce must take ' + . $array_arg_atomic_type->type_params[1] . ' but only accepts ' . $item_param->type, + $item_param->type_location + ?: new CodeLocation($statements_source, $function_call_arg) + ), + $statements_source->getSuppressedIssues() + )) { + // fall through + } + + return Type::getMixed(); + } + } + + return $reduce_return_type; + } + + if ($function_call_arg instanceof PhpParser\Node\Scalar\String_ + || $function_call_arg instanceof PhpParser\Node\Expr\Array_ + || $function_call_arg instanceof PhpParser\Node\Expr\BinaryOp\Concat + ) { + $mapping_function_ids = CallAnalyzer::getFunctionIdsFromCallableArg( + $statements_source, + $function_call_arg + ); + + $call_map = InternalCallMapHandler::getCallMap(); + + foreach ($mapping_function_ids as $mapping_function_id) { + $mapping_function_id_parts = explode('&', $mapping_function_id); + + $part_match_found = false; + + foreach ($mapping_function_id_parts as $mapping_function_id_part) { + if (isset($call_map[$mapping_function_id_part][0])) { + if ($call_map[$mapping_function_id_part][0]) { + $mapped_function_return = + Type::parseString($call_map[$mapping_function_id_part][0]); + + $reduce_return_type = Type::combineUnionTypes( + $reduce_return_type, + $mapped_function_return + ); + + $part_match_found = true; + } + } elseif ($mapping_function_id_part) { + if (strpos($mapping_function_id_part, '::') !== false) { + if ($mapping_function_id_part[0] === '$') { + $mapping_function_id_part = \substr($mapping_function_id_part, 1); + } + + [$callable_fq_class_name, $method_name] = explode('::', $mapping_function_id_part); + + if (in_array($callable_fq_class_name, ['self', 'static', 'parent'], true)) { + continue; + } + + $method_id = new \Psalm\Internal\MethodIdentifier( + $callable_fq_class_name, + strtolower($method_name) + ); + + if (!$codebase->methods->methodExists( + $method_id, + !$context->collect_initializations + && !$context->collect_mutations + ? $context->calling_method_id + : null, + $codebase->collect_locations + ? new CodeLocation( + $statements_source, + $function_call_arg + ) : null, + null, + $statements_source->getFilePath() + )) { + continue; + } + + $part_match_found = true; + + $self_class = 'self'; + + $return_type = $codebase->methods->getMethodReturnType( + $method_id, + $self_class + ) ?: Type::getMixed(); + + $reduce_return_type = Type::combineUnionTypes( + $reduce_return_type, + $return_type + ); + } else { + if (!$codebase->functions->functionExists( + $statements_source, + strtolower($mapping_function_id_part) + ) + ) { + return Type::getMixed(); + } + + $part_match_found = true; + + $function_storage = $codebase->functions->getStorage( + $statements_source, + strtolower($mapping_function_id_part) + ); + + $return_type = $function_storage->return_type ?: Type::getMixed(); + + $reduce_return_type = Type::combineUnionTypes( + $reduce_return_type, + $return_type + ); + } + } + } + + if ($part_match_found === false) { + return Type::getMixed(); + } + } + + return $reduce_return_type; + } + + return Type::getMixed(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..0ef3bae795022d9727c7393d7c8774b65d5c80e7 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php @@ -0,0 +1,67 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $first_arg = $call_args[0]->value ?? null; + + $first_arg_array = $first_arg + && ($first_arg_type = $statements_source->node_data->getType($first_arg)) + && $first_arg_type->hasType('array') + && ($array_atomic_type = $first_arg_type->getAtomicTypes()['array']) + && ($array_atomic_type instanceof Type\Atomic\TArray + || $array_atomic_type instanceof Type\Atomic\TKeyedArray + || $array_atomic_type instanceof Type\Atomic\TList) + ? $array_atomic_type + : null; + + if (!$first_arg_array) { + return Type::getArray(); + } + + if ($first_arg_array instanceof Type\Atomic\TArray) { + return new Type\Union([clone $first_arg_array]); + } + + if ($first_arg_array instanceof Type\Atomic\TList) { + $second_arg = $call_args[1]->value ?? null; + + if (!$second_arg + || (($second_arg_type = $statements_source->node_data->getType($second_arg)) + && $second_arg_type->isFalse() + ) + ) { + return new Type\Union([clone $first_arg_array]); + } + + return new Type\Union([new Type\Atomic\TArray([Type::getInt(), clone $first_arg_array->type_param])]); + } + + return new Type\Union([$first_arg_array->getGenericArrayType()]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySliceReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySliceReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..e78fba97d4a86843fcd97841ce13e628382bfc09 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySliceReturnTypeProvider.php @@ -0,0 +1,73 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $first_arg = isset($call_args[0]->value) ? $call_args[0]->value : null; + + $first_arg_array = $first_arg + && ($first_arg_type = $statements_source->node_data->getType($first_arg)) + && $first_arg_type->hasType('array') + && ($array_atomic_type = $first_arg_type->getAtomicTypes()['array']) + && ($array_atomic_type instanceof Type\Atomic\TArray + || $array_atomic_type instanceof Type\Atomic\TKeyedArray + || $array_atomic_type instanceof Type\Atomic\TList) + ? $array_atomic_type + : null; + + if (!$first_arg_array) { + return Type::getArray(); + } + + $dont_preserve_int_keys = !isset($call_args[3]->value) + || (($third_arg_type = $statements_source->node_data->getType($call_args[3]->value)) + && ((string) $third_arg_type === 'false')); + + $already_cloned = false; + + if ($first_arg_array instanceof Type\Atomic\TKeyedArray) { + $already_cloned = true; + $first_arg_array = $first_arg_array->getGenericArrayType(); + } + + if ($first_arg_array instanceof Type\Atomic\TArray) { + if (!$already_cloned) { + $first_arg_array = clone $first_arg_array; + } + $array_type = new Type\Atomic\TArray($first_arg_array->type_params); + } else { + $array_type = new Type\Atomic\TArray([Type::getInt(), clone $first_arg_array->type_param]); + } + + if ($dont_preserve_int_keys && $array_type->type_params[0]->isInt()) { + $array_type = new Type\Atomic\TList($array_type->type_params[1]); + } + + return new Type\Union([$array_type]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..4db14fccd4c3f649cb95cfd65cf3d21a44a38a94 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php @@ -0,0 +1,77 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $first_arg = $call_args[0]->value ?? null; + + $first_arg_array = $first_arg + && ($first_arg_type = $statements_source->node_data->getType($first_arg)) + && $first_arg_type->hasType('array') + && ($array_atomic_type = $first_arg_type->getAtomicTypes()['array']) + && ($array_atomic_type instanceof Type\Atomic\TArray + || $array_atomic_type instanceof Type\Atomic\TKeyedArray + || $array_atomic_type instanceof Type\Atomic\TList) + ? $array_atomic_type + : null; + + if (!$first_arg_array) { + return Type::getArray(); + } + + if ($first_arg_array instanceof Type\Atomic\TArray) { + $first_arg_array = clone $first_arg_array; + + if ($first_arg_array instanceof Type\Atomic\TNonEmptyArray) { + $first_arg_array->count = null; + } + + return new Type\Union([$first_arg_array]); + } + + if ($first_arg_array instanceof Type\Atomic\TList) { + if ($first_arg_array instanceof Type\Atomic\TNonEmptyList) { + return new Type\Union([ + new Type\Atomic\TNonEmptyArray([ + Type::getInt(), + clone $first_arg_array->type_param + ]) + ]); + } + + return new Type\Union([ + new Type\Atomic\TArray([ + Type::getInt(), + clone $first_arg_array->type_param + ]) + ]); + } + + return new Type\Union([$first_arg_array->getGenericArrayType()]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayValuesReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayValuesReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..d42d56a55bfcb02cd98461598192bc298c623d5a --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayValuesReturnTypeProvider.php @@ -0,0 +1,69 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $first_arg = $call_args[0]->value ?? null; + + $first_arg_array = $first_arg + && ($first_arg_type = $statements_source->node_data->getType($first_arg)) + && $first_arg_type->hasType('array') + && ($array_atomic_type = $first_arg_type->getAtomicTypes()['array']) + && ($array_atomic_type instanceof Type\Atomic\TArray + || $array_atomic_type instanceof Type\Atomic\TKeyedArray + || $array_atomic_type instanceof Type\Atomic\TList) + ? $array_atomic_type + : null; + + if (!$first_arg_array) { + return Type::getArray(); + } + + if ($first_arg_array instanceof Type\Atomic\TKeyedArray) { + $first_arg_array = $first_arg_array->getGenericArrayType(); + } + + if ($first_arg_array instanceof Type\Atomic\TArray) { + if ($first_arg_array instanceof Type\Atomic\TNonEmptyArray) { + return new Type\Union([ + new Type\Atomic\TNonEmptyList( + clone $first_arg_array->type_params[1] + ) + ]); + } + + return new Type\Union([ + new Type\Atomic\TList( + clone $first_arg_array->type_params[1] + ) + ]); + } + + return new Type\Union([clone $first_arg_array]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ClosureFromCallableReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ClosureFromCallableReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..a3302fb72bae65ff39db2d897d11d68ec307088b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ClosureFromCallableReturnTypeProvider.php @@ -0,0 +1,76 @@ + $call_args + */ + public static function getMethodReturnType( + StatementsSource $source, + string $fq_classlike_name, + string $method_name_lowercase, + array $call_args, + Context $context, + CodeLocation $code_location, + ?array $template_type_parameters = null, + ?string $called_fq_classlike_name = null, + ?string $called_method_name_lowercase = null + ): ?Type\Union { + if (!$source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return null; + } + + $type_provider = $source->getNodeTypeProvider(); + $codebase = $source->getCodebase(); + + if ($method_name_lowercase === 'fromcallable') { + $closure_types = []; + + if (isset($call_args[0]) + && ($input_type = $type_provider->getType($call_args[0]->value)) + ) { + foreach ($input_type->getAtomicTypes() as $atomic_type) { + $candidate_callable = CallableTypeComparator::getCallableFromAtomic( + $codebase, + $atomic_type, + null, + $source + ); + + if ($candidate_callable) { + $closure_types[] = new Type\Atomic\TClosure( + 'Closure', + $candidate_callable->params, + $candidate_callable->return_type, + $candidate_callable->is_pure + ); + } else { + return Type::getClosure(); + } + } + } + + if ($closure_types) { + return TypeCombination::combineTypes($closure_types, $codebase); + } + + return Type::getClosure(); + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/DomNodeAppendChild.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/DomNodeAppendChild.php new file mode 100644 index 0000000000000000000000000000000000000000..acd5f34dbe3aea6d8ff675a863e8b787b25ad8a1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/DomNodeAppendChild.php @@ -0,0 +1,44 @@ + $call_args + */ + public static function getMethodReturnType( + StatementsSource $source, + string $fq_classlike_name, + string $method_name_lowercase, + array $call_args, + Context $context, + CodeLocation $code_location, + ?array $template_type_parameters = null, + ?string $called_fq_classlike_name = null, + ?string $called_method_name_lowercase = null + ): ?Type\Union { + if (!$source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if ($method_name_lowercase === 'appendchild' + && ($first_arg_type = $source->node_data->getType($call_args[0]->value)) + && $first_arg_type->hasObjectType() + ) { + return clone $first_arg_type; + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..94b8b45f2e66a918d636191e54ec4a8e23d29045 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php @@ -0,0 +1,76 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if (\count($call_args) >= 2) { + $second_arg_type = $statements_source->node_data->getType($call_args[1]->value); + + $inner_type = new Type\Union([ + $second_arg_type && $second_arg_type->hasLowercaseString() + ? new Type\Atomic\TLowercaseString() + : new Type\Atomic\TString + ]); + + $can_return_empty = isset($call_args[2]) + && ( + !$call_args[2]->value instanceof PhpParser\Node\Scalar\LNumber + || $call_args[2]->value->value < 0 + ); + + if ($call_args[0]->value instanceof PhpParser\Node\Scalar\String_) { + if ($call_args[0]->value->value === '') { + return Type::getFalse(); + } + + return new Type\Union([ + $can_return_empty + ? new Type\Atomic\TList($inner_type) + : new Type\Atomic\TNonEmptyList($inner_type) + ]); + } elseif (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value)) + && $first_arg_type->hasString() + ) { + $falsable_array = new Type\Union([ + $can_return_empty + ? new Type\Atomic\TList($inner_type) + : new Type\Atomic\TNonEmptyList($inner_type), + new Type\Atomic\TFalse + ]); + + if ($statements_source->getCodebase()->config->ignore_internal_falsable_issues) { + $falsable_array->ignore_falsable_issues = true; + } + + return $falsable_array; + } + } + + return Type::getMixed(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..b96da3e5ab971530f200e4608b2cce49d9e852ac --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php @@ -0,0 +1,154 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + throw new \UnexpectedValueException(); + } + + $filter_type = null; + + if (isset($call_args[1]) + && ($second_arg_type = $statements_source->node_data->getType($call_args[1]->value)) + && $second_arg_type->isSingleIntLiteral() + ) { + $filter_type_type = $second_arg_type->getSingleIntLiteral(); + + $filter_type = null; + + switch ($filter_type_type->value) { + case \FILTER_VALIDATE_INT: + $filter_type = Type::getInt(); + break; + + case \FILTER_VALIDATE_FLOAT: + $filter_type = Type::getFloat(); + break; + + case \FILTER_VALIDATE_BOOLEAN: + $filter_type = Type::getBool(); + + break; + + case \FILTER_VALIDATE_IP: + case \FILTER_VALIDATE_MAC: + case \FILTER_VALIDATE_REGEXP: + case \FILTER_VALIDATE_URL: + case \FILTER_VALIDATE_EMAIL: + case \FILTER_VALIDATE_DOMAIN: + $filter_type = Type::getString(); + break; + } + + $has_object_like = false; + $filter_null = false; + + if (isset($call_args[2]) + && ($third_arg_type = $statements_source->node_data->getType($call_args[2]->value)) + && $filter_type + ) { + foreach ($third_arg_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TKeyedArray) { + $has_object_like = true; + + if (isset($atomic_type->properties['options']) + && $atomic_type->properties['options']->hasArray() + && ($options_array = $atomic_type->properties['options']->getAtomicTypes()['array']) + && $options_array instanceof Type\Atomic\TKeyedArray + && isset($options_array->properties['default']) + ) { + $filter_type = Type::combineUnionTypes( + $filter_type, + $options_array->properties['default'] + ); + } else { + $filter_type->addType(new Type\Atomic\TFalse); + } + + if (isset($atomic_type->properties['flags']) + && $atomic_type->properties['flags']->isSingleIntLiteral() + ) { + $filter_flag_type = + $atomic_type->properties['flags']->getSingleIntLiteral(); + + if ($filter_type->hasBool() + && $filter_flag_type->value === \FILTER_NULL_ON_FAILURE + ) { + $filter_type->addType(new Type\Atomic\TNull); + } + } + } elseif ($atomic_type instanceof Type\Atomic\TLiteralInt) { + if ($atomic_type->value === \FILTER_NULL_ON_FAILURE) { + $filter_null = true; + $filter_type->addType(new Type\Atomic\TNull); + } + } + } + } + + if (!$has_object_like && !$filter_null && $filter_type) { + $filter_type->addType(new Type\Atomic\TFalse); + } + } + + if (!$filter_type) { + $filter_type = Type::getMixed(); + } + + if ($statements_source->data_flow_graph + && !\in_array('TaintedInput', $statements_source->getSuppressedIssues()) + ) { + $function_return_sink = DataFlowNode::getForMethodReturn( + $function_id, + $function_id, + null, + $code_location + ); + + $statements_source->data_flow_graph->addNode($function_return_sink); + + $function_param_sink = DataFlowNode::getForMethodArgument( + $function_id, + $function_id, + 0, + null, + $code_location + ); + + $statements_source->data_flow_graph->addNode($function_param_sink); + + $statements_source->data_flow_graph->addPath( + $function_param_sink, + $function_return_sink, + 'arg' + ); + + $filter_type->parent_nodes = [$function_return_sink->id => $function_return_sink]; + } + + return $filter_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/FirstArgStringReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/FirstArgStringReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..58ab242042ce78e73e34bbd3706fe8b35f625adf --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/FirstArgStringReturnTypeProvider.php @@ -0,0 +1,46 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ): ?Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + $return_type = Type::getString(); + + if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value)) + && $first_arg_type->isString() + ) { + return $return_type; + } + + $return_type->addType(new Type\Atomic\TNull); + $return_type->ignore_nullable_issues = true; + + return $return_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/GetClassMethodsReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/GetClassMethodsReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..634bfd01f5c4e2c46809d799e9faab7a5e2f5453 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/GetClassMethodsReturnTypeProvider.php @@ -0,0 +1,41 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ): ?Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value)) + && ($first_arg_type->hasObjectType() || $first_arg_type->hasString()) + ) { + return Type::parseString('array'); + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..4e2ddf3e0ed3a806963c14b64c25c01d38357312 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php @@ -0,0 +1,41 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ): ?Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value)) + && $first_arg_type->isObjectType() + ) { + return Type::parseString('array'); + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..3889a7a797365cb64d0dffbbaa24bb1e6391a647 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php @@ -0,0 +1,33 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + return Type::getInt(true); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/IteratorToArrayReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/IteratorToArrayReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..21b35127b5f2b1044e24fb5aa1513365dd88d54e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/IteratorToArrayReturnTypeProvider.php @@ -0,0 +1,105 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value)) + && $first_arg_type->hasObjectType() + ) { + $key_type = null; + $value_type = null; + + $codebase = $statements_source->getCodebase(); + + foreach ($first_arg_type->getAtomicTypes() as $call_arg_atomic_type) { + if ($call_arg_atomic_type instanceof Type\Atomic\TNamedObject + && AtomicTypeComparator::isContainedBy( + $codebase, + $call_arg_atomic_type, + new Type\Atomic\TIterable([Type::getMixed(), Type::getMixed()]) + ) + ) { + $has_valid_iterator = true; + ForeachAnalyzer::handleIterable( + $statements_source, + $call_arg_atomic_type, + $call_args[0]->value, + $codebase, + $context, + $key_type, + $value_type, + $has_valid_iterator + ); + } + } + + if ($value_type) { + $second_arg_type = isset($call_args[1]) + ? $statements_source->node_data->getType($call_args[1]->value) + : null; + + if ($second_arg_type + && ((string) $second_arg_type === 'false') + ) { + return new Type\Union([ + new Type\Atomic\TList($value_type), + ]); + } + + $key_type = $key_type + && (!isset($call_args[1]) + || ($second_arg_type && ((string) $second_arg_type === 'true'))) + ? $key_type + : Type::getArrayKey(); + + if ($key_type->hasMixed()) { + $key_type = Type::getArrayKey(); + } + + return new Type\Union([ + new Type\Atomic\TArray([ + $key_type, + $value_type, + ]), + ]); + } + } + + $callmap_callables = InternalCallMapHandler::getCallablesFromCallMap($function_id); + + assert($callmap_callables && $callmap_callables[0]->return_type); + + return $callmap_callables[0]->return_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..6c6c71b23bf3ad330b63bac866475510923b6573 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php @@ -0,0 +1,51 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + foreach ($call_args as $call_arg) { + if (!($call_arg_type = $statements_source->node_data->getType($call_arg->value)) + || !$call_arg_type->isInt() + ) { + $value_type = new Type\Union([new Type\Atomic\TInt, new Type\Atomic\TFalse]); + + $codebase = $statements_source->getCodebase(); + + if ($codebase->config->ignore_internal_falsable_issues) { + $value_type->ignore_falsable_issues = true; + } + + return $value_type; + } + } + + return Type::getInt(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..6bf2bbb71e61bf822babd40e6cc6d3088e3ff8db --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php @@ -0,0 +1,151 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if (count($call_args) > 1) { + if ($component_type = $statements_source->node_data->getType($call_args[1]->value)) { + if (!$component_type->hasMixed()) { + $codebase = $statements_source->getCodebase(); + + $acceptable_string_component_type = new Type\Union([ + new Type\Atomic\TLiteralInt(PHP_URL_SCHEME), + new Type\Atomic\TLiteralInt(PHP_URL_USER), + new Type\Atomic\TLiteralInt(PHP_URL_PASS), + new Type\Atomic\TLiteralInt(PHP_URL_HOST), + new Type\Atomic\TLiteralInt(PHP_URL_PATH), + new Type\Atomic\TLiteralInt(PHP_URL_QUERY), + new Type\Atomic\TLiteralInt(PHP_URL_FRAGMENT), + ]); + + $acceptable_int_component_type = new Type\Union([ + new Type\Atomic\TLiteralInt(PHP_URL_PORT), + ]); + + if (UnionTypeComparator::isContainedBy( + $codebase, + $component_type, + $acceptable_string_component_type + )) { + $nullable_falsable_string = new Type\Union([ + new Type\Atomic\TString, + new Type\Atomic\TFalse, + new Type\Atomic\TNull, + ]); + + $codebase = $statements_source->getCodebase(); + + if ($codebase->config->ignore_internal_nullable_issues) { + $nullable_falsable_string->ignore_nullable_issues = true; + } + + if ($codebase->config->ignore_internal_falsable_issues) { + $nullable_falsable_string->ignore_falsable_issues = true; + } + + return $nullable_falsable_string; + } + + if (UnionTypeComparator::isContainedBy( + $codebase, + $component_type, + $acceptable_int_component_type + )) { + $nullable_falsable_int = new Type\Union([ + new Type\Atomic\TInt, + new Type\Atomic\TFalse, + new Type\Atomic\TNull, + ]); + + $codebase = $statements_source->getCodebase(); + + if ($codebase->config->ignore_internal_nullable_issues) { + $nullable_falsable_int->ignore_nullable_issues = true; + } + + if ($codebase->config->ignore_internal_falsable_issues) { + $nullable_falsable_int->ignore_falsable_issues = true; + } + + return $nullable_falsable_int; + } + } + } + + $nullable_string_or_int = new Type\Union([ + new Type\Atomic\TString, + new Type\Atomic\TInt, + new Type\Atomic\TNull, + ]); + + $codebase = $statements_source->getCodebase(); + + if ($codebase->config->ignore_internal_nullable_issues) { + $nullable_string_or_int->ignore_nullable_issues = true; + } + + return $nullable_string_or_int; + } + + $component_types = [ + 'scheme' => Type::getString(), + 'user' => Type::getString(), + 'pass' => Type::getString(), + 'host' => Type::getString(), + 'port' => Type::getInt(), + 'path' => Type::getString(), + 'query' => Type::getString(), + 'fragment' => Type::getString(), + ]; + + foreach ($component_types as $component_type) { + $component_type->possibly_undefined = true; + } + + $return_type = new Type\Union([ + new Type\Atomic\TKeyedArray($component_types), + new Type\Atomic\TFalse(), + ]); + + if ($statements_source->getCodebase()->config->ignore_internal_falsable_issues) { + $return_type->ignore_falsable_issues = true; + } + + return $return_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..c6a7f5b6aaec073a442552010c2dff72148469ca --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php @@ -0,0 +1,103 @@ + $call_args + */ + public static function getMethodReturnType( + StatementsSource $source, + string $fq_classlike_name, + string $method_name_lowercase, + array $call_args, + Context $context, + CodeLocation $code_location, + ?array $template_type_parameters = null, + ?string $called_fq_classlike_name = null, + ?string $called_method_name_lowercase = null + ): ?Type\Union { + if ($method_name_lowercase === 'fetch' + && \class_exists('PDO') + && isset($call_args[0]) + && ($first_arg_type = $source->getNodeTypeProvider()->getType($call_args[0]->value)) + && $first_arg_type->isSingleIntLiteral() + ) { + $fetch_mode = $first_arg_type->getSingleIntLiteral()->value; + + switch ($fetch_mode) { + case \PDO::FETCH_ASSOC: // array|false + return new Type\Union([ + new Type\Atomic\TArray([ + Type::getString(), + Type::getScalar(), + ]), + new Type\Atomic\TFalse(), + ]); + + case \PDO::FETCH_BOTH: // array|false + return new Type\Union([ + new Type\Atomic\TArray([ + Type::getArrayKey(), + Type::getScalar() + ]), + new Type\Atomic\TFalse(), + ]); + + case \PDO::FETCH_BOUND: // bool + return Type::getBool(); + + case \PDO::FETCH_CLASS: // object|false + return new Type\Union([ + new Type\Atomic\TObject(), + new Type\Atomic\TFalse(), + ]); + + case \PDO::FETCH_LAZY: // object|false + // This actually returns a PDORow object, but that class is + // undocumented, and its attributes are all dynamic anyway + return new Type\Union([ + new Type\Atomic\TObject(), + new Type\Atomic\TFalse(), + ]); + + case \PDO::FETCH_NAMED: // array>|false + return new Type\Union([ + new Type\Atomic\TArray([ + Type::getString(), + new Type\Union([ + new Type\Atomic\TScalar(), + new Type\Atomic\TList(Type::getScalar()) + ]) + ]), + new Type\Atomic\TFalse(), + ]); + + case \PDO::FETCH_NUM: // list|false + return new Type\Union([ + new Type\Atomic\TList(Type::getScalar()), + new Type\Atomic\TFalse(), + ]); + + case \PDO::FETCH_OBJ: // stdClass|false + return new Type\Union([ + new Type\Atomic\TNamedObject('stdClass'), + new Type\Atomic\TFalse(), + ]); + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php new file mode 100644 index 0000000000000000000000000000000000000000..a9047a2e2583df5168454061167ae62c0299d576 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php @@ -0,0 +1,112 @@ + $call_args + * + * @return ?array + */ + public static function getMethodParams( + string $fq_classlike_name, + string $method_name_lowercase, + ?array $call_args = null, + ?StatementsSource $statements_source = null, + ?Context $context = null, + ?CodeLocation $code_location = null + ): ?array { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return null; + } + + if ($method_name_lowercase === 'setfetchmode') { + if (!$context + || !$call_args + || \Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze( + $statements_source, + $call_args[0]->value, + $context + ) === false + ) { + return null; + } + + if (($first_call_arg_type = $statements_source->node_data->getType($call_args[0]->value)) + && $first_call_arg_type->isSingleIntLiteral() + ) { + $params = [ + new \Psalm\Storage\FunctionLikeParameter( + 'mode', + false, + Type::getInt(), + null, + null, + false + ), + ]; + + $value = $first_call_arg_type->getSingleIntLiteral()->value; + + switch ($value) { + case \PDO::FETCH_COLUMN: + $params[] = new \Psalm\Storage\FunctionLikeParameter( + 'colno', + false, + Type::getInt(), + null, + null, + false + ); + break; + + case \PDO::FETCH_CLASS: + $params[] = new \Psalm\Storage\FunctionLikeParameter( + 'classname', + false, + Type::getClassString(), + null, + null, + false + ); + + $params[] = new \Psalm\Storage\FunctionLikeParameter( + 'ctorargs', + false, + Type::getArray(), + null, + null, + true + ); + break; + + case \PDO::FETCH_INTO: + $params[] = new \Psalm\Storage\FunctionLikeParameter( + 'object', + false, + Type::getObject(), + null, + null, + false + ); + break; + } + + return $params; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/SimpleXmlElementAsXml.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/SimpleXmlElementAsXml.php new file mode 100644 index 0000000000000000000000000000000000000000..d3c2fd2d669e825c11d8c89a12883f0f2f50dd46 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/SimpleXmlElementAsXml.php @@ -0,0 +1,40 @@ + $call_args + */ + public static function getMethodReturnType( + StatementsSource $source, + string $fq_classlike_name, + string $method_name_lowercase, + array $call_args, + Context $context, + CodeLocation $code_location, + ?array $template_type_parameters = null, + ?string $called_fq_classlike_name = null, + ?string $called_method_name_lowercase = null + ): ?Type\Union { + if ($method_name_lowercase === 'asxml' + && !count($call_args) + ) { + return Type::parseString('string|false'); + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..5b2c3fd5ecacb6056dca57313b013ebe589fd8e9 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php @@ -0,0 +1,60 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if ($subject_type = $statements_source->node_data->getType($call_args[2]->value)) { + if (!$subject_type->hasString() && $subject_type->hasArray()) { + return Type::getArray(); + } + + $return_type = Type::getString(); + + if (in_array($function_id, ['preg_replace', 'preg_replace_callback'], true)) { + $return_type->addType(new Type\Atomic\TNull()); + + $codebase = $statements_source->getCodebase(); + + if ($codebase->config->ignore_internal_nullable_issues) { + $return_type->ignore_nullable_issues = true; + } + } + + return $return_type; + } + + return Type::getMixed(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/VersionCompareReturnTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/VersionCompareReturnTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..b8ee1ba21f1642f2070b9002fe1b57306f45c9be --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/ReturnTypeProvider/VersionCompareReturnTypeProvider.php @@ -0,0 +1,79 @@ + $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ) : Type\Union { + if (!$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if (count($call_args) > 2) { + $operator_type = $statements_source->node_data->getType($call_args[2]->value); + + if ($operator_type) { + if (!$operator_type->hasMixed()) { + $acceptable_operator_type = new Type\Union([ + new Type\Atomic\TLiteralString('<'), + new Type\Atomic\TLiteralString('lt'), + new Type\Atomic\TLiteralString('<='), + new Type\Atomic\TLiteralString('le'), + new Type\Atomic\TLiteralString('>'), + new Type\Atomic\TLiteralString('gt'), + new Type\Atomic\TLiteralString('>='), + new Type\Atomic\TLiteralString('ge'), + new Type\Atomic\TLiteralString('=='), + new Type\Atomic\TLiteralString('='), + new Type\Atomic\TLiteralString('eq'), + new Type\Atomic\TLiteralString('!='), + new Type\Atomic\TLiteralString('<>'), + new Type\Atomic\TLiteralString('ne'), + ]); + + $codebase = $statements_source->getCodebase(); + + if (UnionTypeComparator::isContainedBy( + $codebase, + $operator_type, + $acceptable_operator_type + )) { + return Type::getBool(); + } + } + } + + return new Type\Union([ + new Type\Atomic\TBool, + new Type\Atomic\TNull, + ]); + } + + return new Type\Union([ + new Type\Atomic\TLiteralInt(-1), + new Type\Atomic\TLiteralInt(0), + new Type\Atomic\TLiteralInt(1), + ]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/StatementsProvider.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/StatementsProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..4665961aa49483fa1dfd529bdc1dfa4e28e55700 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Provider/StatementsProvider.php @@ -0,0 +1,459 @@ +> + */ + private $unchanged_members = []; + + /** + * @var array> + */ + private $unchanged_signature_members = []; + + /** + * @var array> + */ + private $changed_members = []; + + /** + * @var array> + */ + private $diff_map = []; + + /** + * @var PhpParser\Lexer|null + */ + private static $lexer; + + /** + * @var PhpParser\Parser|null + */ + private static $parser; + + public function __construct( + FileProvider $file_provider, + ?ParserCacheProvider $parser_cache_provider = null, + ?FileStorageCacheProvider $file_storage_cache_provider = null + ) { + $this->file_provider = $file_provider; + $this->parser_cache_provider = $parser_cache_provider; + $this->this_modified_time = filemtime(__FILE__); + $this->file_storage_cache_provider = $file_storage_cache_provider; + } + + /** + * @return list<\PhpParser\Node\Stmt> + */ + public function getStatementsForFile(string $file_path, string $php_version, ?Progress $progress = null): array + { + if ($progress === null) { + $progress = new VoidProgress(); + } + + $from_cache = false; + + $version = (string) PHP_PARSER_VERSION . $this->this_modified_time; + + $file_contents = $this->file_provider->getContents($file_path); + $modified_time = $this->file_provider->getModifiedTime($file_path); + + $config = \Psalm\Config::getInstance(); + + if (!$this->parser_cache_provider + || (!$config->isInProjectDirs($file_path) && \strpos($file_path, 'vendor')) + ) { + $progress->debug('Parsing ' . $file_path . "\n"); + + $stmts = self::parseStatements($file_contents, $php_version, $file_path); + + return $stmts ?: []; + } + + $file_content_hash = md5($version . $file_contents); + + $stmts = $this->parser_cache_provider->loadStatementsFromCache( + $file_path, + $modified_time, + $file_content_hash + ); + + if ($stmts === null) { + $progress->debug('Parsing ' . $file_path . "\n"); + + $existing_statements = $this->parser_cache_provider->loadExistingStatementsFromCache($file_path); + + /** @psalm-suppress DocblockTypeContradiction */ + if ($existing_statements && !$existing_statements[0] instanceof PhpParser\Node\Stmt) { + $existing_statements = null; + } + + $existing_file_contents = $this->parser_cache_provider->loadExistingFileContentsFromCache($file_path); + + // this happens after editing temporary file + if ($existing_file_contents === $file_contents && $existing_statements) { + $this->diff_map[$file_path] = []; + $this->parser_cache_provider->saveStatementsToCache( + $file_path, + $file_content_hash, + $existing_statements, + true + ); + + return $existing_statements; + } + + $file_changes = null; + + $existing_statements_copy = null; + + if ($existing_statements + && $existing_file_contents + && abs(strlen($existing_file_contents) - strlen($file_contents)) < 5000 + ) { + $file_changes = \Psalm\Internal\Diff\FileDiffer::getDiff($existing_file_contents, $file_contents); + + if (count($file_changes) < 10) { + $traverser = new PhpParser\NodeTraverser; + $traverser->addVisitor(new \Psalm\Internal\PhpVisitor\CloningVisitor); + // performs a deep clone + /** @var list */ + $existing_statements_copy = $traverser->traverse($existing_statements); + } else { + $file_changes = null; + } + } + + $stmts = self::parseStatements( + $file_contents, + $php_version, + $file_path, + $existing_file_contents, + $existing_statements_copy, + $file_changes + ); + + if ($existing_file_contents && $existing_statements) { + [$unchanged_members, $unchanged_signature_members, $changed_members, $diff_map] + = \Psalm\Internal\Diff\FileStatementsDiffer::diff( + $existing_statements, + $stmts, + $existing_file_contents, + $file_contents + ); + + $unchanged_members = array_map( + /** + * @param int $_ + * + * @return bool + */ + function ($_): bool { + return true; + }, + array_flip($unchanged_members) + ); + + $unchanged_signature_members = array_map( + /** + * @param int $_ + * + * @return bool + */ + function ($_): bool { + return true; + }, + array_flip($unchanged_signature_members) + ); + + $file_path_hash = \md5($file_path); + + $changed_members = array_map( + function (string $key) use ($file_path_hash) : string { + if (substr($key, 0, 4) === 'use:') { + return $key . ':' . $file_path_hash; + } + + return $key; + }, + $changed_members + ); + + $changed_members = array_map( + /** + * @param int $_ + * + * @return bool + */ + function ($_): bool { + return true; + }, + array_flip($changed_members) + ); + + if (isset($this->unchanged_members[$file_path])) { + $this->unchanged_members[$file_path] = array_intersect_key( + $this->unchanged_members[$file_path], + $unchanged_members + ); + } else { + $this->unchanged_members[$file_path] = $unchanged_members; + } + + if (isset($this->unchanged_signature_members[$file_path])) { + $this->unchanged_signature_members[$file_path] = array_intersect_key( + $this->unchanged_signature_members[$file_path], + $unchanged_signature_members + ); + } else { + $this->unchanged_signature_members[$file_path] = $unchanged_signature_members; + } + + if (isset($this->changed_members[$file_path])) { + $this->changed_members[$file_path] = array_merge( + $this->changed_members[$file_path], + $changed_members + ); + } else { + $this->changed_members[$file_path] = $changed_members; + } + + $this->diff_map[$file_path] = $diff_map; + } + + if ($this->file_storage_cache_provider) { + $this->file_storage_cache_provider->removeCacheForFile($file_path); + } + + $this->parser_cache_provider->cacheFileContents($file_path, $file_contents); + } else { + $from_cache = true; + $this->diff_map[$file_path] = []; + } + + $this->parser_cache_provider->saveStatementsToCache($file_path, $file_content_hash, $stmts, $from_cache); + + if (!$stmts) { + return []; + } + + return $stmts; + } + + /** + * @return array> + */ + public function getChangedMembers(): array + { + return $this->changed_members; + } + + /** + * @param array> $more_changed_members + * + */ + public function addChangedMembers(array $more_changed_members): void + { + $this->changed_members = array_merge($more_changed_members, $this->changed_members); + } + + /** + * @return array> + */ + public function getUnchangedSignatureMembers(): array + { + return $this->unchanged_signature_members; + } + + /** + * @param array> $more_unchanged_members + * + */ + public function addUnchangedSignatureMembers(array $more_unchanged_members): void + { + $this->unchanged_signature_members = array_merge($more_unchanged_members, $this->unchanged_signature_members); + } + + public function setUnchangedFile(string $file_path): void + { + if (!isset($this->diff_map[$file_path])) { + $this->diff_map[$file_path] = []; + } + } + + /** + * @return array> + */ + public function getDiffMap(): array + { + return $this->diff_map; + } + + /** + * @param array> $diff_map + * + */ + public function addDiffMap(array $diff_map): void + { + $this->diff_map = array_merge($diff_map, $this->diff_map); + } + + public function resetDiffs(): void + { + $this->changed_members = []; + $this->unchanged_members = []; + $this->unchanged_signature_members = []; + $this->diff_map = []; + } + + /** + * @param list<\PhpParser\Node\Stmt> $existing_statements + * @param array $file_changes + * + * @return list<\PhpParser\Node\Stmt> + */ + public static function parseStatements( + string $file_contents, + string $php_version, + ?string $file_path = null, + ?string $existing_file_contents = null, + ?array $existing_statements = null, + ?array $file_changes = null + ): array { + $attributes = [ + 'comments', 'startLine', 'startFilePos', 'endFilePos', + ]; + + if (!self::$lexer) { + self::$lexer = new PhpParser\Lexer\Emulative([ + 'usedAttributes' => $attributes, + 'phpVersion' => $php_version, + ]); + } + + if (!self::$parser) { + self::$parser = (new PhpParser\ParserFactory())->create(PhpParser\ParserFactory::ONLY_PHP7, self::$lexer); + } + + $used_cached_statements = false; + + $error_handler = new \PhpParser\ErrorHandler\Collecting(); + + if ($existing_statements && $file_changes && $existing_file_contents) { + $clashing_traverser = new \Psalm\Internal\PhpTraverser\CustomTraverser; + $offset_analyzer = new \Psalm\Internal\PhpVisitor\PartialParserVisitor( + self::$parser, + $error_handler, + $file_changes, + $existing_file_contents, + $file_contents + ); + $clashing_traverser->addVisitor($offset_analyzer); + $clashing_traverser->traverse($existing_statements); + + if (!$offset_analyzer->mustRescan()) { + $used_cached_statements = true; + $stmts = $existing_statements; + } else { + try { + /** @var list<\PhpParser\Node\Stmt> */ + $stmts = self::$parser->parse($file_contents, $error_handler) ?: []; + } catch (\Throwable $t) { + $stmts = []; + + // hope this got caught below + } + } + } else { + try { + /** @var list<\PhpParser\Node\Stmt> */ + $stmts = self::$parser->parse($file_contents, $error_handler) ?: []; + } catch (\Throwable $t) { + $stmts = []; + + // hope this got caught below + } + } + + if ($error_handler->hasErrors() && $file_path) { + $config = \Psalm\Config::getInstance(); + + foreach ($error_handler->getErrors() as $error) { + if ($error->hasColumnInfo()) { + \Psalm\IssueBuffer::add( + new \Psalm\Issue\ParseError( + $error->getMessage(), + new \Psalm\CodeLocation\ParseErrorLocation( + $error, + $file_contents, + $file_path, + $config->shortenFileName($file_path) + ) + ) + ); + } + } + } + + $error_handler->clearErrors(); + + $resolving_traverser = new PhpParser\NodeTraverser; + $name_resolver = new \Psalm\Internal\PhpVisitor\SimpleNameResolver( + $error_handler, + $used_cached_statements ? $file_changes : [] + ); + $resolving_traverser->addVisitor($name_resolver); + $resolving_traverser->traverse($stmts); + + return $stmts; + } + + public static function clearLexer() : void + { + self::$lexer = null; + } + + public static function clearParser(): void + { + self::$parser = null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/ReferenceConstraint.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/ReferenceConstraint.php new file mode 100644 index 0000000000000000000000000000000000000000..30f210000f616f82f43cfc387a02a17c69fa27e6 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/ReferenceConstraint.php @@ -0,0 +1,32 @@ +type = clone $type; + + if ($this->type->getLiteralStrings()) { + $this->type->addType(new Type\Atomic\TString); + } + + if ($this->type->getLiteralInts()) { + $this->type->addType(new Type\Atomic\TInt); + } + + if ($this->type->getLiteralFloats()) { + $this->type->addType(new Type\Atomic\TFloat); + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/RuntimeCaches.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/RuntimeCaches.php new file mode 100644 index 0000000000000000000000000000000000000000..6f2441f9ec9195e0f9613e9a1785366a1666e9af --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/RuntimeCaches.php @@ -0,0 +1,26 @@ + + */ + public $templates = []; + + /** + * @var array + */ + public $template_extends = []; + + /** + * @var array + */ + public $template_implements = []; + + /** + * @var ?string + */ + public $yield = null; + + /** + * @var array + */ + public $properties = []; + + /** + * @var array + */ + public $methods = []; + + /** + * @var bool + */ + public $sealed_properties = false; + + /** + * @var bool + */ + public $sealed_methods = false; + + /** + * @var bool + */ + public $override_property_visibility = false; + + /** + * @var bool + */ + public $override_method_visibility = false; + + /** + * @var bool + */ + public $mutation_free = false; + + /** + * @var bool + */ + public $external_mutation_free = false; + + /** + * @var bool + */ + public $taint_specialize = false; + + /** + * @var array + */ + public $suppressed_issues = []; + + /** + * @var list}> + */ + public $imported_types = []; + + /** + * @var bool + */ + public $consistent_constructor = false; + + /** @var bool */ + public $stub_override = false; + + /** + * @var null|string + */ + public $extension_requirement; + + /** + * @var array + */ + public $implementation_requirements = []; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/DocblockParser.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/DocblockParser.php new file mode 100644 index 0000000000000000000000000000000000000000..3dffef3b36456e8012a33a1dfc6d25ecd98b1d0a --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/DocblockParser.php @@ -0,0 +1,228 @@ + $line) { + if (preg_match('/^[ \t]*\*?\s*@\w/i', $line)) { + $last = $k; + } elseif (preg_match('/^\s*\r?$/', $line)) { + $last = false; + } elseif ($last !== false) { + $old_last_line = $lines[$last]; + $lines[$last] = $old_last_line . "\n" . $line; + + unset($lines[$k]); + } + } + + $line_offset = 0; + + foreach ($lines as $k => $line) { + $original_line_length = strlen($line); + + $line = str_replace("\r", '', $line); + + if ($first_line_padding === null) { + $asterisk_pos = strpos($line, '*'); + + if ($asterisk_pos) { + $first_line_padding = substr($line, 0, $asterisk_pos - 1); + } + } + + if (preg_match('/^[ \t]*\*?\s*@([\w\-\\\:]+)[\t ]*(.*)$/sm', $line, $matches, PREG_OFFSET_CAPTURE)) { + /** @var array $matches */ + [$_, $type_info, $data_info] = $matches; + + [$type] = $type_info; + [$data, $data_offset] = $data_info; + + if (strpos($data, '*')) { + $data = rtrim(preg_replace('/^[ \t]*\*\s*$/m', '', $data)); + } + + if (empty($special[$type])) { + $special[$type] = []; + } + + $data_offset += $line_offset; + + $special[$type][$data_offset + 3] = $data; + + unset($lines[$k]); + } else { + // Strip the leading *, if present. + $lines[$k] = str_replace("\t", ' ', $line); + $lines[$k] = preg_replace('/^ *\*/', '', $line); + } + + $line_offset += $original_line_length + 1; + } + + // Smush the whole docblock to the left edge. + $min_indent = 80; + foreach ($lines as $k => $line) { + $indent = strspn($line, ' '); + if ($indent === strlen($line)) { + // This line consists of only spaces. Trim it completely. + $lines[$k] = ''; + continue; + } + $min_indent = min($indent, $min_indent); + } + if ($min_indent > 0) { + foreach ($lines as $k => $line) { + if (strlen($line) < $min_indent) { + continue; + } + $lines[$k] = substr($line, $min_indent); + } + } + $docblock = implode("\n", $lines); + $docblock = rtrim($docblock); + + // Trim any empty lines off the front, but leave the indent level if there + // is one. + $docblock = preg_replace('/^\s*\n/', '', $docblock); + + $parsed = new ParsedDocblock($docblock, $special, $first_line_padding ?: ''); + + self::resolveTags($parsed); + + return $parsed; + } + + private static function resolveTags(ParsedDocblock $docblock) : void + { + if (isset($docblock->tags['template']) + || isset($docblock->tags['psalm-template']) + || isset($docblock->tags['phpstan-template']) + ) { + $docblock->combined_tags['template'] + = ($docblock->tags['template'] ?? []) + + ($docblock->tags['phpstan-template'] ?? []) + + ($docblock->tags['psalm-template'] ?? []); + } + + if (isset($docblock->tags['template-covariant']) + || isset($docblock->tags['psalm-template-covariant']) + || isset($docblock->tags['phpstan-template-covariant']) + ) { + $docblock->combined_tags['template-covariant'] + = ($docblock->tags['template-covariant'] ?? []) + + ($docblock->tags['phpstan-template-covariant'] ?? []) + + ($docblock->tags['psalm-template-covariant'] ?? []); + } + + if (isset($docblock->tags['template-extends']) + || isset($docblock->tags['inherits']) + || isset($docblock->tags['extends']) + || isset($docblock->tags['psalm-extends']) + || isset($docblock->tags['phpstan-extends']) + ) { + $docblock->combined_tags['extends'] + = ($docblock->tags['template-extends'] ?? []) + + ($docblock->tags['inherits'] ?? []) + + ($docblock->tags['extends'] ?? []) + + ($docblock->tags['psalm-extends'] ?? []) + + ($docblock->tags['phpstan-extends'] ?? []); + } + + if (isset($docblock->tags['template-implements']) + || isset($docblock->tags['implements']) + || isset($docblock->tags['phpstan-implements']) + || isset($docblock->tags['psalm-implements']) + ) { + $docblock->combined_tags['implements'] + = ($docblock->tags['template-implements'] ?? []) + + ($docblock->tags['implements'] ?? []) + + ($docblock->tags['phpstan-implements'] ?? []) + + ($docblock->tags['psalm-implements'] ?? []); + } + + if (isset($docblock->tags['template-use']) + || isset($docblock->tags['use']) + || isset($docblock->tags['phpstan-use']) + || isset($docblock->tags['psalm-use']) + ) { + $docblock->combined_tags['use'] + = ($docblock->tags['template-use'] ?? []) + + ($docblock->tags['use'] ?? []) + + ($docblock->tags['phpstan-use'] ?? []) + + ($docblock->tags['psalm-use'] ?? []); + } + + if (isset($docblock->tags['method']) + || isset($docblock->tags['psalm-method']) + ) { + $docblock->combined_tags['method'] + = ($docblock->tags['method'] ?? []) + + ($docblock->tags['psalm-method'] ?? []); + } + + if (isset($docblock->tags['return']) + || isset($docblock->tags['psalm-return']) + || isset($docblock->tags['phpstan-return']) + ) { + if (isset($docblock->tags['psalm-return'])) { + $docblock->combined_tags['return'] = $docblock->tags['psalm-return']; + } elseif (isset($docblock->tags['phpstan-return'])) { + $docblock->combined_tags['return'] = $docblock->tags['phpstan-return']; + } else { + $docblock->combined_tags['return'] = $docblock->tags['return']; + } + } + + if (isset($docblock->tags['param']) + || isset($docblock->tags['psalm-param']) + || isset($docblock->tags['phpstan-param']) + ) { + $docblock->combined_tags['param'] + = ($docblock->tags['param'] ?? []) + + ($docblock->tags['phpstan-param'] ?? []) + + ($docblock->tags['psalm-param'] ?? []); + } + + if (isset($docblock->tags['var']) + || isset($docblock->tags['psalm-var']) + || isset($docblock->tags['phpstan-var']) + ) { + $docblock->combined_tags['var'] + = ($docblock->tags['var'] ?? []) + + ($docblock->tags['phpstan-var'] ?? []) + + ($docblock->tags['psalm-var'] ?? []); + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/FileScanner.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/FileScanner.php new file mode 100644 index 0000000000000000000000000000000000000000..3dfa1f4dd4734612092d1467afd48bc64452b0ec --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/FileScanner.php @@ -0,0 +1,115 @@ +file_path = $file_path; + $this->file_name = $file_name; + $this->will_analyze = $will_analyze; + } + + public function scan( + Codebase $codebase, + FileStorage $file_storage, + bool $storage_from_cache = false, + ?Progress $progress = null + ): void { + if ($progress === null) { + $progress = new VoidProgress(); + } + + if ((!$this->will_analyze || $file_storage->deep_scan) + && $storage_from_cache + && !$codebase->register_stub_files + ) { + return; + } + + $stmts = $codebase->statements_provider->getStatementsForFile( + $file_storage->file_path, + $codebase->php_major_version . '.' . $codebase->php_minor_version, + $progress + ); + + foreach ($stmts as $stmt) { + if (!$stmt instanceof PhpParser\Node\Stmt\ClassLike + && !$stmt instanceof PhpParser\Node\Stmt\Function_ + && !($stmt instanceof PhpParser\Node\Stmt\Expression + && $stmt->expr instanceof PhpParser\Node\Expr\Include_) + ) { + $file_storage->has_extra_statements = true; + break; + } + } + + if ($this->will_analyze) { + $progress->debug('Deep scanning ' . $file_storage->file_path . "\n"); + } else { + $progress->debug('Scanning ' . $file_storage->file_path . "\n"); + } + + $traverser = new NodeTraverser(); + $traverser->addVisitor( + new ReflectorVisitor($codebase, $file_storage, $this) + ); + + $traverser->traverse($stmts); + + $file_storage->deep_scan = $this->will_analyze; + } + + public function getFilePath(): string + { + return $this->file_path; + } + + public function getFileName(): string + { + return $this->file_name; + } + + public function getRootFilePath(): string + { + return $this->file_path; + } + + public function getRootFileName(): string + { + return $this->file_name; + } + + public function getAliases(): \Psalm\Aliases + { + return new \Psalm\Aliases(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/FunctionDocblockComment.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/FunctionDocblockComment.php new file mode 100644 index 0000000000000000000000000000000000000000..570924e28532fcf77f9cb5854261f539fef3abe1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/FunctionDocblockComment.php @@ -0,0 +1,199 @@ + + */ + public $params = []; + + /** + * @var array + */ + public $params_out = []; + + /** + * @var array{type:string, line_number: int}|null + */ + public $self_out; + + /** + * @var array + */ + public $globals = []; + + /** + * Whether or not the function is deprecated + * + * @var bool + */ + public $deprecated = false; + + /** + * If set, the function is internal to the given namespace. + * + * @var null|string + */ + public $psalm_internal = null; + + /** + * Whether or not the function is internal + * + * @var bool + */ + public $internal = false; + + /** + * Whether or not the function uses get_args + * + * @var bool + */ + public $variadic = false; + + /** + * Whether or not the function is pure + * + * @var bool + */ + public $pure = false; + + /** + * Whether or not to specialize a given call (useful for taint analysis) + * + * @var bool + */ + public $specialize_call = false; + + /** + * Represents the flow from function params to return type + * + * @var array + */ + public $flows = []; + + /** + * @var array + */ + public $added_taints = []; + + /** + * @var array + */ + public $removed_taints = []; + + /** + * @var array + */ + public $taint_sink_params = []; + + /** + * @var array + */ + public $taint_source_types = []; + + /** + * @var array + */ + public $assert_untainted_params = []; + + /** + * Whether or not to ignore the nullability of this function's return type + * + * @var bool + */ + public $ignore_nullable_return = false; + + /** + * Whether or not to ignore the nullability of this function's return type + * + * @var bool + */ + public $ignore_falsable_return = false; + + /** + * @var array + */ + public $suppressed_issues = []; + + /** + * @var array + */ + public $throws = []; + + /** + * @var array + */ + public $templates = []; + + /** + * @var array + */ + public $template_typeofs = []; + + /** + * @var array + */ + public $assertions = []; + + /** + * @var array + */ + public $if_true_assertions = []; + + /** + * @var array + */ + public $if_false_assertions = []; + + /** + * @var bool + */ + public $inheritdoc = false; + + /** + * @var bool + */ + public $mutation_free = false; + + /** + * @var bool + */ + public $external_mutation_free = false; + + /** + * @var bool + */ + public $no_named_args = false; + + /** @var bool */ + public $stub_override = false; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/ParsedDocblock.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/ParsedDocblock.php new file mode 100644 index 0000000000000000000000000000000000000000..ea159e84abd8991626ed3d2b5d0b2bd0d6fcc0a6 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/ParsedDocblock.php @@ -0,0 +1,95 @@ +> */ + public $tags = []; + + /** @var array> */ + public $combined_tags = []; + + /** + * @var bool + */ + private static $shouldAddNewLineBetweenAnnotations = true; + + /** @param array> $tags */ + public function __construct(string $description, array $tags, string $first_line_padding = '') + { + $this->description = $description; + $this->tags = $tags; + $this->first_line_padding = $first_line_padding; + } + + public function render(string $left_padding) : string + { + $doc_comment_text = '/**' . "\n"; + + $trimmed_description = trim($this->description); + + if ($trimmed_description !== '') { + $description_lines = explode("\n", $this->description); + + foreach ($description_lines as $line) { + $doc_comment_text .= $left_padding . ' *' . (trim($line) ? ' ' . $line : '') . "\n"; + } + } + + if ($this->tags) { + if ($trimmed_description !== '') { + $doc_comment_text .= $left_padding . ' *' . "\n"; + } + + $last_type = null; + + foreach ($this->tags as $type => $lines) { + if ($last_type !== null + && $last_type !== 'psalm-return' + && static::shouldAddNewLineBetweenAnnotations() + ) { + $doc_comment_text .= $left_padding . ' *' . "\n"; + } + + foreach ($lines as $line) { + $doc_comment_text .= $left_padding . ' * @' . $type . ($line !== '' ? ' ' . $line : '') . "\n"; + } + + $last_type = $type; + } + } + + $doc_comment_text .= $left_padding . ' */' . "\n" . $left_padding; + + return $doc_comment_text; + } + + private static function shouldAddNewLineBetweenAnnotations(): bool + { + return static::$shouldAddNewLineBetweenAnnotations; + } + + /** + * Sets whether a new line should be added between the annotations or not. + * + */ + public static function addNewLineBetweenAnnotations(bool $should = true): void + { + static::$shouldAddNewLineBetweenAnnotations = $should; + } + + public static function resetNewlineBetweenAnnotations(): void + { + static::$shouldAddNewLineBetweenAnnotations = true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php new file mode 100644 index 0000000000000000000000000000000000000000..7923e90d36886f4931ff89d7d565c01b58543204 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php @@ -0,0 +1,395 @@ + $args + */ + public static function handleOverride(array $args, Codebase $codebase): void + { + $identifier = $args[0]->value; + + if (!$args[1]->value instanceof PhpParser\Node\Expr\FuncCall + || !$args[1]->value->name instanceof PhpParser\Node\Name + ) { + return; + } + + $map = []; + + if ($args[1]->value->name->parts === ['map'] + && $args[1]->value->args + && $args[1]->value->args[0]->value instanceof PhpParser\Node\Expr\Array_ + ) { + foreach ($args[1]->value->args[0]->value->items as $array_item) { + if ($array_item + && $array_item->key instanceof PhpParser\Node\Scalar\String_ + ) { + if ($array_item->value instanceof PhpParser\Node\Expr\ClassConstFetch + && $array_item->value->class instanceof PhpParser\Node\Name\FullyQualified + && $array_item->value->name instanceof PhpParser\Node\Identifier + && strtolower($array_item->value->name->name) + ) { + $map[$array_item->key->value] = new Type\Union([ + new Type\Atomic\TNamedObject(implode('\\', $array_item->value->class->parts)) + ]); + } elseif ($array_item->value instanceof PhpParser\Node\Scalar\String_) { + $map[$array_item->key->value] = $array_item->value->value; + } + } + } + } + + $type_offset = null; + + if ($args[1]->value->name->parts === ['type'] + && $args[1]->value->args + && $args[1]->value->args[0]->value instanceof PhpParser\Node\Scalar\LNumber + ) { + $type_offset = $args[1]->value->args[0]->value->value; + } + + $element_type_offset = null; + + if ($args[1]->value->name->parts === ['elementType'] + && $args[1]->value->args + && $args[1]->value->args[0]->value instanceof PhpParser\Node\Scalar\LNumber + ) { + $element_type_offset = $args[1]->value->args[0]->value->value; + } + + if ($identifier instanceof PhpParser\Node\Expr\StaticCall + && $identifier->class instanceof PhpParser\Node\Name\FullyQualified + && $identifier->name instanceof PhpParser\Node\Identifier + && $identifier->args + && $identifier->args[0]->value instanceof PhpParser\Node\Scalar\LNumber + ) { + $meta_fq_classlike_name = implode('\\', $identifier->class->parts); + + $meta_method_name = strtolower($identifier->name->name); + + if ($map) { + $offset = $identifier->args[0]->value->value; + + $codebase->methods->return_type_provider->registerClosure( + $meta_fq_classlike_name, + /** + * @param array $call_args + */ + function ( + \Psalm\StatementsSource $statements_analyzer, + string $fq_classlike_name, + string $method_name, + array $call_args, + Context $_context, + CodeLocation $_code_location + ) use ( + $map, + $offset, + $meta_fq_classlike_name, + $meta_method_name + ): ?Type\Union { + if (!$statements_analyzer instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if ($meta_method_name !== $method_name + || $meta_fq_classlike_name !== $fq_classlike_name + ) { + return null; + } + + if (isset($call_args[$offset]->value) + && ($call_arg_type = $statements_analyzer->node_data->getType($call_args[$offset]->value)) + && $call_arg_type->isSingleStringLiteral() + ) { + $offset_arg_value = $call_arg_type->getSingleStringLiteral()->value; + + if ($mapped_type = $map[$offset_arg_value] ?? null) { + if ($mapped_type instanceof Type\Union) { + return clone $mapped_type; + } + } + + if (($mapped_type = $map[''] ?? null) && is_string($mapped_type)) { + if (strpos($mapped_type, '@') !== false) { + $mapped_type = str_replace('@', $offset_arg_value, $mapped_type); + + if (strpos($mapped_type, '.') === false) { + return new Type\Union([ + new Type\Atomic\TNamedObject($mapped_type) + ]); + } + } + } + } + + return null; + } + ); + } elseif ($type_offset !== null) { + $codebase->methods->return_type_provider->registerClosure( + $meta_fq_classlike_name, + /** + * @param array $call_args + */ + function ( + \Psalm\StatementsSource $statements_analyzer, + string $fq_classlike_name, + string $method_name, + array $call_args, + Context $_context, + CodeLocation $_code_location + ) use ( + $map, + $type_offset, + $meta_fq_classlike_name, + $meta_method_name + ): ?Type\Union { + if (!$statements_analyzer instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if ($meta_method_name !== $method_name + || $meta_fq_classlike_name !== $fq_classlike_name + ) { + return null; + } + + if (isset($call_args[$type_offset]->value) + && ($call_arg_type + = $statements_analyzer->node_data->getType($call_args[$type_offset]->value)) + ) { + return clone $call_arg_type; + } + + return null; + } + ); + } elseif ($element_type_offset !== null) { + $codebase->methods->return_type_provider->registerClosure( + $meta_fq_classlike_name, + /** + * @param array $call_args + */ + function ( + \Psalm\StatementsSource $statements_analyzer, + string $fq_classlike_name, + string $method_name, + array $call_args, + Context $_context, + CodeLocation $_code_location + ) use ( + $map, + $element_type_offset, + $meta_fq_classlike_name, + $meta_method_name + ): ?Type\Union { + if (!$statements_analyzer instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if ($meta_method_name !== $method_name + || $meta_fq_classlike_name !== $fq_classlike_name + ) { + return null; + } + + if (isset($call_args[$element_type_offset]->value) + && ($call_arg_type + = $statements_analyzer->node_data->getType($call_args[$element_type_offset]->value)) + && $call_arg_type->hasArray() + ) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var Type\Atomic\TArray|Type\Atomic\TKeyedArray|Type\Atomic\TList + */ + $array_atomic_type = $call_arg_type->getAtomicTypes()['array']; + + if ($array_atomic_type instanceof Type\Atomic\TKeyedArray) { + return $array_atomic_type->getGenericValueType(); + } + + if ($array_atomic_type instanceof Type\Atomic\TList) { + return $array_atomic_type->type_param; + } + + return clone $array_atomic_type->type_params[1]; + } + + return null; + } + ); + } + } + + if ($identifier instanceof PhpParser\Node\Expr\FuncCall + && $identifier->name instanceof PhpParser\Node\Name\FullyQualified + && $identifier->args + && $identifier->args[0]->value instanceof PhpParser\Node\Scalar\LNumber + ) { + $function_id = implode('\\', $identifier->name->parts); + + if ($map) { + $offset = $identifier->args[0]->value->value; + + $codebase->functions->return_type_provider->registerClosure( + $function_id, + /** + * @param non-empty-string $function_id + * @param array $call_args + */ + function ( + \Psalm\StatementsSource $statements_analyzer, + string $function_id, + array $call_args, + Context $_context, + CodeLocation $_code_location + ) use ( + $map, + $offset + ) : Type\Union { + if (!$statements_analyzer instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if (isset($call_args[$offset]->value) + && ($call_arg_type + = $statements_analyzer->node_data->getType($call_args[$offset]->value)) + && $call_arg_type->isSingleStringLiteral() + ) { + $offset_arg_value = $call_arg_type->getSingleStringLiteral()->value; + + if ($mapped_type = $map[$offset_arg_value] ?? null) { + if ($mapped_type instanceof Type\Union) { + return clone $mapped_type; + } + } + + if (($mapped_type = $map[''] ?? null) && is_string($mapped_type)) { + if (strpos($mapped_type, '@') !== false) { + $mapped_type = str_replace('@', $offset_arg_value, $mapped_type); + + if (strpos($mapped_type, '.') === false) { + return new Type\Union([ + new Type\Atomic\TNamedObject($mapped_type) + ]); + } + } + } + } + + $storage = $statements_analyzer->getCodebase()->functions->getStorage( + $statements_analyzer, + strtolower($function_id) + ); + + return $storage->return_type ?: Type::getMixed(); + } + ); + } elseif ($type_offset !== null) { + $codebase->functions->return_type_provider->registerClosure( + $function_id, + /** + * @param non-empty-string $function_id + * @param array $call_args + */ + function ( + \Psalm\StatementsSource $statements_analyzer, + string $function_id, + array $call_args, + Context $_context, + CodeLocation $_code_location + ) use ( + $map, + $type_offset + ) : Type\Union { + if (!$statements_analyzer instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if (isset($call_args[$type_offset]->value) + && ($call_arg_type + = $statements_analyzer->node_data->getType($call_args[$type_offset]->value)) + ) { + return clone $call_arg_type; + } + + $storage = $statements_analyzer->getCodebase()->functions->getStorage( + $statements_analyzer, + strtolower($function_id) + ); + + return $storage->return_type ?: Type::getMixed(); + } + ); + } elseif ($element_type_offset !== null) { + $codebase->functions->return_type_provider->registerClosure( + $function_id, + /** + * @param non-empty-string $function_id + * @param array $call_args + */ + function ( + \Psalm\StatementsSource $statements_analyzer, + string $function_id, + array $call_args, + Context $_context, + CodeLocation $_code_location + ) use ( + $map, + $element_type_offset + ) : Type\Union { + if (!$statements_analyzer instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + return Type::getMixed(); + } + + if (isset($call_args[$element_type_offset]->value) + && ($call_arg_type + = $statements_analyzer->node_data->getType($call_args[$element_type_offset]->value)) + && $call_arg_type->hasArray() + ) { + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + * @var Type\Atomic\TArray|Type\Atomic\TKeyedArray|Type\Atomic\TList + */ + $array_atomic_type = $call_arg_type->getAtomicTypes()['array']; + + if ($array_atomic_type instanceof Type\Atomic\TKeyedArray) { + return $array_atomic_type->getGenericValueType(); + } + + if ($array_atomic_type instanceof Type\Atomic\TList) { + return $array_atomic_type->type_param; + } + + return clone $array_atomic_type->type_params[1]; + } + + $storage = $statements_analyzer->getCodebase()->functions->getStorage( + $statements_analyzer, + strtolower($function_id) + ); + + return $storage->return_type ?: Type::getMixed(); + } + ); + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php new file mode 100644 index 0000000000000000000000000000000000000000..fde629653e6302bb8bd245a1057801862c2119a2 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php @@ -0,0 +1,23 @@ +array = $left; + $this->offset = $right; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php new file mode 100644 index 0000000000000000000000000000000000000000..fe5e1b96fb977e47160ac5f573f46e4b674471cf --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php @@ -0,0 +1,20 @@ + */ + public $entries; + + /** @param list $entries */ + public function __construct(array $entries) + { + $this->entries = $entries; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php new file mode 100644 index 0000000000000000000000000000000000000000..7f789b9ff4362396c3ff60f31f1127cc04bc8ec3 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php @@ -0,0 +1,23 @@ +fqcln = $fqcln; + $this->name = $name; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php new file mode 100644 index 0000000000000000000000000000000000000000..a222fb038e0fb0b9a10f8d680a076f36211bd042 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php @@ -0,0 +1,23 @@ +name = $name; + $this->is_fully_qualified = $is_fully_qualified; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php new file mode 100644 index 0000000000000000000000000000000000000000..7ba4d8d32435ed7e003b4f7e84b9dabc6f1388b3 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php @@ -0,0 +1,23 @@ +key = $key; + $this->value = $value; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php new file mode 100644 index 0000000000000000000000000000000000000000..6fa9727ab2e057a03c53bec0ed861741648d7b72 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php @@ -0,0 +1,20 @@ +value = $value; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php new file mode 100644 index 0000000000000000000000000000000000000000..90c2f9e9c207498edcd09cd89584dbbcb7612fac --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php @@ -0,0 +1,10 @@ +left = $left; + $this->right = $right; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseOr.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseOr.php new file mode 100644 index 0000000000000000000000000000000000000000..751fc5eebb41da5a6603970e5b698b1d59856037 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseOr.php @@ -0,0 +1,10 @@ +cond = $cond; + $this->if = $if; + $this->else = $else; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php new file mode 100644 index 0000000000000000000000000000000000000000..5f96f7d1ac6f2248809e368191d8ca9035e8c9bb --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php @@ -0,0 +1,10 @@ + + */ + public $removed_taints = []; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/CaseScope.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/CaseScope.php new file mode 100644 index 0000000000000000000000000000000000000000..bad6dc99471d5e5d27129f830f56cafc759e1536 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/CaseScope.php @@ -0,0 +1,32 @@ +|null + */ + public $break_vars; + + public function __construct(Context $parent_context) + { + $this->parent_context = $parent_context; + } + + public function __destruct() + { + /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ + $this->parent_context = null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/FinallyScope.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/FinallyScope.php new file mode 100644 index 0000000000000000000000000000000000000000..fc8e7cd87508d6df073dfbf78d06c0abafe63d3f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/FinallyScope.php @@ -0,0 +1,23 @@ + + */ + public $vars_in_scope = []; + + /** + * @param array $vars_in_scope + */ + public function __construct(array $vars_in_scope) + { + $this->vars_in_scope = $vars_in_scope; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/IfConditionalScope.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/IfConditionalScope.php new file mode 100644 index 0000000000000000000000000000000000000000..3e0c68d743a40b46bb7fcf3b7f61bc346c344be6 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/IfConditionalScope.php @@ -0,0 +1,46 @@ + + */ + public $cond_referenced_var_ids; + + /** + * @var array + */ + public $cond_assigned_var_ids; + + /** @var list<\Psalm\Internal\Clause> */ + public $entry_clauses; + + /** + * @param array $cond_referenced_var_ids + * @param array $cond_assigned_var_ids + * @param list<\Psalm\Internal\Clause> $entry_clauses + */ + public function __construct( + Context $if_context, + Context $original_context, + array $cond_referenced_var_ids, + array $cond_assigned_var_ids, + array $entry_clauses + ) { + $this->if_context = $if_context; + $this->original_context = $original_context; + $this->cond_referenced_var_ids = $cond_referenced_var_ids; + $this->cond_assigned_var_ids = $cond_assigned_var_ids; + $this->entry_clauses = $entry_clauses; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/IfScope.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/IfScope.php new file mode 100644 index 0000000000000000000000000000000000000000..affc2db277e17094473a3ca5afe6e85a1c296f91 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/IfScope.php @@ -0,0 +1,92 @@ +|null + */ + public $new_vars = null; + + /** + * @var array + */ + public $new_vars_possibly_in_scope = []; + + /** + * @var array|null + */ + public $redefined_vars = null; + + /** + * @var array|null + */ + public $assigned_var_ids = null; + + /** + * @var array + */ + public $possibly_assigned_var_ids = []; + + /** + * @var array + */ + public $possibly_redefined_vars = []; + + /** + * @var array + */ + public $updated_vars = []; + + /** + * @var array>> + */ + public $negated_types = []; + + /** + * @var array + */ + public $if_cond_changed_var_ids = []; + + /** + * @var array|null + */ + public $negatable_if_types = null; + + /** + * @var list + */ + public $negated_clauses = []; + + /** + * These are the set of clauses that could be applied after the `if` + * statement, if the `if` statement contains branches with leaving statements, + * and the else leaves too + * + * @var list + */ + public $reasonable_clauses = []; + + /** + * Variables that were mixed, but are no longer + * + * @var array|null + */ + public $possible_param_types = null; + + /** + * @var string[] + */ + public $final_actions = []; + + /** + * @var ?\Psalm\Context + */ + public $mic_drop_context; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/LoopScope.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/LoopScope.php new file mode 100644 index 0000000000000000000000000000000000000000..aa4092df46d74e24a7ff3010aaf85e67112f6060 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/LoopScope.php @@ -0,0 +1,73 @@ +|null + */ + public $redefined_loop_vars = []; + + /** + * @var array + */ + public $possibly_redefined_loop_vars = []; + + /** + * @var array|null + */ + public $possibly_redefined_loop_parent_vars = null; + + /** + * @var array + */ + public $possibly_defined_loop_parent_vars = []; + + /** + * @var array + */ + public $vars_possibly_in_scope = []; + + /** + * @var array + */ + public $protected_var_ids = []; + + /** + * @var string[] + */ + public $final_actions = []; + + public function __construct(Context $loop_context, Context $parent_context) + { + $this->loop_context = $loop_context; + $this->loop_parent_context = $parent_context; + } + + public function __destruct() + { + $this->loop_context = null; + $this->loop_parent_context = null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/SwitchScope.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/SwitchScope.php new file mode 100644 index 0000000000000000000000000000000000000000..9025e65578a4c3c2fa3744d86395d154edc3b8ea --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Scope/SwitchScope.php @@ -0,0 +1,52 @@ +|null + */ + public $new_vars_in_scope = null; + + /** + * @var array + */ + public $new_vars_possibly_in_scope = []; + + /** + * @var array|null + */ + public $redefined_vars = null; + + /** + * @var array|null + */ + public $possibly_redefined_vars = null; + + /** + * @var array + */ + public $leftover_statements = []; + + /** + * @var PhpParser\Node\Expr|null + */ + public $leftover_case_equality_expr = null; + + /** + * @var list + */ + public $negated_clauses = []; + + /** + * @var array|null + */ + public $new_assigned_var_ids = null; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php new file mode 100644 index 0000000000000000000000000000000000000000..803ea08de0027e72470ebb784c083d511d4766bb --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php @@ -0,0 +1,297 @@ + array_merge( + self::getConstantNodes($codebase, $storage), + self::getPropertyNodes($storage), + self::getMethodNodes($storage) + ) + ]; + + $docblock = new ParsedDocblock('', []); + + $template_offset = 0; + + foreach ($storage->template_types ?: [] as $template_name => $map) { + $type = array_values($map)[0][0]; + + $key = isset($storage->template_covariants[$template_offset]) ? 'template-covariant' : 'template'; + + $docblock->tags[$key][] = $template_name . ' as ' . $type->toNamespacedString( + null, + [], + null, + false + ); + + $template_offset++; + } + + $attrs = [ + 'comments' => $docblock->tags + ? [ + new PhpParser\Comment\Doc( + \rtrim($docblock->render(' ')) + ) + ] + : [] + ]; + + if ($storage->is_interface) { + if ($storage->direct_interface_parents) { + $subnodes['extends'] = []; + + foreach ($storage->direct_interface_parents as $direct_interface_parent) { + $subnodes['extends'][] = new PhpParser\Node\Name\FullyQualified($direct_interface_parent); + } + } + + return new PhpParser\Node\Stmt\Interface_( + $classlike_name, + $subnodes, + $attrs + ); + } + + if ($storage->is_trait) { + return new PhpParser\Node\Stmt\Trait_( + $classlike_name, + $subnodes, + $attrs + ); + } + + if ($storage->parent_class) { + $subnodes['extends'] = new PhpParser\Node\Name\FullyQualified($storage->parent_class); + } else + + if ($storage->direct_class_interfaces) { + $subnodes['implements'] = []; + foreach ($storage->direct_class_interfaces as $direct_class_interface) { + $subnodes['implements'][] = new PhpParser\Node\Name\FullyQualified($direct_class_interface); + } + } + + return new PhpParser\Node\Stmt\Class_( + $classlike_name, + $subnodes, + $attrs + ); + } + + /** + * @return list + */ + private static function getConstantNodes(\Psalm\Codebase $codebase, ClassLikeStorage $storage) : array + { + $constant_nodes = []; + + foreach ($storage->constants as $constant_name => $constant_storage) { + if ($constant_storage->unresolved_node) { + $type = new Type\Union([ + \Psalm\Internal\Codebase\ConstantTypeResolver::resolve( + $codebase->classlikes, + $constant_storage->unresolved_node + ) + ]); + } elseif ($constant_storage->type) { + $type = $constant_storage->type; + } else { + throw new \UnexpectedValueException('bad'); + } + + $constant_nodes[] = new PhpParser\Node\Stmt\ClassConst( + [ + new PhpParser\Node\Const_( + $constant_name, + StubsGenerator::getExpressionFromType($type) + ) + ], + $constant_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC + ? PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC + : ($constant_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED + ? PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED + : PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE) + ); + } + + return $constant_nodes; + } + + /** + * @return list + */ + private static function getPropertyNodes(ClassLikeStorage $storage) : array + { + $namespace_name = implode('\\', array_slice(explode('\\', $storage->name), 0, -1)); + + $property_nodes = []; + + foreach ($storage->properties as $property_name => $property_storage) { + switch ($property_storage->visibility) { + case ClassLikeAnalyzer::VISIBILITY_PRIVATE: + $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE; + break; + case ClassLikeAnalyzer::VISIBILITY_PROTECTED: + $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED; + break; + default: + $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC; + break; + } + + $docblock = new ParsedDocblock('', []); + + if ($property_storage->type + && $property_storage->signature_type !== $property_storage->type + ) { + $docblock->tags['var'][] = $property_storage->type->toNamespacedString( + $namespace_name, + [], + null, + false + ); + } + + $property_nodes[] = new PhpParser\Node\Stmt\Property( + $flag | ($property_storage->is_static ? PhpParser\Node\Stmt\Class_::MODIFIER_STATIC : 0), + [ + new PhpParser\Node\Stmt\PropertyProperty( + $property_name, + $property_storage->suggested_type + ? StubsGenerator::getExpressionFromType($property_storage->suggested_type) + : null + ) + ], + [ + 'comments' => $docblock->tags + ? [ + new PhpParser\Comment\Doc( + \rtrim($docblock->render(' ')) + ) + ] + : [] + ], + $property_storage->signature_type + ? StubsGenerator::getParserTypeFromPsalmType($property_storage->signature_type) + : null + ); + } + + return $property_nodes; + } + + /** + * @return list + */ + private static function getMethodNodes(ClassLikeStorage $storage): array { + $namespace_name = implode('\\', array_slice(explode('\\', $storage->name), 0, -1)); + $method_nodes = []; + + foreach ($storage->methods as $method_storage) { + if (!$method_storage->cased_name) { + throw new \UnexpectedValueException('very bad'); + } + + switch ($method_storage->visibility) { + case \ReflectionProperty::IS_PRIVATE: + $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE; + break; + case \ReflectionProperty::IS_PROTECTED: + $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED; + break; + default: + $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC; + break; + } + + $docblock = new ParsedDocblock('', []); + + foreach ($method_storage->template_types ?: [] as $template_name => $map) { + $type = array_values($map)[0][0]; + + $docblock->tags['template'][] = $template_name . ' as ' . $type->toNamespacedString( + $namespace_name, + [], + null, + false + ); + } + + foreach ($method_storage->params as $param) { + if ($param->type && $param->type !== $param->signature_type) { + $docblock->tags['param'][] = $param->type->toNamespacedString( + $namespace_name, + [], + null, + false + ) . ' $' . $param->name; + } + } + + if ($method_storage->return_type + && $method_storage->signature_return_type !== $method_storage->return_type + ) { + $docblock->tags['return'][] = $method_storage->return_type->toNamespacedString( + $namespace_name, + [], + null, + false + ); + } + + foreach ($method_storage->throws ?: [] as $exception_name => $_) { + $docblock->tags['throws'][] = Type::getStringFromFQCLN( + $exception_name, + $namespace_name, + [], + null, + false + ); + } + + $method_nodes[] = new PhpParser\Node\Stmt\ClassMethod( + $method_storage->cased_name, + [ + 'flags' => $flag + | ($method_storage->is_static ? PhpParser\Node\Stmt\Class_::MODIFIER_STATIC : 0) + | ($method_storage->abstract ? PhpParser\Node\Stmt\Class_::MODIFIER_ABSTRACT : 0), + 'params' => StubsGenerator::getFunctionParamNodes($method_storage), + 'returnType' => $method_storage->signature_return_type + ? StubsGenerator::getParserTypeFromPsalmType($method_storage->signature_return_type) + : null, + 'stmts' => $storage->is_interface || $method_storage->abstract ? null : [], + ], + [ + 'comments' => $docblock->tags + ? [ + new PhpParser\Comment\Doc( + \rtrim($docblock->render(' ')) + ) + ] + : [] + ] + ); + } + + return $method_nodes; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php new file mode 100644 index 0000000000000000000000000000000000000000..91dab630e7df566e87047a2b472d851fe98d10fa --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php @@ -0,0 +1,349 @@ +getAll() as $storage) { + if (\strpos($storage->name, 'Psalm\\') === 0) { + continue; + } + + if ($storage->location + && strpos($storage->location->file_path, $psalm_base) === 0 + ) { + continue; + } + + if ($storage->stubbed) { + continue; + } + + $name_parts = explode('\\', $storage->name); + + $classlike_name = array_pop($name_parts); + $namespace_name = implode('\\', $name_parts); + + if (!isset($namespaced_nodes[$namespace_name])) { + $namespaced_nodes[$namespace_name] = []; + } + + $namespaced_nodes[$namespace_name][$classlike_name] = ClassLikeStubGenerator::getClassLikeNode( + $codebase, + $storage, + $classlike_name + ); + } + + $all_function_names = []; + + foreach ($codebase->functions->getAllStubbedFunctions() as $function_storage) { + if ($function_storage->location + && \strpos($function_storage->location->file_path, $psalm_base) === 0 + ) { + continue; + } + + if (!$function_storage->cased_name) { + throw new \UnexpectedValueException('very bad'); + } + + $fq_name = $function_storage->cased_name; + + $all_function_names[$fq_name] = true; + + $name_parts = explode('\\', $fq_name); + $function_name = array_pop($name_parts); + + $namespace_name = implode('\\', $name_parts); + + $namespaced_nodes[$namespace_name][$fq_name] = self::getFunctionNode( + $function_storage, + $function_name, + $namespace_name + ); + } + + foreach ($codebase->getAllStubbedConstants() as $fq_name => $type) { + if ($type->isMixed()) { + continue; + } + + $name_parts = explode('\\', $fq_name); + $constant_name = array_pop($name_parts); + + $namespace_name = implode('\\', $name_parts); + + $namespaced_nodes[$namespace_name][$fq_name] = new PhpParser\Node\Stmt\Const_( + [ + new PhpParser\Node\Const_( + $constant_name, + self::getExpressionFromType($type) + ) + ] + ); + } + + foreach ($file_provider->getAll() as $file_storage) { + if (\strpos($file_storage->file_path, $psalm_base) === 0) { + continue; + } + + foreach ($file_storage->functions as $function_storage) { + if (!$function_storage->cased_name) { + continue; + } + + $fq_name = $function_storage->cased_name; + + if (isset($all_function_names[$fq_name])) { + continue; + } + + $all_function_names[$fq_name] = true; + + $name_parts = explode('\\', $fq_name); + $function_name = array_pop($name_parts); + + $namespace_name = implode('\\', $name_parts); + + $namespaced_nodes[$namespace_name][$fq_name] = self::getFunctionNode( + $function_storage, + $function_name, + $namespace_name + ); + } + + foreach ($file_storage->constants as $fq_name => $type) { + if ($type->isMixed()) { + continue; + } + + if ($type->isMixed()) { + continue; + } + + $name_parts = explode('\\', $fq_name); + $constant_name = array_pop($name_parts); + + $namespace_name = implode('\\', $name_parts); + + $namespaced_nodes[$namespace_name][$fq_name] = new PhpParser\Node\Stmt\Const_( + [ + new PhpParser\Node\Const_( + $constant_name, + self::getExpressionFromType($type) + ) + ] + ); + } + } + + ksort($namespaced_nodes); + + $namespace_stmts = []; + + foreach ($namespaced_nodes as $namespace_name => $stmts) { + ksort($stmts); + + $namespace_stmts[] = new PhpParser\Node\Stmt\Namespace_( + $namespace_name ? new PhpParser\Node\Name($namespace_name) : null, + array_values($stmts), + ['kind' => PhpParser\Node\Stmt\Namespace_::KIND_BRACED] + ); + } + + $prettyPrinter = new PhpParser\PrettyPrinter\Standard; + return $prettyPrinter->prettyPrintFile($namespace_stmts); + } + + private static function getFunctionNode( + \Psalm\Storage\FunctionLikeStorage $function_storage, + string $function_name, + string $namespace_name + ) : PhpParser\Node\Stmt\Function_ { + $docblock = new ParsedDocblock('', []); + + foreach ($function_storage->template_types ?: [] as $template_name => $map) { + $type = array_values($map)[0][0]; + + $docblock->tags['template'][] = $template_name . ' as ' . $type->toNamespacedString( + $namespace_name, + [], + null, + false + ); + } + + foreach ($function_storage->params as $param) { + if ($param->type && $param->type !== $param->signature_type) { + $docblock->tags['param'][] = $param->type->toNamespacedString( + $namespace_name, + [], + null, + false + ) . ' $' . $param->name; + } + } + + if ($function_storage->return_type + && $function_storage->signature_return_type !== $function_storage->return_type + ) { + $docblock->tags['return'][] = $function_storage->return_type->toNamespacedString( + $namespace_name, + [], + null, + false + ); + } + + foreach ($function_storage->throws ?: [] as $exception_name => $_) { + $docblock->tags['throws'][] = Type::getStringFromFQCLN( + $exception_name, + $namespace_name, + [], + null, + false + ); + } + + return new PhpParser\Node\Stmt\Function_( + $function_name, + [ + 'params' => self::getFunctionParamNodes($function_storage), + 'returnType' => $function_storage->signature_return_type + ? self::getParserTypeFromPsalmType($function_storage->signature_return_type) + : null, + 'stmts' => [], + ], + [ + 'comments' => $docblock->tags + ? [ + new PhpParser\Comment\Doc( + \rtrim($docblock->render(' ')) + ) + ] + : [] + ] + ); + } + + /** + * @return list + */ + public static function getFunctionParamNodes(\Psalm\Storage\FunctionLikeStorage $method_storage): array + { + $param_nodes = []; + + foreach ($method_storage->params as $param) { + $param_nodes[] = new PhpParser\Node\Param( + new PhpParser\Node\Expr\Variable($param->name), + $param->default_type + ? self::getExpressionFromType($param->default_type) + : null, + $param->signature_type + ? self::getParserTypeFromPsalmType($param->signature_type) + : null, + $param->by_ref, + $param->is_variadic + ); + } + + return $param_nodes; + } + + /** + * @return PhpParser\Node\Identifier|PhpParser\Node\Name\FullyQualified|PhpParser\Node\NullableType|null + */ + public static function getParserTypeFromPsalmType(Type\Union $type): ?PhpParser\NodeAbstract + { + $nullable = $type->isNullable(); + + foreach ($type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TNull) { + continue; + } + + if ($atomic_type instanceof Type\Atomic\Scalar + || $atomic_type instanceof Type\Atomic\TObject + || $atomic_type instanceof Type\Atomic\TArray + || $atomic_type instanceof Type\Atomic\TIterable + ) { + $identifier_string = $atomic_type->toPhpString(null, [], null, 8, 0); + + if ($identifier_string === null) { + throw new \UnexpectedValueException( + $atomic_type->getId() . ' could not be converted to an identifier' + ); + } + $identifier = new PhpParser\Node\Identifier($identifier_string); + + if ($nullable) { + return new PhpParser\Node\NullableType($identifier); + } + + return $identifier; + } + + if ($atomic_type instanceof Type\Atomic\TNamedObject) { + $name_node = new PhpParser\Node\Name\FullyQualified($atomic_type->value); + + if ($nullable) { + return new PhpParser\Node\NullableType($name_node); + } + + return $name_node; + } + } + + return null; + } + + public static function getExpressionFromType(Type\Union $type) : PhpParser\Node\Expr + { + foreach ($type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TLiteralString) { + return new PhpParser\Node\Scalar\String_($atomic_type->value); + } + + if ($atomic_type instanceof Type\Atomic\TLiteralInt) { + return new PhpParser\Node\Scalar\LNumber($atomic_type->value); + } + + if ($atomic_type instanceof Type\Atomic\TLiteralFloat) { + return new PhpParser\Node\Scalar\DNumber($atomic_type->value); + } + + if ($atomic_type instanceof Type\Atomic\TFalse) { + return new PhpParser\Node\Expr\ConstFetch(new PhpParser\Node\Name('false')); + } + + if ($atomic_type instanceof Type\Atomic\TTrue) { + return new PhpParser\Node\Expr\ConstFetch(new PhpParser\Node\Name('true')); + } + + if ($atomic_type instanceof Type\Atomic\TNull) { + return new PhpParser\Node\Expr\ConstFetch(new PhpParser\Node\Name('null')); + } + + if ($atomic_type instanceof Type\Atomic\TArray) { + return new PhpParser\Node\Expr\Array_([]); + } + } + + return new PhpParser\Node\Scalar\String_('Psalm could not infer this type'); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ArrayType.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ArrayType.php new file mode 100644 index 0000000000000000000000000000000000000000..f176d29ace3ee270e76f0037f09633ade782aa6b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ArrayType.php @@ -0,0 +1,59 @@ +key = $key; + $this->value = $value; + $this->is_list = $is_list; + } + + /** + * @return self|null + */ + public static function infer(Type\Atomic $type): ?self + { + if ($type instanceof Type\Atomic\TKeyedArray) { + return new self( + $type->getGenericKeyType(), + $type->getGenericValueType(), + $type->is_list + ); + } + + if ($type instanceof Type\Atomic\TList) { + return new self( + Type::getInt(), + $type->type_param, + true + ); + } + + if ($type instanceof Type\Atomic\TArray) { + return new self( + $type->type_params[0], + $type->type_params[1], + false + ); + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/AssertionReconciler.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/AssertionReconciler.php new file mode 100644 index 0000000000000000000000000000000000000000..2197ec631fb0abd06ff0d25342423bd0517569e6 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/AssertionReconciler.php @@ -0,0 +1,1299 @@ + null, + * - empty(bool) => false, + * - notEmpty(Object|null) => Object, + * - notEmpty(Object|false) => Object + * + * @param string[] $suppressed_issues + * @param array> $template_type_map + * @param-out 0|1|2 $failed_reconciliation + */ + public static function reconcile( + string $assertion, + ?Union $existing_var_type, + ?string $key, + StatementsAnalyzer $statements_analyzer, + bool $inside_loop, + array $template_type_map, + ?CodeLocation $code_location = null, + array $suppressed_issues = [], + ?int &$failed_reconciliation = 0, + bool $negated = false + ) : Union { + $codebase = $statements_analyzer->getCodebase(); + + $is_strict_equality = false; + $is_loose_equality = false; + $is_equality = false; + $is_negation = false; + $failed_reconciliation = 0; + + if ($assertion[0] === '!') { + $assertion = substr($assertion, 1); + $is_negation = true; + } + + if ($assertion[0] === '=') { + $assertion = substr($assertion, 1); + $is_strict_equality = true; + $is_equality = true; + } + + if ($assertion[0] === '~') { + $assertion = substr($assertion, 1); + $is_loose_equality = true; + $is_equality = true; + } + + if ($assertion[0] === '>') { + $assertion = 'falsy'; + $is_negation = true; + } + + if ($existing_var_type === null + && is_string($key) + && VariableFetchAnalyzer::isSuperGlobal($key) + ) { + $existing_var_type = VariableFetchAnalyzer::getGlobalType($key); + } + + if ($existing_var_type === null) { + if (($assertion === 'isset' && !$is_negation) + || ($assertion === 'empty' && $is_negation) + ) { + return Type::getMixed($inside_loop); + } + + if ($assertion === 'array-key-exists' + || $assertion === 'non-empty-countable' + || strpos($assertion, 'has-at-least-') === 0 + || strpos($assertion, 'has-exactly-') === 0 + ) { + return Type::getMixed(); + } + + if (!$is_negation && $assertion !== 'falsy' && $assertion !== 'empty') { + if ($is_equality) { + $bracket_pos = strpos($assertion, '('); + + if ($bracket_pos) { + $assertion = substr($assertion, 0, $bracket_pos); + } + } + + try { + return Type::parseString($assertion, null, $template_type_map); + } catch (\Exception $e) { + return Type::getMixed(); + } + } + + return Type::getMixed(); + } + + $old_var_type_string = $existing_var_type->getId(); + + if ($is_negation) { + return NegatedAssertionReconciler::reconcile( + $statements_analyzer, + $assertion, + $is_strict_equality, + $is_loose_equality, + $existing_var_type, + $template_type_map, + $old_var_type_string, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation + ); + } + + $simply_reconciled_type = SimpleAssertionReconciler::reconcile( + $assertion, + $codebase, + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + $is_strict_equality, + $inside_loop + ); + + if ($simply_reconciled_type) { + return $simply_reconciled_type; + } + + if (substr($assertion, 0, 4) === 'isa-') { + $assertion = substr($assertion, 4); + + $allow_string_comparison = false; + + if (substr($assertion, 0, 7) === 'string-') { + $assertion = substr($assertion, 7); + $allow_string_comparison = true; + } + + if ($existing_var_type->hasMixed()) { + $type = new Type\Union([ + new Type\Atomic\TNamedObject($assertion), + ]); + + if ($allow_string_comparison) { + $type->addType( + new Type\Atomic\TClassString( + $assertion, + new Type\Atomic\TNamedObject($assertion) + ) + ); + } + + return $type; + } + + $existing_has_object = $existing_var_type->hasObjectType(); + $existing_has_string = $existing_var_type->hasString(); + + if ($existing_has_object && !$existing_has_string) { + $new_type = Type::parseString($assertion, null, $template_type_map); + } elseif ($existing_has_string && !$existing_has_object) { + if (!$allow_string_comparison && $code_location) { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + 'Cannot allow string comparison to object for ' . $key, + $code_location, + null + ), + $suppressed_issues + )) { + // fall through + } + + $new_type = Type::getMixed(); + } else { + $new_type_has_interface_string = $codebase->interfaceExists($assertion); + + $old_type_has_interface_string = false; + + foreach ($existing_var_type->getAtomicTypes() as $existing_type_part) { + if ($existing_type_part instanceof TClassString + && $existing_type_part->as_type + && $codebase->interfaceExists($existing_type_part->as_type->value) + ) { + $old_type_has_interface_string = true; + break; + } + } + + if (isset($template_type_map[$assertion])) { + $new_type = Type::parseString( + 'class-string<' . $assertion . '>', + null, + $template_type_map + ); + } else { + $new_type = Type::getClassString($assertion); + } + + if (( + $new_type_has_interface_string + && !UnionTypeComparator::isContainedBy( + $codebase, + $existing_var_type, + $new_type + ) + ) + || ( + $old_type_has_interface_string + && !UnionTypeComparator::isContainedBy( + $codebase, + $new_type, + $existing_var_type + ) + ) + ) { + $new_type_part = Atomic::create($assertion, null, $template_type_map); + + $acceptable_atomic_types = []; + + foreach ($existing_var_type->getAtomicTypes() as $existing_var_type_part) { + if (!$new_type_part instanceof TNamedObject + || !$existing_var_type_part instanceof TClassString + ) { + $acceptable_atomic_types = []; + + break; + } + + if (!$existing_var_type_part->as_type instanceof TNamedObject) { + $acceptable_atomic_types = []; + + break; + } + + $existing_var_type_part = $existing_var_type_part->as_type; + + if (AtomicTypeComparator::isContainedBy( + $codebase, + $existing_var_type_part, + $new_type_part + )) { + $acceptable_atomic_types[] = clone $existing_var_type_part; + continue; + } + + if ($codebase->classExists($existing_var_type_part->value) + || $codebase->interfaceExists($existing_var_type_part->value) + ) { + $existing_var_type_part = clone $existing_var_type_part; + $existing_var_type_part->addIntersectionType($new_type_part); + $acceptable_atomic_types[] = $existing_var_type_part; + } + } + + if (count($acceptable_atomic_types) === 1) { + return new Type\Union([ + new TClassString('object', $acceptable_atomic_types[0]), + ]); + } + } + } + } else { + $new_type = Type::getMixed(); + } + } elseif (substr($assertion, 0, 9) === 'getclass-') { + $assertion = substr($assertion, 9); + $new_type = Type::parseString($assertion, null, $template_type_map); + } else { + $bracket_pos = strpos($assertion, '('); + + if ($bracket_pos) { + return self::handleLiteralEquality( + $assertion, + $bracket_pos, + $is_loose_equality, + $existing_var_type, + $old_var_type_string, + $key, + $negated, + $code_location, + $suppressed_issues + ); + } + + $new_type = Type::parseString($assertion, null, $template_type_map); + } + + if ($existing_var_type->hasMixed()) { + if ($is_loose_equality + && $new_type->hasScalarType() + ) { + return $existing_var_type; + } + + return $new_type; + } + + return self::refine( + $statements_analyzer, + $assertion, + $new_type, + $existing_var_type, + $template_type_map, + $key, + $negated, + $code_location, + $is_equality, + $is_loose_equality, + $suppressed_issues, + $failed_reconciliation + ); + } + + /** + * @param 0|1|2 $failed_reconciliation + * @param string[] $suppressed_issues + * @param array> $template_type_map + * @param-out 0|1|2 $failed_reconciliation + */ + private static function refine( + StatementsAnalyzer $statements_analyzer, + string $assertion, + Union $new_type, + Union $existing_var_type, + array $template_type_map, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + bool $is_equality, + bool $is_loose_equality, + array $suppressed_issues, + int &$failed_reconciliation + ) : Union { + $codebase = $statements_analyzer->getCodebase(); + + $old_var_type_string = $existing_var_type->getId(); + + $new_type_has_interface = false; + + if ($new_type->hasObjectType()) { + foreach ($new_type->getAtomicTypes() as $new_type_part) { + if ($new_type_part instanceof TNamedObject && + $codebase->interfaceExists($new_type_part->value) + ) { + $new_type_has_interface = true; + break; + } + } + } + + $old_type_has_interface = false; + + if ($existing_var_type->hasObjectType()) { + foreach ($existing_var_type->getAtomicTypes() as $existing_type_part) { + if ($existing_type_part instanceof TNamedObject && + $codebase->interfaceExists($existing_type_part->value) + ) { + $old_type_has_interface = true; + break; + } + } + } + + try { + if (strpos($assertion, '<') || strpos($assertion, '[') || strpos($assertion, '{')) { + $new_type_union = Type::parseString($assertion); + + $new_type_part = \array_values($new_type_union->getAtomicTypes())[0]; + } else { + $new_type_part = Atomic::create($assertion, null, $template_type_map); + } + } catch (\Psalm\Exception\TypeParseTreeException $e) { + $new_type_part = new TMixed(); + + if ($code_location) { + if (IssueBuffer::accepts( + new InvalidDocblock( + $assertion . ' cannot be used in an assertion', + $code_location + ), + $suppressed_issues + )) { + // fall through + } + } + } + + if ($new_type_part instanceof Type\Atomic\TTemplateParam + && $new_type_part->as->isSingle() + ) { + $new_as_atomic = \array_values($new_type_part->as->getAtomicTypes())[0]; + + $acceptable_atomic_types = []; + + foreach ($existing_var_type->getAtomicTypes() as $existing_var_type_part) { + if ($existing_var_type_part instanceof TNamedObject + || $existing_var_type_part instanceof TTemplateParam + ) { + $new_type_part->addIntersectionType($existing_var_type_part); + $acceptable_atomic_types[] = clone $existing_var_type_part; + } else { + if (AtomicTypeComparator::isContainedBy( + $codebase, + $existing_var_type_part, + $new_as_atomic + )) { + $acceptable_atomic_types[] = clone $existing_var_type_part; + } + } + } + + if ($acceptable_atomic_types) { + $new_type_part->as = new Type\Union($acceptable_atomic_types); + + return new Type\Union([$new_type_part]); + } + } + + if ($new_type_part instanceof Type\Atomic\TKeyedArray) { + $acceptable_atomic_types = []; + + foreach ($existing_var_type->getAtomicTypes() as $existing_var_type_part) { + if ($existing_var_type_part instanceof Type\Atomic\TKeyedArray) { + if (!array_intersect_key( + $existing_var_type_part->properties, + $new_type_part->properties + )) { + $existing_var_type_part = clone $existing_var_type_part; + $existing_var_type_part->properties = array_merge( + $existing_var_type_part->properties, + $new_type_part->properties + ); + + $acceptable_atomic_types[] = $existing_var_type_part; + } + } + } + + if ($acceptable_atomic_types) { + return new Type\Union($acceptable_atomic_types); + } + } + + if ($new_type_part instanceof TNamedObject + && (( + $new_type_has_interface + && !UnionTypeComparator::isContainedBy( + $codebase, + $existing_var_type, + $new_type + ) + ) + || ( + $old_type_has_interface + && !UnionTypeComparator::isContainedBy( + $codebase, + $new_type, + $existing_var_type + ) + )) + ) { + $acceptable_atomic_types = []; + + foreach ($existing_var_type->getAtomicTypes() as $existing_var_type_part) { + if (AtomicTypeComparator::isContainedBy( + $codebase, + $existing_var_type_part, + $new_type_part + )) { + $acceptable_atomic_types[] = clone $existing_var_type_part; + continue; + } + + if ($existing_var_type_part instanceof TNamedObject + && ($codebase->classExists($existing_var_type_part->value) + || $codebase->interfaceExists($existing_var_type_part->value)) + ) { + $existing_var_type_part = clone $existing_var_type_part; + $existing_var_type_part->addIntersectionType($new_type_part); + $acceptable_atomic_types[] = $existing_var_type_part; + } + + if ($existing_var_type_part instanceof TTemplateParam) { + $existing_var_type_part = clone $existing_var_type_part; + $existing_var_type_part->addIntersectionType($new_type_part); + $acceptable_atomic_types[] = $existing_var_type_part; + } + } + + if ($acceptable_atomic_types) { + return new Type\Union($acceptable_atomic_types); + } + } elseif (!$new_type->hasMixed()) { + $has_match = true; + + if ($key + && $code_location + && $new_type->getId() === $existing_var_type->getId() + && !$is_equality + && (!($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer) + || ($key !== '$this' + && !($existing_var_type->hasLiteralClassString() && $new_type->hasLiteralClassString()))) + ) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + $assertion, + true, + $negated, + $code_location, + $suppressed_issues + ); + } + + $any_scalar_type_match_found = false; + + if ($code_location + && $key + && !$is_equality + && $new_type_part instanceof TNamedObject + && !$new_type_has_interface + && (!($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer) + || ($key !== '$this' + && !($existing_var_type->hasLiteralClassString() && $new_type->hasLiteralClassString()))) + && UnionTypeComparator::isContainedBy( + $codebase, + $existing_var_type, + $new_type + ) + ) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + $assertion, + true, + $negated, + $code_location, + $suppressed_issues + ); + } + + $new_type = self::filterTypeWithAnother( + $codebase, + $existing_var_type, + $new_type, + $template_type_map, + $has_match, + $any_scalar_type_match_found + ); + + if ($code_location + && !$has_match + && (!$is_loose_equality || !$any_scalar_type_match_found) + ) { + if ($assertion === 'null') { + if ($existing_var_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + 'Cannot resolve types for ' . $key . ' - docblock-defined type ' + . $existing_var_type . ' does not contain null', + $code_location, + $existing_var_type->getId() . ' null' + ), + $suppressed_issues + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TypeDoesNotContainNull( + 'Cannot resolve types for ' . $key . ' - ' . $existing_var_type + . ' does not contain null', + $code_location, + $existing_var_type->getId() + ), + $suppressed_issues + )) { + // fall through + } + } + } elseif (!($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer) + || ($key !== '$this' + && !($existing_var_type->hasLiteralClassString() && $new_type->hasLiteralClassString())) + ) { + if ($existing_var_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + 'Cannot resolve types for ' . $key . ' - docblock-defined type ' + . $existing_var_type->getId() . ' does not contain ' . $new_type->getId(), + $code_location, + $existing_var_type->getId() . ' ' . $new_type->getId() + ), + $suppressed_issues + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + 'Cannot resolve types for ' . $key . ' - ' . $existing_var_type->getId() + . ' does not contain ' . $new_type->getId(), + $code_location, + $existing_var_type->getId() . ' ' . $new_type->getId() + ), + $suppressed_issues + )) { + // fall through + } + } + } + + $failed_reconciliation = 2; + } + } + + return $new_type; + } + + + + /** + * @param array> $template_type_map + */ + private static function filterTypeWithAnother( + Codebase $codebase, + Type\Union $existing_type, + Type\Union $new_type, + array $template_type_map, + bool &$has_match = false, + bool &$any_scalar_type_match_found = false + ) : Type\Union { + $matching_atomic_types = []; + + $has_cloned_type = false; + + foreach ($new_type->getAtomicTypes() as $new_type_part) { + $has_local_match = false; + + foreach ($existing_type->getAtomicTypes() as $key => $existing_type_part) { + // special workaround because PHP allows floats to contain ints, but we don’t want this + // behaviour here + if ($existing_type_part instanceof Type\Atomic\TFloat + && $new_type_part instanceof Type\Atomic\TInt + ) { + $any_scalar_type_match_found = true; + continue; + } + + $atomic_comparison_results = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); + + if ($existing_type_part instanceof TNamedObject) { + $existing_type_part->was_static = false; + } + + $atomic_contained_by = AtomicTypeComparator::isContainedBy( + $codebase, + $new_type_part, + $existing_type_part, + true, + false, + $atomic_comparison_results + ); + + if ($atomic_contained_by) { + $has_local_match = true; + + if ($atomic_comparison_results->type_coerced + && get_class($new_type_part) === Type\Atomic\TNamedObject::class + && $existing_type_part instanceof Type\Atomic\TGenericObject + ) { + // this is a hack - it's not actually rigorous, as the params may be different + $matching_atomic_types[] = new Type\Atomic\TGenericObject( + $new_type_part->value, + $existing_type_part->type_params + ); + } + } elseif (AtomicTypeComparator::isContainedBy( + $codebase, + $existing_type_part, + $new_type_part, + true, + false, + null + )) { + $has_local_match = true; + $matching_atomic_types[] = $existing_type_part; + } + + if ($new_type_part instanceof Type\Atomic\TKeyedArray + && $existing_type_part instanceof Type\Atomic\TList + ) { + $new_type_key = $new_type_part->getGenericKeyType(); + $new_type_value = $new_type_part->getGenericValueType(); + + if (!$new_type_key->hasString()) { + $has_param_match = false; + + $new_type_value = self::filterTypeWithAnother( + $codebase, + $existing_type_part->type_param, + $new_type_value, + $template_type_map, + $has_param_match, + $any_scalar_type_match_found + ); + + $hybrid_type_part = new Type\Atomic\TKeyedArray($new_type_part->properties); + $hybrid_type_part->previous_key_type = Type::getInt(); + $hybrid_type_part->previous_value_type = $new_type_value; + $hybrid_type_part->is_list = true; + + if (!$has_cloned_type) { + $new_type = clone $new_type; + $has_cloned_type = true; + } + + $has_local_match = true; + + $new_type->removeType($key); + $new_type->addType($hybrid_type_part); + + continue; + } + } + + if ($new_type_part instanceof Type\Atomic\TTemplateParam + && $existing_type_part instanceof Type\Atomic\TTemplateParam + && $new_type_part->param_name !== $existing_type_part->param_name + && $new_type_part->as->hasObject() + && $existing_type_part->as->hasObject() + ) { + $new_type_part->extra_types[$existing_type_part->getKey()] = $existing_type_part; + $matching_atomic_types[] = $new_type_part; + $has_local_match = true; + + continue; + } + + if ($has_local_match + && $new_type_part instanceof Type\Atomic\TNamedObject + && $existing_type_part instanceof Type\Atomic\TTemplateParam + && $existing_type_part->as->hasObjectType() + ) { + $existing_type_part = clone $existing_type_part; + $existing_type_part->as = self::filterTypeWithAnother( + $codebase, + $existing_type_part->as, + new Type\Union([$new_type_part]), + $template_type_map + ); + + $matching_atomic_types[] = $existing_type_part; + $has_local_match = true; + + continue; + } + + if (($new_type_part instanceof Type\Atomic\TGenericObject + || $new_type_part instanceof Type\Atomic\TArray + || $new_type_part instanceof Type\Atomic\TIterable) + && ($existing_type_part instanceof Type\Atomic\TGenericObject + || $existing_type_part instanceof Type\Atomic\TArray + || $existing_type_part instanceof Type\Atomic\TIterable) + && count($new_type_part->type_params) === count($existing_type_part->type_params) + ) { + $has_any_param_match = false; + + foreach ($new_type_part->type_params as $i => $new_param) { + $existing_param = $existing_type_part->type_params[$i]; + + $has_param_match = true; + + $new_param = self::filterTypeWithAnother( + $codebase, + $existing_param, + $new_param, + $template_type_map, + $has_param_match, + $any_scalar_type_match_found + ); + + if ($template_type_map) { + $new_param->replaceTemplateTypesWithArgTypes( + new TemplateResult([], $template_type_map), + $codebase + ); + } + + $existing_type->bustCache(); + + if ($has_param_match + && $existing_type_part->type_params[$i]->getId() !== $new_param->getId() + ) { + $existing_type_part->type_params[$i] = $new_param; + + if (!$has_local_match) { + $has_any_param_match = true; + } + } + } + + if ($has_any_param_match) { + $has_local_match = true; + $matching_atomic_types[] = $existing_type_part; + $atomic_comparison_results->type_coerced = true; + } + } + + if (($new_type_part instanceof Type\Atomic\TArray + || $new_type_part instanceof Type\Atomic\TIterable) + && $existing_type_part instanceof Type\Atomic\TList + ) { + $has_any_param_match = false; + + $new_param = $new_type_part->type_params[1]; + $existing_param = $existing_type_part->type_param; + + $has_param_match = true; + + $new_param = self::filterTypeWithAnother( + $codebase, + $existing_param, + $new_param, + $template_type_map, + $has_param_match, + $any_scalar_type_match_found + ); + + if ($template_type_map) { + $new_param->replaceTemplateTypesWithArgTypes( + new TemplateResult([], $template_type_map), + $codebase + ); + } + + $existing_type->bustCache(); + + if ($has_param_match + && $existing_type_part->type_param->getId() !== $new_param->getId() + ) { + $existing_type_part->type_param = $new_param; + + if (!$has_local_match) { + $has_any_param_match = true; + } + } + + if ($has_any_param_match) { + $has_local_match = true; + $matching_atomic_types[] = $existing_type_part; + $atomic_comparison_results->type_coerced = true; + } + } + + if ($atomic_contained_by || $atomic_comparison_results->type_coerced) { + if ($atomic_contained_by + && $existing_type_part instanceof TNamedObject + && $new_type_part instanceof TNamedObject + && $existing_type_part->extra_types + && !$codebase->classExists($existing_type_part->value) + && !$codebase->classExists($new_type_part->value) + && !array_filter( + $existing_type_part->extra_types, + function ($extra_type) use ($codebase): bool { + return $extra_type instanceof TNamedObject + && $codebase->classExists($extra_type->value); + } + ) + ) { + if (!$has_cloned_type) { + $new_type = clone $new_type; + $has_cloned_type = true; + } + + $new_type->removeType($key); + $new_type->addType($existing_type_part); + $new_type->from_docblock = $existing_type_part->from_docblock; + } + + continue; + } + + if ($atomic_comparison_results->scalar_type_match_found) { + $any_scalar_type_match_found = true; + } + } + + if (!$has_local_match) { + $has_match = false; + break; + } + } + + if ($matching_atomic_types) { + return new Type\Union($matching_atomic_types); + } + + return $new_type; + } + + /** + * @param string[] $suppressed_issues + */ + private static function handleLiteralEquality( + string $assertion, + int $bracket_pos, + bool $is_loose_equality, + Type\Union $existing_var_type, + string $old_var_type_string, + ?string $var_id, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues + ) : Type\Union { + $value = substr($assertion, $bracket_pos + 1, -1); + + $scalar_type = substr($assertion, 0, $bracket_pos); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + if ($scalar_type === 'int') { + $value = (int) $value; + + if ($existing_var_type->hasMixed() + || $existing_var_type->hasScalar() + || $existing_var_type->hasNumeric() + || $existing_var_type->hasArrayKey() + ) { + if ($is_loose_equality) { + return $existing_var_type; + } + + return new Type\Union([new Type\Atomic\TLiteralInt($value)]); + } + + $has_int = false; + + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { + if ($existing_var_atomic_type instanceof TInt) { + $has_int = true; + } elseif ($existing_var_atomic_type instanceof TTemplateParam) { + if ($existing_var_atomic_type->as->hasMixed() + || $existing_var_atomic_type->as->hasScalar() + || $existing_var_atomic_type->as->hasNumeric() + || $existing_var_atomic_type->as->hasArrayKey() + ) { + if ($is_loose_equality) { + return $existing_var_type; + } + + return new Type\Union([new Type\Atomic\TLiteralInt($value)]); + } + + if ($existing_var_atomic_type->as->hasInt()) { + $has_int = true; + } + } + } + + if ($has_int) { + $existing_int_types = $existing_var_type->getLiteralInts(); + + if ($existing_int_types) { + $can_be_equal = false; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $atomic_key => $atomic_type) { + if ($atomic_key !== $assertion + && !($atomic_type instanceof Type\Atomic\TPositiveInt && $value > 0) + ) { + $existing_var_type->removeType($atomic_key); + $did_remove_type = true; + } else { + $can_be_equal = true; + } + } + + if ($var_id + && $code_location + && (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1)) + ) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + $can_be_equal, + $negated, + $code_location, + $suppressed_issues + ); + } + } else { + $existing_var_type = new Type\Union([new Type\Atomic\TLiteralInt($value)]); + } + } elseif ($var_id && $code_location && !$is_loose_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + false, + $negated, + $code_location, + $suppressed_issues + ); + } elseif ($is_loose_equality && $existing_var_type->hasFloat()) { + // convert floats to ints + $existing_float_types = $existing_var_type->getLiteralFloats(); + + if ($existing_float_types) { + $can_be_equal = false; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $atomic_key => $_) { + if (substr($atomic_key, 0, 6) === 'float(') { + $atomic_key = 'int(' . substr($atomic_key, 6); + } + if ($atomic_key !== $assertion) { + $existing_var_type->removeType($atomic_key); + $did_remove_type = true; + } else { + $can_be_equal = true; + } + } + + if ($var_id + && $code_location + && (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1)) + ) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + $can_be_equal, + $negated, + $code_location, + $suppressed_issues + ); + } + } + } + } elseif ($scalar_type === 'string' + || $scalar_type === 'class-string' + || $scalar_type === 'interface-string' + || $scalar_type === 'callable-string' + || $scalar_type === 'trait-string' + ) { + if ($existing_var_type->hasMixed() + || $existing_var_type->hasScalar() + || $existing_var_type->hasArrayKey() + ) { + if ($is_loose_equality) { + return $existing_var_type; + } + + if ($scalar_type === 'class-string' + || $scalar_type === 'interface-string' + || $scalar_type === 'trait-string' + ) { + return new Type\Union([new Type\Atomic\TLiteralClassString($value)]); + } + + return new Type\Union([new Type\Atomic\TLiteralString($value)]); + } + + $has_string = false; + + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { + if ($existing_var_atomic_type instanceof TString) { + $has_string = true; + } elseif ($existing_var_atomic_type instanceof TTemplateParam) { + if ($existing_var_atomic_type->as->hasMixed() + || $existing_var_atomic_type->as->hasString() + || $existing_var_atomic_type->as->hasScalar() + || $existing_var_atomic_type->as->hasArrayKey() + ) { + if ($is_loose_equality) { + return $existing_var_type; + } + + $existing_var_atomic_type = clone $existing_var_atomic_type; + + $existing_var_atomic_type->as = self::handleLiteralEquality( + $assertion, + $bracket_pos, + $is_loose_equality, + $existing_var_atomic_type->as, + $old_var_type_string, + $var_id, + $negated, + $code_location, + $suppressed_issues + ); + + return new Type\Union([$existing_var_atomic_type]); + } + + if ($existing_var_atomic_type->as->hasString()) { + $has_string = true; + } + } + } + + if ($has_string) { + $existing_string_types = $existing_var_type->getLiteralStrings(); + + if ($existing_string_types) { + $can_be_equal = false; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $atomic_key => $_) { + if ($atomic_key !== $assertion) { + $existing_var_type->removeType($atomic_key); + $did_remove_type = true; + } else { + $can_be_equal = true; + } + } + + if ($var_id + && $code_location + && (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1)) + ) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + $can_be_equal, + $negated, + $code_location, + $suppressed_issues + ); + } + } else { + if ($scalar_type === 'class-string' + || $scalar_type === 'interface-string' + || $scalar_type === 'trait-string' + ) { + $existing_var_type = new Type\Union([new Type\Atomic\TLiteralClassString($value)]); + } else { + $existing_var_type = new Type\Union([new Type\Atomic\TLiteralString($value)]); + } + } + } elseif ($var_id && $code_location && !$is_loose_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + false, + $negated, + $code_location, + $suppressed_issues + ); + } + } elseif ($scalar_type === 'float') { + $value = (float) $value; + + if ($existing_var_type->hasMixed() || $existing_var_type->hasScalar() || $existing_var_type->hasNumeric()) { + if ($is_loose_equality) { + return $existing_var_type; + } + + return new Type\Union([new Type\Atomic\TLiteralFloat($value)]); + } + + if ($existing_var_type->hasFloat()) { + $existing_float_types = $existing_var_type->getLiteralFloats(); + + if ($existing_float_types) { + $can_be_equal = false; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $atomic_key => $_) { + if ($atomic_key !== $assertion) { + $existing_var_type->removeType($atomic_key); + $did_remove_type = true; + } else { + $can_be_equal = true; + } + } + + if ($var_id + && $code_location + && (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1)) + ) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + $can_be_equal, + $negated, + $code_location, + $suppressed_issues + ); + } + } else { + $existing_var_type = new Type\Union([new Type\Atomic\TLiteralFloat($value)]); + } + } elseif ($var_id && $code_location && !$is_loose_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + false, + $negated, + $code_location, + $suppressed_issues + ); + } elseif ($is_loose_equality && $existing_var_type->hasInt()) { + // convert ints to floats + $existing_float_types = $existing_var_type->getLiteralInts(); + + if ($existing_float_types) { + $can_be_equal = false; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $atomic_key => $_) { + if (substr($atomic_key, 0, 4) === 'int(') { + $atomic_key = 'float(' . substr($atomic_key, 4); + } + if ($atomic_key !== $assertion) { + $existing_var_type->removeType($atomic_key); + $did_remove_type = true; + } else { + $can_be_equal = true; + } + } + + if ($var_id + && $code_location + && (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1)) + ) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $var_id, + $assertion, + $can_be_equal, + $negated, + $code_location, + $suppressed_issues + ); + } + } + } + } + + return $existing_var_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..7311231b670663d525faeed641e4073f4531a62d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php @@ -0,0 +1,233 @@ +type_params[0]->getAtomicTypes() as $atomic_key_type) { + if ($atomic_key_type instanceof TLiteralString || $atomic_key_type instanceof TLiteralInt) { + $properties[$atomic_key_type->value] = clone $input_type_part->type_params[1]; + $properties[$atomic_key_type->value]->possibly_undefined = true; + } else { + $all_string_int_literals = false; + } + } + + if ($all_string_int_literals && $properties) { + $input_type_part = new TKeyedArray($properties); + + return KeyedArrayComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $allow_interface_equality, + $atomic_comparison_result + ); + } + } + + if ($container_type_part instanceof TList + && $input_type_part instanceof TKeyedArray + ) { + if ($input_type_part->is_list) { + $input_type_part = $input_type_part->getList(); + } else { + return false; + } + } + + if ($container_type_part instanceof TList + && $input_type_part instanceof TClassStringMap + ) { + return false; + } + + if ($container_type_part instanceof TList + && $input_type_part instanceof TArray + && $input_type_part->type_params[1]->isEmpty() + ) { + return !$container_type_part instanceof TNonEmptyList; + } + + if ($input_type_part instanceof TList + && $container_type_part instanceof TList + ) { + if (!UnionTypeComparator::isContainedBy( + $codebase, + $input_type_part->type_param, + $container_type_part->type_param, + $input_type_part->type_param->ignore_nullable_issues, + $input_type_part->type_param->ignore_falsable_issues, + $atomic_comparison_result, + $allow_interface_equality + )) { + return false; + } + + return $input_type_part instanceof TNonEmptyList + || !$container_type_part instanceof TNonEmptyList; + } + + if ($container_type_part instanceof TKeyedArray) { + $generic_container_type_part = $container_type_part->getGenericArrayType(); + + $container_type_part = $generic_container_type_part; + } + + if ($input_type_part instanceof TKeyedArray) { + $input_type_part = $input_type_part->getGenericArrayType(); + } + + if ($input_type_part instanceof TClassStringMap) { + $input_type_part = new TArray([ + $input_type_part->getStandinKeyParam(), + clone $input_type_part->value_param + ]); + } + + if ($container_type_part instanceof TClassStringMap) { + $container_type_part = new TArray([ + $container_type_part->getStandinKeyParam(), + clone $container_type_part->value_param + ]); + } + + if ($container_type_part instanceof TList) { + $all_types_contain = false; + + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + $container_type_part = new TArray([Type::getInt(), clone $container_type_part->type_param]); + } + + if ($input_type_part instanceof TList) { + if ($input_type_part instanceof TNonEmptyList) { + $input_type_part = new TNonEmptyArray([Type::getInt(), clone $input_type_part->type_param]); + } else { + $input_type_part = new TArray([Type::getInt(), clone $input_type_part->type_param]); + } + } + + foreach ($input_type_part->type_params as $i => $input_param) { + if ($i > 1) { + break; + } + + $container_param = $container_type_part->type_params[$i]; + + if ($i === 0 + && $input_param->hasMixed() + && $container_param->hasString() + && $container_param->hasInt() + ) { + continue; + } + + if ($input_param->isEmpty() + && $container_type_part instanceof Type\Atomic\TNonEmptyArray + ) { + return false; + } + + $param_comparison_result = new TypeComparisonResult(); + + if (!$input_param->isEmpty() && + !UnionTypeComparator::isContainedBy( + $codebase, + $input_param, + $container_param, + $input_param->ignore_nullable_issues, + $input_param->ignore_falsable_issues, + $param_comparison_result, + $allow_interface_equality + ) + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced + = $param_comparison_result->type_coerced === true + && $atomic_comparison_result->type_coerced !== false; + + $atomic_comparison_result->type_coerced_from_mixed + = $param_comparison_result->type_coerced_from_mixed === true + && $atomic_comparison_result->type_coerced_from_mixed !== false; + + $atomic_comparison_result->type_coerced_from_as_mixed + = $param_comparison_result->type_coerced_from_as_mixed === true + && $atomic_comparison_result->type_coerced_from_as_mixed !== false; + + $atomic_comparison_result->to_string_cast + = $param_comparison_result->to_string_cast === true + && $atomic_comparison_result->to_string_cast !== false; + + $atomic_comparison_result->type_coerced_from_scalar + = $param_comparison_result->type_coerced_from_scalar === true + && $atomic_comparison_result->type_coerced_from_scalar !== false; + + $atomic_comparison_result->scalar_type_match_found + = $param_comparison_result->scalar_type_match_found === true + && $atomic_comparison_result->scalar_type_match_found !== false; + } + + if (!$param_comparison_result->type_coerced_from_as_mixed) { + $all_types_contain = false; + } + } + } + + if ($container_type_part instanceof Type\Atomic\TNonEmptyArray + && !$input_type_part instanceof Type\Atomic\TNonEmptyArray + ) { + if ($all_types_contain && $atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if ($all_types_contain) { + if ($atomic_comparison_result) { + $atomic_comparison_result->to_string_cast = false; + } + + return true; + } + + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..f31bc97e06da98f287130b8d659dadf6dcd2a978 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php @@ -0,0 +1,704 @@ +extra_types))) + && ($input_type_part instanceof TTemplateParam + || ($input_type_part instanceof TNamedObject + && isset($input_type_part->extra_types))) + ) { + return ObjectComparator::isShallowlyContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $allow_interface_equality, + $atomic_comparison_result + ); + } + + if ($container_type_part instanceof TMixed + || ($container_type_part instanceof TTemplateParam + && $container_type_part->as->isMixed() + && !$container_type_part->extra_types + && $input_type_part instanceof TMixed) + ) { + if (get_class($container_type_part) === TEmptyMixed::class + && get_class($input_type_part) === TMixed::class + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_mixed = true; + } + + return false; + } + + return true; + } + + if ($input_type_part instanceof TNever || $input_type_part instanceof Type\Atomic\TEmpty) { + return true; + } + + if ($input_type_part instanceof TMixed + || ($input_type_part instanceof TTemplateParam + && $input_type_part->as->isMixed() + && !$input_type_part->extra_types) + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_mixed = true; + } + + return false; + } + + if ($input_type_part instanceof TNull) { + if ($container_type_part instanceof TNull) { + return true; + } + + if ($container_type_part instanceof TTemplateParam + && ($container_type_part->as->isNullable() || $container_type_part->as->isMixed()) + ) { + return true; + } + + return false; + } + + if ($container_type_part instanceof TNull) { + return false; + } + + if ($input_type_part instanceof Scalar && $container_type_part instanceof Scalar) { + return ScalarTypeComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $allow_interface_equality, + $allow_float_int_equality, + $atomic_comparison_result + ); + } + + if ($input_type_part instanceof Type\Atomic\TCallableKeyedArray + && $container_type_part instanceof TArray + ) { + return ArrayTypeComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $allow_interface_equality, + $atomic_comparison_result + ); + } + + if (($container_type_part instanceof Type\Atomic\TCallable + && $input_type_part instanceof Type\Atomic\TCallable) + || ($container_type_part instanceof Type\Atomic\TClosure + && $input_type_part instanceof Type\Atomic\TClosure) + ) { + return CallableTypeComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $atomic_comparison_result + ); + } + + if ($container_type_part instanceof Type\Atomic\TClosure && $input_type_part instanceof TCallable) { + if (CallableTypeComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $atomic_comparison_result + ) === false + ) { + return false; + } + + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if ($container_type_part instanceof Type\Atomic\TClosure) { + if (!$input_type_part instanceof Type\Atomic\TClosure) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_mixed = true; + } + + return false; + } + + return CallableTypeComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $atomic_comparison_result + ); + } + + if ($container_type_part instanceof TCallable && $input_type_part instanceof Type\Atomic\TClosure) { + return CallableTypeComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $atomic_comparison_result + ); + } + + if ($input_type_part instanceof TNamedObject && + $input_type_part->value === 'Closure' && + $container_type_part instanceof TCallable + ) { + return true; + } + + if ($input_type_part instanceof TObject && + $container_type_part instanceof TCallable + ) { + return true; + } + + if ($input_type_part instanceof Type\Atomic\TCallableObject && + $container_type_part instanceof TObject + ) { + return true; + } + + if (($container_type_part instanceof TKeyedArray + && $input_type_part instanceof TKeyedArray) + || ($container_type_part instanceof TObjectWithProperties + && $input_type_part instanceof TObjectWithProperties) + ) { + return KeyedArrayComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $allow_interface_equality, + $atomic_comparison_result + ); + } + + if (($input_type_part instanceof TArray + || $input_type_part instanceof TList + || $input_type_part instanceof TKeyedArray + || $input_type_part instanceof TClassStringMap) + && ($container_type_part instanceof TArray + || $container_type_part instanceof TList + || $container_type_part instanceof TKeyedArray + || $container_type_part instanceof TClassStringMap) + ) { + return ArrayTypeComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $allow_interface_equality, + $atomic_comparison_result + ); + } + + if (($input_type_part instanceof TNamedObject + || ($input_type_part instanceof TTemplateParam + && $input_type_part->as->hasObjectType()) + || $input_type_part instanceof TIterable) + && ($container_type_part instanceof TNamedObject + || ($container_type_part instanceof TTemplateParam + && $container_type_part->isObjectType()) + || $container_type_part instanceof TIterable) + && ObjectComparator::isShallowlyContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $allow_interface_equality, + $atomic_comparison_result + ) + ) { + if ($container_type_part instanceof TGenericObject || $container_type_part instanceof TIterable) { + return GenericTypeComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $allow_interface_equality, + $atomic_comparison_result + ); + } + + if ($container_type_part instanceof TNamedObject + && $input_type_part instanceof TNamedObject + && $container_type_part->was_static + && !$input_type_part->was_static + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if ($atomic_comparison_result) { + $atomic_comparison_result->to_string_cast = false; + } + + return true; + } + + if (get_class($input_type_part) === TObject::class + && get_class($container_type_part) === TObject::class + ) { + return true; + } + + if ($container_type_part instanceof TTemplateParam && $input_type_part instanceof TTemplateParam) { + return UnionTypeComparator::isContainedBy( + $codebase, + $input_type_part->as, + $container_type_part->as, + false, + false, + $atomic_comparison_result, + $allow_interface_equality + ); + } + + if ($container_type_part instanceof TTemplateParam) { + foreach ($container_type_part->as->getAtomicTypes() as $container_as_type_part) { + if (self::isContainedBy( + $codebase, + $input_type_part, + $container_as_type_part, + $allow_interface_equality, + $allow_float_int_equality, + $atomic_comparison_result + )) { + if ($allow_interface_equality + || ($input_type_part instanceof TArray + && !$input_type_part->type_params[1]->isEmpty()) + || $input_type_part instanceof TKeyedArray + ) { + return true; + } + } + } + + return false; + } + + if ($container_type_part instanceof TConditional) { + $atomic_types = array_merge( + array_values($container_type_part->if_type->getAtomicTypes()), + array_values($container_type_part->else_type->getAtomicTypes()) + ); + + foreach ($atomic_types as $container_as_type_part) { + if (self::isContainedBy( + $codebase, + $input_type_part, + $container_as_type_part, + $allow_interface_equality, + $allow_float_int_equality, + $atomic_comparison_result + )) { + return true; + } + } + + return false; + } + + if ($input_type_part instanceof TTemplateParam) { + if ($input_type_part->extra_types) { + foreach ($input_type_part->extra_types as $extra_type) { + if (self::isContainedBy( + $codebase, + $extra_type, + $container_type_part, + $allow_interface_equality, + $allow_float_int_equality, + $atomic_comparison_result + )) { + return true; + } + } + } + + foreach ($input_type_part->as->getAtomicTypes() as $input_as_type_part) { + if ($input_as_type_part instanceof TNull && $container_type_part instanceof TNull) { + continue; + } + + if (self::isContainedBy( + $codebase, + $input_as_type_part, + $container_type_part, + $allow_interface_equality, + $allow_float_int_equality, + $atomic_comparison_result + )) { + return true; + } + } + + return false; + } + + if ($input_type_part instanceof TConditional) { + $input_atomic_types = array_merge( + array_values($input_type_part->if_type->getAtomicTypes()), + array_values($input_type_part->else_type->getAtomicTypes()) + ); + + foreach ($input_atomic_types as $input_as_type_part) { + if (self::isContainedBy( + $codebase, + $input_as_type_part, + $container_type_part, + $allow_interface_equality, + $allow_float_int_equality, + $atomic_comparison_result + )) { + return true; + } + } + + return false; + } + + if ($input_type_part instanceof TNamedObject + && $input_type_part->value === 'static' + && $container_type_part instanceof TNamedObject + && strtolower($container_type_part->value) === 'self' + ) { + return true; + } + + if ($container_type_part instanceof TIterable) { + if ($input_type_part instanceof TArray + || $input_type_part instanceof TKeyedArray + || $input_type_part instanceof TList + ) { + if ($input_type_part instanceof TKeyedArray) { + $input_type_part = $input_type_part->getGenericArrayType(); + } elseif ($input_type_part instanceof TList) { + $input_type_part = new TArray([Type::getInt(), $input_type_part->type_param]); + } + + $all_types_contain = true; + + foreach ($input_type_part->type_params as $i => $input_param) { + $container_param_offset = $i - (2 - count($container_type_part->type_params)); + + if ($container_param_offset === -1) { + continue; + } + + $container_param = $container_type_part->type_params[$container_param_offset]; + + if ($i === 0 + && $input_param->hasMixed() + && $container_param->hasString() + && $container_param->hasInt() + ) { + continue; + } + + $array_comparison_result = new TypeComparisonResult(); + + if (!$input_param->isEmpty() + && !UnionTypeComparator::isContainedBy( + $codebase, + $input_param, + $container_param, + $input_param->ignore_nullable_issues, + $input_param->ignore_falsable_issues, + $array_comparison_result, + $allow_interface_equality + ) + && !$array_comparison_result->type_coerced_from_scalar + ) { + if ($atomic_comparison_result && $array_comparison_result->type_coerced_from_mixed) { + $atomic_comparison_result->type_coerced_from_mixed = true; + } + $all_types_contain = false; + } + } + + if ($all_types_contain) { + if ($atomic_comparison_result) { + $atomic_comparison_result->to_string_cast = false; + } + + return true; + } + + return false; + } + + if ($input_type_part->hasTraversableInterface($codebase)) { + return true; + } + } + + if ($container_type_part instanceof TString || $container_type_part instanceof TScalar) { + if ($input_type_part instanceof TNamedObject) { + // check whether the object has a __toString method + if ($codebase->classOrInterfaceExists($input_type_part->value)) { + if ($codebase->php_major_version >= 8 + && ($input_type_part->value === 'Stringable' + || ($codebase->classlikes->classExists($input_type_part->value) + && $codebase->classlikes->classImplements($input_type_part->value, 'Stringable')) + || $codebase->classlikes->interfaceExtends($input_type_part->value, 'Stringable')) + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->to_string_cast = true; + } + + return true; + } + + if ($codebase->methods->methodExists( + new \Psalm\Internal\MethodIdentifier( + $input_type_part->value, + '__tostring' + ) + )) { + if ($atomic_comparison_result) { + $atomic_comparison_result->to_string_cast = true; + } + + return true; + } + } + + // PHP 5.6 doesn't support this natively, so this introduces a bug *just* when checking PHP 5.6 code + if ($input_type_part->value === 'ReflectionType') { + if ($atomic_comparison_result) { + $atomic_comparison_result->to_string_cast = true; + } + + return true; + } + } elseif ($input_type_part instanceof TObjectWithProperties + && isset($input_type_part->methods['__toString']) + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->to_string_cast = true; + } + + return true; + } + } + + if ($container_type_part instanceof TCallable && + ( + $input_type_part instanceof TLiteralString + || $input_type_part instanceof TCallableString + || $input_type_part instanceof TArray + || $input_type_part instanceof TKeyedArray + || $input_type_part instanceof TList + || ( + $input_type_part instanceof TNamedObject && + $codebase->classOrInterfaceExists($input_type_part->value) && + $codebase->methodExists($input_type_part->value . '::__invoke') + ) + ) + ) { + return CallableTypeComparator::isNotExplicitlyCallableTypeCallable( + $codebase, + $input_type_part, + $container_type_part, + $atomic_comparison_result + ); + } + + if ($container_type_part instanceof TObject + && $input_type_part instanceof TNamedObject + ) { + if ($container_type_part instanceof TObjectWithProperties + && $input_type_part->value !== 'stdClass' + ) { + return KeyedArrayComparator::isContainedByObjectWithProperties( + $codebase, + $input_type_part, + $container_type_part, + $allow_interface_equality, + $atomic_comparison_result + ); + } + + return true; + } + + if ($container_type_part instanceof TNamedObject + && $input_type_part instanceof TNamedObject + && $container_type_part->was_static + && !$input_type_part->was_static + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if ($input_type_part instanceof TObject && $container_type_part instanceof TNamedObject) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if ($container_type_part instanceof TNamedObject + && $input_type_part instanceof TNamedObject + && $codebase->classOrInterfaceExists($input_type_part->value) + && ( + ( + $codebase->classExists($container_type_part->value) + && $codebase->classExtendsOrImplements( + $container_type_part->value, + $input_type_part->value + ) + ) + || + ( + $codebase->interfaceExists($container_type_part->value) + && $codebase->interfaceExtends( + $container_type_part->value, + $input_type_part->value + ) + ) + ) + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + return $input_type_part->getKey() === $container_type_part->getKey(); + } + + /** + * Does the input param atomic type match the given param atomic type + */ + public static function canBeIdentical( + Codebase $codebase, + Type\Atomic $type1_part, + Type\Atomic $type2_part + ) : bool { + if ((get_class($type1_part) === TList::class + && $type2_part instanceof Type\Atomic\TNonEmptyList) + || (get_class($type2_part) === TList::class + && $type1_part instanceof Type\Atomic\TNonEmptyList) + ) { + return UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $type1_part->type_param, + $type2_part->type_param + ); + } + + if ((get_class($type1_part) === TArray::class + && $type2_part instanceof Type\Atomic\TNonEmptyArray) + || (get_class($type2_part) === TArray::class + && $type1_part instanceof Type\Atomic\TNonEmptyArray) + ) { + return UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $type1_part->type_params[0], + $type2_part->type_params[0] + ) + && UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $type1_part->type_params[1], + $type2_part->type_params[1] + ); + } + + $first_comparison_result = new TypeComparisonResult(); + $second_comparison_result = new TypeComparisonResult(); + + $either_contains = (AtomicTypeComparator::isContainedBy( + $codebase, + $type1_part, + $type2_part, + true, + false, + $first_comparison_result + ) + && !$first_comparison_result->to_string_cast + ) || (AtomicTypeComparator::isContainedBy( + $codebase, + $type2_part, + $type1_part, + true, + false, + $second_comparison_result + ) + && !$second_comparison_result->to_string_cast + ) || ($first_comparison_result->type_coerced + && $second_comparison_result->type_coerced + ); + + if ($either_contains) { + return true; + } + + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..5ff9cf642db6e6f632c59da843a5d682944df31c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php @@ -0,0 +1,437 @@ +is_pure && !$input_type_part->is_pure) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = $input_type_part->is_pure === null; + } + + return false; + } + + if ($container_type_part->params !== null && $input_type_part->params === null) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_mixed = true; + } + + return false; + } + + if ($input_type_part->params !== null && $container_type_part->params !== null) { + foreach ($input_type_part->params as $i => $input_param) { + $container_param = null; + + if (isset($container_type_part->params[$i])) { + $container_param = $container_type_part->params[$i]; + } elseif ($container_type_part->params) { + $last_param = end($container_type_part->params); + + if ($last_param->is_variadic) { + $container_param = $last_param; + } + } + + if (!$container_param) { + if ($input_param->is_optional) { + break; + } + + return false; + } + + if ($container_param->type + && !$container_param->type->hasMixed() + && !UnionTypeComparator::isContainedBy( + $codebase, + $container_param->type, + $input_param->type ?: Type::getMixed(), + false, + false, + $atomic_comparison_result + ) + ) { + return false; + } + } + } + + if (isset($container_type_part->return_type)) { + if (!isset($input_type_part->return_type)) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_mixed = true; + } + + return false; + } + + $input_return = $input_type_part->return_type; + + if ($input_return->isVoid() && $container_type_part->return_type->isNullable()) { + return true; + } + + if (!$container_type_part->return_type->isVoid() + && !UnionTypeComparator::isContainedBy( + $codebase, + $input_return, + $container_type_part->return_type, + false, + false, + $atomic_comparison_result + ) + ) { + return false; + } + } + + return true; + } + + public static function isNotExplicitlyCallableTypeCallable( + Codebase $codebase, + Type\Atomic $input_type_part, + TCallable $container_type_part, + ?TypeComparisonResult $atomic_comparison_result + ) : bool { + if ($input_type_part instanceof TList) { + if ($input_type_part->type_param->isMixed() + || $input_type_part->type_param->hasScalar() + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced_from_mixed = true; + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if (!$input_type_part->type_param->hasString()) { + return false; + } + + if (!$input_type_part instanceof Type\Atomic\TCallableList) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced_from_mixed = true; + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + } + + if ($input_type_part instanceof TArray) { + if ($input_type_part->type_params[1]->isMixed() + || $input_type_part->type_params[1]->hasScalar() + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced_from_mixed = true; + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if (!$input_type_part->type_params[1]->hasString()) { + return false; + } + + if (!$input_type_part instanceof Type\Atomic\TCallableArray) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced_from_mixed = true; + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + } elseif ($input_type_part instanceof TKeyedArray) { + $method_id = self::getCallableMethodIdFromTKeyedArray($input_type_part); + + if ($method_id === 'not-callable') { + return false; + } + + if (!$method_id) { + return true; + } + + try { + $method_id = $codebase->methods->getDeclaringMethodId($method_id); + + if (!$method_id) { + return false; + } + + $codebase->methods->getStorage($method_id); + } catch (\Exception $e) { + return false; + } + } + + $input_callable = self::getCallableFromAtomic($codebase, $input_type_part, $container_type_part); + + if ($input_callable) { + if (self::isContainedBy( + $codebase, + $input_callable, + $container_type_part, + $atomic_comparison_result + ) === false + ) { + return false; + } + } + + return true; + } + + /** + * @return TCallable|TClosure|null + */ + public static function getCallableFromAtomic( + Codebase $codebase, + Type\Atomic $input_type_part, + ?TCallable $container_type_part = null, + ?StatementsAnalyzer $statements_analyzer = null + ): ?Atomic { + if ($input_type_part instanceof TCallable || $input_type_part instanceof TClosure) { + return $input_type_part; + } + + if ($input_type_part instanceof TLiteralString && $input_type_part->value) { + try { + $function_storage = $codebase->functions->getStorage( + $statements_analyzer, + strtolower($input_type_part->value) + ); + + return new TCallable( + 'callable', + $function_storage->params, + $function_storage->return_type, + $function_storage->pure + ); + } catch (\UnexpectedValueException $e) { + if (InternalCallMapHandler::inCallMap($input_type_part->value)) { + $args = []; + + $nodes = new \Psalm\Internal\Provider\NodeDataProvider(); + + if ($container_type_part && $container_type_part->params) { + foreach ($container_type_part->params as $i => $param) { + $arg = new \PhpParser\Node\Arg( + new \PhpParser\Node\Expr\Variable('_' . $i) + ); + + if ($param->type) { + $nodes->setType($arg->value, $param->type); + } + + $args[] = $arg; + } + } + + $matching_callable = \Psalm\Internal\Codebase\InternalCallMapHandler::getCallableFromCallMapById( + $codebase, + $input_type_part->value, + $args, + $nodes + ); + + $must_use = false; + + $matching_callable->is_pure = $codebase->functions->isCallMapFunctionPure( + $codebase, + $statements_analyzer ? $statements_analyzer->node_data : null, + $input_type_part->value, + null, + $must_use + ); + + return $matching_callable; + } + } + } elseif ($input_type_part instanceof TKeyedArray) { + $method_id = self::getCallableMethodIdFromTKeyedArray($input_type_part); + if ($method_id && $method_id !== 'not-callable') { + try { + $method_storage = $codebase->methods->getStorage($method_id); + $method_fqcln = $method_id->fq_class_name; + + $converted_return_type = null; + + if ($method_storage->return_type) { + $converted_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $method_storage->return_type, + $method_fqcln, + $method_fqcln, + null + ); + } + + return new TCallable( + 'callable', + $method_storage->params, + $converted_return_type, + $method_storage->pure + ); + } catch (\UnexpectedValueException $e) { + // do nothing + } + } + } elseif ($input_type_part instanceof TNamedObject + && $input_type_part->value === 'Closure' + ) { + return new TCallable(); + } elseif ($input_type_part instanceof TNamedObject + && $codebase->classExists($input_type_part->value) + ) { + $invoke_id = new \Psalm\Internal\MethodIdentifier( + $input_type_part->value, + '__invoke' + ); + + if ($codebase->methods->methodExists($invoke_id)) { + $declaring_method_id = $codebase->methods->getDeclaringMethodId($invoke_id); + + if ($declaring_method_id) { + $method_storage = $codebase->methods->getStorage($declaring_method_id); + $method_fqcln = $invoke_id->fq_class_name; + $converted_return_type = null; + if ($method_storage->return_type) { + $converted_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $method_storage->return_type, + $method_fqcln, + $method_fqcln, + null + ); + } + + return new TCallable( + 'callable', + $method_storage->params, + $converted_return_type, + $method_storage->pure + ); + } + } + } + + return null; + } + + /** @return null|'not-callable'|\Psalm\Internal\MethodIdentifier */ + public static function getCallableMethodIdFromTKeyedArray( + TKeyedArray $input_type_part, + ?Codebase $codebase = null, + ?string $calling_method_id = null, + ?string $file_name = null + ) { + if (!isset($input_type_part->properties[0]) + || !isset($input_type_part->properties[1]) + ) { + return 'not-callable'; + } + + [$lhs, $rhs] = $input_type_part->properties; + + $rhs_low_info = $rhs->hasMixed() || $rhs->hasScalar(); + + if ($rhs_low_info || !$rhs->isSingleStringLiteral()) { + if (!$rhs_low_info && !$rhs->hasString()) { + return 'not-callable'; + } + + if ($codebase && ($calling_method_id || $file_name)) { + foreach ($lhs->getAtomicTypes() as $lhs_atomic_type) { + if ($lhs_atomic_type instanceof TNamedObject) { + $codebase->analyzer->addMixedMemberName( + strtolower($lhs_atomic_type->value) . '::', + $calling_method_id ?: $file_name + ); + } + } + } + + return null; + } + + $method_name = $rhs->getSingleStringLiteral()->value; + + $class_name = null; + + if ($lhs->isSingleStringLiteral()) { + $class_name = $lhs->getSingleStringLiteral()->value; + if ($class_name[0] === '\\') { + $class_name = \substr($class_name, 1); + } + } elseif ($lhs->isSingle()) { + foreach ($lhs->getAtomicTypes() as $lhs_atomic_type) { + if ($lhs_atomic_type instanceof TNamedObject) { + $class_name = $lhs_atomic_type->value; + } elseif ($lhs_atomic_type instanceof Type\Atomic\TClassString + && $lhs_atomic_type->as + ) { + $class_name = $lhs_atomic_type->as; + } + } + } + + if ($class_name === 'self' + || $class_name === 'static' + || $class_name === 'parent' + ) { + return null; + } + + if (!$class_name) { + if ($codebase && ($calling_method_id || $file_name)) { + $codebase->analyzer->addMixedMemberName( + strtolower($method_name), + $calling_method_id ?: $file_name + ); + } + + return null; + } + + return new \Psalm\Internal\MethodIdentifier( + $class_name, + strtolower($method_name) + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ClassStringComparator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ClassStringComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..3bec6c09ac1ad3a663e3e9791062fce5b4b8297c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ClassStringComparator.php @@ -0,0 +1,91 @@ +value === $input_type_part->value; + } + + if ($container_type_part instanceof TTemplateParamClass + && get_class($input_type_part) === TClassString::class + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if ($container_type_part instanceof TClassString + && $container_type_part->as === 'object' + && !$container_type_part->as_type + ) { + return true; + } + + if ($input_type_part instanceof TClassString + && $input_type_part->as === 'object' + && !$input_type_part->as_type + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_scalar = true; + } + + return false; + } + + $fake_container_object = $container_type_part instanceof TClassString + && $container_type_part->as_type + ? $container_type_part->as_type + : new TNamedObject( + $container_type_part instanceof TClassString + ? $container_type_part->as + : $container_type_part->value + ); + + $fake_input_object = $input_type_part instanceof TClassString + && $input_type_part->as_type + ? $input_type_part->as_type + : new TNamedObject( + $input_type_part instanceof TClassString + ? $input_type_part->as + : $input_type_part->value + ); + + return AtomicTypeComparator::isContainedBy( + $codebase, + $fake_input_object, + $fake_container_object, + $allow_interface_equality, + false, + $atomic_comparison_result + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..26b3b62ce13b0ad27546d8504d7a159e6958f49b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php @@ -0,0 +1,244 @@ +extra_types + && !$input_type_part instanceof TIterable + ) { + $container_type_part = new TGenericObject( + 'Traversable', + $container_type_part->type_params + ); + } + + if (!$input_type_part instanceof TGenericObject && !$input_type_part instanceof TIterable) { + if ($input_type_part instanceof TNamedObject + && $codebase->classExists($input_type_part->value) + ) { + $class_storage = $codebase->classlike_storage_provider->get($input_type_part->value); + + $container_class = $container_type_part->value; + + // attempt to transform it + if (isset($class_storage->template_type_extends[$container_class])) { + $extends_list = $class_storage->template_type_extends[$container_class]; + + $generic_params = []; + + foreach ($extends_list as $key => $value) { + if (is_string($key)) { + $generic_params[] = $value; + } + } + + if (!$generic_params) { + return false; + } + + $input_type_part = new TGenericObject( + $input_type_part->value, + $generic_params + ); + } + } + + if (!$input_type_part instanceof TGenericObject) { + if ($input_type_part instanceof TNamedObject) { + $input_type_part = new TGenericObject( + $input_type_part->value, + array_fill(0, count($container_type_part->type_params), Type::getMixed()) + ); + } else { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_mixed = true; + } + + return false; + } + } + } + + $container_type_params_covariant = []; + + $input_type_params = \Psalm\Internal\Type\UnionTemplateHandler::getMappedGenericTypeParams( + $codebase, + $input_type_part, + $container_type_part, + $container_type_params_covariant + ); + + foreach ($input_type_params as $i => $input_param) { + if (!isset($container_type_part->type_params[$i])) { + break; + } + + $container_param = $container_type_part->type_params[$i]; + + if ($input_param->isEmpty()) { + if ($atomic_comparison_result) { + if (!$atomic_comparison_result->replacement_atomic_type) { + $atomic_comparison_result->replacement_atomic_type = clone $input_type_part; + } + + if ($atomic_comparison_result->replacement_atomic_type instanceof TGenericObject) { + /** @psalm-suppress PropertyTypeCoercion */ + $atomic_comparison_result->replacement_atomic_type->type_params[$i] + = clone $container_param; + } + } + + continue; + } + + $param_comparison_result = new TypeComparisonResult(); + + if (!UnionTypeComparator::isContainedBy( + $codebase, + $input_param, + $container_param, + $input_param->ignore_nullable_issues, + $input_param->ignore_falsable_issues, + $param_comparison_result, + $allow_interface_equality + )) { + if ($input_type_part->value === 'Generator' + && $i === 2 + && $param_comparison_result->type_coerced_from_mixed + ) { + continue; + } + + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced + = $param_comparison_result->type_coerced === true + && $atomic_comparison_result->type_coerced !== false; + + $atomic_comparison_result->type_coerced_from_mixed + = $param_comparison_result->type_coerced_from_mixed === true + && $atomic_comparison_result->type_coerced_from_mixed !== false; + + $atomic_comparison_result->type_coerced_from_as_mixed + = $param_comparison_result->type_coerced_from_as_mixed === true + && $atomic_comparison_result->type_coerced_from_as_mixed !== false; + + $atomic_comparison_result->to_string_cast + = $param_comparison_result->to_string_cast === true + && $atomic_comparison_result->to_string_cast !== false; + + $atomic_comparison_result->type_coerced_from_scalar + = $param_comparison_result->type_coerced_from_scalar === true + && $atomic_comparison_result->type_coerced_from_scalar !== false; + + $atomic_comparison_result->scalar_type_match_found + = $param_comparison_result->scalar_type_match_found === true + && $atomic_comparison_result->scalar_type_match_found !== false; + } + + if (!$param_comparison_result->type_coerced_from_as_mixed) { + $all_types_contain = false; + } + } elseif (!$input_type_part instanceof TIterable + && !$container_type_part instanceof TIterable + && !$container_param->hasTemplate() + && !$input_param->hasTemplate() + ) { + if ($input_param->hasEmptyArray() + || $input_param->hasLiteralValue() + ) { + if ($atomic_comparison_result) { + if (!$atomic_comparison_result->replacement_atomic_type) { + $atomic_comparison_result->replacement_atomic_type = clone $input_type_part; + } + + if ($atomic_comparison_result->replacement_atomic_type instanceof TGenericObject) { + /** @psalm-suppress PropertyTypeCoercion */ + $atomic_comparison_result->replacement_atomic_type->type_params[$i] + = clone $container_param; + } + } + } else { + if (!($container_type_params_covariant[$i] ?? false) + && !$container_param->had_template + ) { + // Make sure types are basically the same + if (!UnionTypeComparator::isContainedBy( + $codebase, + $container_param, + $input_param, + $container_param->ignore_nullable_issues, + $container_param->ignore_falsable_issues, + $param_comparison_result, + $allow_interface_equality + ) || $param_comparison_result->type_coerced + ) { + if ($container_param->hasFormerStaticObject() + && $input_param->isFormerStaticObject() + && UnionTypeComparator::isContainedBy( + $codebase, + $input_param, + $container_param, + $container_param->ignore_nullable_issues, + $container_param->ignore_falsable_issues, + $param_comparison_result, + $allow_interface_equality + ) + ) { + // do nothing + } else { + if ($container_param->hasMixed() || $container_param->isArrayKey()) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced_from_mixed = true; + } + } else { + $all_types_contain = false; + } + + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = false; + } + } + } + } + } + } + } + + if ($all_types_contain) { + if ($atomic_comparison_result) { + $atomic_comparison_result->to_string_cast = false; + } + + return true; + } + + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..bb9def59321249c0f6c5bcf24ff9dfc9af3f2f74 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php @@ -0,0 +1,166 @@ +properties as $key => $container_property_type) { + if (!isset($input_type_part->properties[$key])) { + if (!$container_property_type->possibly_undefined) { + $all_types_contain = false; + } + + continue; + } + + $input_property_type = $input_type_part->properties[$key]; + + $property_type_comparison = new TypeComparisonResult(); + + if (!$input_property_type->isEmpty() + && !UnionTypeComparator::isContainedBy( + $codebase, + $input_property_type, + $container_property_type, + $input_property_type->ignore_nullable_issues, + $input_property_type->ignore_falsable_issues, + $property_type_comparison, + $allow_interface_equality + ) + && !$property_type_comparison->type_coerced_from_scalar + ) { + $inverse_property_type_comparison = new TypeComparisonResult(); + + if ($atomic_comparison_result) { + if (UnionTypeComparator::isContainedBy( + $codebase, + $container_property_type, + $input_property_type, + false, + false, + $inverse_property_type_comparison, + $allow_interface_equality + ) + || $inverse_property_type_comparison->type_coerced_from_scalar + ) { + $atomic_comparison_result->type_coerced = true; + } + } + + $all_types_contain = false; + } + } + + if ($all_types_contain) { + if ($atomic_comparison_result) { + $atomic_comparison_result->to_string_cast = false; + } + + return true; + } + + return false; + } + + public static function isContainedByObjectWithProperties( + Codebase $codebase, + TNamedObject $input_type_part, + TObjectWithProperties $container_type_part, + bool $allow_interface_equality, + ?TypeComparisonResult $atomic_comparison_result + ) : bool { + $all_types_contain = true; + + foreach ($container_type_part->properties as $property_name => $container_property_type) { + if (!is_string($property_name)) { + continue; + } + + if (!$codebase->classlikes->classOrInterfaceExists($input_type_part->value)) { + $all_types_contain = false; + + continue; + } + + if (!$codebase->properties->propertyExists( + $input_type_part->value . '::$' . $property_name, + true + )) { + $all_types_contain = false; + + continue; + } + + $property_declaring_class = (string) $codebase->properties->getDeclaringClassForProperty( + $input_type_part . '::$' . $property_name, + true + ); + + $class_storage = $codebase->classlike_storage_provider->get($property_declaring_class); + + $input_property_storage = $class_storage->properties[$property_name]; + + $input_property_type = $input_property_storage->type ?: Type::getMixed(); + + $property_type_comparison = new TypeComparisonResult(); + + if (!$input_property_type->isEmpty() + && !UnionTypeComparator::isContainedBy( + $codebase, + $input_property_type, + $container_property_type, + false, + false, + $property_type_comparison, + $allow_interface_equality + ) + && !$property_type_comparison->type_coerced_from_scalar + ) { + $inverse_property_type_comparison = new TypeComparisonResult(); + + if (UnionTypeComparator::isContainedBy( + $codebase, + $container_property_type, + $input_property_type, + false, + false, + $inverse_property_type_comparison, + $allow_interface_equality + ) + || $inverse_property_type_comparison->type_coerced_from_scalar + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + } + + $all_types_contain = false; + } + } + + return $all_types_contain; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ObjectComparator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ObjectComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..1b5726bae7735d60116527f44c36e92a5ea9b415 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ObjectComparator.php @@ -0,0 +1,286 @@ +extra_types ?: []; + $intersection_input_types[$input_type_part->getKey(false)] = $input_type_part; + + if ($input_type_part instanceof TTemplateParam) { + foreach ($input_type_part->as->getAtomicTypes() as $g) { + if ($g instanceof TNamedObject && $g->extra_types) { + $intersection_input_types = array_merge( + $intersection_input_types, + $g->extra_types + ); + } + } + } + + $intersection_container_types = $container_type_part->extra_types ?: []; + $intersection_container_types[$container_type_part->getKey(false)] = $container_type_part; + + if ($container_type_part instanceof TTemplateParam) { + foreach ($container_type_part->as->getAtomicTypes() as $g) { + if ($g instanceof TNamedObject && $g->extra_types) { + $intersection_container_types = array_merge( + $intersection_container_types, + $g->extra_types + ); + } + } + } + + foreach ($intersection_container_types as $container_type_key => $intersection_container_type) { + $container_was_static = false; + + if ($intersection_container_type instanceof TIterable) { + $intersection_container_type_lower = 'iterable'; + } elseif ($intersection_container_type instanceof TObjectWithProperties) { + $intersection_container_type_lower = 'object'; + } elseif ($intersection_container_type instanceof TTemplateParam) { + if (!$allow_interface_equality) { + if (isset($intersection_input_types[$container_type_key])) { + continue; + } + + if (\substr($intersection_container_type->defining_class, 0, 3) === 'fn-') { + foreach ($intersection_input_types as $intersection_input_type) { + if ($intersection_input_type instanceof TTemplateParam + && \substr($intersection_input_type->defining_class, 0, 3) === 'fn-' + && $intersection_input_type->defining_class + !== $intersection_container_type->defining_class + ) { + continue 2; + } + } + } + + return false; + } + + if ($intersection_container_type->as->isMixed()) { + continue; + } + + $intersection_container_type_lower = null; + + foreach ($intersection_container_type->as->getAtomicTypes() as $g) { + if ($g instanceof TNull) { + continue; + } + + if ($g instanceof TObject) { + continue 2; + } + + if (!$g instanceof TNamedObject) { + continue 2; + } + + $intersection_container_type_lower = strtolower($g->value); + } + + if ($intersection_container_type_lower === null) { + return false; + } + } else { + $container_was_static = $intersection_container_type->was_static; + + $intersection_container_type_lower = strtolower( + $codebase->classlikes->getUnAliasedName( + $intersection_container_type->value + ) + ); + } + + foreach ($intersection_input_types as $intersection_input_key => $intersection_input_type) { + $input_was_static = false; + + if ($intersection_input_type instanceof TIterable) { + $intersection_input_type_lower = 'iterable'; + } elseif ($intersection_input_type instanceof TObjectWithProperties) { + $intersection_input_type_lower = 'object'; + } elseif ($intersection_input_type instanceof TTemplateParam) { + if ($intersection_input_type->as->isMixed()) { + continue; + } + + $intersection_input_type_lower = null; + + foreach ($intersection_input_type->as->getAtomicTypes() as $g) { + if ($g instanceof TNull) { + continue; + } + + if (!$g instanceof TNamedObject) { + continue 2; + } + + $intersection_input_type_lower = strtolower($g->value); + } + + if ($intersection_input_type_lower === null) { + return false; + } + } else { + $input_was_static = $intersection_input_type->was_static; + + $intersection_input_type_lower = strtolower( + $codebase->classlikes->getUnAliasedName( + $intersection_input_type->value + ) + ); + } + + if ($intersection_container_type instanceof TTemplateParam + && $intersection_input_type instanceof TTemplateParam + ) { + if ($intersection_container_type->param_name !== $intersection_input_type->param_name + || ((string)$intersection_container_type->defining_class + !== (string)$intersection_input_type->defining_class + && \substr($intersection_input_type->defining_class, 0, 3) !== 'fn-' + && \substr($intersection_container_type->defining_class, 0, 3) !== 'fn-') + ) { + if (\substr($intersection_input_type->defining_class, 0, 3) !== 'fn-') { + $input_class_storage = $codebase->classlike_storage_provider->get( + $intersection_input_type->defining_class + ); + + if (isset($input_class_storage->template_type_extends + [$intersection_container_type->defining_class] + [$intersection_container_type->param_name]) + ) { + continue; + } + } + + return false; + } + } + + if (!$intersection_container_type instanceof TTemplateParam + || $intersection_input_type instanceof TTemplateParam + ) { + if ($intersection_container_type_lower === $intersection_input_type_lower) { + if ($container_was_static + && !$input_was_static + && !$intersection_input_type instanceof TTemplateParam + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + continue; + } + + continue 2; + } + + if ($intersection_input_type_lower === 'generator' + && in_array($intersection_container_type_lower, ['iterator', 'traversable', 'iterable'], true) + ) { + continue 2; + } + + if ($intersection_container_type_lower === 'iterable') { + if ($intersection_input_type_lower === 'traversable' + || ($codebase->classlikes->classExists($intersection_input_type_lower) + && $codebase->classlikes->classImplements( + $intersection_input_type_lower, + 'Traversable' + )) + || ($codebase->classlikes->interfaceExists($intersection_input_type_lower) + && $codebase->classlikes->interfaceExtends( + $intersection_input_type_lower, + 'Traversable' + )) + ) { + continue 2; + } + } + + if ($intersection_input_type_lower === 'traversable' + && $intersection_container_type_lower === 'iterable' + ) { + continue 2; + } + + $input_type_is_interface = $codebase->interfaceExists($intersection_input_type_lower); + $container_type_is_interface = $codebase->interfaceExists($intersection_container_type_lower); + + if ($allow_interface_equality + && $container_type_is_interface + && ($input_type_is_interface || !isset($intersection_container_types[$intersection_input_key])) + ) { + continue 2; + } + + if ($codebase->classExists($intersection_input_type_lower) + && $codebase->classOrInterfaceExists($intersection_container_type_lower) + && $codebase->classExtendsOrImplements( + $intersection_input_type_lower, + $intersection_container_type_lower + ) + ) { + if ($container_was_static && !$input_was_static) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + continue; + } + + continue 2; + } + + if ($input_type_is_interface + && $codebase->interfaceExtends( + $intersection_input_type_lower, + $intersection_container_type_lower + ) + ) { + continue 2; + } + } + + if (ExpressionAnalyzer::isMock($intersection_input_type_lower)) { + return true; + } + } + + return false; + } + + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php new file mode 100644 index 0000000000000000000000000000000000000000..19d1efef6483314b8950b84deea2fead70868cd0 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php @@ -0,0 +1,479 @@ +type_coerced = true; + } + + return false; + } + + if (($input_type_part instanceof Type\Atomic\TLowercaseString + || $input_type_part instanceof Type\Atomic\TNonEmptyLowercaseString) + && get_class($container_type_part) === TString::class + ) { + return true; + } + + if (($container_type_part instanceof Type\Atomic\TLowercaseString + || $container_type_part instanceof Type\Atomic\TNonEmptyLowercaseString) + && $input_type_part instanceof TString + ) { + if (($input_type_part instanceof Type\Atomic\TLowercaseString + && $container_type_part instanceof Type\Atomic\TLowercaseString) + || ($input_type_part instanceof Type\Atomic\TNonEmptyLowercaseString + && $container_type_part instanceof Type\Atomic\TNonEmptyLowercaseString) + ) { + return true; + } + + if ($input_type_part instanceof Type\Atomic\TNonEmptyLowercaseString + && $container_type_part instanceof Type\Atomic\TLowercaseString + ) { + return true; + } + + if ($input_type_part instanceof Type\Atomic\TLowercaseString + && $container_type_part instanceof Type\Atomic\TNonEmptyLowercaseString + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if ($input_type_part instanceof TLiteralString) { + if (strtolower($input_type_part->value) === $input_type_part->value) { + return $input_type_part->value || $container_type_part instanceof Type\Atomic\TLowercaseString; + } + + return false; + } + + if ($input_type_part instanceof TClassString) { + return false; + } + + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if ($container_type_part instanceof TDependentGetClass) { + $first_type = array_values($container_type_part->as_type->getAtomicTypes())[0]; + + $container_type_part = new TClassString( + 'object', + $first_type instanceof TNamedObject ? $first_type : null + ); + } + + if ($input_type_part instanceof TDependentGetClass) { + $first_type = array_values($input_type_part->as_type->getAtomicTypes())[0]; + + if ($first_type instanceof TTemplateParam) { + $object_type = array_values($first_type->as->getAtomicTypes())[0]; + + $input_type_part = new TTemplateParamClass( + $first_type->param_name, + $first_type->as->getId(), + $object_type instanceof TNamedObject ? $object_type : null, + $first_type->defining_class + ); + } else { + $input_type_part = new TClassString( + 'object', + $first_type instanceof TNamedObject ? $first_type : null + ); + } + } + + if ($input_type_part instanceof TDependentGetType) { + $input_type_part = new TString(); + + if ($container_type_part instanceof TLiteralString) { + return isset(ClassLikeAnalyzer::GETTYPE_TYPES[$container_type_part->value]); + } + } + + if ($container_type_part instanceof TDependentGetDebugType) { + return $input_type_part instanceof TString; + } + + if ($input_type_part instanceof TDependentGetDebugType) { + $input_type_part = new TString(); + } + + if ($container_type_part instanceof TDependentGetType) { + $container_type_part = new TString(); + + if ($input_type_part instanceof TLiteralString) { + return isset(ClassLikeAnalyzer::GETTYPE_TYPES[$input_type_part->value]); + } + } + + if ($input_type_part instanceof TFalse + && $container_type_part instanceof TBool + && !($container_type_part instanceof TTrue) + ) { + return true; + } + + if ($input_type_part instanceof TTrue + && $container_type_part instanceof TBool + && !($container_type_part instanceof TFalse) + ) { + return true; + } + + // from https://wiki.php.net/rfc/scalar_type_hints_v5: + // + // > int types can resolve a parameter type of float + if ($input_type_part instanceof TInt + && $container_type_part instanceof TFloat + && !$container_type_part instanceof TLiteralFloat + && $allow_float_int_equality + ) { + return true; + } + + if ($container_type_part instanceof TArrayKey + && $input_type_part instanceof TNumeric + ) { + return true; + } + + if ($container_type_part instanceof TArrayKey + && ($input_type_part instanceof TInt + || $input_type_part instanceof TString + || $input_type_part instanceof Type\Atomic\TTemplateKeyOf) + ) { + return true; + } + + if ($input_type_part instanceof Type\Atomic\TTemplateKeyOf) { + foreach ($input_type_part->as->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof TArray) { + /** @var Scalar $array_key_atomic */ + foreach ($atomic_type->type_params[0]->getAtomicTypes() as $array_key_atomic) { + if (!self::isContainedBy( + $codebase, + $array_key_atomic, + $container_type_part, + $allow_interface_equality, + $allow_float_int_equality, + $atomic_comparison_result + )) { + return false; + } + } + } + } + + return true; + } + + if ($input_type_part instanceof TArrayKey && + ($container_type_part instanceof TInt || $container_type_part instanceof TString) + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_mixed = true; + $atomic_comparison_result->scalar_type_match_found = true; + } + + return false; + } + + if ($container_type_part instanceof TScalar && $input_type_part instanceof Scalar) { + return true; + } + + if (get_class($container_type_part) === TInt::class && $input_type_part instanceof TLiteralInt) { + return true; + } + + if (get_class($container_type_part) === TInt::class && $input_type_part instanceof TPositiveInt) { + return true; + } + + if (get_class($container_type_part) === TFloat::class && $input_type_part instanceof TLiteralFloat) { + return true; + } + + if ($container_type_part instanceof TNonEmptyString + && $input_type_part instanceof TLiteralString + && $input_type_part->value === '' + ) { + return false; + } + + if ((get_class($container_type_part) === TString::class + || get_class($container_type_part) === TNonEmptyString::class + || get_class($container_type_part) === TSingleLetter::class) + && $input_type_part instanceof TLiteralString + ) { + return true; + } + + if ((get_class($input_type_part) === TInt::class && $container_type_part instanceof TLiteralInt) + || (get_class($input_type_part) === TPositiveInt::class + && $container_type_part instanceof TLiteralInt + && $container_type_part->value > 0) + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_scalar = true; + } + + return false; + } + + if ($input_type_part instanceof TInt && $container_type_part instanceof TPositiveInt) { + if ($input_type_part instanceof TPositiveInt) { + return true; + } + + if ($input_type_part instanceof TLiteralInt) { + return $input_type_part->value > 0; + } + + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_scalar = true; + } + + return false; + } + + if (get_class($input_type_part) === TFloat::class && $container_type_part instanceof TLiteralFloat) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_scalar = true; + } + + return false; + } + + if ((get_class($input_type_part) === TString::class + || get_class($input_type_part) === TSingleLetter::class + || get_class($input_type_part) === TNonEmptyString::class) + && $container_type_part instanceof TLiteralString + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_scalar = true; + } + + return false; + } + + if (($input_type_part instanceof Type\Atomic\TLowercaseString + || $input_type_part instanceof Type\Atomic\TNonEmptyLowercaseString) + && $container_type_part instanceof TLiteralString + && strtolower($container_type_part->value) === $container_type_part->value + ) { + if ($atomic_comparison_result + && ($container_type_part->value || $input_type_part instanceof Type\Atomic\TLowercaseString) + ) { + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->type_coerced_from_scalar = true; + } + + return false; + } + + if (($container_type_part instanceof TClassString || $container_type_part instanceof TLiteralClassString) + && ($input_type_part instanceof TClassString || $input_type_part instanceof TLiteralClassString) + ) { + return ClassStringComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $allow_interface_equality, + $atomic_comparison_result + ); + } + + if ($container_type_part instanceof TString && $input_type_part instanceof TTraitString) { + return true; + } + + if ($container_type_part instanceof TTraitString + && (get_class($input_type_part) === TString::class + || get_class($input_type_part) === TNonEmptyString::class) + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if (($input_type_part instanceof TClassString + || $input_type_part instanceof TLiteralClassString) + && (get_class($container_type_part) === TString::class + || get_class($container_type_part) === TSingleLetter::class + || get_class($container_type_part) === TNonEmptyString::class) + ) { + return true; + } + + if ($input_type_part instanceof TCallableString + && (get_class($container_type_part) === TString::class + || get_class($container_type_part) === TSingleLetter::class + || get_class($container_type_part) === TNonEmptyString::class) + ) { + return true; + } + + if ($container_type_part instanceof TString + && ($input_type_part instanceof TNumericString + || $input_type_part instanceof THtmlEscapedString) + ) { + if ($container_type_part instanceof TLiteralString) { + if (\is_numeric($container_type_part->value) && $atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + return true; + } + + if ($input_type_part instanceof TString + && ($container_type_part instanceof TNumericString + || $container_type_part instanceof THtmlEscapedString) + ) { + if ($input_type_part instanceof TLiteralString) { + return \is_numeric($input_type_part->value); + } + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if ($container_type_part instanceof TCallableString + && $input_type_part instanceof TLiteralString + ) { + $input_callable = CallableTypeComparator::getCallableFromAtomic($codebase, $input_type_part); + $container_callable = CallableTypeComparator::getCallableFromAtomic($codebase, $container_type_part); + + if ($input_callable && $container_callable) { + if (CallableTypeComparator::isContainedBy( + $codebase, + $input_callable, + $container_callable, + $atomic_comparison_result ?: new TypeComparisonResult() + ) === false + ) { + return false; + } + } + + return true; + } + + if ($input_type_part->getKey() === $container_type_part->getKey()) { + return true; + } + + if (($container_type_part instanceof TClassString + || $container_type_part instanceof TLiteralClassString + || $container_type_part instanceof TCallableString) + && $input_type_part instanceof TString + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + + return false; + } + + if ($container_type_part instanceof TNumeric + && $input_type_part->isNumericType() + ) { + return true; + } + + if ($input_type_part instanceof TNumeric) { + if ($container_type_part->isNumericType()) { + if ($atomic_comparison_result) { + $atomic_comparison_result->scalar_type_match_found = true; + } + } + } + + if ($input_type_part instanceof Scalar) { + if (!$container_type_part instanceof TLiteralInt + && !$container_type_part instanceof TLiteralString + && !$container_type_part instanceof TLiteralFloat + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->scalar_type_match_found = true; + } + } + } + + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php new file mode 100644 index 0000000000000000000000000000000000000000..78f2698735a8b8e675793c7dea477079b6db0f18 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php @@ -0,0 +1,30 @@ +scalar_type_match_found = true; + } + + if ($input_type->possibly_undefined + && !$input_type->possibly_undefined_from_try + && !$container_type->possibly_undefined + ) { + return false; + } + + if ($container_type->hasMixed() && !$container_type->isEmptyMixed()) { + return true; + } + + $container_has_template = $container_type->hasTemplateOrStatic(); + + $input_atomic_types = \array_reverse($input_type->getAtomicTypes()); + + while ($input_type_part = \array_pop($input_atomic_types)) { + if ($input_type_part instanceof TNull && $ignore_null) { + continue; + } + + if ($input_type_part instanceof TFalse && $ignore_false) { + continue; + } + + if ($input_type_part instanceof TTemplateParam + && !$container_has_template + && !$input_type_part->extra_types + ) { + $input_atomic_types = array_merge($input_type_part->as->getAtomicTypes(), $input_atomic_types); + continue; + } + + $type_match_found = false; + $scalar_type_match_found = false; + $all_to_string_cast = true; + + $all_type_coerced = null; + $all_type_coerced_from_mixed = null; + $all_type_coerced_from_as_mixed = null; + + $some_type_coerced = false; + $some_type_coerced_from_mixed = false; + + if ($input_type_part instanceof TArrayKey + && ($container_type->hasInt() && $container_type->hasString()) + ) { + continue; + } + + foreach ($container_type->getAtomicTypes() as $container_type_part) { + if ($ignore_null + && $container_type_part instanceof TNull + && !$input_type_part instanceof TNull + ) { + continue; + } + + if ($ignore_false + && $container_type_part instanceof TFalse + && !$input_type_part instanceof TFalse + ) { + continue; + } + + if ($union_comparison_result) { + $atomic_comparison_result = new TypeComparisonResult(); + } else { + $atomic_comparison_result = null; + } + + $is_atomic_contained_by = AtomicTypeComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + $allow_interface_equality, + true, + $atomic_comparison_result + ); + + if ($input_type_part instanceof TMixed + && $input_type->from_template_default + && $input_type->from_docblock + && $atomic_comparison_result + && $atomic_comparison_result->type_coerced_from_mixed + ) { + $atomic_comparison_result->type_coerced_from_as_mixed = true; + } + + if ($atomic_comparison_result) { + if ($atomic_comparison_result->scalar_type_match_found !== null) { + $scalar_type_match_found = $atomic_comparison_result->scalar_type_match_found; + } + + if ($union_comparison_result + && $atomic_comparison_result->type_coerced_from_scalar !== null + ) { + $union_comparison_result->type_coerced_from_scalar + = $atomic_comparison_result->type_coerced_from_scalar; + } + + if ($is_atomic_contained_by + && $union_comparison_result + && $atomic_comparison_result->replacement_atomic_type + ) { + if (!$union_comparison_result->replacement_union_type) { + $union_comparison_result->replacement_union_type = clone $input_type; + } + + $union_comparison_result->replacement_union_type->removeType($input_type->getKey()); + + $union_comparison_result->replacement_union_type->addType( + $atomic_comparison_result->replacement_atomic_type + ); + } + } + + if ($input_type_part instanceof TNumeric + && $container_type->hasString() + && $container_type->hasInt() + && $container_type->hasFloat() + ) { + $scalar_type_match_found = false; + $is_atomic_contained_by = true; + } + + if ($atomic_comparison_result) { + if ($atomic_comparison_result->type_coerced) { + $some_type_coerced = true; + } + + if ($atomic_comparison_result->type_coerced_from_mixed) { + $some_type_coerced_from_mixed = true; + } + + if ($atomic_comparison_result->type_coerced !== true || $all_type_coerced === false) { + $all_type_coerced = false; + } else { + $all_type_coerced = true; + } + + if ($atomic_comparison_result->type_coerced_from_mixed !== true + || $all_type_coerced_from_mixed === false + ) { + $all_type_coerced_from_mixed = false; + } else { + $all_type_coerced_from_mixed = true; + } + + if ($atomic_comparison_result->type_coerced_from_as_mixed !== true + || $all_type_coerced_from_as_mixed === false + ) { + $all_type_coerced_from_as_mixed = false; + } else { + $all_type_coerced_from_as_mixed = true; + } + } + + if ($is_atomic_contained_by) { + $type_match_found = true; + + if ($atomic_comparison_result) { + if ($atomic_comparison_result->to_string_cast !== true) { + $all_to_string_cast = false; + } + } + + $all_type_coerced_from_mixed = false; + $all_type_coerced_from_as_mixed = false; + $all_type_coerced = false; + } + } + + if ($union_comparison_result) { + // only set this flag if we're definite that the only + // reason the type match has been found is because there + // was a __toString cast + if ($all_to_string_cast && $type_match_found) { + $union_comparison_result->to_string_cast = true; + } + + if ($all_type_coerced) { + $union_comparison_result->type_coerced = true; + } + + if ($all_type_coerced_from_mixed) { + $union_comparison_result->type_coerced_from_mixed = true; + + if (($input_type->from_template_default && $input_type->from_docblock) + || $all_type_coerced_from_as_mixed + ) { + $union_comparison_result->type_coerced_from_as_mixed = true; + } + } + } + + if (!$type_match_found) { + if ($union_comparison_result) { + if ($some_type_coerced) { + $union_comparison_result->type_coerced = true; + } + + if ($some_type_coerced_from_mixed) { + $union_comparison_result->type_coerced_from_mixed = true; + + if (($input_type->from_template_default && $input_type->from_docblock) + || $all_type_coerced_from_as_mixed + ) { + $union_comparison_result->type_coerced_from_as_mixed = true; + } + } + + if (!$scalar_type_match_found) { + $union_comparison_result->scalar_type_match_found = false; + } + } + + return false; + } + } + + return true; + } + + /** + * Used for comparing signature typehints, uses PHP's light contravariance rules + * + * + */ + public static function isContainedByInPhp( + ?Type\Union $input_type, + Type\Union $container_type + ): bool { + if (!$input_type) { + return false; + } + + if ($input_type->getId() === $container_type->getId()) { + return true; + } + + if ($input_type->isNullable() && !$container_type->isNullable()) { + return false; + } + + $input_type_not_null = clone $input_type; + $input_type_not_null->removeType('null'); + + $container_type_not_null = clone $container_type; + $container_type_not_null->removeType('null'); + + if ($input_type_not_null->getId() === $container_type_not_null->getId()) { + return true; + } + + if ($input_type_not_null->hasArray() && $container_type_not_null->hasType('iterable')) { + return true; + } + + return false; + } + + /** + * Used for comparing docblock types to signature types before we know about all types + * + */ + public static function isSimplyContainedBy( + Type\Union $input_type, + Type\Union $container_type + ) : bool { + if ($input_type->getId() === $container_type->getId()) { + return true; + } + + if ($input_type->isNullable() && !$container_type->isNullable()) { + return false; + } + + $input_type_not_null = clone $input_type; + $input_type_not_null->removeType('null'); + + $container_type_not_null = clone $container_type; + $container_type_not_null->removeType('null'); + + foreach ($input_type->getAtomicTypes() as $input_key => $input_type_part) { + foreach ($container_type->getAtomicTypes() as $container_key => $container_type_part) { + if (get_class($container_type_part) === TNamedObject::class + && $input_type_part instanceof TNamedObject + && $input_type_part->value === $container_type_part->value + ) { + continue 2; + } + + if ($input_key === $container_key) { + continue 2; + } + } + + return false; + } + + + + return true; + } + + /** + * Does the input param type match the given param type + */ + public static function canBeContainedBy( + Codebase $codebase, + Type\Union $input_type, + Type\Union $container_type, + bool $ignore_null = false, + bool $ignore_false = false, + array &$matching_input_keys = [] + ): bool { + if ($container_type->hasMixed()) { + return true; + } + + if ($input_type->possibly_undefined && !$container_type->possibly_undefined) { + return false; + } + + foreach ($container_type->getAtomicTypes() as $container_type_part) { + if ($container_type_part instanceof TNull && $ignore_null) { + continue; + } + + if ($container_type_part instanceof TFalse && $ignore_false) { + continue; + } + + foreach ($input_type->getAtomicTypes() as $input_type_part) { + $atomic_comparison_result = new TypeComparisonResult(); + $is_atomic_contained_by = AtomicTypeComparator::isContainedBy( + $codebase, + $input_type_part, + $container_type_part, + false, + false, + $atomic_comparison_result + ); + + if (($is_atomic_contained_by && !$atomic_comparison_result->to_string_cast) + || $atomic_comparison_result->type_coerced_from_mixed + ) { + $matching_input_keys[$input_type_part->getKey()] = true; + } + } + } + + return !!$matching_input_keys; + } + + /** + * Can any part of the $type1 be equal to any part of $type2 + * + */ + public static function canExpressionTypesBeIdentical( + Codebase $codebase, + Type\Union $type1, + Type\Union $type2 + ): bool { + if ($type1->hasMixed() || $type2->hasMixed()) { + return true; + } + + if ($type1->isNullable() && $type2->isNullable()) { + return true; + } + + foreach ($type1->getAtomicTypes() as $type1_part) { + foreach ($type2->getAtomicTypes() as $type2_part) { + $either_contains = AtomicTypeComparator::canBeIdentical( + $codebase, + $type1_part, + $type2_part + ); + + if ($either_contains) { + return true; + } + } + } + + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/NegatedAssertionReconciler.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/NegatedAssertionReconciler.php new file mode 100644 index 0000000000000000000000000000000000000000..8b440d30b19bb7e18b7505b8753a47f3dd662f6c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/NegatedAssertionReconciler.php @@ -0,0 +1,417 @@ +> $template_type_map + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + * + */ + public static function reconcile( + StatementsAnalyzer $statements_analyzer, + string $assertion, + bool $is_strict_equality, + bool $is_loose_equality, + Type\Union $existing_var_type, + array $template_type_map, + string $old_var_type_string, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation + ): Type\Union { + $is_equality = $is_strict_equality || $is_loose_equality; + + // this is a specific value comparison type that cannot be negated + if ($is_equality && $bracket_pos = strpos($assertion, '(')) { + if ($existing_var_type->hasMixed()) { + return $existing_var_type; + } + + return self::handleLiteralNegatedEquality( + $statements_analyzer, + $assertion, + $bracket_pos, + $existing_var_type, + $old_var_type_string, + $key, + $negated, + $code_location, + $suppressed_issues, + $is_strict_equality + ); + } + + if ($is_equality && $assertion === 'positive-numeric') { + return $existing_var_type; + } + + if (!$is_equality) { + if ($assertion === 'isset') { + if ($existing_var_type->possibly_undefined) { + return Type::getEmpty(); + } + + if (!$existing_var_type->isNullable() + && $key + && strpos($key, '[') === false + && $key !== '$_SESSION' + ) { + foreach ($existing_var_type->getAtomicTypes() as $atomic) { + if (!$existing_var_type->hasMixed() + || $atomic instanceof Type\Atomic\TNonEmptyMixed + ) { + $failed_reconciliation = 2; + + if ($code_location) { + if ($existing_var_type->from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + 'Cannot resolve types for ' . $key . ' with docblock-defined type ' + . $existing_var_type . ' and !isset assertion', + $code_location, + null + ), + $suppressed_issues + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + 'Cannot resolve types for ' . $key . ' with type ' + . $existing_var_type . ' and !isset assertion', + $code_location, + null + ), + $suppressed_issues + )) { + // fall through + } + } + } + + return $existing_var_type->from_docblock + ? Type::getNull() + : Type::getEmpty(); + } + } + } + + return Type::getNull(); + } elseif ($assertion === 'array-key-exists') { + return Type::getEmpty(); + } elseif (substr($assertion, 0, 9) === 'in-array-') { + return $existing_var_type; + } elseif (substr($assertion, 0, 14) === 'has-array-key-') { + return $existing_var_type; + } + } + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + if ($assertion === 'false' && isset($existing_var_atomic_types['bool'])) { + $existing_var_type->removeType('bool'); + $existing_var_type->addType(new TTrue); + } elseif ($assertion === 'true' && isset($existing_var_atomic_types['bool'])) { + $existing_var_type->removeType('bool'); + $existing_var_type->addType(new TFalse); + } else { + $simple_negated_type = SimpleNegatedAssertionReconciler::reconcile( + $assertion, + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + $is_strict_equality + ); + + if ($simple_negated_type) { + return $simple_negated_type; + } + } + + if ($assertion === 'iterable' || $assertion === 'countable') { + $existing_var_type->removeType('array'); + } + + if (!$is_equality + && isset($existing_var_atomic_types['int']) + && $existing_var_type->from_calculation + && ($assertion === 'int' || $assertion === 'float') + ) { + $existing_var_type->removeType($assertion); + + if ($assertion === 'int') { + $existing_var_type->addType(new Type\Atomic\TFloat); + } else { + $existing_var_type->addType(new Type\Atomic\TInt); + } + + $existing_var_type->from_calculation = false; + + return $existing_var_type; + } + + if (strtolower($assertion) === 'traversable' + && isset($existing_var_atomic_types['iterable']) + ) { + /** @var Type\Atomic\TIterable */ + $iterable = $existing_var_atomic_types['iterable']; + $existing_var_type->removeType('iterable'); + $existing_var_type->addType(new TArray( + [ + $iterable->type_params[0]->hasMixed() + ? Type::getArrayKey() + : clone $iterable->type_params[0], + clone $iterable->type_params[1], + ] + )); + } elseif (strtolower($assertion) === 'int' + && isset($existing_var_type->getAtomicTypes()['array-key']) + ) { + $existing_var_type->removeType('array-key'); + $existing_var_type->addType(new TString); + } elseif (substr($assertion, 0, 9) === 'getclass-') { + $assertion = substr($assertion, 9); + } elseif (!$is_equality) { + $codebase = $statements_analyzer->getCodebase(); + + // if there wasn't a direct hit, go deeper, eliminating subtypes + if (!$existing_var_type->removeType($assertion)) { + foreach ($existing_var_type->getAtomicTypes() as $part_name => $existing_var_type_part) { + if (!$existing_var_type_part->isObjectType() || strpos($assertion, '-')) { + continue; + } + + $new_type_part = Atomic::create($assertion); + + if (!$new_type_part instanceof TNamedObject) { + continue; + } + + if (AtomicTypeComparator::isContainedBy( + $codebase, + $existing_var_type_part, + $new_type_part, + false, + false + )) { + $existing_var_type->removeType($part_name); + } elseif (AtomicTypeComparator::isContainedBy( + $codebase, + $new_type_part, + $existing_var_type_part, + false, + false + )) { + $existing_var_type->different = true; + } + } + } + } + + if ($is_strict_equality + && $assertion !== 'isset' + && ($key !== '$this' + || !($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer)) + ) { + $assertion = Type::parseString($assertion, null, $template_type_map); + + if ($key + && $code_location + && !UnionTypeComparator::canExpressionTypesBeIdentical( + $statements_analyzer->getCodebase(), + $existing_var_type, + $assertion + ) + ) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!=' . $assertion, + true, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if (empty($existing_var_type->getAtomicTypes())) { + if ($key !== '$this' + || !($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer) + ) { + if ($key && $code_location && !$is_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!' . $assertion, + false, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + $failed_reconciliation = 2; + + return new Type\Union([new Type\Atomic\TEmptyMixed]); + } + + return $existing_var_type; + } + + /** + * @param string[] $suppressed_issues + * + */ + private static function handleLiteralNegatedEquality( + StatementsAnalyzer $statements_analyzer, + string $assertion, + int $bracket_pos, + Type\Union $existing_var_type, + string $old_var_type_string, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + bool $is_strict_equality + ): Type\Union { + $scalar_type = substr($assertion, 0, $bracket_pos); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + $did_remove_type = false; + $did_match_literal_type = false; + + $scalar_var_type = null; + + if ($scalar_type === 'int') { + if ($existing_var_type->hasInt()) { + if ($existing_int_types = $existing_var_type->getLiteralInts()) { + if (!$existing_var_type->hasPositiveInt()) { + $did_match_literal_type = true; + } + + if (isset($existing_int_types[$assertion])) { + $existing_var_type->removeType($assertion); + + $did_remove_type = true; + } + } + } else { + $scalar_value = substr($assertion, $bracket_pos + 1, -1); + $scalar_var_type = Type::getInt(false, (int) $scalar_value); + } + } elseif ($scalar_type === 'string' + || $scalar_type === 'class-string' + || $scalar_type === 'interface-string' + || $scalar_type === 'trait-string' + || $scalar_type === 'callable-string' + ) { + if ($existing_var_type->hasString()) { + if ($existing_string_types = $existing_var_type->getLiteralStrings()) { + $did_match_literal_type = true; + + if (isset($existing_string_types[$assertion])) { + $existing_var_type->removeType($assertion); + + $did_remove_type = true; + } + } elseif ($assertion === 'string()') { + $existing_var_type->addType(new Type\Atomic\TNonEmptyString()); + } + } elseif ($scalar_type === 'string') { + $scalar_value = substr($assertion, $bracket_pos + 1, -1); + $scalar_var_type = Type::getString($scalar_value); + } + } elseif ($scalar_type === 'float') { + if ($existing_var_type->hasFloat()) { + if ($existing_float_types = $existing_var_type->getLiteralFloats()) { + $did_match_literal_type = true; + + if (isset($existing_float_types[$assertion])) { + $existing_var_type->removeType($assertion); + + $did_remove_type = true; + } + } + } else { + $scalar_value = substr($assertion, $bracket_pos + 1, -1); + $scalar_var_type = Type::getFloat((float) $scalar_value); + } + } + + if ($key && $code_location) { + if ($did_match_literal_type + && (!$did_remove_type || count($existing_var_atomic_types) === 1) + ) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!' . $assertion, + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } elseif ($scalar_var_type + && $is_strict_equality + && ($key !== '$this' + || !($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer)) + ) { + if (!UnionTypeComparator::canExpressionTypesBeIdentical( + $statements_analyzer->getCodebase(), + $existing_var_type, + $scalar_var_type + )) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!=' . $assertion, + true, + $negated, + $code_location, + $suppressed_issues + ); + } + } + } + + return $existing_var_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree.php new file mode 100644 index 0000000000000000000000000000000000000000..21e02d4e93c5bfbcd6bf235e77e81a15d9f3ce07 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree.php @@ -0,0 +1,42 @@ + + */ + public $children = []; + + /** + * @var null|ParseTree + */ + public $parent; + + /** + * @var bool + */ + public $possibly_undefined = false; + + public function __construct(?ParseTree $parent = null) + { + $this->parent = $parent; + } + + public function __destruct() + { + $this->parent = null; + } + + public function cleanParents() : void + { + foreach ($this->children as $child) { + $child->cleanParents(); + } + + $this->parent = null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php new file mode 100644 index 0000000000000000000000000000000000000000..9ad5960545bee3c38a9b6e7e4d3b1d591ed1aaac --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php @@ -0,0 +1,18 @@ +value = $value; + $this->parent = $parent; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableWithReturnTypeTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableWithReturnTypeTree.php new file mode 100644 index 0000000000000000000000000000000000000000..2581a6ebf3e840eaf12eef1ebc63eca4fac6ab3d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/CallableWithReturnTypeTree.php @@ -0,0 +1,9 @@ +condition = $condition; + $this->parent = $parent; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/EncapsulationTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/EncapsulationTree.php new file mode 100644 index 0000000000000000000000000000000000000000..afeaeae21ee006fdbf5adcf7f81ffd9bdd2f6d16 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/EncapsulationTree.php @@ -0,0 +1,13 @@ +value = $value; + $this->parent = $parent; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/IndexedAccessTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/IndexedAccessTree.php new file mode 100644 index 0000000000000000000000000000000000000000..ed2637dd63ad6cf9832ce376063f65241cf052d5 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/IndexedAccessTree.php @@ -0,0 +1,19 @@ +value = $value; + $this->parent = $parent; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/IntersectionTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/IntersectionTree.php new file mode 100644 index 0000000000000000000000000000000000000000..bd0592743313b22ca8112af477b8837f1a4132b7 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/IntersectionTree.php @@ -0,0 +1,9 @@ +value = $value; + $this->parent = $parent; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/KeyedArrayTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/KeyedArrayTree.php new file mode 100644 index 0000000000000000000000000000000000000000..da54d14bf985c725f9889de6a5032a23dd10c610 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/KeyedArrayTree.php @@ -0,0 +1,24 @@ +value = $value; + $this->parent = $parent; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodParamTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodParamTree.php new file mode 100644 index 0000000000000000000000000000000000000000..b7e898780c70e7b75297c4519b5b95f4279b740f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodParamTree.php @@ -0,0 +1,40 @@ +name = $name; + $this->byref = $byref; + $this->variadic = $variadic; + $this->parent = $parent; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodTree.php new file mode 100644 index 0000000000000000000000000000000000000000..65d1c6fb30600a79e042e178ac0df14961bf6bac --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodTree.php @@ -0,0 +1,19 @@ +value = $value; + $this->parent = $parent; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodWithReturnTypeTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodWithReturnTypeTree.php new file mode 100644 index 0000000000000000000000000000000000000000..25605258d291c5c60247e68aba8fd8c352ae6de0 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/MethodWithReturnTypeTree.php @@ -0,0 +1,9 @@ +param_name = $param_name; + $this->as = $as; + $this->parent = $parent; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php new file mode 100644 index 0000000000000000000000000000000000000000..dcfb2b86c35fdcde98adb6495606228c0badd521 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php @@ -0,0 +1,19 @@ +param_name = $param_name; + $this->parent = $parent; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/UnionTree.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/UnionTree.php new file mode 100644 index 0000000000000000000000000000000000000000..681a54b68e41a98e0d6f6ec95d2ab8a309a17df6 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTree/UnionTree.php @@ -0,0 +1,9 @@ +offset_start = $offset_start; + $this->offset_end = $offset_end; + $this->value = $value; + $this->parent = $parent; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTreeCreator.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTreeCreator.php new file mode 100644 index 0000000000000000000000000000000000000000..b24b8c0a89f883120faa9e5719e1dfbe0b673d91 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/ParseTreeCreator.php @@ -0,0 +1,828 @@ + */ + private $type_tokens; + + /** @var int */ + private $type_token_count; + + /** @var int */ + private $t = 0; + + /** + * @param list $type_tokens + */ + public function __construct(array $type_tokens) + { + $this->type_tokens = $type_tokens; + $this->type_token_count = count($type_tokens); + $this->parse_tree = new ParseTree\Root(); + $this->current_leaf = $this->parse_tree; + } + + public function create() : ParseTree + { + while ($this->t < $this->type_token_count) { + $type_token = $this->type_tokens[$this->t]; + + switch ($type_token[0]) { + case '<': + case '{': + case ']': + throw new TypeParseTreeException('Unexpected token ' . $type_token[0]); + + case '[': + $this->handleOpenSquareBracket(); + break; + + case '(': + $this->handleOpenRoundBracket(); + break; + + case ')': + $this->handleClosedRoundBracket(); + break; + + case '>': + do { + if ($this->current_leaf->parent === null) { + throw new TypeParseTreeException('Cannot parse generic type'); + } + + $this->current_leaf = $this->current_leaf->parent; + } while (!$this->current_leaf instanceof ParseTree\GenericTree); + + $this->current_leaf->terminated = true; + + break; + + case '}': + do { + if ($this->current_leaf->parent === null) { + throw new TypeParseTreeException('Cannot parse array type'); + } + + $this->current_leaf = $this->current_leaf->parent; + } while (!$this->current_leaf instanceof ParseTree\KeyedArrayTree); + + $this->current_leaf->terminated = true; + + break; + + case ',': + $this->handleComma(); + break; + + case '...': + case '=': + $this->handleEllipsisOrEquals($type_token); + break; + + case ':': + $this->handleColon(); + break; + + case ' ': + $this->handleSpace(); + break; + + case '?': + $this->handleQuestionMark(); + break; + + case '|': + $this->handleBar(); + break; + + case '&': + $this->handleAmpersand(); + break; + + case 'is': + case 'as': + $this->handleIsOrAs($type_token); + break; + + default: + $this->handleValue($type_token); + break; + } + + $this->t++; + } + + $this->parse_tree->cleanParents(); + + if ($this->current_leaf !== $this->parse_tree + && ($this->parse_tree instanceof ParseTree\GenericTree + || $this->parse_tree instanceof ParseTree\CallableTree + || $this->parse_tree instanceof ParseTree\KeyedArrayTree) + ) { + throw new TypeParseTreeException( + 'Unterminated bracket' + ); + } + + return $this->parse_tree; + } + + /** + * @param array{0: string, 1: int} $current_token + */ + private function createMethodParam(array $current_token, ParseTree $current_parent) : void + { + $byref = false; + $variadic = false; + $has_default = false; + $default = ''; + + if ($current_token[0] === '&') { + throw new TypeParseTreeException('Magic args cannot be passed by reference'); + } + + if ($current_token[0] === '...') { + $variadic = true; + + ++$this->t; + $current_token = $this->t < $this->type_token_count ? $this->type_tokens[$this->t] : null; + } + + if (!$current_token || $current_token[0][0] !== '$') { + throw new TypeParseTreeException('Unexpected token after space'); + } + + $new_parent_leaf = new ParseTree\MethodParamTree( + $current_token[0], + $byref, + $variadic, + $current_parent + ); + + for ($j = $this->t + 1; $j < $this->type_token_count; ++$j) { + $ahead_type_token = $this->type_tokens[$j]; + + if ($ahead_type_token[0] === ',' + || ($ahead_type_token[0] === ')' && $this->type_tokens[$j - 1][0] !== '(') + ) { + $this->t = $j - 1; + break; + } + + if ($has_default) { + $default .= $ahead_type_token[0]; + } + + if ($ahead_type_token[0] === '=') { + $has_default = true; + continue; + } + + if ($j === $this->type_token_count - 1) { + throw new TypeParseTreeException('Unterminated method'); + } + } + + $new_parent_leaf->default = $default; + + if ($this->current_leaf !== $current_parent) { + $new_parent_leaf->children = [$this->current_leaf]; + $this->current_leaf->parent = $new_parent_leaf; + array_pop($current_parent->children); + } + + $current_parent->children[] = $new_parent_leaf; + + $this->current_leaf = $new_parent_leaf; + } + + private function handleOpenSquareBracket() : void + { + if ($this->current_leaf instanceof ParseTree\Root) { + throw new TypeParseTreeException('Unexpected token ['); + } + + $indexed_access = false; + + $next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; + + if (!$next_token || $next_token[0] !== ']') { + $next_next_token = $this->t + 2 < $this->type_token_count ? $this->type_tokens[$this->t + 2] : null; + + if ($next_next_token !== null && $next_next_token[0] === ']') { + $indexed_access = true; + ++$this->t; + } else { + throw new TypeParseTreeException('Unexpected token ['); + } + } + + $current_parent = $this->current_leaf->parent; + + if ($indexed_access) { + if ($next_token === null) { + throw new TypeParseTreeException('Unexpected token ['); + } + + $new_parent_leaf = new ParseTree\IndexedAccessTree($next_token[0], $current_parent); + } else { + if ($this->current_leaf instanceof ParseTree\KeyedArrayPropertyTree) { + throw new TypeParseTreeException('Unexpected token ['); + } + + $new_parent_leaf = new ParseTree\GenericTree('array', $current_parent); + } + + $this->current_leaf->parent = $new_parent_leaf; + $new_parent_leaf->children = [$this->current_leaf]; + + if ($current_parent) { + array_pop($current_parent->children); + $current_parent->children[] = $new_parent_leaf; + } else { + $this->parse_tree = $new_parent_leaf; + } + + $this->current_leaf = $new_parent_leaf; + ++$this->t; + } + + private function handleOpenRoundBracket() : void + { + if ($this->current_leaf instanceof ParseTree\Value) { + throw new TypeParseTreeException('Unrecognised token ('); + } + + $new_parent = !$this->current_leaf instanceof ParseTree\Root ? $this->current_leaf : null; + + $new_leaf = new ParseTree\EncapsulationTree( + $new_parent + ); + + if ($this->current_leaf instanceof ParseTree\Root) { + $this->current_leaf = $this->parse_tree = $new_leaf; + return; + } + + if ($new_leaf->parent) { + $new_leaf->parent->children[] = $new_leaf; + } + + $this->current_leaf = $new_leaf; + } + + private function handleClosedRoundBracket() : void + { + $prev_token = $this->t > 0 ? $this->type_tokens[$this->t - 1] : null; + + if ($prev_token !== null + && $prev_token[0] === '(' + && $this->current_leaf instanceof ParseTree\CallableTree + ) { + return; + } + + do { + if ($this->current_leaf->parent === null) { + break; + } + + $this->current_leaf = $this->current_leaf->parent; + } while (!$this->current_leaf instanceof ParseTree\EncapsulationTree + && !$this->current_leaf instanceof ParseTree\CallableTree + && !$this->current_leaf instanceof ParseTree\MethodTree); + + if ($this->current_leaf instanceof ParseTree\EncapsulationTree + || $this->current_leaf instanceof ParseTree\CallableTree + ) { + $this->current_leaf->terminated = true; + } + } + + private function handleComma() : void + { + if ($this->current_leaf instanceof ParseTree\Root) { + throw new TypeParseTreeException('Unexpected token ,'); + } + + if (!$this->current_leaf->parent) { + throw new TypeParseTreeException('Cannot parse comma without a parent node'); + } + + $context_node = $this->current_leaf; + + if ($context_node instanceof ParseTree\GenericTree + || $context_node instanceof ParseTree\KeyedArrayTree + || $context_node instanceof ParseTree\CallableTree + || $context_node instanceof ParseTree\MethodTree + ) { + $context_node = $context_node->parent; + } + + while ($context_node + && !$context_node instanceof ParseTree\GenericTree + && !$context_node instanceof ParseTree\KeyedArrayTree + && !$context_node instanceof ParseTree\CallableTree + && !$context_node instanceof ParseTree\MethodTree + ) { + $context_node = $context_node->parent; + } + + if (!$context_node) { + throw new TypeParseTreeException('Cannot parse comma in non-generic/array type'); + } + + $this->current_leaf = $context_node; + } + + /** @param array{0: string, 1: int} $type_token */ + private function handleEllipsisOrEquals(array $type_token) : void + { + $prev_token = $this->t > 0 ? $this->type_tokens[$this->t - 1] : null; + + if ($prev_token && ($prev_token[0] === '...' || $prev_token[0] === '=')) { + throw new TypeParseTreeException('Cannot have duplicate tokens'); + } + + $current_parent = $this->current_leaf->parent; + + if ($this->current_leaf instanceof ParseTree\MethodTree && $type_token[0] === '...') { + $this->createMethodParam($type_token, $this->current_leaf); + return; + } + + while ($current_parent + && !$current_parent instanceof ParseTree\CallableTree + && !$current_parent instanceof ParseTree\CallableParamTree + ) { + $this->current_leaf = $current_parent; + $current_parent = $current_parent->parent; + } + + if (!$current_parent) { + if ($this->current_leaf instanceof ParseTree\CallableTree + && $type_token[0] === '...' + ) { + $current_parent = $this->current_leaf; + } else { + throw new TypeParseTreeException('Unexpected token ' . $type_token[0]); + } + } + + if ($current_parent instanceof ParseTree\CallableParamTree) { + throw new TypeParseTreeException('Cannot have variadic param with a default'); + } + + $new_leaf = new ParseTree\CallableParamTree($current_parent); + $new_leaf->has_default = $type_token[0] === '='; + $new_leaf->variadic = $type_token[0] === '...'; + + if ($current_parent !== $this->current_leaf) { + $new_leaf->children = [$this->current_leaf]; + $this->current_leaf->parent = $new_leaf; + + array_pop($current_parent->children); + $current_parent->children[] = $new_leaf; + } else { + $current_parent->children[] = $new_leaf; + } + + $this->current_leaf = $new_leaf; + } + + private function handleColon() : void + { + if ($this->current_leaf instanceof ParseTree\Root) { + throw new TypeParseTreeException('Unexpected token :'); + } + + $current_parent = $this->current_leaf->parent; + + if ($this->current_leaf instanceof ParseTree\CallableTree) { + $new_parent_leaf = new ParseTree\CallableWithReturnTypeTree($current_parent); + $this->current_leaf->parent = $new_parent_leaf; + $new_parent_leaf->children = [$this->current_leaf]; + + if ($current_parent) { + array_pop($current_parent->children); + $current_parent->children[] = $new_parent_leaf; + } else { + $this->parse_tree = $new_parent_leaf; + } + + $this->current_leaf = $new_parent_leaf; + return; + } + + if ($this->current_leaf instanceof ParseTree\MethodTree) { + $new_parent_leaf = new ParseTree\MethodWithReturnTypeTree($current_parent); + $this->current_leaf->parent = $new_parent_leaf; + $new_parent_leaf->children = [$this->current_leaf]; + + if ($current_parent) { + array_pop($current_parent->children); + $current_parent->children[] = $new_parent_leaf; + } else { + $this->parse_tree = $new_parent_leaf; + } + + $this->current_leaf = $new_parent_leaf; + return; + } + + if ($current_parent && $current_parent instanceof ParseTree\KeyedArrayPropertyTree) { + return; + } + + while (($current_parent instanceof ParseTree\UnionTree + || $current_parent instanceof ParseTree\CallableWithReturnTypeTree) + && $this->current_leaf->parent + ) { + $this->current_leaf = $this->current_leaf->parent; + $current_parent = $this->current_leaf->parent; + } + + if ($current_parent && $current_parent instanceof ParseTree\ConditionalTree) { + if (count($current_parent->children) > 1) { + throw new TypeParseTreeException('Cannot process colon in conditional twice'); + } + + $this->current_leaf = $current_parent; + return; + } + + if (!$current_parent) { + throw new TypeParseTreeException('Cannot process colon without parent'); + } + + if (!$this->current_leaf instanceof ParseTree\Value) { + throw new TypeParseTreeException('Unexpected LHS of property'); + } + + if (!$current_parent instanceof ParseTree\KeyedArrayTree) { + throw new TypeParseTreeException('Saw : outside of object-like array'); + } + + $prev_token = $this->t > 0 ? $this->type_tokens[$this->t - 1] : null; + + $new_parent_leaf = new ParseTree\KeyedArrayPropertyTree($this->current_leaf->value, $current_parent); + $new_parent_leaf->possibly_undefined = $prev_token !== null && $prev_token[0] === '?'; + $this->current_leaf->parent = $new_parent_leaf; + + array_pop($current_parent->children); + $current_parent->children[] = $new_parent_leaf; + + $this->current_leaf = $new_parent_leaf; + } + + private function handleSpace() : void + { + if ($this->current_leaf instanceof ParseTree\Root) { + throw new TypeParseTreeException('Unexpected space'); + } + + if ($this->current_leaf instanceof ParseTree\KeyedArrayTree) { + return; + } + + $current_parent = $this->current_leaf->parent; + + if ($current_parent instanceof ParseTree\CallableTree) { + return; + } + + while ($current_parent && !$current_parent instanceof ParseTree\MethodTree) { + $this->current_leaf = $current_parent; + $current_parent = $current_parent->parent; + } + + $next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; + + if (!$current_parent instanceof ParseTree\MethodTree || !$next_token) { + throw new TypeParseTreeException('Unexpected space'); + } + + ++$this->t; + + $this->createMethodParam($next_token, $current_parent); + } + + private function handleQuestionMark() : void + { + $next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; + + if ($next_token === null || $next_token[0] !== ':') { + while (($this->current_leaf instanceof ParseTree\Value + || $this->current_leaf instanceof ParseTree\UnionTree + || ($this->current_leaf instanceof ParseTree\KeyedArrayTree + && $this->current_leaf->terminated) + || ($this->current_leaf instanceof ParseTree\GenericTree + && $this->current_leaf->terminated) + || ($this->current_leaf instanceof ParseTree\EncapsulationTree + && $this->current_leaf->terminated) + || ($this->current_leaf instanceof ParseTree\CallableTree + && $this->current_leaf->terminated) + || $this->current_leaf instanceof ParseTree\IntersectionTree) + && $this->current_leaf->parent + ) { + $this->current_leaf = $this->current_leaf->parent; + } + + if ($this->current_leaf instanceof ParseTree\TemplateIsTree && $this->current_leaf->parent) { + $current_parent = $this->current_leaf->parent; + + $new_leaf = new ParseTree\ConditionalTree( + $this->current_leaf, + $this->current_leaf->parent + ); + + $this->current_leaf->parent = $new_leaf; + + array_pop($current_parent->children); + $current_parent->children[] = $new_leaf; + $this->current_leaf = $new_leaf; + } else { + $new_parent = !$this->current_leaf instanceof ParseTree\Root ? $this->current_leaf : null; + + if (!$next_token) { + throw new TypeParseTreeException('Unexpected token ?'); + } + + $new_leaf = new ParseTree\NullableTree( + $new_parent + ); + + if ($this->current_leaf instanceof ParseTree\Root) { + $this->current_leaf = $this->parse_tree = $new_leaf; + return; + } + + if ($new_leaf->parent) { + $new_leaf->parent->children[] = $new_leaf; + } + + $this->current_leaf = $new_leaf; + } + } + } + + private function handleBar() : void + { + if ($this->current_leaf instanceof ParseTree\Root) { + throw new TypeParseTreeException('Unexpected token |'); + } + + $current_parent = $this->current_leaf->parent; + + if ($current_parent instanceof ParseTree\CallableWithReturnTypeTree) { + $this->current_leaf = $current_parent; + $current_parent = $current_parent->parent; + } + + if ($current_parent instanceof ParseTree\NullableTree) { + $this->current_leaf = $current_parent; + $current_parent = $current_parent->parent; + } + + if ($this->current_leaf instanceof ParseTree\UnionTree) { + throw new TypeParseTreeException('Unexpected token |'); + } + + if ($current_parent && $current_parent instanceof ParseTree\UnionTree) { + $this->current_leaf = $current_parent; + return; + } + + if ($current_parent && $current_parent instanceof ParseTree\IntersectionTree) { + $this->current_leaf = $current_parent; + $current_parent = $this->current_leaf->parent; + } + + if ($current_parent instanceof ParseTree\TemplateIsTree) { + $new_parent_leaf = new ParseTree\UnionTree($this->current_leaf); + $new_parent_leaf->children = [$this->current_leaf]; + $new_parent_leaf->parent = $current_parent; + $this->current_leaf->parent = $new_parent_leaf; + } else { + $new_parent_leaf = new ParseTree\UnionTree($current_parent); + $new_parent_leaf->children = [$this->current_leaf]; + $this->current_leaf->parent = $new_parent_leaf; + } + + if ($current_parent) { + array_pop($current_parent->children); + $current_parent->children[] = $new_parent_leaf; + } else { + $this->parse_tree = $new_parent_leaf; + } + + $this->current_leaf = $new_parent_leaf; + } + + private function handleAmpersand() : void + { + if ($this->current_leaf instanceof ParseTree\Root) { + throw new TypeParseTreeException( + 'Unexpected &' + ); + } + + $current_parent = $this->current_leaf->parent; + + if ($this->current_leaf instanceof ParseTree\MethodTree && $current_parent) { + $this->createMethodParam($this->type_tokens[$this->t], $current_parent); + return; + } + + if ($current_parent && $current_parent instanceof ParseTree\IntersectionTree) { + $this->current_leaf = $current_parent; + return; + } + + $new_parent_leaf = new ParseTree\IntersectionTree($current_parent); + $new_parent_leaf->children = [$this->current_leaf]; + $this->current_leaf->parent = $new_parent_leaf; + + if ($current_parent) { + array_pop($current_parent->children); + $current_parent->children[] = $new_parent_leaf; + } else { + $this->parse_tree = $new_parent_leaf; + } + + $this->current_leaf = $new_parent_leaf; + } + + /** @param array{0: string, 1: int} $type_token */ + private function handleIsOrAs(array $type_token) : void + { + if ($this->t === 0) { + $this->handleValue($type_token); + } else { + $current_parent = $this->current_leaf->parent; + + if ($current_parent) { + array_pop($current_parent->children); + } + + if ($type_token[0] === 'as') { + $next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; + + if (!$this->current_leaf instanceof ParseTree\Value + || !$current_parent instanceof ParseTree\GenericTree + || !$next_token + ) { + throw new TypeParseTreeException('Unexpected token ' . $type_token[0]); + } + + $this->current_leaf = new ParseTree\TemplateAsTree( + $this->current_leaf->value, + $next_token[0], + $current_parent + ); + + $current_parent->children[] = $this->current_leaf; + ++$this->t; + } elseif ($this->current_leaf instanceof ParseTree\Value) { + $this->current_leaf = new ParseTree\TemplateIsTree( + $this->current_leaf->value, + $current_parent + ); + + if ($current_parent) { + $current_parent->children[] = $this->current_leaf; + } + } + } + } + + /** @param array{0: string, 1: int} $type_token */ + private function handleValue(array $type_token) : void + { + $new_parent = !$this->current_leaf instanceof ParseTree\Root ? $this->current_leaf : null; + + if ($this->current_leaf instanceof ParseTree\MethodTree && $type_token[0][0] === '$') { + $this->createMethodParam($type_token, $this->current_leaf); + return; + } + + $next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; + + switch ($next_token[0] ?? null) { + case '<': + $new_leaf = new ParseTree\GenericTree( + $type_token[0], + $new_parent + ); + ++$this->t; + break; + + case '{': + $new_leaf = new ParseTree\KeyedArrayTree( + $type_token[0], + $new_parent + ); + ++$this->t; + break; + + case '(': + if (in_array( + $type_token[0], + ['callable', 'pure-callable', 'Closure', '\Closure', 'pure-Closure'], + true + )) { + $new_leaf = new ParseTree\CallableTree( + $type_token[0], + $new_parent + ); + } elseif ($type_token[0] !== 'array' + && $type_token[0][0] !== '\\' + && $this->current_leaf instanceof ParseTree\Root + ) { + $new_leaf = new ParseTree\MethodTree( + $type_token[0], + $new_parent + ); + } else { + throw new TypeParseTreeException( + 'Bracket must be preceded by “Closure”, “callable”, "pure-callable" or a valid @method name' + ); + } + + ++$this->t; + break; + + case '::': + $nexter_token = $this->t + 2 < $this->type_token_count ? $this->type_tokens[$this->t + 2] : null; + + if ($this->current_leaf instanceof ParseTree\KeyedArrayTree) { + throw new TypeParseTreeException( + 'Unexpected :: in array key' + ); + } + + if (!$nexter_token + || (!preg_match('/^([a-zA-Z_][a-zA-Z_0-9]*\*?|\*)$/', $nexter_token[0]) + && strtolower($nexter_token[0]) !== 'class') + ) { + throw new TypeParseTreeException( + 'Invalid class constant ' . ($nexter_token[0] ?? '') + ); + } + + $new_leaf = new ParseTree\Value( + $type_token[0] . '::' . $nexter_token[0], + $type_token[1], + $type_token[1] + 2 + strlen($nexter_token[0]), + $new_parent + ); + + $this->t += 2; + + break; + + default: + if ($type_token[0] === '$this') { + $type_token[0] = 'static'; + } + + $new_leaf = new ParseTree\Value( + $type_token[0], + $type_token[1], + $type_token[1] + strlen($type_token[0]), + $new_parent + ); + break; + } + + if ($this->current_leaf instanceof ParseTree\Root) { + $this->current_leaf = $this->parse_tree = $new_leaf; + return; + } + + if ($new_leaf->parent) { + $new_leaf->parent->children[] = $new_leaf; + } + + $this->current_leaf = $new_leaf; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/SimpleAssertionReconciler.php new file mode 100644 index 0000000000000000000000000000000000000000..b78f0d36a3dda4c4c871ef1b5676ce6259e61c91 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -0,0 +1,2308 @@ +hasMixed()) { + return $existing_var_type; + } + + if ($assertion === 'isset') { + $existing_var_type->removeType('null'); + + if (empty($existing_var_type->getAtomicTypes())) { + $failed_reconciliation = 2; + + if ($code_location) { + if (IssueBuffer::accepts( + new TypeDoesNotContainType( + 'Cannot resolve types for ' . $key . ' on null var', + $code_location, + null + ), + $suppressed_issues + )) { + // fall through + } + } + + return Type::getEmpty(); + } + + if ($existing_var_type->hasType('empty')) { + $existing_var_type->removeType('empty'); + $existing_var_type->addType(new TMixed($inside_loop)); + } + + $existing_var_type->possibly_undefined = false; + $existing_var_type->possibly_undefined_from_try = false; + + return $existing_var_type; + } + + if ($assertion === 'array-key-exists') { + $existing_var_type->possibly_undefined = false; + + return $existing_var_type; + } + + if (substr($assertion, 0, 9) === 'in-array-') { + return self::reconcileInArray( + $codebase, + $existing_var_type, + substr($assertion, 9) + ); + } + + if (substr($assertion, 0, 14) === 'has-array-key-') { + return self::reconcileHasArrayKey( + $existing_var_type, + substr($assertion, 14) + ); + } + + if ($assertion === 'falsy' || $assertion === 'empty') { + return self::reconcileFalsyOrEmpty( + $assertion, + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation + ); + } + + if ($assertion === 'object') { + return self::reconcileObject( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'resource') { + return self::reconcileResource( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'callable') { + return self::reconcileCallable( + $codebase, + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'iterable') { + return self::reconcileIterable( + $codebase, + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'array') { + return self::reconcileArray( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'list') { + return self::reconcileList( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + false + ); + } + + if ($assertion === 'non-empty-list') { + return self::reconcileList( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + true + ); + } + + if ($assertion === 'Traversable') { + return self::reconcileTraversable( + $codebase, + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'countable') { + return self::reconcileCountable( + $codebase, + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'string-array-access') { + return self::reconcileStringArrayAccess( + $codebase, + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $inside_loop + ); + } + + if ($assertion === 'int-or-string-array-access') { + return self::reconcileIntArrayAccess( + $codebase, + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $inside_loop + ); + } + + if ($assertion === 'numeric') { + return self::reconcileNumeric( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'scalar') { + return self::reconcileScalar( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'bool') { + return self::reconcileBool( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'string') { + return self::reconcileString( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + $is_strict_equality + ); + } + + if ($assertion === 'int') { + return self::reconcileInt( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + $is_strict_equality + ); + } + + if ($assertion === 'positive-numeric') { + return self::reconcilePositiveNumeric( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'float' + && $existing_var_type->from_calculation + && $existing_var_type->hasInt() + ) { + return Type::getFloat(); + } + + if ($assertion === 'non-empty-countable') { + return self::reconcileNonEmptyCountable( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + null + ); + } + + if (substr($assertion, 0, 13) === 'has-at-least-') { + return self::reconcileNonEmptyCountable( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + (int) substr($assertion, 13) + ); + } + + if (substr($assertion, 0, 12) === 'has-exactly-') { + /** @psalm-suppress ArgumentTypeCoercion */ + return self::reconcileExactlyCountable( + $existing_var_type, + (int) substr($assertion, 12) + ); + } + + if (substr($assertion, 0, 10) === 'hasmethod-') { + return self::reconcileHasMethod( + $codebase, + substr($assertion, 10), + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation + ); + } + + if ($existing_var_type->isSingle() + && $existing_var_type->hasTemplate() + && strpos($assertion, '-') === false + && strpos($assertion, '(') === false + ) { + foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof TTemplateParam) { + if ($atomic_type->as->hasMixed() + || $atomic_type->as->hasObject() + ) { + $atomic_type->as = Type::parseString($assertion); + + return $existing_var_type; + } + } + } + } + + return null; + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileNonEmptyCountable( + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality, + ?int $min_count + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + + if ($existing_var_type->hasType('array')) { + $array_atomic_type = $existing_var_type->getAtomicTypes()['array']; + $did_remove_type = false; + + if ($array_atomic_type instanceof TArray) { + if (!$array_atomic_type instanceof Type\Atomic\TNonEmptyArray + || ($array_atomic_type->count < $min_count) + ) { + if ($array_atomic_type->getId() === 'array') { + $existing_var_type->removeType('array'); + } else { + $non_empty_array = new Type\Atomic\TNonEmptyArray( + $array_atomic_type->type_params + ); + + if ($min_count) { + $non_empty_array->count = $min_count; + } + + $existing_var_type->addType($non_empty_array); + } + + $did_remove_type = true; + } + } elseif ($array_atomic_type instanceof TList) { + if (!$array_atomic_type instanceof Type\Atomic\TNonEmptyList + || ($array_atomic_type->count < $min_count) + ) { + $non_empty_list = new Type\Atomic\TNonEmptyList( + $array_atomic_type->type_param + ); + + if ($min_count) { + $non_empty_list->count = $min_count; + } + + $did_remove_type = true; + $existing_var_type->addType($non_empty_list); + } + } elseif ($array_atomic_type instanceof Type\Atomic\TKeyedArray) { + foreach ($array_atomic_type->properties as $property_type) { + if ($property_type->possibly_undefined) { + $did_remove_type = true; + break; + } + } + } + + if (!$is_equality + && !$existing_var_type->hasMixed() + && (!$did_remove_type || empty($existing_var_type->getAtomicTypes())) + ) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'non-empty-countable', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + } + + return $existing_var_type; + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + * @param positive-int $count + */ + private static function reconcileExactlyCountable( + Union $existing_var_type, + int $count + ) : Union { + if ($existing_var_type->hasType('array')) { + $array_atomic_type = $existing_var_type->getAtomicTypes()['array']; + + if ($array_atomic_type instanceof TArray) { + $non_empty_array = new Type\Atomic\TNonEmptyArray( + $array_atomic_type->type_params + ); + + $non_empty_array->count = $count; + + $existing_var_type->addType( + $non_empty_array + ); + } elseif ($array_atomic_type instanceof TList) { + $non_empty_list = new Type\Atomic\TNonEmptyList( + $array_atomic_type->type_param + ); + + $non_empty_list->count = $count; + + $existing_var_type->addType( + $non_empty_list + ); + } + } + + return $existing_var_type; + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcilePositiveNumeric( + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + + $did_remove_type = false; + + $positive_types = []; + + foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TLiteralInt) { + if ($atomic_type->value < 1) { + $did_remove_type = true; + } else { + $positive_types[] = $atomic_type; + } + } elseif ($atomic_type instanceof Type\Atomic\TPositiveInt) { + $positive_types[] = $atomic_type; + } elseif (get_class($atomic_type) === TInt::class) { + $positive_types[] = new Type\Atomic\TPositiveInt(); + $did_remove_type = true; + } else { + // for now allow this check everywhere else + if (!$atomic_type instanceof Type\Atomic\TNull + && !$atomic_type instanceof TFalse + ) { + $positive_types[] = $atomic_type; + } + + $did_remove_type = true; + } + } + + if (!$is_equality + && !$existing_var_type->hasMixed() + && (!$did_remove_type || !$positive_types) + ) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'positive-numeric', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($positive_types) { + return new Type\Union($positive_types); + } + + $failed_reconciliation = 2; + + return Type::getEmpty(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileHasMethod( + Codebase $codebase, + string $method_name, + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + $object_types = []; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $type) { + if ($type instanceof TNamedObject + && $codebase->classOrInterfaceExists($type->value) + ) { + $object_types[] = $type; + + if (!$codebase->methodExists($type->value . '::' . $method_name)) { + $match_found = false; + + if ($type->extra_types) { + foreach ($type->extra_types as $extra_type) { + if ($extra_type instanceof TNamedObject + && $codebase->classOrInterfaceExists($extra_type->value) + && $codebase->methodExists($extra_type->value . '::' . $method_name) + ) { + $match_found = true; + } elseif ($extra_type instanceof Atomic\TObjectWithProperties) { + $match_found = true; + + if (!isset($extra_type->methods[$method_name])) { + $extra_type->methods[$method_name] = 'object::' . $method_name; + $did_remove_type = true; + } + } + } + } + + if (!$match_found) { + $obj = new Atomic\TObjectWithProperties( + [], + [$method_name => $type->value . '::' . $method_name] + ); + $type->extra_types[$obj->getKey()] = $obj; + $did_remove_type = true; + } + } + } elseif ($type instanceof Atomic\TObjectWithProperties) { + $object_types[] = $type; + + if (!isset($type->methods[$method_name])) { + $type->methods[$method_name] = 'object::' . $method_name; + $did_remove_type = true; + } + } elseif ($type instanceof TObject || $type instanceof TMixed) { + $object_types[] = new Atomic\TObjectWithProperties( + [], + [$method_name => 'object::' . $method_name] + ); + $did_remove_type = true; + } elseif ($type instanceof TString) { + // we don’t know + $object_types[] = $type; + $did_remove_type = true; + } elseif ($type instanceof TTemplateParam) { + $object_types[] = $type; + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if (!$object_types || !$did_remove_type) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'object with method ' . $method_name, + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($object_types) { + return new Type\Union($object_types); + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileString( + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality, + bool $is_strict_equality + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + if ($existing_var_type->hasMixed()) { + if ($is_equality && !$is_strict_equality) { + return $existing_var_type; + } + + return Type::getString(); + } + + $string_types = []; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $type) { + if ($type instanceof TString) { + $string_types[] = $type; + + if (get_class($type) === TString::class) { + $type->from_docblock = false; + } + } elseif ($type instanceof TCallable) { + $string_types[] = new Type\Atomic\TCallableString; + $did_remove_type = true; + } elseif ($type instanceof TNumeric) { + $string_types[] = new TNumericString; + $did_remove_type = true; + } elseif ($type instanceof TScalar || $type instanceof TArrayKey) { + $string_types[] = new TString; + $did_remove_type = true; + } elseif ($type instanceof TTemplateParam) { + if ($type->as->hasString() || $type->as->hasMixed()) { + $type = clone $type; + + $type->as = self::reconcileString( + $type->as, + null, + false, + null, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + $is_strict_equality + ); + + $string_types[] = $type; + } + + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$did_remove_type || !$string_types) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'string', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($string_types) { + return new Type\Union($string_types); + } + + $failed_reconciliation = 2; + + return $existing_var_type->from_docblock + ? Type::getMixed() + : Type::getEmpty(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileInt( + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality, + bool $is_strict_equality + ) : Union { + if ($existing_var_type->hasMixed()) { + if ($is_equality && !$is_strict_equality) { + return $existing_var_type; + } + + return Type::getInt(); + } + + $old_var_type_string = $existing_var_type->getId(); + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + $int_types = []; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $type) { + if ($type instanceof TInt) { + $int_types[] = $type; + + if (get_class($type) === TInt::class) { + $type->from_docblock = false; + } + + if ($existing_var_type->from_calculation) { + $did_remove_type = true; + } + } elseif ($type instanceof TNumeric) { + $int_types[] = new TInt; + $did_remove_type = true; + } elseif ($type instanceof TScalar || $type instanceof TArrayKey) { + $int_types[] = new TInt; + $did_remove_type = true; + } elseif ($type instanceof TTemplateParam) { + if ($type->as->hasInt() || $type->as->hasMixed()) { + $type = clone $type; + + $type->as = self::reconcileInt( + $type->as, + null, + false, + null, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + $is_strict_equality + ); + + $int_types[] = $type; + } + + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$did_remove_type || !$int_types) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'int', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($int_types) { + return new Type\Union($int_types); + } + + $failed_reconciliation = 2; + + return $existing_var_type->from_docblock + ? Type::getMixed() + : Type::getEmpty(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileBool( + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Union { + if ($existing_var_type->hasMixed()) { + return Type::getBool(); + } + + $bool_types = []; + $did_remove_type = false; + + $old_var_type_string = $existing_var_type->getId(); + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + foreach ($existing_var_atomic_types as $type) { + if ($type instanceof TBool) { + $bool_types[] = $type; + $type->from_docblock = false; + } elseif ($type instanceof TScalar) { + $bool_types[] = new TBool; + $did_remove_type = true; + } elseif ($type instanceof TTemplateParam) { + if ($type->as->hasBool() || $type->as->hasMixed()) { + $type = clone $type; + + $type->as = self::reconcileBool( + $type->as, + null, + false, + null, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + + $bool_types[] = $type; + } + + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$did_remove_type || !$bool_types) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'bool', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($bool_types) { + return new Type\Union($bool_types); + } + + $failed_reconciliation = 2; + + return $existing_var_type->from_docblock + ? Type::getMixed() + : Type::getEmpty(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileScalar( + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Union { + if ($existing_var_type->hasMixed()) { + return Type::getScalar(); + } + + $scalar_types = []; + $did_remove_type = false; + + $old_var_type_string = $existing_var_type->getId(); + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + foreach ($existing_var_atomic_types as $type) { + if ($type instanceof Scalar) { + $scalar_types[] = $type; + } elseif ($type instanceof TTemplateParam) { + if ($type->as->hasScalar() || $type->as->hasMixed()) { + $type = clone $type; + + $type->as = self::reconcileScalar( + $type->as, + null, + false, + null, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + + $scalar_types[] = $type; + } + + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$did_remove_type || !$scalar_types) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'scalar', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($scalar_types) { + return new Type\Union($scalar_types); + } + + $failed_reconciliation = 2; + + return $existing_var_type->from_docblock + ? Type::getMixed() + : Type::getEmpty(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileNumeric( + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Union { + if ($existing_var_type->hasMixed()) { + return Type::getNumeric(); + } + + $old_var_type_string = $existing_var_type->getId(); + + $numeric_types = []; + $did_remove_type = false; + + if ($existing_var_type->hasString()) { + $did_remove_type = true; + $existing_var_type->removeType('string'); + $existing_var_type->addType(new TNumericString); + } + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TNumeric || $type instanceof TNumericString) { + // this is a workaround for a possible issue running + // is_numeric($a) && is_string($a) + $did_remove_type = true; + $numeric_types[] = $type; + } elseif ($type->isNumericType()) { + $numeric_types[] = $type; + } elseif ($type instanceof TScalar) { + $did_remove_type = true; + $numeric_types[] = new TNumeric(); + } elseif ($type instanceof TArrayKey) { + $did_remove_type = true; + $numeric_types[] = new TInt(); + $numeric_types[] = new TNumericString(); + } elseif ($type instanceof TTemplateParam) { + if ($type->as->hasNumeric() || $type->as->hasMixed()) { + $type = clone $type; + + $type->as = self::reconcileNumeric( + $type->as, + null, + false, + null, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + + $numeric_types[] = $type; + } + + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$did_remove_type || !$numeric_types) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'numeric', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($numeric_types) { + return new Type\Union($numeric_types); + } + + $failed_reconciliation = 2; + + return $existing_var_type->from_docblock + ? Type::getMixed() + : Type::getEmpty(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileObject( + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Union { + if ($existing_var_type->hasMixed()) { + return Type::getObject(); + } + + $old_var_type_string = $existing_var_type->getId(); + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + $object_types = []; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $type) { + if ($type->isObjectType()) { + $object_types[] = $type; + } elseif ($type instanceof TCallable) { + $object_types[] = new Type\Atomic\TCallableObject(); + $did_remove_type = true; + } elseif ($type instanceof TTemplateParam + && $type->as->isMixed() + ) { + $type = clone $type; + $type->as = Type::getObject(); + $object_types[] = $type; + $did_remove_type = true; + } elseif ($type instanceof TTemplateParam) { + if ($type->as->hasObject() || $type->as->hasMixed()) { + $type = clone $type; + + $type->as = self::reconcileObject( + $type->as, + null, + false, + null, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + + $object_types[] = $type; + } + + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$object_types || !$did_remove_type) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'object', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($object_types) { + return new Type\Union($object_types); + } + + $failed_reconciliation = 2; + + return $existing_var_type->from_docblock + ? Type::getMixed() + : Type::getEmpty(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileResource( + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Union { + if ($existing_var_type->hasMixed()) { + return Type::getResource(); + } + + $old_var_type_string = $existing_var_type->getId(); + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + $resource_types = []; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $type) { + if ($type instanceof TResource) { + $resource_types[] = $type; + } else { + $did_remove_type = true; + } + } + + if ((!$resource_types || !$did_remove_type) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'resource', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($resource_types) { + return new Type\Union($resource_types); + } + + $failed_reconciliation = 2; + + return $existing_var_type->from_docblock + ? Type::getMixed() + : Type::getEmpty(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileCountable( + Codebase $codebase, + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + + if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) { + return new Type\Union([ + new Type\Atomic\TArray([Type::getArrayKey(), Type::getMixed()]), + new Type\Atomic\TNamedObject('Countable'), + ]); + } + + $iterable_types = []; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $type) { + if ($type->isCountable($codebase)) { + $iterable_types[] = $type; + } elseif ($type instanceof TObject) { + $iterable_types[] = new TNamedObject('Countable'); + $did_remove_type = true; + } elseif ($type instanceof TNamedObject || $type instanceof Type\Atomic\TIterable) { + $countable = new TNamedObject('Countable'); + $type->extra_types[$countable->getKey()] = $countable; + $iterable_types[] = $type; + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$iterable_types || !$did_remove_type) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'countable', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($iterable_types) { + return new Type\Union($iterable_types); + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileIterable( + Codebase $codebase, + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) { + return new Type\Union([new Type\Atomic\TIterable]); + } + + $iterable_types = []; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $type) { + if ($type->isIterable($codebase)) { + $iterable_types[] = $type; + } elseif ($type instanceof TObject) { + $iterable_types[] = new Type\Atomic\TNamedObject('Traversable'); + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$iterable_types || !$did_remove_type) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'iterable', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($iterable_types) { + return new Type\Union($iterable_types); + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileInArray( + Codebase $codebase, + Union $existing_var_type, + string $assertion + ) : Union { + if (strpos($assertion, '::')) { + [$fq_classlike_name, $const_name] = explode('::', $assertion); + + $class_constant_type = $codebase->classlikes->getClassConstantType( + $fq_classlike_name, + $const_name, + \ReflectionProperty::IS_PRIVATE + ); + + if ($class_constant_type) { + foreach ($class_constant_type->getAtomicTypes() as $const_type_atomic) { + if ($const_type_atomic instanceof Type\Atomic\TKeyedArray + || $const_type_atomic instanceof Type\Atomic\TArray + ) { + if ($const_type_atomic instanceof Type\Atomic\TKeyedArray) { + $const_type_atomic = $const_type_atomic->getGenericArrayType(); + } + + if (UnionTypeComparator::isContainedBy( + $codebase, + $const_type_atomic->type_params[0], + $existing_var_type + )) { + return clone $const_type_atomic->type_params[0]; + } + } + } + } + } + + $existing_var_type->removeType('null'); + + return $existing_var_type; + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileHasArrayKey( + Union $existing_var_type, + string $assertion + ) : Union { + foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof Type\Atomic\TKeyedArray) { + $is_class_string = false; + + if (strpos($assertion, '::class')) { + [$assertion] = explode('::', $assertion); + $is_class_string = true; + } + + if (isset($atomic_type->properties[$assertion])) { + $atomic_type->properties[$assertion]->possibly_undefined = false; + } else { + $atomic_type->properties[$assertion] = Type::getMixed(); + + if ($is_class_string) { + $atomic_type->class_strings[$assertion] = true; + } + } + } + } + + return $existing_var_type; + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileTraversable( + Codebase $codebase, + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) { + return new Type\Union([new Type\Atomic\TNamedObject('Traversable')]); + } + + $traversable_types = []; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $type) { + if ($type->hasTraversableInterface($codebase)) { + $traversable_types[] = $type; + } elseif ($type instanceof Atomic\TIterable) { + $clone_type = clone $type; + $traversable_types[] = new Atomic\TGenericObject('Traversable', $clone_type->type_params); + $did_remove_type = true; + } elseif ($type instanceof TObject) { + $traversable_types[] = new TNamedObject('Traversable'); + $did_remove_type = true; + } elseif ($type instanceof TNamedObject) { + $traversable = new TNamedObject('Traversable'); + $type->extra_types[$traversable->getKey()] = $traversable; + $traversable_types[] = $type; + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$traversable_types || !$did_remove_type) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'Traversable', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($traversable_types) { + return new Type\Union($traversable_types); + } + + $failed_reconciliation = 2; + + return $existing_var_type->from_docblock + ? Type::getMixed() + : Type::getEmpty(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileArray( + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) { + return Type::getArray(); + } + + $array_types = []; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $type) { + if ($type instanceof TArray || $type instanceof TKeyedArray || $type instanceof TList) { + $array_types[] = $type; + } elseif ($type instanceof TCallable) { + $array_types[] = new TCallableKeyedArray([ + new Union([new TClassString, new TObject]), + Type::getString() + ]); + + $did_remove_type = true; + } elseif ($type instanceof Atomic\TIterable) { + $clone_type = clone $type; + + self::refineArrayKey($clone_type->type_params[0]); + + $array_types[] = new TArray($clone_type->type_params); + + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$array_types || !$did_remove_type) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'array', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + } + + if ($array_types) { + return \Psalm\Internal\Type\TypeCombination::combineTypes($array_types); + } + + $failed_reconciliation = 2; + + return $existing_var_type->from_docblock + ? Type::getMixed() + : Type::getEmpty(); + } + + private static function refineArrayKey(Union $key_type) : void + { + foreach ($key_type->getAtomicTypes() as $key => $cat) { + if ($cat instanceof TTemplateParam) { + self::refineArrayKey($cat->as); + $key_type->bustCache(); + } elseif ($cat instanceof TScalar || $cat instanceof TMixed) { + $key_type->removeType($key); + $key_type->addType(new Type\Atomic\TArrayKey()); + } elseif (!$cat instanceof TString && !$cat instanceof TInt) { + $key_type->removeType($key); + $key_type->addType(new Type\Atomic\TArrayKey()); + } + } + + if (!$key_type->getAtomicTypes()) { + // this should ideally prompt some sort of error + $key_type->addType(new Type\Atomic\TArrayKey()); + } + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileList( + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality, + bool $is_non_empty + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) { + return $is_non_empty ? Type::getNonEmptyList() : Type::getList(); + } + + $array_types = []; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $type) { + if ($type instanceof TList + || ($type instanceof TKeyedArray && $type->is_list) + ) { + if ($is_non_empty && $type instanceof TList && !$type instanceof TNonEmptyList) { + $array_types[] = new TNonEmptyList($type->type_param); + $did_remove_type = true; + } else { + $array_types[] = $type; + } + } elseif ($type instanceof TArray || $type instanceof TKeyedArray) { + if ($type instanceof TKeyedArray) { + $type = $type->getGenericArrayType(); + } + + if ($type->type_params[0]->hasArrayKey() + || $type->type_params[0]->hasInt() + ) { + if ($type instanceof TNonEmptyArray) { + $array_types[] = new TNonEmptyList($type->type_params[1]); + } else { + $array_types[] = new TList($type->type_params[1]); + } + } + + $did_remove_type = true; + } elseif ($type instanceof TCallable) { + $array_types[] = new TCallableKeyedArray([ + new Union([new TClassString, new TObject]), + Type::getString() + ]); + + $did_remove_type = true; + } elseif ($type instanceof Atomic\TIterable) { + $clone_type = clone $type; + $array_types[] = new TList($clone_type->type_params[1]); + + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$array_types || !$did_remove_type) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'array', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + } + + if ($array_types) { + return \Psalm\Internal\Type\TypeCombination::combineTypes($array_types); + } + + $failed_reconciliation = 2; + + return $existing_var_type->from_docblock + ? Type::getMixed() + : Type::getEmpty(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileStringArrayAccess( + Codebase $codebase, + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $inside_loop + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) { + return new Union([ + new Atomic\TNonEmptyArray([Type::getArrayKey(), Type::getMixed()]), + new TNamedObject('ArrayAccess'), + ]); + } + + $array_types = []; + + foreach ($existing_var_atomic_types as $type) { + if ($type->isArrayAccessibleWithStringKey($codebase)) { + if (get_class($type) === TArray::class) { + $array_types[] = new Atomic\TNonEmptyArray($type->type_params); + } elseif (get_class($type) === TList::class) { + $array_types[] = new Atomic\TNonEmptyList($type->type_param); + } else { + $array_types[] = $type; + } + } elseif ($type instanceof TTemplateParam) { + $array_types[] = $type; + } + } + + if (!$array_types) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'string-array-access', + true, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($array_types) { + return new Type\Union($array_types); + } + + $failed_reconciliation = 2; + + return Type::getMixed($inside_loop); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileIntArrayAccess( + Codebase $codebase, + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $inside_loop + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + if ($existing_var_type->hasMixed()) { + return Type::getMixed(); + } + + $array_types = []; + + foreach ($existing_var_atomic_types as $type) { + if ($type->isArrayAccessibleWithIntOrStringKey($codebase)) { + if (get_class($type) === TArray::class) { + $array_types[] = new Atomic\TNonEmptyArray($type->type_params); + } else { + $array_types[] = $type; + } + } elseif ($type instanceof TTemplateParam) { + $array_types[] = $type; + } + } + + if (!$array_types) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'int-or-string-array-access', + true, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($array_types) { + return \Psalm\Internal\Type\TypeCombination::combineTypes($array_types); + } + + $failed_reconciliation = 2; + + return Type::getMixed($inside_loop); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileCallable( + Codebase $codebase, + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Union { + if ($existing_var_type->hasMixed()) { + return Type::parseString('callable'); + } + + $old_var_type_string = $existing_var_type->getId(); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + $callable_types = []; + $did_remove_type = false; + + foreach ($existing_var_atomic_types as $type) { + if ($type->isCallableType()) { + $callable_types[] = $type; + } elseif ($type instanceof TObject) { + $callable_types[] = new Type\Atomic\TCallableObject(); + $did_remove_type = true; + } elseif ($type instanceof TNamedObject + && $codebase->classExists($type->value) + && $codebase->methodExists($type->value . '::__invoke') + ) { + $callable_types[] = $type; + } elseif (get_class($type) === TString::class + || get_class($type) === Type\Atomic\TNonEmptyString::class + ) { + $callable_types[] = new Type\Atomic\TCallableString(); + $did_remove_type = true; + } elseif (get_class($type) === Type\Atomic\TLiteralString::class + && \Psalm\Internal\Codebase\InternalCallMapHandler::inCallMap($type->value) + ) { + $callable_types[] = $type; + $did_remove_type = true; + } elseif ($type instanceof TArray) { + $type = clone $type; + $type = new TCallableArray($type->type_params); + $callable_types[] = $type; + $did_remove_type = true; + } elseif ($type instanceof TList) { + $type = clone $type; + $type = new TCallableList($type->type_param); + $callable_types[] = $type; + $did_remove_type = true; + } elseif ($type instanceof TKeyedArray) { + $type = clone $type; + $type = new TCallableKeyedArray($type->properties); + $callable_types[] = $type; + $did_remove_type = true; + } elseif ($type instanceof TTemplateParam) { + if ($type->as->isMixed()) { + $type = clone $type; + $type->as = new Type\Union([new Type\Atomic\TCallable]); + } + $callable_types[] = $type; + $did_remove_type = true; + } else { + $did_remove_type = true; + } + } + + if ((!$callable_types || !$did_remove_type) && !$is_equality) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + 'callable', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($callable_types) { + return \Psalm\Internal\Type\TypeCombination::combineTypes($callable_types); + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileFalsyOrEmpty( + string $assertion, + Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation + ) : Union { + $old_var_type_string = $existing_var_type->getId(); + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + $did_remove_type = $existing_var_type->hasDefinitelyNumericType(false) + || $existing_var_type->hasType('iterable'); + + if ($existing_var_type->hasMixed()) { + if ($existing_var_type->isMixed() + && $existing_var_atomic_types['mixed'] instanceof Type\Atomic\TNonEmptyMixed + ) { + if ($code_location + && $key + && IssueBuffer::accepts( + new ParadoxicalCondition( + 'Found a paradox when evaluating ' . $key + . ' of type ' . $existing_var_type->getId() + . ' and trying to reconcile it with a ' . $assertion . ' assertion', + $code_location + ), + $suppressed_issues + ) + ) { + // fall through + } + + return Type::getMixed(); + } + + if (!$existing_var_atomic_types['mixed'] instanceof Type\Atomic\TEmptyMixed) { + $did_remove_type = true; + $existing_var_type->removeType('mixed'); + + if (!$existing_var_atomic_types['mixed'] instanceof Type\Atomic\TNonEmptyMixed) { + $existing_var_type->addType(new Type\Atomic\TEmptyMixed); + } + } elseif ($existing_var_type->isMixed()) { + if ($code_location + && $key + && IssueBuffer::accepts( + new RedundantCondition( + 'Found a redundant condition when evaluating ' . $key + . ' of type ' . $existing_var_type->getId() + . ' and trying to reconcile it with a ' . $assertion . ' assertion', + $code_location, + $existing_var_type->getId() . ' ' . $assertion + ), + $suppressed_issues + ) + ) { + // fall through + } + } + + if ($existing_var_type->isMixed()) { + return $existing_var_type; + } + } + + if ($existing_var_type->hasScalar()) { + if ($existing_var_type->isSingle() + && $existing_var_atomic_types['scalar'] instanceof Type\Atomic\TNonEmptyScalar + ) { + if ($code_location + && $key + && IssueBuffer::accepts( + new ParadoxicalCondition( + 'Found a paradox when evaluating ' . $key + . ' of type ' . $existing_var_type->getId() + . ' and trying to reconcile it with a ' . $assertion . ' assertion', + $code_location + ), + $suppressed_issues + ) + ) { + // fall through + } + + return Type::getScalar(); + } + + if (!$existing_var_atomic_types['scalar'] instanceof Type\Atomic\TEmptyScalar) { + $did_remove_type = true; + $existing_var_type->removeType('scalar'); + + if (!$existing_var_atomic_types['scalar'] instanceof Type\Atomic\TNonEmptyScalar) { + $existing_var_type->addType(new Type\Atomic\TEmptyScalar); + } + } elseif ($existing_var_type->isSingle()) { + if ($code_location + && $key + && IssueBuffer::accepts( + new RedundantCondition( + 'Found a redundant condition when evaluating ' . $key + . ' of type ' . $existing_var_type->getId() + . ' and trying to reconcile it with a ' . $assertion . ' assertion', + $code_location, + $existing_var_type->getId() . ' ' . $assertion + ), + $suppressed_issues + ) + ) { + // fall through + } + } + + if ($existing_var_type->isSingle()) { + return $existing_var_type; + } + } + + if ($existing_var_type->hasType('bool')) { + $did_remove_type = true; + $existing_var_type->removeType('bool'); + $existing_var_type->addType(new TFalse); + } + + if ($existing_var_type->hasType('true')) { + $did_remove_type = true; + $existing_var_type->removeType('true'); + } + + if ($existing_var_type->hasString()) { + $existing_string_types = $existing_var_type->getLiteralStrings(); + + if ($existing_string_types) { + foreach ($existing_string_types as $string_key => $literal_type) { + if ($literal_type->value) { + $existing_var_type->removeType($string_key); + $did_remove_type = true; + } + } + } else { + $did_remove_type = true; + if ($existing_var_type->hasType('class-string')) { + $existing_var_type->removeType('class-string'); + } + + if ($existing_var_type->hasType('callable-string')) { + $existing_var_type->removeType('callable-string'); + } + + if ($existing_var_type->hasType('string')) { + $existing_var_type->removeType('string'); + + if (!$existing_var_atomic_types['string'] instanceof Type\Atomic\TNonEmptyString) { + $existing_var_type->addType(new Type\Atomic\TLiteralString('')); + $existing_var_type->addType(new Type\Atomic\TLiteralString('0')); + } + } + } + } + + if ($existing_var_type->hasInt()) { + $existing_int_types = $existing_var_type->getLiteralInts(); + + if ($existing_int_types) { + foreach ($existing_int_types as $int_key => $literal_type) { + if ($literal_type->value) { + $existing_var_type->removeType($int_key); + $did_remove_type = true; + } + } + } else { + $did_remove_type = true; + $existing_var_type->removeType('int'); + $existing_var_type->addType(new Type\Atomic\TLiteralInt(0)); + } + } + + if ($existing_var_type->hasFloat()) { + $existing_float_types = $existing_var_type->getLiteralFloats(); + + if ($existing_float_types) { + foreach ($existing_float_types as $float_key => $literal_type) { + if ($literal_type->value) { + $existing_var_type->removeType($float_key); + $did_remove_type = true; + } + } + } else { + $did_remove_type = true; + $existing_var_type->removeType('float'); + $existing_var_type->addType(new Type\Atomic\TLiteralFloat(0)); + } + } + + if ($existing_var_type->hasNumeric()) { + $existing_int_types = $existing_var_type->getLiteralInts(); + + if ($existing_int_types) { + foreach ($existing_int_types as $int_key => $literal_type) { + if ($literal_type->value) { + $existing_var_type->removeType($int_key); + } + } + } + + $existing_string_types = $existing_var_type->getLiteralStrings(); + + if ($existing_string_types) { + foreach ($existing_string_types as $string_key => $literal_type) { + if ($literal_type->value) { + $existing_var_type->removeType($string_key); + } + } + } + + $existing_float_types = $existing_var_type->getLiteralFloats(); + + if ($existing_float_types) { + foreach ($existing_float_types as $float_key => $literal_type) { + if ($literal_type->value) { + $existing_var_type->removeType($float_key); + } + } + } + + $did_remove_type = true; + $existing_var_type->removeType('numeric'); + $existing_var_type->addType(new Type\Atomic\TEmptyNumeric); + } + + if (isset($existing_var_atomic_types['array'])) { + $array_atomic_type = $existing_var_atomic_types['array']; + + if ($array_atomic_type instanceof Type\Atomic\TNonEmptyArray + || $array_atomic_type instanceof Type\Atomic\TNonEmptyList + || ($array_atomic_type instanceof Type\Atomic\TKeyedArray + && array_filter( + $array_atomic_type->properties, + function (Type\Union $t): bool { + return !$t->possibly_undefined; + } + )) + ) { + $did_remove_type = true; + + $existing_var_type->removeType('array'); + } elseif ($array_atomic_type->getId() !== 'array') { + $did_remove_type = true; + + $existing_var_type->addType(new TArray( + [ + new Type\Union([new TEmpty]), + new Type\Union([new TEmpty]), + ] + )); + } + } + + if (isset($existing_var_atomic_types['scalar']) + && $existing_var_atomic_types['scalar']->getId() !== 'empty-scalar' + ) { + $did_remove_type = true; + $existing_var_type->addType(new Type\Atomic\TEmptyScalar); + } + + foreach ($existing_var_atomic_types as $type_key => $type) { + if ($type instanceof TNamedObject + || $type instanceof TObject + || $type instanceof TResource + || $type instanceof TCallable + || $type instanceof TClassString + ) { + $did_remove_type = true; + + $existing_var_type->removeType($type_key); + } + + if ($type instanceof TTemplateParam) { + $did_remove_type = true; + } + } + + if ((!$did_remove_type || empty($existing_var_type->getAtomicTypes())) + && ($assertion !== 'empty' || !$existing_var_type->possibly_undefined) + ) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + $assertion, + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + + if ($existing_var_type->getAtomicTypes()) { + return $existing_var_type; + } + + $failed_reconciliation = 2; + + return $assertion === 'empty' && $existing_var_type->possibly_undefined + ? Type::getEmpty() + : Type::getMixed(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php new file mode 100644 index 0000000000000000000000000000000000000000..f29fdfac18129ebe909c364da25d34803e5421c1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -0,0 +1,1603 @@ +> $template_type_map + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + * + * @return Type\Union + */ + public static function reconcile( + string $assertion, + Type\Union $existing_var_type, + ?string $key = null, + bool $negated = false, + ?CodeLocation $code_location = null, + array $suppressed_issues = [], + int &$failed_reconciliation = 0, + bool $is_equality = false, + bool $is_strict_equality = false + ) : ?Type\Union { + if ($assertion === 'object' && !$existing_var_type->hasMixed()) { + return self::reconcileObject( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'scalar' && !$existing_var_type->hasMixed()) { + return self::reconcileScalar( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'resource' && !$existing_var_type->hasMixed()) { + return self::reconcileResource( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'bool' && !$existing_var_type->hasMixed()) { + return self::reconcileBool( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'numeric' && !$existing_var_type->hasMixed()) { + return self::reconcileNumeric( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'float' && !$existing_var_type->hasMixed()) { + return self::reconcileFloat( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'int' && !$existing_var_type->hasMixed()) { + return self::reconcileInt( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'string' && !$existing_var_type->hasMixed()) { + return self::reconcileString( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'array' && !$existing_var_type->hasMixed()) { + return self::reconcileArray( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'falsy' || $assertion === 'empty') { + return self::reconcileFalsyOrEmpty( + $assertion, + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + $is_strict_equality + ); + } + + if ($assertion === 'null' && !$existing_var_type->hasMixed()) { + return self::reconcileNull( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'false' && !$existing_var_type->hasMixed()) { + return self::reconcileFalse( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + } + + if ($assertion === 'non-empty-countable') { + return self::reconcileNonEmptyCountable( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + null + ); + } + + if ($assertion === 'callable') { + return self::reconcileCallable( + $existing_var_type + ); + } + + if (substr($assertion, 0, 13) === 'has-at-least-') { + return self::reconcileNonEmptyCountable( + $existing_var_type, + $key, + $negated, + $code_location, + $suppressed_issues, + $failed_reconciliation, + $is_equality, + (int) substr($assertion, 13) + ); + } + + if (substr($assertion, 0, 12) === 'has-exactly-') { + return $existing_var_type; + } + + return null; + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileCallable( + Type\Union $existing_var_type + ) : Type\Union { + foreach ($existing_var_type->getAtomicTypes() as $atomic_key => $type) { + if ($type instanceof Type\Atomic\TLiteralString + && \Psalm\Internal\Codebase\InternalCallMapHandler::inCallMap($type->value) + ) { + $existing_var_type->removeType($atomic_key); + } + + if ($type->isCallableType()) { + $existing_var_type->removeType($atomic_key); + } + } + + return $existing_var_type; + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileBool( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $non_bool_types = []; + $did_remove_type = false; + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TTemplateParam) { + if (!$type->as->hasBool()) { + $non_bool_types[] = $type; + } + + $did_remove_type = true; + } elseif (!$type instanceof TBool + || ($is_equality && get_class($type) === TBool::class) + ) { + if ($type instanceof TScalar) { + $did_remove_type = true; + $non_bool_types[] = new TString(); + $non_bool_types[] = new TInt(); + $non_bool_types[] = new TFloat(); + } else { + $non_bool_types[] = $type; + } + } else { + $did_remove_type = true; + } + } + + if (!$did_remove_type || !$non_bool_types) { + if ($key && $code_location && !$is_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!bool', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($non_bool_types) { + return new Type\Union($non_bool_types); + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileNonEmptyCountable( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality, + ?int $min_count + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + if (isset($existing_var_atomic_types['array'])) { + $array_atomic_type = $existing_var_atomic_types['array']; + $did_remove_type = false; + + if (($array_atomic_type instanceof Type\Atomic\TNonEmptyArray + || $array_atomic_type instanceof Type\Atomic\TNonEmptyList) + && ($min_count === null + || $array_atomic_type->count >= $min_count) + ) { + $did_remove_type = true; + + $existing_var_type->removeType('array'); + } elseif ($array_atomic_type->getId() !== 'array') { + $did_remove_type = true; + + if (!$min_count) { + $existing_var_type->addType(new TArray( + [ + new Type\Union([new TEmpty]), + new Type\Union([new TEmpty]), + ] + )); + } + } elseif ($array_atomic_type instanceof Type\Atomic\TKeyedArray) { + $did_remove_type = true; + + foreach ($array_atomic_type->properties as $property_type) { + if (!$property_type->possibly_undefined) { + $did_remove_type = false; + break; + } + } + } + + if (!$is_equality + && !$existing_var_type->hasMixed() + && (!$did_remove_type || empty($existing_var_type->getAtomicTypes())) + ) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!non-empty-countable', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + } + } + + return $existing_var_type; + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileNull( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $did_remove_type = false; + + if ($existing_var_type->hasType('null')) { + $did_remove_type = true; + $existing_var_type->removeType('null'); + } + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TTemplateParam) { + $type->as = self::reconcileNull( + $type->as, + null, + false, + null, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + + $did_remove_type = true; + $existing_var_type->bustCache(); + } + } + + if (!$did_remove_type || empty($existing_var_type->getAtomicTypes())) { + if ($key && $code_location && !$is_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!null', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($existing_var_type->getAtomicTypes()) { + return $existing_var_type; + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileFalse( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $did_remove_type = $existing_var_type->hasScalar(); + + if ($existing_var_type->hasType('false')) { + $did_remove_type = true; + $existing_var_type->removeType('false'); + } + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TTemplateParam) { + $type->as = self::reconcileFalse( + $type->as, + null, + false, + null, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + + $did_remove_type = true; + $existing_var_type->bustCache(); + } + } + + if (!$did_remove_type || empty($existing_var_type->getAtomicTypes())) { + if ($key && $code_location && !$is_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!false', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($existing_var_type->getAtomicTypes()) { + return $existing_var_type; + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileFalsyOrEmpty( + string $assertion, + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality, + bool $is_strict_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + $did_remove_type = $existing_var_type->hasDefinitelyNumericType(false) + || $existing_var_type->isEmpty() + || $existing_var_type->hasType('bool') + || $existing_var_type->possibly_undefined + || $existing_var_type->possibly_undefined_from_try + || $existing_var_type->hasType('iterable'); + + if ($is_strict_equality && $assertion === 'empty') { + $existing_var_type->removeType('null'); + $existing_var_type->removeType('false'); + + if ($existing_var_type->hasType('array') + && $existing_var_type->getAtomicTypes()['array']->getId() === 'array' + ) { + $existing_var_type->removeType('array'); + } + + if ($existing_var_type->hasMixed()) { + $existing_var_type->removeType('mixed'); + + if (!$existing_var_atomic_types['mixed'] instanceof Type\Atomic\TEmptyMixed) { + $existing_var_type->addType(new Type\Atomic\TNonEmptyMixed); + } + } + + if ($existing_var_type->hasScalar()) { + $existing_var_type->removeType('scalar'); + + if (!$existing_var_atomic_types['scalar'] instanceof Type\Atomic\TEmptyScalar) { + $existing_var_type->addType(new Type\Atomic\TNonEmptyScalar); + } + } + + if (isset($existing_var_atomic_types['string'])) { + $existing_var_type->removeType('string'); + + if ($existing_var_atomic_types['string'] instanceof Type\Atomic\TLowercaseString) { + $existing_var_type->addType(new Type\Atomic\TNonEmptyLowercaseString); + } else { + $existing_var_type->addType(new Type\Atomic\TNonEmptyString); + } + } + + self::removeFalsyNegatedLiteralTypes( + $existing_var_type, + $did_remove_type + ); + + $existing_var_type->possibly_undefined = false; + $existing_var_type->possibly_undefined_from_try = false; + + if ($existing_var_type->getAtomicTypes()) { + return $existing_var_type; + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + if ($existing_var_type->hasMixed()) { + if ($existing_var_type->isMixed() + && $existing_var_atomic_types['mixed'] instanceof Type\Atomic\TEmptyMixed + ) { + if ($code_location + && $key + && IssueBuffer::accepts( + new ParadoxicalCondition( + 'Found a paradox when evaluating ' . $key + . ' of type ' . $existing_var_type->getId() + . ' and trying to reconcile it with a non-' . $assertion . ' assertion', + $code_location + ), + $suppressed_issues + ) + ) { + // fall through + } + + return Type::getMixed(); + } + + if (!$existing_var_atomic_types['mixed'] instanceof Type\Atomic\TNonEmptyMixed) { + $did_remove_type = true; + $existing_var_type->removeType('mixed'); + + if (!$existing_var_atomic_types['mixed'] instanceof Type\Atomic\TEmptyMixed) { + $existing_var_type->addType(new Type\Atomic\TNonEmptyMixed); + } + } elseif ($existing_var_type->isMixed() && !$is_equality) { + if ($code_location + && $key + && IssueBuffer::accepts( + new RedundantCondition( + 'Found a redundant condition when evaluating ' . $key + . ' of type ' . $existing_var_type->getId() + . ' and trying to reconcile it with a non-' . $assertion . ' assertion', + $code_location, + $existing_var_type->getId() . ' ' . $assertion + ), + $suppressed_issues + ) + ) { + // fall through + } + } + + if ($existing_var_type->isMixed()) { + return $existing_var_type; + } + } + + if ($existing_var_type->hasScalar()) { + if (!$existing_var_atomic_types['scalar'] instanceof Type\Atomic\TNonEmptyScalar) { + $did_remove_type = true; + $existing_var_type->removeType('scalar'); + + if (!$existing_var_atomic_types['scalar'] instanceof Type\Atomic\TEmptyScalar) { + $existing_var_type->addType(new Type\Atomic\TNonEmptyScalar); + } + } elseif ($existing_var_type->isSingle() && !$is_equality) { + if ($code_location + && $key + && IssueBuffer::accepts( + new RedundantCondition( + 'Found a redundant condition when evaluating ' . $key + . ' of type ' . $existing_var_type->getId() + . ' and trying to reconcile it with a non-' . $assertion . ' assertion', + $code_location, + $existing_var_type->getId() . ' ' . $assertion + ), + $suppressed_issues + ) + ) { + // fall through + } + } + + if ($existing_var_type->isSingle()) { + return $existing_var_type; + } + } + + if (isset($existing_var_atomic_types['string'])) { + if (!$existing_var_atomic_types['string'] instanceof Type\Atomic\TNonEmptyString + && !$existing_var_atomic_types['string'] instanceof Type\Atomic\TClassString + && !$existing_var_atomic_types['string'] instanceof Type\Atomic\TDependentGetClass + ) { + $did_remove_type = true; + + $existing_var_type->removeType('string'); + + if ($existing_var_atomic_types['string'] instanceof Type\Atomic\TLowercaseString) { + $existing_var_type->addType(new Type\Atomic\TNonEmptyLowercaseString); + } else { + $existing_var_type->addType(new Type\Atomic\TNonEmptyString); + } + } elseif ($existing_var_type->isSingle() && !$is_equality) { + if ($code_location + && $key + && IssueBuffer::accepts( + new RedundantCondition( + 'Found a redundant condition when evaluating ' . $key + . ' of type ' . $existing_var_type->getId() + . ' and trying to reconcile it with a non-' . $assertion . ' assertion', + $code_location, + $existing_var_type->getId() . ' ' . $assertion + ), + $suppressed_issues + ) + ) { + // fall through + } + } + + if ($existing_var_type->isSingle()) { + return $existing_var_type; + } + } + + if ($existing_var_type->hasType('null')) { + $did_remove_type = true; + $existing_var_type->removeType('null'); + } + + if ($existing_var_type->hasType('false')) { + $did_remove_type = true; + $existing_var_type->removeType('false'); + } + + if ($existing_var_type->hasType('bool')) { + $did_remove_type = true; + $existing_var_type->removeType('bool'); + $existing_var_type->addType(new TTrue); + } + + foreach ($existing_var_atomic_types as $existing_var_atomic_type) { + if ($existing_var_atomic_type instanceof Type\Atomic\TTemplateParam) { + if (!$is_equality && !$existing_var_atomic_type->as->isMixed()) { + $template_did_fail = 0; + + $existing_var_atomic_type = clone $existing_var_atomic_type; + + $existing_var_atomic_type->as = self::reconcileFalsyOrEmpty( + $assertion, + $existing_var_atomic_type->as, + $key, + $negated, + $code_location, + $suppressed_issues, + $template_did_fail, + $is_equality, + $is_strict_equality + ); + + $did_remove_type = true; + + if (!$template_did_fail) { + $existing_var_type->addType($existing_var_atomic_type); + } + } + } + } + + self::removeFalsyNegatedLiteralTypes( + $existing_var_type, + $did_remove_type + ); + + $existing_var_type->possibly_undefined = false; + $existing_var_type->possibly_undefined_from_try = false; + + if ((!$did_remove_type || empty($existing_var_type->getAtomicTypes())) && !$existing_var_type->hasTemplate()) { + if ($key && $code_location && !$is_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!' . $assertion, + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($existing_var_type->getAtomicTypes()) { + return $existing_var_type; + } + + $failed_reconciliation = 2; + + return Type::getEmpty(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileScalar( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $non_scalar_types = []; + $did_remove_type = false; + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TTemplateParam) { + if (!$is_equality && !$type->as->isMixed()) { + $template_did_fail = 0; + + $type = clone $type; + + $type->as = self::reconcileScalar( + $type->as, + null, + false, + null, + $suppressed_issues, + $template_did_fail, + $is_equality + ); + + $did_remove_type = true; + + if (!$template_did_fail) { + $non_scalar_types[] = $type; + } + } else { + $did_remove_type = true; + $non_scalar_types[] = $type; + } + } elseif (!($type instanceof Scalar)) { + $non_scalar_types[] = $type; + } else { + $did_remove_type = true; + + if ($is_equality) { + $non_scalar_types[] = $type; + } + } + } + + if (!$did_remove_type || !$non_scalar_types) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!scalar', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($non_scalar_types) { + $type = new Type\Union($non_scalar_types); + $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues; + $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues; + $type->from_docblock = $existing_var_type->from_docblock; + return $type; + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileObject( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $non_object_types = []; + $did_remove_type = false; + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TTemplateParam) { + if (!$is_equality && !$type->as->isMixed()) { + $template_did_fail = 0; + + $type = clone $type; + + $type->as = self::reconcileObject( + $type->as, + null, + false, + null, + $suppressed_issues, + $template_did_fail, + $is_equality + ); + + $did_remove_type = true; + + if (!$template_did_fail) { + $non_object_types[] = $type; + } + } else { + $did_remove_type = true; + $non_object_types[] = $type; + } + } elseif ($type instanceof TCallable) { + $non_object_types[] = new Atomic\TCallableArray([ + Type::getArrayKey(), + Type::getMixed() + ]); + $non_object_types[] = new Atomic\TCallableString(); + $did_remove_type = true; + } elseif (!$type->isObjectType()) { + $non_object_types[] = $type; + } else { + $did_remove_type = true; + + if ($is_equality) { + $non_object_types[] = $type; + } + } + } + + if (!$non_object_types || !$did_remove_type) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!object', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($non_object_types) { + $type = new Type\Union($non_object_types); + $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues; + $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues; + $type->from_docblock = $existing_var_type->from_docblock; + return $type; + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileNumeric( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $non_numeric_types = []; + $did_remove_type = $existing_var_type->hasString() + || $existing_var_type->hasScalar(); + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TTemplateParam) { + if (!$is_equality && !$type->as->isMixed()) { + $template_did_fail = 0; + + $type = clone $type; + + $type->as = self::reconcileNumeric( + $type->as, + null, + false, + null, + $suppressed_issues, + $template_did_fail, + $is_equality + ); + + $did_remove_type = true; + + if (!$template_did_fail) { + $non_numeric_types[] = $type; + } + } else { + $did_remove_type = true; + $non_numeric_types[] = $type; + } + } elseif ($type instanceof TArrayKey) { + $did_remove_type = true; + $non_numeric_types[] = new TString(); + } elseif (!$type->isNumericType()) { + $non_numeric_types[] = $type; + } else { + $did_remove_type = true; + + if ($is_equality) { + $non_numeric_types[] = $type; + } + } + } + + if (!$non_numeric_types || !$did_remove_type) { + if ($key && $code_location && !$is_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!numeric', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($non_numeric_types) { + $type = new Type\Union($non_numeric_types); + $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues; + $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues; + $type->from_docblock = $existing_var_type->from_docblock; + return $type; + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileInt( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $non_int_types = []; + $did_remove_type = false; + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TTemplateParam) { + if (!$is_equality && !$type->as->isMixed()) { + $template_did_fail = 0; + + $type = clone $type; + + $type->as = self::reconcileInt( + $type->as, + null, + false, + null, + $suppressed_issues, + $template_did_fail, + $is_equality + ); + + $did_remove_type = true; + + if (!$template_did_fail) { + $non_int_types[] = $type; + } + } else { + $did_remove_type = true; + $non_int_types[] = $type; + } + } elseif ($type instanceof TArrayKey) { + $did_remove_type = true; + $non_int_types[] = new TString(); + } elseif ($type instanceof TScalar) { + $did_remove_type = true; + $non_int_types[] = new TString(); + $non_int_types[] = new TFloat(); + $non_int_types[] = new TBool(); + } elseif ($type instanceof TInt) { + $did_remove_type = true; + + if ($is_equality) { + $non_int_types[] = $type; + } elseif ($existing_var_type->from_calculation) { + $non_int_types[] = new TFloat(); + } + } else { + $non_int_types[] = $type; + } + } + + if (!$non_int_types || !$did_remove_type) { + if ($key && $code_location && !$is_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!int', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($non_int_types) { + $type = new Type\Union($non_int_types); + $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues; + $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues; + $type->from_docblock = $existing_var_type->from_docblock; + return $type; + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileFloat( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $non_float_types = []; + $did_remove_type = false; + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TTemplateParam) { + if (!$is_equality && !$type->as->isMixed()) { + $template_did_fail = 0; + + $type = clone $type; + + $type->as = self::reconcileFloat( + $type->as, + null, + false, + null, + $suppressed_issues, + $template_did_fail, + $is_equality + ); + + $did_remove_type = true; + + if (!$template_did_fail) { + $non_float_types[] = $type; + } + } else { + $did_remove_type = true; + $non_float_types[] = $type; + } + } elseif ($type instanceof TScalar) { + $did_remove_type = true; + $non_float_types[] = new TString(); + $non_float_types[] = new TInt(); + $non_float_types[] = new TBool(); + } elseif ($type instanceof TFloat) { + $did_remove_type = true; + + if ($is_equality) { + $non_float_types[] = $type; + } + } else { + $non_float_types[] = $type; + } + } + + if (!$non_float_types || !$did_remove_type) { + if ($key && $code_location && !$is_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!float', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($non_float_types) { + $type = new Type\Union($non_float_types); + $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues; + $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues; + $type->from_docblock = $existing_var_type->from_docblock; + return $type; + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileString( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $non_string_types = []; + $did_remove_type = $existing_var_type->hasScalar(); + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TTemplateParam) { + if (!$is_equality && !$type->as->isMixed()) { + $template_did_fail = 0; + + $type = clone $type; + + $type->as = self::reconcileString( + $type->as, + null, + false, + null, + $suppressed_issues, + $template_did_fail, + $is_equality + ); + + $did_remove_type = true; + + if (!$template_did_fail) { + $non_string_types[] = $type; + } + } else { + $did_remove_type = true; + $non_string_types[] = $type; + } + } elseif ($type instanceof TArrayKey) { + $non_string_types[] = new TInt(); + $did_remove_type = true; + } elseif ($type instanceof TCallable) { + $non_string_types[] = new Atomic\TCallableArray([ + Type::getArrayKey(), + Type::getMixed() + ]); + $non_string_types[] = new Atomic\TCallableObject(); + $did_remove_type = true; + } elseif ($type instanceof TNumeric) { + $non_string_types[] = $type; + $did_remove_type = true; + } elseif ($type instanceof TScalar) { + $did_remove_type = true; + $non_string_types[] = new TFloat(); + $non_string_types[] = new TInt(); + $non_string_types[] = new TBool(); + } elseif (!$type instanceof TString) { + $non_string_types[] = $type; + } else { + $did_remove_type = true; + + if ($is_equality) { + $non_string_types[] = $type; + } + } + } + + if (!$non_string_types || !$did_remove_type) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!string', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($non_string_types) { + $type = new Type\Union($non_string_types); + $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues; + $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues; + $type->from_docblock = $existing_var_type->from_docblock; + return $type; + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileArray( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $non_array_types = []; + $did_remove_type = $existing_var_type->hasScalar(); + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TTemplateParam) { + if (!$is_equality && !$type->as->isMixed()) { + $template_did_fail = 0; + + $type = clone $type; + + $type->as = self::reconcileArray( + $type->as, + null, + false, + null, + $suppressed_issues, + $template_did_fail, + $is_equality + ); + + $did_remove_type = true; + + if (!$template_did_fail) { + $non_array_types[] = $type; + } + } else { + $did_remove_type = true; + $non_array_types[] = $type; + } + } elseif ($type instanceof TCallable) { + $non_array_types[] = new Atomic\TCallableString(); + $non_array_types[] = new Atomic\TCallableObject(); + $did_remove_type = true; + } elseif ($type instanceof Atomic\TIterable) { + if (!$type->type_params[0]->isMixed() || !$type->type_params[1]->isMixed()) { + $non_array_types[] = new Atomic\TGenericObject('Traversable', $type->type_params); + } else { + $non_array_types[] = new TNamedObject('Traversable'); + } + + $did_remove_type = true; + } elseif (!$type instanceof TArray + && !$type instanceof TKeyedArray + && !$type instanceof Atomic\TList + ) { + $non_array_types[] = $type; + } else { + $did_remove_type = true; + + if ($is_equality) { + $non_array_types[] = $type; + } + } + } + + if ((!$non_array_types || !$did_remove_type)) { + if ($key && $code_location) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!array', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($non_array_types) { + $type = new Type\Union($non_array_types); + $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues; + $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues; + $type->from_docblock = $existing_var_type->from_docblock; + return $type; + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * @param 0|1|2 $failed_reconciliation + */ + private static function reconcileResource( + Type\Union $existing_var_type, + ?string $key, + bool $negated, + ?CodeLocation $code_location, + array $suppressed_issues, + int &$failed_reconciliation, + bool $is_equality + ) : Type\Union { + $old_var_type_string = $existing_var_type->getId(); + $did_remove_type = false; + + if ($existing_var_type->hasType('resource')) { + $did_remove_type = true; + $existing_var_type->removeType('resource'); + } + + foreach ($existing_var_type->getAtomicTypes() as $type) { + if ($type instanceof TTemplateParam) { + $type->as = self::reconcileResource( + $type->as, + null, + false, + null, + $suppressed_issues, + $failed_reconciliation, + $is_equality + ); + + $did_remove_type = true; + $existing_var_type->bustCache(); + } + } + + if (!$did_remove_type || empty($existing_var_type->getAtomicTypes())) { + if ($key && $code_location && !$is_equality) { + self::triggerIssueForImpossible( + $existing_var_type, + $old_var_type_string, + $key, + '!resource', + !$did_remove_type, + $negated, + $code_location, + $suppressed_issues + ); + } + + if (!$did_remove_type) { + $failed_reconciliation = 1; + } + } + + if ($existing_var_type->getAtomicTypes()) { + return $existing_var_type; + } + + $failed_reconciliation = 2; + + return Type::getMixed(); + } + + private static function removeFalsyNegatedLiteralTypes( + Type\Union $existing_var_type, + bool &$did_remove_type + ): void { + if ($existing_var_type->hasString()) { + $existing_string_types = $existing_var_type->getLiteralStrings(); + + if ($existing_string_types) { + foreach ($existing_string_types as $string_key => $literal_type) { + if (!$literal_type->value) { + $existing_var_type->removeType($string_key); + $did_remove_type = true; + } + } + } else { + $did_remove_type = true; + } + } + + if ($existing_var_type->hasInt()) { + $existing_int_types = $existing_var_type->getLiteralInts(); + + if ($existing_int_types) { + foreach ($existing_int_types as $int_key => $literal_type) { + if (!$literal_type->value) { + $existing_var_type->removeType($int_key); + $did_remove_type = true; + } + } + } else { + $did_remove_type = true; + } + } + + if ($existing_var_type->hasType('array')) { + $array_atomic_type = $existing_var_type->getAtomicTypes()['array']; + + if ($array_atomic_type instanceof Type\Atomic\TArray + && !$array_atomic_type instanceof Type\Atomic\TNonEmptyArray + ) { + $did_remove_type = true; + + if ($array_atomic_type->getId() === 'array') { + $existing_var_type->removeType('array'); + } else { + $existing_var_type->addType( + new Type\Atomic\TNonEmptyArray( + $array_atomic_type->type_params + ) + ); + } + } elseif ($array_atomic_type instanceof Type\Atomic\TList + && !$array_atomic_type instanceof Type\Atomic\TNonEmptyList + ) { + $did_remove_type = true; + + $existing_var_type->addType( + new Type\Atomic\TNonEmptyList( + $array_atomic_type->type_param + ) + ); + } elseif ($array_atomic_type instanceof Type\Atomic\TKeyedArray + && !$array_atomic_type->sealed + ) { + $did_remove_type = true; + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TemplateResult.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TemplateResult.php new file mode 100644 index 0000000000000000000000000000000000000000..3593d102887f8595a9c7b9f481978249592d1b8b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TemplateResult.php @@ -0,0 +1,38 @@ +> + */ + public $template_types; + + /** + * @var array> + */ + public $upper_bounds; + + /** + * @var array> + */ + public $lower_bounds = []; + + /** + * @var list + */ + public $lower_bounds_unintersectable_types = []; + + /** + * @param array> $template_types + * @param array> $upper_bounds + */ + public function __construct(array $template_types, array $upper_bounds) + { + $this->template_types = $template_types; + $this->upper_bounds = $upper_bounds; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias.php new file mode 100644 index 0000000000000000000000000000000000000000..2e27ad134a20e23f060cf4849e3d34d83bb23a8d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias.php @@ -0,0 +1,6 @@ + + */ + public $replacement_atomic_types; + + /** + * @param list<\Psalm\Type\Atomic> $replacement_atomic_types + */ + public function __construct(array $replacement_atomic_types) + { + $this->replacement_atomic_types = $replacement_atomic_types; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php new file mode 100644 index 0000000000000000000000000000000000000000..2d0703a6f2f9e8aa687ed79ff7b9207009567601 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php @@ -0,0 +1,21 @@ + + */ + public $replacement_tokens; + + /** + * @param list $replacement_tokens + */ + public function __construct(array $replacement_tokens) + { + $this->replacement_tokens = $replacement_tokens; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php new file mode 100644 index 0000000000000000000000000000000000000000..63fe0fa534a35b9ed889075a6a9ff349f6af5e50 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php @@ -0,0 +1,32 @@ +declaring_fq_classlike_name = $declaring_fq_classlike_name; + $this->alias_name = $alias_name; + $this->line_number = $line_number; + $this->start_offset = $start_offset; + $this->end_offset = $end_offset; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeCombination.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeCombination.php new file mode 100644 index 0000000000000000000000000000000000000000..aacf439b31c48ed452f03030cd03da61b555ad64 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeCombination.php @@ -0,0 +1,1454 @@ + */ + private $value_types = []; + + /** @var array|null */ + private $named_object_types = []; + + /** @var list */ + private $array_type_params = []; + + /** @var array> */ + private $builtin_type_params = []; + + /** @var array> */ + private $object_type_params = []; + + /** @var array|null */ + private $array_counts = []; + + /** @var bool */ + private $array_sometimes_filled = false; + + /** @var bool */ + private $array_always_filled = true; + + /** @var array */ + private $objectlike_entries = []; + + /** @var array */ + private $objectlike_class_strings = []; + + /** @var bool */ + private $objectlike_sealed = true; + + /** @var ?Union */ + private $objectlike_key_type = null; + + /** @var ?Union */ + private $objectlike_value_type = null; + + /** @var bool */ + private $has_mixed = false; + + /** @var bool */ + private $empty_mixed = false; + + /** @var bool */ + private $non_empty_mixed = false; + + /** @var ?bool */ + private $mixed_from_loop_isset = null; + + /** @var array|null */ + private $strings = []; + + /** @var array|null */ + private $ints = []; + + /** @var array|null */ + private $floats = []; + + /** @var array|null */ + private $class_string_types = []; + + /** + * @var array|null + */ + private $extra_types; + + /** @var ?bool */ + private $all_arrays_lists; + + /** @var ?bool */ + private $all_arrays_callable; + + /** @var ?bool */ + private $all_arrays_class_string_maps; + + /** @var array */ + private $class_string_map_names = []; + + /** @var array */ + private $class_string_map_as_types = []; + + /** + * Combines types together + * - so `int + string = int|string` + * - so `array + array = array` + * - and `array + string = array|string` + * - and `array + array = array` + * - and `array + array = array` + * - and `array + array = array` + * + * @param non-empty-list $types + * @param int $literal_limit any greater number of literal types than this + * will be merged to a scalar + * + */ + public static function combineTypes( + array $types, + ?Codebase $codebase = null, + bool $overwrite_empty_array = false, + bool $allow_mixed_union = true, + int $literal_limit = 500 + ): Union { + if (in_array(null, $types, true)) { + return Type::getMixed(); + } + + if (count($types) === 1) { + $union_type = new Union([$types[0]]); + + if ($types[0]->from_docblock) { + $union_type->from_docblock = true; + } + + return $union_type; + } + + $combination = new TypeCombination(); + + $from_docblock = false; + + foreach ($types as $type) { + $from_docblock = $from_docblock || $type->from_docblock; + + $result = self::scrapeTypeProperties( + $type, + $combination, + $codebase, + $overwrite_empty_array, + $allow_mixed_union, + $literal_limit + ); + + if ($result) { + if ($from_docblock) { + $result->from_docblock = true; + } + + return $result; + } + } + + if (count($combination->value_types) === 1 + && !count($combination->objectlike_entries) + && !$combination->array_type_params + && !$combination->builtin_type_params + && !$combination->object_type_params + && !$combination->named_object_types + && !$combination->strings + && !$combination->class_string_types + && !$combination->ints + && !$combination->floats + ) { + if (isset($combination->value_types['false'])) { + $union_type = Type::getFalse(); + + if ($from_docblock) { + $union_type->from_docblock = true; + } + + return $union_type; + } + + if (isset($combination->value_types['true'])) { + $union_type = Type::getTrue(); + + if ($from_docblock) { + $union_type->from_docblock = true; + } + + return $union_type; + } + } elseif (isset($combination->value_types['void'])) { + unset($combination->value_types['void']); + + // if we're merging with another type, we cannot represent it in PHP + $from_docblock = true; + + if (!isset($combination->value_types['null'])) { + $combination->value_types['null'] = new TNull(); + } + } + + if (isset($combination->value_types['true']) && isset($combination->value_types['false'])) { + unset($combination->value_types['true'], $combination->value_types['false']); + + $combination->value_types['bool'] = new TBool(); + } + + if ($combination->array_type_params + && (isset($combination->named_object_types['Traversable']) + || isset($combination->builtin_type_params['Traversable'])) + && ( + ($codebase && !$codebase->config->allow_phpstorm_generics) + || isset($combination->builtin_type_params['Traversable']) + || (isset($combination->named_object_types['Traversable']) + && $combination->named_object_types['Traversable']->from_docblock) + ) + && !$combination->extra_types + ) { + $array_param_types = $combination->array_type_params; + $traversable_param_types = $combination->builtin_type_params['Traversable'] + ?? [Type::getMixed(), Type::getMixed()]; + + $combined_param_types = []; + + foreach ($array_param_types as $i => $array_param_type) { + $combined_param_types[] = Type::combineUnionTypes($array_param_type, $traversable_param_types[$i]); + } + + $combination->value_types['iterable'] = new TIterable($combined_param_types); + + $combination->array_type_params = []; + + /** + * @psalm-suppress PossiblyNullArrayAccess + */ + unset( + $combination->value_types['array'], + $combination->named_object_types['Traversable'], + $combination->builtin_type_params['Traversable'] + ); + } + + if ($combination->empty_mixed && $combination->non_empty_mixed) { + $combination->value_types['mixed'] = new TMixed((bool) $combination->mixed_from_loop_isset); + } + + $new_types = []; + + if (count($combination->objectlike_entries)) { + if ($combination->array_type_params + && $combination->array_type_params[0]->allStringLiterals() + && $combination->array_always_filled + ) { + foreach ($combination->array_type_params[0]->getAtomicTypes() as $atomic_key_type) { + if ($atomic_key_type instanceof TLiteralString) { + $combination->objectlike_entries[$atomic_key_type->value] + = $combination->array_type_params[1]; + } + } + + $combination->array_type_params = []; + $combination->objectlike_sealed = false; + } + + if (!$combination->array_type_params + || $combination->array_type_params[1]->isEmpty() + ) { + if (!$overwrite_empty_array + && ($combination->array_type_params + && ($combination->array_type_params[1]->isEmpty() + || $combination->array_type_params[1]->isMixed())) + ) { + foreach ($combination->objectlike_entries as $objectlike_entry) { + $objectlike_entry->possibly_undefined = true; + } + } + + if ($combination->objectlike_value_type + && $combination->objectlike_value_type->isMixed() + ) { + $combination->objectlike_entries = array_filter( + $combination->objectlike_entries, + function (Type\Union $type) : bool { + return !$type->possibly_undefined; + } + ); + } + + if ($combination->objectlike_entries) { + if ($combination->all_arrays_callable) { + $objectlike = new TCallableKeyedArray($combination->objectlike_entries); + } else { + $objectlike = new TKeyedArray($combination->objectlike_entries); + } + + if ($combination->objectlike_sealed && !$combination->array_type_params) { + $objectlike->sealed = true; + } + + if ($combination->objectlike_key_type) { + $objectlike->previous_key_type = $combination->objectlike_key_type; + } elseif ($combination->array_type_params + && $combination->array_type_params[0]->isArrayKey() + ) { + $objectlike->previous_key_type = $combination->array_type_params[0]; + } + + if ($combination->objectlike_value_type) { + $objectlike->previous_value_type = $combination->objectlike_value_type; + } elseif ($combination->array_type_params + && $combination->array_type_params[1]->isMixed() + ) { + $objectlike->previous_value_type = $combination->array_type_params[1]; + } + + if ($combination->all_arrays_lists) { + $objectlike->is_list = true; + } + + $new_types[] = $objectlike; + } else { + $new_types[] = new Type\Atomic\TArray([Type::getArrayKey(), Type::getMixed()]); + } + + // if we're merging an empty array with an object-like, clobber empty array + $combination->array_type_params = []; + } + } + + if ($generic_type_params = $combination->array_type_params) { + if ($combination->objectlike_entries) { + $objectlike_generic_type = null; + + $objectlike_keys = []; + + foreach ($combination->objectlike_entries as $property_name => $property_type) { + if ($objectlike_generic_type) { + $objectlike_generic_type = Type::combineUnionTypes( + $property_type, + $objectlike_generic_type, + $codebase, + $overwrite_empty_array + ); + } else { + $objectlike_generic_type = clone $property_type; + } + + if (is_int($property_name)) { + $objectlike_keys[$property_name] = new TLiteralInt($property_name); + } elseif (isset($type->class_strings[$property_name])) { + $objectlike_keys[$property_name] = new TLiteralClassString($property_name); + } else { + $objectlike_keys[$property_name] = new TLiteralString($property_name); + } + } + + if ($combination->objectlike_value_type) { + $objectlike_generic_type = Type::combineUnionTypes( + $combination->objectlike_value_type, + $objectlike_generic_type, + $codebase, + $overwrite_empty_array + ); + } + + $objectlike_generic_type->possibly_undefined = false; + + $objectlike_key_type = new Type\Union(array_values($objectlike_keys)); + + if ($combination->objectlike_key_type) { + $objectlike_key_type = Type::combineUnionTypes( + $combination->objectlike_key_type, + $objectlike_key_type, + $codebase, + $overwrite_empty_array + ); + } + + $generic_type_params[0] = Type::combineUnionTypes( + $generic_type_params[0], + $objectlike_key_type, + $codebase, + $overwrite_empty_array, + $allow_mixed_union + ); + + if (!$generic_type_params[1]->isMixed()) { + $generic_type_params[1] = Type::combineUnionTypes( + $generic_type_params[1], + $objectlike_generic_type, + $codebase, + $overwrite_empty_array, + $allow_mixed_union + ); + } + } + + if ($combination->all_arrays_callable) { + $array_type = new TCallableArray($generic_type_params); + } elseif ($combination->array_always_filled + || ($combination->array_sometimes_filled && $overwrite_empty_array) + || ($combination->objectlike_entries + && $combination->objectlike_sealed + && $overwrite_empty_array) + ) { + if ($combination->all_arrays_lists) { + if ($combination->objectlike_entries + && $combination->objectlike_sealed + ) { + $array_type = new TKeyedArray([$generic_type_params[1]]); + $array_type->previous_key_type = Type::getInt(); + $array_type->previous_value_type = $combination->array_type_params[1]; + $array_type->is_list = true; + } else { + $array_type = new TNonEmptyList($generic_type_params[1]); + + if ($combination->array_counts && count($combination->array_counts) === 1) { + $array_type->count = array_keys($combination->array_counts)[0]; + } + } + } else { + $array_type = new TNonEmptyArray($generic_type_params); + + if ($combination->array_counts && count($combination->array_counts) === 1) { + $array_type->count = array_keys($combination->array_counts)[0]; + } + } + } else { + if ($combination->all_arrays_class_string_maps + && count($combination->class_string_map_as_types) === 1 + && count($combination->class_string_map_names) === 1 + ) { + $array_type = new Type\Atomic\TClassStringMap( + array_keys($combination->class_string_map_names)[0], + array_values($combination->class_string_map_as_types)[0], + $generic_type_params[1] + ); + } elseif ($combination->all_arrays_lists) { + $array_type = new TList($generic_type_params[1]); + } else { + $array_type = new TArray($generic_type_params); + } + } + + $new_types[] = $array_type; + } + + if ($combination->extra_types) { + /** @psalm-suppress PropertyTypeCoercion */ + $combination->extra_types = self::combineTypes( + array_values($combination->extra_types), + $codebase + )->getAtomicTypes(); + } + + foreach ($combination->builtin_type_params as $generic_type => $generic_type_params) { + if ($generic_type === 'iterable') { + $new_types[] = new TIterable($generic_type_params); + } else { + $generic_object = new TGenericObject($generic_type, $generic_type_params); + /** @psalm-suppress PropertyTypeCoercion */ + $generic_object->extra_types = $combination->extra_types; + $new_types[] = $generic_object; + + if ($combination->named_object_types) { + unset($combination->named_object_types[$generic_type]); + } + } + } + + foreach ($combination->object_type_params as $generic_type => $generic_type_params) { + $generic_type = substr($generic_type, 0, (int) strpos($generic_type, '<')); + + $generic_object = new TGenericObject($generic_type, $generic_type_params); + /** @psalm-suppress PropertyTypeCoercion */ + $generic_object->extra_types = $combination->extra_types; + $new_types[] = $generic_object; + } + + if ($combination->class_string_types) { + if ($combination->strings) { + foreach ($combination->strings as $k => $string) { + if ($string instanceof TLiteralClassString) { + $combination->class_string_types[$string->value] = new TNamedObject($string->value); + unset($combination->strings[$k]); + } + } + } + + if (!isset($combination->value_types['string'])) { + $object_type = self::combineTypes( + array_values($combination->class_string_types), + $codebase + ); + + foreach ($object_type->getAtomicTypes() as $object_atomic_type) { + if ($object_atomic_type instanceof TNamedObject) { + $new_types[] = new TClassString($object_atomic_type->value, $object_atomic_type); + } elseif ($object_atomic_type instanceof TObject) { + $new_types[] = new TClassString(); + } + } + } + } + + if ($combination->strings) { + $new_types = array_merge($new_types, array_values($combination->strings)); + } + + if ($combination->ints) { + $new_types = array_merge($new_types, array_values($combination->ints)); + } + + if ($combination->floats) { + $new_types = array_merge($new_types, array_values($combination->floats)); + } + + if (isset($combination->value_types['string']) + && isset($combination->value_types['int']) + && isset($combination->value_types['bool']) + && isset($combination->value_types['float']) + ) { + unset( + $combination->value_types['string'], + $combination->value_types['int'], + $combination->value_types['bool'], + $combination->value_types['float'] + ); + $combination->value_types['scalar'] = new TScalar; + } + + if ($combination->named_object_types !== null) { + $combination->value_types += $combination->named_object_types; + } + + $has_empty = (int) isset($combination->value_types['empty']); + + foreach ($combination->value_types as $type) { + if ($type instanceof TMixed + && $combination->mixed_from_loop_isset + && (count($combination->value_types) > (1 + $has_empty) || count($new_types) > $has_empty) + ) { + continue; + } + + if ($type instanceof TEmpty + && (count($combination->value_types) > 1 || count($new_types)) + ) { + continue; + } + + $new_types[] = $type; + } + + if (!$new_types) { + throw new \UnexpectedValueException('There should be types here'); + } + + $union_type = new Union($new_types); + + if ($from_docblock) { + $union_type->from_docblock = true; + } + + return $union_type; + } + + private static function scrapeTypeProperties( + Atomic $type, + TypeCombination $combination, + ?Codebase $codebase, + bool $overwrite_empty_array, + bool $allow_mixed_union, + int $literal_limit + ): ?Union { + if ($type instanceof TMixed) { + $combination->has_mixed = true; + if ($type->from_loop_isset) { + if ($combination->mixed_from_loop_isset === null) { + $combination->mixed_from_loop_isset = true; + } else { + return null; + } + } else { + $combination->mixed_from_loop_isset = false; + } + + if ($type instanceof TNonEmptyMixed) { + $combination->non_empty_mixed = true; + + if ($combination->empty_mixed) { + return null; + } + } elseif ($type instanceof TEmptyMixed) { + $combination->empty_mixed = true; + + if ($combination->non_empty_mixed) { + return null; + } + } else { + $combination->empty_mixed = true; + $combination->non_empty_mixed = true; + } + + if (!$allow_mixed_union) { + return Type::getMixed((bool) $combination->mixed_from_loop_isset); + } + } + + // deal with false|bool => bool + if (($type instanceof TFalse || $type instanceof TTrue) && isset($combination->value_types['bool'])) { + return null; + } + + if (get_class($type) === TBool::class && isset($combination->value_types['false'])) { + unset($combination->value_types['false']); + } + + if (get_class($type) === TBool::class && isset($combination->value_types['true'])) { + unset($combination->value_types['true']); + } + + if ($type instanceof TArray && isset($combination->builtin_type_params['iterable'])) { + $type_key = 'iterable'; + } elseif ($type instanceof TArray + && $type->type_params[1]->isMixed() + && isset($combination->value_types['iterable']) + ) { + $type_key = 'iterable'; + $combination->builtin_type_params['iterable'] = [Type::getMixed(), Type::getMixed()]; + } elseif ($type instanceof TNamedObject + && $type->value === 'Traversable' + && (isset($combination->builtin_type_params['iterable']) || isset($combination->value_types['iterable'])) + ) { + $type_key = 'iterable'; + + if (!isset($combination->builtin_type_params['iterable'])) { + $combination->builtin_type_params['iterable'] = [Type::getMixed(), Type::getMixed()]; + } + + if (!$type instanceof TGenericObject) { + $type = new TGenericObject($type->value, [Type::getMixed(), Type::getMixed()]); + } + } elseif ($type instanceof TNamedObject && ($type->value === 'Traversable' || $type->value === 'Generator')) { + $type_key = $type->value; + } else { + $type_key = $type->getKey(); + } + + if ($type instanceof TIterable + && $combination->array_type_params + && ($type->has_docblock_params || $combination->array_type_params[1]->isMixed()) + ) { + if (!isset($combination->builtin_type_params['iterable'])) { + $combination->builtin_type_params['iterable'] = $combination->array_type_params; + } else { + foreach ($combination->array_type_params as $i => $array_type_param) { + $iterable_type_param = $combination->builtin_type_params['iterable'][$i]; + /** @psalm-suppress PropertyTypeCoercion */ + $combination->builtin_type_params['iterable'][$i] = Type::combineUnionTypes( + $iterable_type_param, + $array_type_param + ); + } + } + + $combination->array_type_params = []; + } + + if ($type instanceof TIterable + && (isset($combination->named_object_types['Traversable']) + || isset($combination->builtin_type_params['Traversable'])) + ) { + if (!isset($combination->builtin_type_params['iterable'])) { + $combination->builtin_type_params['iterable'] + = $combination->builtin_type_params['Traversable'] ?? [Type::getMixed(), Type::getMixed()]; + } elseif (isset($combination->builtin_type_params['Traversable'])) { + foreach ($combination->builtin_type_params['Traversable'] as $i => $array_type_param) { + $iterable_type_param = $combination->builtin_type_params['iterable'][$i]; + /** @psalm-suppress PropertyTypeCoercion */ + $combination->builtin_type_params['iterable'][$i] = Type::combineUnionTypes( + $iterable_type_param, + $array_type_param + ); + } + } else { + $combination->builtin_type_params['iterable'] = [Type::getMixed(), Type::getMixed()]; + } + + /** @psalm-suppress PossiblyNullArrayAccess */ + unset( + $combination->named_object_types['Traversable'], + $combination->builtin_type_params['Traversable'] + ); + } + + if ($type instanceof TNamedObject + || $type instanceof TTemplateParam + || $type instanceof TIterable + || $type instanceof Type\Atomic\TObjectWithProperties + ) { + if ($type->extra_types) { + $combination->extra_types = array_merge( + $combination->extra_types ?: [], + $type->extra_types + ); + } + } + + if ($type instanceof TArray && $type_key === 'array') { + if ($type instanceof TCallableArray && isset($combination->value_types['callable'])) { + return null; + } + + foreach ($type->type_params as $i => $type_param) { + if (isset($combination->array_type_params[$i])) { + $combination->array_type_params[$i] = Type::combineUnionTypes( + $combination->array_type_params[$i], + $type_param, + $codebase, + $overwrite_empty_array + ); + } else { + /** @psalm-suppress PropertyTypeCoercion */ + $combination->array_type_params[$i] = $type_param; + } + } + + if ($type instanceof TNonEmptyArray) { + if ($combination->array_counts !== null) { + if ($type->count === null) { + $combination->array_counts = null; + } else { + $combination->array_counts[$type->count] = true; + } + } + + $combination->array_sometimes_filled = true; + } else { + $combination->array_always_filled = false; + } + + if (!$type->type_params[1]->isEmpty()) { + $combination->all_arrays_lists = false; + $combination->all_arrays_class_string_maps = false; + } + + if ($type instanceof TCallableArray) { + if ($combination->all_arrays_callable !== false) { + $combination->all_arrays_callable = true; + } + } else { + $combination->all_arrays_callable = false; + } + + return null; + } + + if ($type instanceof TList) { + foreach ([Type::getInt(), $type->type_param] as $i => $type_param) { + if (isset($combination->array_type_params[$i])) { + /** @psalm-suppress PropertyTypeCoercion */ + $combination->array_type_params[$i] = Type::combineUnionTypes( + $combination->array_type_params[$i], + $type_param, + $codebase, + $overwrite_empty_array + ); + } else { + /** @psalm-suppress PropertyTypeCoercion */ + $combination->array_type_params[$i] = $type_param; + } + } + + if ($type instanceof TNonEmptyList) { + if ($combination->array_counts !== null) { + if ($type->count === null) { + $combination->array_counts = null; + } else { + $combination->array_counts[$type->count] = true; + } + } + + $combination->array_sometimes_filled = true; + } else { + $combination->array_always_filled = false; + } + + if ($combination->all_arrays_lists !== false) { + $combination->all_arrays_lists = true; + } + + $combination->all_arrays_callable = false; + $combination->all_arrays_class_string_maps = false; + + return null; + } + + if ($type instanceof Atomic\TClassStringMap) { + foreach ([$type->getStandinKeyParam(), $type->value_param] as $i => $type_param) { + if (isset($combination->array_type_params[$i])) { + /** @psalm-suppress PropertyTypeCoercion */ + $combination->array_type_params[$i] = Type::combineUnionTypes( + $combination->array_type_params[$i], + $type_param, + $codebase, + $overwrite_empty_array + ); + } else { + /** @psalm-suppress PropertyTypeCoercion */ + $combination->array_type_params[$i] = $type_param; + } + } + + $combination->array_always_filled = false; + + if ($combination->all_arrays_class_string_maps !== false) { + $combination->all_arrays_class_string_maps = true; + $combination->class_string_map_names[$type->param_name] = true; + $combination->class_string_map_as_types[(string) $type->as_type] = $type->as_type; + } + + return null; + } + + if (($type instanceof TGenericObject && ($type->value === 'Traversable' || $type->value === 'Generator')) + || ($type instanceof TIterable && $type->has_docblock_params) + || ($type instanceof TArray && $type_key === 'iterable') + ) { + foreach ($type->type_params as $i => $type_param) { + if (isset($combination->builtin_type_params[$type_key][$i])) { + /** @psalm-suppress PropertyTypeCoercion */ + $combination->builtin_type_params[$type_key][$i] = Type::combineUnionTypes( + $combination->builtin_type_params[$type_key][$i], + $type_param, + $codebase, + $overwrite_empty_array + ); + } else { + /** @psalm-suppress PropertyTypeCoercion */ + $combination->builtin_type_params[$type_key][$i] = $type_param; + } + } + + return null; + } + + if ($type instanceof TGenericObject) { + foreach ($type->type_params as $i => $type_param) { + if (isset($combination->object_type_params[$type_key][$i])) { + /** @psalm-suppress PropertyTypeCoercion */ + $combination->object_type_params[$type_key][$i] = Type::combineUnionTypes( + $combination->object_type_params[$type_key][$i], + $type_param, + $codebase, + $overwrite_empty_array + ); + } else { + /** @psalm-suppress PropertyTypeCoercion */ + $combination->object_type_params[$type_key][$i] = $type_param; + } + } + + return null; + } + + if ($type instanceof TKeyedArray) { + if ($type instanceof TCallableKeyedArray && isset($combination->value_types['callable'])) { + return null; + } + + $existing_objectlike_entries = (bool) $combination->objectlike_entries; + $possibly_undefined_entries = $combination->objectlike_entries; + $combination->objectlike_sealed = $combination->objectlike_sealed && $type->sealed; + + if ($type->previous_value_type) { + if (!$combination->objectlike_value_type) { + $combination->objectlike_value_type = $type->previous_value_type; + } else { + $combination->objectlike_value_type = Type::combineUnionTypes( + $type->previous_value_type, + $combination->objectlike_value_type, + $codebase, + $overwrite_empty_array + ); + } + } + + if ($type->previous_key_type) { + if (!$combination->objectlike_key_type) { + $combination->objectlike_key_type = $type->previous_key_type; + } else { + $combination->objectlike_key_type = Type::combineUnionTypes( + $type->previous_key_type, + $combination->objectlike_key_type, + $codebase, + $overwrite_empty_array + ); + } + } + + $has_defined_keys = false; + + foreach ($type->properties as $candidate_property_name => $candidate_property_type) { + $value_type = isset($combination->objectlike_entries[$candidate_property_name]) + ? $combination->objectlike_entries[$candidate_property_name] + : null; + + if (!$value_type) { + $combination->objectlike_entries[$candidate_property_name] = clone $candidate_property_type; + // it's possibly undefined if there are existing objectlike entries and + $combination->objectlike_entries[$candidate_property_name]->possibly_undefined + = $existing_objectlike_entries || $candidate_property_type->possibly_undefined; + } else { + $combination->objectlike_entries[$candidate_property_name] = Type::combineUnionTypes( + $value_type, + $candidate_property_type, + $codebase, + $overwrite_empty_array + ); + } + + if (is_string($candidate_property_name) + && isset($type->class_strings[$candidate_property_name]) + ) { + $combination->objectlike_class_strings[$candidate_property_name] = true; + } + + if (!$type->previous_value_type) { + unset($possibly_undefined_entries[$candidate_property_name]); + } + + if (!$candidate_property_type->possibly_undefined) { + $has_defined_keys = true; + } + } + + if (!$has_defined_keys) { + $combination->array_always_filled = false; + } + + if ($combination->array_counts !== null) { + $combination->array_counts[count($type->properties)] = true; + } + + foreach ($possibly_undefined_entries as $possibly_undefined_type) { + $possibly_undefined_type->possibly_undefined = true; + } + + if (!$type->is_list) { + $combination->all_arrays_lists = false; + } elseif ($combination->all_arrays_lists !== false) { + $combination->all_arrays_lists = true; + } + + if ($type instanceof TCallableKeyedArray) { + if ($combination->all_arrays_callable !== false) { + $combination->all_arrays_callable = true; + } + } else { + $combination->all_arrays_callable = false; + } + + $combination->all_arrays_class_string_maps = false; + + return null; + } + + if ($type instanceof TObject) { + if ($type instanceof TCallableObject && isset($combination->value_types['callable'])) { + return null; + } + + $combination->named_object_types = null; + $combination->value_types[$type_key] = $type; + + return null; + } + + if ($type instanceof TIterable) { + $combination->value_types[$type_key] = $type; + + return null; + } + + if ($type instanceof TNamedObject) { + if ($combination->named_object_types === null) { + return null; + } + + if (isset($combination->named_object_types[$type_key])) { + return null; + } + + if (!$codebase) { + $combination->named_object_types[$type_key] = $type; + + return null; + } + + if (!$codebase->classlikes->classOrInterfaceExists($type_key)) { + // write this to the main list + $combination->value_types[$type_key] = $type; + + return null; + } + + $is_class = $codebase->classExists($type_key); + + foreach ($combination->named_object_types as $key => $_) { + if ($codebase->classExists($key)) { + if ($codebase->classExtendsOrImplements($key, $type_key)) { + unset($combination->named_object_types[$key]); + continue; + } + + if ($is_class) { + if ($codebase->classExtends($type_key, $key)) { + return null; + } + } + } else { + if ($codebase->interfaceExtends($key, $type_key)) { + unset($combination->named_object_types[$key]); + continue; + } + + if ($is_class) { + if ($codebase->classImplements($type_key, $key)) { + return null; + } + } else { + if ($codebase->interfaceExtends($type_key, $key)) { + return null; + } + } + } + } + + $combination->named_object_types[$type_key] = $type; + + return null; + } + + if ($type instanceof TScalar) { + $combination->strings = null; + $combination->ints = null; + $combination->floats = null; + unset( + $combination->value_types['string'], + $combination->value_types['int'], + $combination->value_types['bool'], + $combination->value_types['true'], + $combination->value_types['false'], + $combination->value_types['float'] + ); + $combination->value_types[$type_key] = $type; + + return null; + } + + if ($type instanceof Scalar && isset($combination->value_types['scalar'])) { + return null; + } + + if ($type instanceof TArrayKey) { + $combination->strings = null; + $combination->ints = null; + unset( + $combination->value_types['string'], + $combination->value_types['int'] + ); + $combination->value_types[$type_key] = $type; + + return null; + } + + if ($type instanceof TString) { + if ($type instanceof TCallableString && isset($combination->value_types['callable'])) { + return null; + } + + if (isset($combination->value_types['array-key'])) { + return null; + } + + if ($type instanceof Type\Atomic\TTemplateParamClass) { + $combination->value_types[$type_key] = $type; + } elseif ($type instanceof Type\Atomic\TClassString) { + if (!$type->as_type) { + $combination->class_string_types['object'] = new TObject(); + } else { + $combination->class_string_types[$type->as] = $type->as_type; + } + } elseif ($type instanceof TLiteralString) { + if ($combination->strings !== null && count($combination->strings) < $literal_limit) { + $combination->strings[$type_key] = $type; + } else { + $shared_classlikes = $codebase ? $combination->getSharedTypes($codebase) : []; + + $combination->strings = null; + + if (isset($combination->value_types['string']) + && $combination->value_types['string'] instanceof Type\Atomic\TNumericString + && \is_numeric($type->value) + ) { + // do nothing + } elseif (isset($combination->value_types['class-string']) + && $type instanceof TLiteralClassString + ) { + // do nothing + } elseif ($type instanceof TLiteralClassString) { + $type_classlikes = $codebase + ? self::getClassLikes($codebase, $type->value) + : []; + + $mutual = array_intersect_key($type_classlikes, $shared_classlikes); + + if ($mutual) { + $first_class = array_keys($mutual)[0]; + + $combination->class_string_types[$first_class] = new TNamedObject($first_class); + } else { + $combination->class_string_types['object'] = new TObject(); + } + } else { + if (isset($combination->value_types['string']) + && $combination->value_types['string'] instanceof Type\Atomic\TLowercaseString + && \strtolower($type->value) === $type->value + ) { + // do nothing + } else { + $combination->value_types['string'] = new TString(); + } + } + } + } else { + $type_key = 'string'; + + if (!isset($combination->value_types['string'])) { + if ($combination->strings) { + if ($type instanceof Type\Atomic\TNumericString) { + $has_non_numeric_string = false; + + foreach ($combination->strings as $string_type) { + if (!\is_numeric($string_type->value)) { + $has_non_numeric_string = true; + break; + } + } + + if ($has_non_numeric_string) { + $combination->value_types['string'] = new TString(); + } else { + $combination->value_types['string'] = $type; + } + + $combination->strings = null; + } elseif ($type instanceof Type\Atomic\TLowercaseString) { + $has_non_lowercase_string = false; + + foreach ($combination->strings as $string_type) { + if (\strtolower($string_type->value) !== $string_type->value) { + $has_non_lowercase_string = true; + break; + } + } + + if ($has_non_lowercase_string) { + $combination->value_types['string'] = new TString(); + } else { + $combination->value_types['string'] = $type; + } + + $combination->strings = null; + } else { + $has_non_literal_class_string = false; + + $shared_classlikes = $codebase ? $combination->getSharedTypes($codebase) : []; + + foreach ($combination->strings as $string_type) { + if (!$string_type instanceof TLiteralClassString) { + $has_non_literal_class_string = true; + break; + } + } + + if ($has_non_literal_class_string || + !$type instanceof TClassString + ) { + $combination->value_types[$type_key] = new TString(); + } else { + if (isset($shared_classlikes[$type->as]) && $type->as_type) { + $combination->class_string_types[$type->as] = $type->as_type; + } else { + $combination->class_string_types['object'] = new TObject(); + } + } + } + } else { + $combination->value_types[$type_key] = $type; + } + } elseif (get_class($combination->value_types['string']) !== TString::class) { + if (get_class($type) === TString::class) { + $combination->value_types['string'] = $type; + } elseif ($combination->value_types['string'] instanceof TTraitString + && $type instanceof TClassString + ) { + $combination->value_types['trait-string'] = $combination->value_types['string']; + $combination->value_types['class-string'] = $type; + + unset($combination->value_types['string']); + } elseif (get_class($combination->value_types['string']) !== get_class($type)) { + if (get_class($type) === TNonEmptyString::class + && get_class($combination->value_types['string']) === TNonEmptyLowercaseString::class + ) { + $combination->value_types['string'] = $type; + } elseif (get_class($combination->value_types['string']) === TNonEmptyString::class + && get_class($type) === TNonEmptyLowercaseString::class + ) { + //no-change + } elseif (get_class($type) === TLowercaseString::class + && get_class($combination->value_types['string']) === TNonEmptyLowercaseString::class + ) { + $combination->value_types['string'] = $type; + } elseif (get_class($combination->value_types['string']) === TLowercaseString::class + && get_class($type) === TNonEmptyLowercaseString::class + ) { + //no-change + } else { + $combination->value_types['string'] = new TString(); + } + } + } + + $combination->strings = null; + } + + return null; + } + + if ($type instanceof TInt) { + if (isset($combination->value_types['array-key'])) { + return null; + } + + $had_zero = isset($combination->ints['int(0)']); + + if ($type instanceof TLiteralInt) { + if ($type->value === 0) { + $had_zero = true; + } + + if ($combination->ints !== null && count($combination->ints) < $literal_limit) { + $combination->ints[$type_key] = $type; + } else { + $combination->ints[$type_key] = $type; + + $all_nonnegative = !array_filter( + $combination->ints, + function ($int): bool { + return $int->value < 0; + } + ); + + $combination->ints = null; + + if (!isset($combination->value_types['int'])) { + $combination->value_types['int'] = $all_nonnegative ? new TPositiveInt() : new TInt(); + } elseif ($combination->value_types['int'] instanceof TPositiveInt + && !$all_nonnegative + ) { + $combination->value_types['int'] = new TInt(); + } + } + } else { + if ($type instanceof TPositiveInt) { + if ($combination->ints) { + $all_nonnegative = !array_filter( + $combination->ints, + function ($int): bool { + return $int->value < 0; + } + ); + + if ($all_nonnegative) { + $combination->value_types['int'] = $type; + } else { + $combination->value_types['int'] = new TInt(); + } + } elseif (!isset($combination->value_types['int'])) { + $combination->value_types['int'] = $type; + } + } else { + $combination->value_types['int'] = $type; + } + + $combination->ints = null; + } + + if ($had_zero + && isset($combination->value_types['int']) + && $combination->value_types['int'] instanceof TPositiveInt + ) { + $combination->ints = ['int(0)' => new TLiteralInt(0)]; + } + + return null; + } + + if ($type instanceof TFloat) { + if ($type instanceof TLiteralFloat) { + if ($combination->floats !== null && count($combination->floats) < $literal_limit) { + $combination->floats[$type_key] = $type; + } else { + $combination->floats = null; + $combination->value_types['float'] = new TFloat(); + } + } else { + $combination->floats = null; + $combination->value_types['float'] = $type; + } + + return null; + } + + if ($type instanceof TCallable && $type_key === 'callable') { + if (($combination->value_types['string'] ?? null) instanceof TCallableString) { + unset($combination->value_types['string']); + } elseif (!empty($combination->array_type_params) && $combination->all_arrays_callable) { + $combination->array_type_params = []; + } elseif (isset($combination->value_types['callable-object'])) { + unset($combination->value_types['callable-object']); + } + } + + $combination->value_types[$type_key] = $type; + return null; + } + + /** + * @return array + */ + private function getSharedTypes(Codebase $codebase) : array + { + /** @var array|null */ + $shared_classlikes = null; + + if ($this->strings) { + foreach ($this->strings as $string_type) { + $classlikes = self::getClassLikes($codebase, $string_type->value); + + if ($shared_classlikes === null) { + $shared_classlikes = $classlikes; + } elseif ($shared_classlikes) { + $shared_classlikes = array_intersect_key($shared_classlikes, $classlikes); + } + } + } + + if ($this->class_string_types) { + foreach ($this->class_string_types as $value_type) { + if ($value_type instanceof TNamedObject) { + $classlikes = self::getClassLikes($codebase, $value_type->value); + + if ($shared_classlikes === null) { + $shared_classlikes = $classlikes; + } elseif ($shared_classlikes) { + $shared_classlikes = array_intersect_key($shared_classlikes, $classlikes); + } + } + } + } + + return $shared_classlikes ?: []; + } + + /** + * @return array + */ + private static function getClassLikes(Codebase $codebase, string $fq_classlike_name): array + { + try { + $class_storage = $codebase->classlike_storage_provider->get($fq_classlike_name); + } catch (\InvalidArgumentException $e) { + return []; + } + + $classlikes = []; + + $classlikes[$fq_classlike_name] = true; + + foreach ($class_storage->parent_classes as $parent_class) { + $classlikes[$parent_class] = true; + } + + foreach ($class_storage->parent_interfaces as $parent_interface) { + $classlikes[$parent_interface] = true; + } + + foreach ($class_storage->class_implements as $interface) { + $classlikes[$interface] = true; + } + + return $classlikes; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeExpander.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeExpander.php new file mode 100644 index 0000000000000000000000000000000000000000..1e087b29283faddc70d6057af1f3dcc677c785dd --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeExpander.php @@ -0,0 +1,613 @@ +getAtomicTypes() as $return_type_part) { + $parts = self::expandAtomic( + $codebase, + $return_type_part, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + + if (is_array($parts)) { + $new_return_type_parts = array_merge($new_return_type_parts, $parts); + $has_array_output = true; + } else { + $new_return_type_parts[] = $parts; + } + } + + if ($has_array_output) { + $fleshed_out_type = TypeCombination::combineTypes( + $new_return_type_parts, + $codebase + ); + } else { + $fleshed_out_type = new Type\Union($new_return_type_parts); + } + + $fleshed_out_type->from_docblock = $return_type->from_docblock; + $fleshed_out_type->ignore_nullable_issues = $return_type->ignore_nullable_issues; + $fleshed_out_type->ignore_falsable_issues = $return_type->ignore_falsable_issues; + $fleshed_out_type->possibly_undefined = $return_type->possibly_undefined; + $fleshed_out_type->possibly_undefined_from_try = $return_type->possibly_undefined_from_try; + $fleshed_out_type->by_ref = $return_type->by_ref; + $fleshed_out_type->initialized = $return_type->initialized; + $fleshed_out_type->had_template = $return_type->had_template; + $fleshed_out_type->parent_nodes = $return_type->parent_nodes; + + return $fleshed_out_type; + } + + /** + * @param string|Type\Atomic\TNamedObject|Type\Atomic\TTemplateParam|null $static_class_type + * + * @return Type\Atomic|non-empty-list + */ + public static function expandAtomic( + Codebase $codebase, + Type\Atomic &$return_type, + ?string $self_class, + $static_class_type, + ?string $parent_class, + bool $evaluate_class_constants = true, + bool $evaluate_conditional_types = false, + bool $final = false + ) { + if ($return_type instanceof TNamedObject + || $return_type instanceof TTemplateParam + ) { + if ($return_type->extra_types) { + $new_intersection_types = []; + + foreach ($return_type->extra_types as &$extra_type) { + self::expandAtomic( + $codebase, + $extra_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types + ); + + if ($extra_type instanceof TNamedObject && $extra_type->extra_types) { + $new_intersection_types = array_merge( + $new_intersection_types, + $extra_type->extra_types + ); + $extra_type->extra_types = []; + } + } + + if ($new_intersection_types) { + $return_type->extra_types = array_merge($return_type->extra_types, $new_intersection_types); + } + } + + if ($return_type instanceof TNamedObject) { + $return_type_lc = strtolower($return_type->value); + + if ($static_class_type && ($return_type_lc === 'static' || $return_type_lc === '$this')) { + if (is_string($static_class_type)) { + $return_type->value = $static_class_type; + } else { + if ($return_type instanceof Type\Atomic\TGenericObject + && $static_class_type instanceof Type\Atomic\TGenericObject + ) { + $return_type->value = $static_class_type->value; + } else { + $return_type = clone $static_class_type; + } + } + + if (!$final && $return_type instanceof TNamedObject) { + $return_type->was_static = true; + } + } elseif ($return_type->was_static + && ($static_class_type instanceof Type\Atomic\TNamedObject + || $static_class_type instanceof Type\Atomic\TTemplateParam) + ) { + $return_type = clone $return_type; + $cloned_static = clone $static_class_type; + $extra_static = $cloned_static->extra_types ?: []; + $cloned_static->extra_types = null; + + if ($cloned_static->getKey(false) !== $return_type->getKey(false)) { + $return_type->extra_types[$static_class_type->getKey()] = clone $cloned_static; + } + + foreach ($extra_static as $extra_static_type) { + if ($extra_static_type->getKey(false) !== $return_type->getKey(false)) { + $return_type->extra_types[$extra_static_type->getKey()] = clone $extra_static_type; + } + } + } elseif ($self_class && $return_type_lc === 'self') { + $return_type->value = $self_class; + } elseif ($parent_class && $return_type_lc === 'parent') { + $return_type->value = $parent_class; + } else { + $return_type->value = $codebase->classlikes->getUnAliasedName($return_type->value); + } + } + } + + if ($return_type instanceof Type\Atomic\TClassString + && $return_type->as_type + ) { + $new_as_type = clone $return_type->as_type; + + self::expandAtomic( + $codebase, + $new_as_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + + if ($new_as_type instanceof TNamedObject) { + $return_type->as_type = $new_as_type; + $return_type->as = $return_type->as_type->value; + } + } elseif ($return_type instanceof Type\Atomic\TTemplateParam) { + $new_as_type = self::expandUnion( + $codebase, + clone $return_type->as, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + + $return_type->as = $new_as_type; + } + + if ($return_type instanceof Type\Atomic\TScalarClassConstant) { + if ($return_type->fq_classlike_name === 'self' && $self_class) { + $return_type->fq_classlike_name = $self_class; + } + + if ($evaluate_class_constants && $codebase->classOrInterfaceExists($return_type->fq_classlike_name)) { + if (strtolower($return_type->const_name) === 'class') { + return new Type\Atomic\TLiteralClassString($return_type->fq_classlike_name); + } + + if (strpos($return_type->const_name, '*') !== false) { + $class_storage = $codebase->classlike_storage_provider->get($return_type->fq_classlike_name); + + $matching_constants = \array_keys($class_storage->constants); + + $const_name_part = \substr($return_type->const_name, 0, -1); + + if ($const_name_part) { + $matching_constants = \array_filter( + $matching_constants, + function ($constant_name) use ($const_name_part): bool { + return $constant_name !== $const_name_part + && \strpos($constant_name, $const_name_part) === 0; + } + ); + } + } else { + $matching_constants = [$return_type->const_name]; + } + + $matching_constant_types = []; + + foreach ($matching_constants as $matching_constant) { + try { + $class_constant = $codebase->classlikes->getClassConstantType( + $return_type->fq_classlike_name, + $matching_constant, + \ReflectionProperty::IS_PRIVATE + ); + } catch (\Psalm\Exception\CircularReferenceException $e) { + $class_constant = null; + } + + if ($class_constant) { + if ($class_constant->isSingle()) { + $class_constant = clone $class_constant; + + $matching_constant_types = \array_merge( + \array_values($class_constant->getAtomicTypes()), + $matching_constant_types + ); + } + } + } + + if ($matching_constant_types) { + return $matching_constant_types; + } + } + + return $return_type; + } + + if ($return_type instanceof Type\Atomic\TTypeAlias) { + $declaring_fq_classlike_name = $return_type->declaring_fq_classlike_name; + + if ($declaring_fq_classlike_name === 'self' && $self_class) { + $declaring_fq_classlike_name = $self_class; + } + + if ($evaluate_class_constants && $codebase->classOrInterfaceExists($declaring_fq_classlike_name)) { + $class_storage = $codebase->classlike_storage_provider->get($declaring_fq_classlike_name); + + $type_alias_name = $return_type->alias_name; + + if (isset($class_storage->type_aliases[$type_alias_name])) { + $resolved_type_alias = $class_storage->type_aliases[$type_alias_name]; + + if ($resolved_type_alias->replacement_atomic_types) { + $replacement_atomic_types = $resolved_type_alias->replacement_atomic_types; + + $recursively_fleshed_out_types = []; + + foreach ($replacement_atomic_types as $replacement_atomic_type) { + $recursively_fleshed_out_type = self::expandAtomic( + $codebase, + $replacement_atomic_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types + ); + + if (is_array($recursively_fleshed_out_type)) { + $recursively_fleshed_out_types = array_merge( + $recursively_fleshed_out_type, + $recursively_fleshed_out_types + ); + } else { + $recursively_fleshed_out_types[] = $recursively_fleshed_out_type; + } + } + + return $recursively_fleshed_out_types; + } + } + } + + return $return_type; + } + + if ($return_type instanceof Type\Atomic\TKeyOfClassConstant + || $return_type instanceof Type\Atomic\TValueOfClassConstant + ) { + if ($return_type->fq_classlike_name === 'self' && $self_class) { + $return_type->fq_classlike_name = $self_class; + } + + if ($evaluate_class_constants && $codebase->classOrInterfaceExists($return_type->fq_classlike_name)) { + try { + $class_constant_type = $codebase->classlikes->getClassConstantType( + $return_type->fq_classlike_name, + $return_type->const_name, + \ReflectionProperty::IS_PRIVATE + ); + } catch (\Psalm\Exception\CircularReferenceException $e) { + $class_constant_type = null; + } + + if ($class_constant_type) { + foreach ($class_constant_type->getAtomicTypes() as $const_type_atomic) { + if ($const_type_atomic instanceof Type\Atomic\TKeyedArray + || $const_type_atomic instanceof Type\Atomic\TArray + ) { + if ($const_type_atomic instanceof Type\Atomic\TKeyedArray) { + $const_type_atomic = $const_type_atomic->getGenericArrayType(); + } + + if ($return_type instanceof Type\Atomic\TKeyOfClassConstant) { + return array_values($const_type_atomic->type_params[0]->getAtomicTypes()); + } + + return array_values($const_type_atomic->type_params[1]->getAtomicTypes()); + } + } + } + } + + return $return_type; + } + + if ($return_type instanceof Type\Atomic\TArray + || $return_type instanceof Type\Atomic\TGenericObject + || $return_type instanceof Type\Atomic\TIterable + ) { + foreach ($return_type->type_params as $k => $type_param) { + /** @psalm-suppress PropertyTypeCoercion */ + $return_type->type_params[$k] = self::expandUnion( + $codebase, + $type_param, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + } + } elseif ($return_type instanceof Type\Atomic\TKeyedArray) { + foreach ($return_type->properties as &$property_type) { + $property_type = self::expandUnion( + $codebase, + $property_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + } + } elseif ($return_type instanceof Type\Atomic\TList) { + $return_type->type_param = self::expandUnion( + $codebase, + $return_type->type_param, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + } + + if ($return_type instanceof Type\Atomic\TObjectWithProperties) { + foreach ($return_type->properties as &$property_type) { + $property_type = self::expandUnion( + $codebase, + $property_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + } + } + + if ($return_type instanceof Type\Atomic\TCallable + || $return_type instanceof Type\Atomic\TClosure + ) { + if ($return_type->params) { + foreach ($return_type->params as $param) { + if ($param->type) { + $param->type = self::expandUnion( + $codebase, + $param->type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + } + } + } + if ($return_type->return_type) { + $return_type->return_type = self::expandUnion( + $codebase, + $return_type->return_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + } + } + + if ($return_type instanceof Type\Atomic\TConditional) { + if ($evaluate_conditional_types) { + $assertion = null; + + if ($return_type->conditional_type->isSingle()) { + foreach ($return_type->conditional_type->getAtomicTypes() as $condition_atomic_type) { + $candidate = self::expandAtomic( + $codebase, + $condition_atomic_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + + if (!is_array($candidate)) { + $assertion = $candidate->getAssertionString(); + } + } + } + + $if_conditional_return_types = []; + + foreach ($return_type->if_type->getAtomicTypes() as $if_atomic_type) { + $candidate = self::expandAtomic( + $codebase, + $if_atomic_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + + $candidate_types = is_array($candidate) ? $candidate : [$candidate]; + + $if_conditional_return_types = array_merge( + $if_conditional_return_types, + $candidate_types + ); + } + + $else_conditional_return_types = []; + + foreach ($return_type->else_type->getAtomicTypes() as $else_atomic_type) { + $candidate = self::expandAtomic( + $codebase, + $else_atomic_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + + $candidate_types = is_array($candidate) ? $candidate : [$candidate]; + + $else_conditional_return_types = array_merge( + $else_conditional_return_types, + $candidate_types + ); + } + + if ($assertion && $return_type->param_name === (string) $return_type->if_type) { + $if_conditional_return_type = TypeCombination::combineTypes( + $if_conditional_return_types, + $codebase + ); + + $if_conditional_return_type = \Psalm\Internal\Type\SimpleAssertionReconciler::reconcile( + $assertion, + $codebase, + $if_conditional_return_type + ); + + + if ($if_conditional_return_type) { + $if_conditional_return_types = array_values($if_conditional_return_type->getAtomicTypes()); + } + } + + if ($assertion && $return_type->param_name === (string) $return_type->else_type) { + $else_conditional_return_type = TypeCombination::combineTypes( + $else_conditional_return_types, + $codebase + ); + + $else_conditional_return_type = \Psalm\Internal\Type\SimpleNegatedAssertionReconciler::reconcile( + $assertion, + $else_conditional_return_type + ); + + if ($else_conditional_return_type) { + $else_conditional_return_types = array_values($else_conditional_return_type->getAtomicTypes()); + } + } + + $all_conditional_return_types = array_merge( + $if_conditional_return_types, + $else_conditional_return_types + ); + + foreach ($all_conditional_return_types as $i => $conditional_return_type) { + if ($conditional_return_type instanceof Type\Atomic\TVoid + && count($all_conditional_return_types) > 1 + ) { + $all_conditional_return_types[$i] = new Type\Atomic\TNull(); + $all_conditional_return_types[$i]->from_docblock = true; + } + } + + $combined = TypeCombination::combineTypes( + array_values($all_conditional_return_types), + $codebase + ); + + return array_values($combined->getAtomicTypes()); + } + + $return_type->conditional_type = self::expandUnion( + $codebase, + $return_type->conditional_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + + $return_type->if_type = self::expandUnion( + $codebase, + $return_type->if_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + + $return_type->else_type = self::expandUnion( + $codebase, + $return_type->else_type, + $self_class, + $static_class_type, + $parent_class, + $evaluate_class_constants, + $evaluate_conditional_types, + $final + ); + } + + return $return_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeParser.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeParser.php new file mode 100644 index 0000000000000000000000000000000000000000..c2cd5058776f3b63d192628c9bd35980dfb37251 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeParser.php @@ -0,0 +1,1017 @@ + $type_tokens + * @param array{int,int}|null $php_version + * @param array> $template_type_map + * @param array $type_aliases + * + */ + public static function parseTokens( + array $type_tokens, + ?array $php_version = null, + array $template_type_map = [], + array $type_aliases = [] + ): Union { + if (count($type_tokens) === 1) { + $only_token = $type_tokens[0]; + + // Note: valid identifiers can include class names or $this + if (!preg_match('@^(\$this|\\\\?[a-zA-Z_\x7f-\xff][\\\\\-0-9a-zA-Z_\x7f-\xff]*)$@', $only_token[0])) { + if (!\is_numeric($only_token[0]) + && strpos($only_token[0], '\'') !== false + && strpos($only_token[0], '"') !== false + ) { + throw new TypeParseTreeException("Invalid type '$only_token[0]'"); + } + } else { + $only_token[0] = TypeTokenizer::fixScalarTerms($only_token[0], $php_version); + + $atomic = Atomic::create($only_token[0], $php_version, $template_type_map, $type_aliases); + $atomic->offset_start = 0; + $atomic->offset_end = strlen($only_token[0]); + + return new Union([$atomic]); + } + } + + $parse_tree = (new ParseTreeCreator($type_tokens))->create(); + $codebase = ProjectAnalyzer::getInstance()->getCodebase(); + $parsed_type = self::getTypeFromTree( + $parse_tree, + $codebase, + $php_version, + $template_type_map, + $type_aliases + ); + + if (!($parsed_type instanceof Union)) { + $parsed_type = new Union([$parsed_type]); + } + + return $parsed_type; + } + + /** + * @param array{int,int}|null $php_version + * @param array> $template_type_map + * @param array $type_aliases + * + * @return Atomic|Union + */ + public static function getTypeFromTree( + ParseTree $parse_tree, + Codebase $codebase, + ?array $php_version = null, + array $template_type_map = [], + array $type_aliases = [] + ): TypeNode { + if ($parse_tree instanceof ParseTree\GenericTree) { + $generic_type = $parse_tree->value; + + $generic_params = []; + + foreach ($parse_tree->children as $i => $child_tree) { + $tree_type = self::getTypeFromTree( + $child_tree, + $codebase, + null, + $template_type_map, + $type_aliases + ); + + if ($generic_type === 'class-string-map' + && $i === 0 + ) { + if ($tree_type instanceof TTemplateParam) { + $template_type_map[$tree_type->param_name] = ['class-string-map' => [$tree_type->as]]; + } elseif ($tree_type instanceof TNamedObject) { + $template_type_map[$tree_type->value] = ['class-string-map' => [\Psalm\Type::getObject()]]; + } + } + + $generic_params[] = $tree_type instanceof Union ? $tree_type : new Union([$tree_type]); + } + + $generic_type_value = TypeTokenizer::fixScalarTerms($generic_type); + + if (($generic_type_value === 'array' + || $generic_type_value === 'non-empty-array' + || $generic_type_value === 'associative-array') + && count($generic_params) === 1 + ) { + array_unshift($generic_params, new Union([new TArrayKey])); + } elseif (count($generic_params) === 1 + && in_array( + $generic_type_value, + ['iterable', 'Traversable', 'Iterator', 'IteratorAggregate', 'arraylike-object'], + true + ) + ) { + array_unshift($generic_params, new Union([new TMixed])); + } elseif ($generic_type_value === 'Generator') { + if (count($generic_params) === 1) { + array_unshift($generic_params, new Union([new TMixed])); + } + + for ($i = 0, $l = 4 - count($generic_params); $i < $l; ++$i) { + $generic_params[] = new Union([new TMixed]); + } + } + + if (!$generic_params) { + throw new TypeParseTreeException('No generic params provided for type'); + } + + if ($generic_type_value === 'array' || $generic_type_value === 'associative-array') { + if ($generic_params[0]->isMixed()) { + $generic_params[0] = \Psalm\Type::getArrayKey(); + } + + return new TArray($generic_params); + } + + if ($generic_type_value === 'arraylike-object') { + $traversable = new TGenericObject('Traversable', $generic_params); + $array_acccess = new TGenericObject('ArrayAccess', $generic_params); + $traversable->extra_types[$array_acccess->getKey()] = $array_acccess; + + return $traversable; + } + + if ($generic_type_value === 'non-empty-array') { + if ($generic_params[0]->isMixed()) { + $generic_params[0] = \Psalm\Type::getArrayKey(); + } + + return new TNonEmptyArray($generic_params); + } + + if ($generic_type_value === 'iterable') { + return new TIterable($generic_params); + } + + if ($generic_type_value === 'list') { + return new TList($generic_params[0]); + } + + if ($generic_type_value === 'non-empty-list') { + return new TNonEmptyList($generic_params[0]); + } + + if ($generic_type_value === 'class-string') { + $class_name = (string) $generic_params[0]; + + if (isset($template_type_map[$class_name])) { + $first_class = array_keys($template_type_map[$class_name])[0]; + + return self::getGenericParamClass( + $class_name, + $template_type_map[$class_name][$first_class][0], + $first_class + ); + } + + $param_union_types = array_values($generic_params[0]->getAtomicTypes()); + + if (count($param_union_types) > 1) { + throw new TypeParseTreeException('Union types are not allowed in class string param'); + } + + if (!$param_union_types[0] instanceof TNamedObject) { + throw new TypeParseTreeException('Class string param should be a named object'); + } + + return new TClassString($class_name, $param_union_types[0]); + } + + if ($generic_type_value === 'class-string-map') { + if (count($generic_params) !== 2) { + throw new TypeParseTreeException( + 'There should only be two params for class-string-map, ' + . count($generic_params) . ' provided' + ); + } + + $template_marker_parts = array_values($generic_params[0]->getAtomicTypes()); + + $template_marker = $template_marker_parts[0]; + + $template_as_type = null; + + if ($template_marker instanceof TNamedObject) { + $template_param_name = $template_marker->value; + } elseif ($template_marker instanceof Atomic\TTemplateParam) { + $template_param_name = $template_marker->param_name; + $template_as_type = array_values($template_marker->as->getAtomicTypes())[0]; + + if (!$template_as_type instanceof TNamedObject) { + throw new TypeParseTreeException( + 'Unrecognised as type' + ); + } + } else { + throw new TypeParseTreeException( + 'Unrecognised class-string-map templated param' + ); + } + + return new TClassStringMap( + $template_param_name, + $template_as_type, + $generic_params[1] + ); + } + + if ($generic_type_value === 'key-of') { + $param_name = (string) $generic_params[0]; + + if (isset($template_type_map[$param_name])) { + $defining_class = array_keys($template_type_map[$param_name])[0]; + + return new Atomic\TTemplateKeyOf( + $param_name, + $defining_class, + $template_type_map[$param_name][$defining_class][0] + ); + } + + $param_union_types = array_values($generic_params[0]->getAtomicTypes()); + + if (count($param_union_types) > 1) { + throw new TypeParseTreeException('Union types are not allowed in key-of type'); + } + + if (!$param_union_types[0] instanceof Atomic\TScalarClassConstant) { + throw new TypeParseTreeException( + 'Untemplated key-of param ' . $param_name . ' should be a class constant' + ); + } + + return new Atomic\TKeyOfClassConstant( + $param_union_types[0]->fq_classlike_name, + $param_union_types[0]->const_name + ); + } + + if ($generic_type_value === 'value-of') { + $param_name = (string) $generic_params[0]; + + if (isset($template_type_map[$param_name])) { + $defining_class = array_keys($template_type_map[$param_name])[0]; + + return new Atomic\TTemplateKeyOf( + $param_name, + $defining_class, + $template_type_map[$param_name][$defining_class][0] + ); + } + + $param_union_types = array_values($generic_params[0]->getAtomicTypes()); + + if (count($param_union_types) > 1) { + throw new TypeParseTreeException('Union types are not allowed in value-of type'); + } + + if (!$param_union_types[0] instanceof Atomic\TScalarClassConstant) { + throw new TypeParseTreeException( + 'Untemplated value-of param ' . $param_name . ' should be a class constant' + ); + } + + return new Atomic\TValueOfClassConstant( + $param_union_types[0]->fq_classlike_name, + $param_union_types[0]->const_name + ); + } + + if (isset(TypeTokenizer::PSALM_RESERVED_WORDS[$generic_type_value]) + && $generic_type_value !== 'self' + && $generic_type_value !== 'static' + ) { + throw new TypeParseTreeException('Cannot create generic object with reserved word'); + } + + return new TGenericObject($generic_type_value, $generic_params); + } + + if ($parse_tree instanceof ParseTree\UnionTree) { + $has_null = false; + + $atomic_types = []; + + foreach ($parse_tree->children as $child_tree) { + if ($child_tree instanceof ParseTree\NullableTree) { + if (!isset($child_tree->children[0])) { + throw new TypeParseTreeException('Invalid ? character'); + } + + $atomic_type = self::getTypeFromTree( + $child_tree->children[0], + $codebase, + null, + $template_type_map, + $type_aliases + ); + $has_null = true; + } else { + $atomic_type = self::getTypeFromTree( + $child_tree, + $codebase, + null, + $template_type_map, + $type_aliases + ); + } + + if ($atomic_type instanceof Union) { + foreach ($atomic_type->getAtomicTypes() as $type) { + $atomic_types[] = $type; + } + + continue; + } + + $atomic_types[] = $atomic_type; + } + + if ($has_null) { + $atomic_types[] = new TNull; + } + + if (!$atomic_types) { + throw new TypeParseTreeException( + 'No atomic types found' + ); + } + + return TypeCombination::combineTypes($atomic_types); + } + + if ($parse_tree instanceof ParseTree\IntersectionTree) { + $intersection_types = array_map( + /** + * @return Atomic + */ + function (ParseTree $child_tree) use ($codebase, $template_type_map, $type_aliases) { + $atomic_type = self::getTypeFromTree( + $child_tree, + $codebase, + null, + $template_type_map, + $type_aliases + ); + + if (!$atomic_type instanceof Atomic) { + throw new TypeParseTreeException( + 'Intersection types cannot contain unions' + ); + } + + return $atomic_type; + }, + $parse_tree->children + ); + + $first_type = \reset($intersection_types); + $last_type = \end($intersection_types); + + $onlyTKeyedArray = $first_type instanceof TKeyedArray + || $last_type instanceof TKeyedArray; + + foreach ($intersection_types as $intersection_type) { + if (!$intersection_type instanceof TKeyedArray + && ($intersection_type !== $first_type + || !$first_type instanceof TArray) + && ($intersection_type !== $last_type + || !$last_type instanceof TArray) + ) { + $onlyTKeyedArray = false; + break; + } + } + + if ($onlyTKeyedArray) { + /** @var non-empty-array */ + $properties = []; + + if ($first_type instanceof TArray) { + \array_shift($intersection_types); + } elseif ($last_type instanceof TArray) { + \array_pop($intersection_types); + } + + /** @var TKeyedArray $intersection_type */ + foreach ($intersection_types as $intersection_type) { + foreach ($intersection_type->properties as $property => $property_type) { + if (!array_key_exists($property, $properties)) { + $properties[$property] = clone $property_type; + continue; + } + + $intersection_type = \Psalm\Type::intersectUnionTypes( + $properties[$property], + $property_type, + $codebase + ); + if ($intersection_type === null) { + throw new TypeParseTreeException( + 'Incompatible intersection types for "' . $property . '", ' + . $properties[$property] . ' and ' . $property_type + . ' provided' + ); + } + $properties[$property] = $intersection_type; + } + } + + $keyed_array = new TKeyedArray($properties); + + if ($first_type instanceof TArray) { + $keyed_array->previous_key_type = $first_type->type_params[0]; + $keyed_array->previous_value_type = $first_type->type_params[1]; + } elseif ($last_type instanceof TArray) { + $keyed_array->previous_key_type = $last_type->type_params[0]; + $keyed_array->previous_value_type = $last_type->type_params[1]; + } + + return $keyed_array; + } + + $keyed_intersection_types = []; + + if ($intersection_types[0] instanceof TTypeAlias) { + foreach ($intersection_types as $intersection_type) { + if (!$intersection_type instanceof TTypeAlias) { + throw new TypeParseTreeException( + 'Intersection types with a type alias can only be comprised of other type aliases, ' + . get_class($intersection_type) . ' provided' + ); + } + + $keyed_intersection_types[$intersection_type->getKey()] = $intersection_type; + } + + $first_type = array_shift($keyed_intersection_types); + + if ($keyed_intersection_types) { + $first_type->extra_types = $keyed_intersection_types; + } + } else { + foreach ($intersection_types as $intersection_type) { + if (!$intersection_type instanceof TIterable + && !$intersection_type instanceof TNamedObject + && !$intersection_type instanceof TTemplateParam + && !$intersection_type instanceof TObjectWithProperties + ) { + throw new TypeParseTreeException( + 'Intersection types must be all objects or all object-like arrays, ' + . get_class($intersection_type) . ' provided' + ); + } + + $keyed_intersection_types[ + $intersection_type instanceof TIterable + ? $intersection_type->getId() + : $intersection_type->getKey() + ] = $intersection_type; + } + + $intersect_static = false; + + if (isset($keyed_intersection_types['static'])) { + unset($keyed_intersection_types['static']); + $intersect_static = true; + } + + if (!$keyed_intersection_types && $intersect_static) { + return new TNamedObject('static'); + } + + $first_type = array_shift($keyed_intersection_types); + + if ($intersect_static + && $first_type instanceof TNamedObject + ) { + $first_type->was_static = true; + } + + if ($keyed_intersection_types) { + $first_type->extra_types = $keyed_intersection_types; + } + } + + return $first_type; + } + + if ($parse_tree instanceof ParseTree\KeyedArrayTree) { + $properties = []; + + $type = $parse_tree->value; + + $is_tuple = true; + + foreach ($parse_tree->children as $i => $property_branch) { + if (!$property_branch instanceof ParseTree\KeyedArrayPropertyTree) { + $property_type = self::getTypeFromTree( + $property_branch, + $codebase, + null, + $template_type_map, + $type_aliases + ); + $property_maybe_undefined = false; + $property_key = (string)$i; + } elseif (count($property_branch->children) === 1) { + $property_type = self::getTypeFromTree( + $property_branch->children[0], + $codebase, + null, + $template_type_map, + $type_aliases + ); + $property_maybe_undefined = $property_branch->possibly_undefined; + $property_key = $property_branch->value; + $is_tuple = false; + } else { + throw new TypeParseTreeException( + 'Missing property type' + ); + } + + if ($property_key[0] === '\'' || $property_key[0] === '"') { + $property_key = \stripslashes(substr($property_key, 1, -1)); + } + + if (!$property_type instanceof Union) { + $property_type = new Union([$property_type]); + } + + if ($property_maybe_undefined) { + $property_type->possibly_undefined = true; + } + + $properties[$property_key] = $property_type; + } + + if ($type !== 'array' && $type !== 'object' && $type !== 'callable-array') { + throw new TypeParseTreeException('Unexpected brace character'); + } + + if (!$properties) { + throw new TypeParseTreeException('No properties supplied for TKeyedArray'); + } + + if ($type === 'object') { + return new TObjectWithProperties($properties); + } + + if ($type === 'callable-array') { + return new Atomic\TCallableKeyedArray($properties); + } + + $object_like = new TKeyedArray($properties); + + if ($is_tuple) { + $object_like->sealed = true; + $object_like->is_list = true; + } + + return $object_like; + } + + if ($parse_tree instanceof ParseTree\CallableWithReturnTypeTree) { + $callable_type = self::getTypeFromTree( + $parse_tree->children[0], + $codebase, + null, + $template_type_map, + $type_aliases + ); + + if (!$callable_type instanceof TCallable && !$callable_type instanceof TClosure) { + throw new \InvalidArgumentException('Parsing callable tree node should return TCallable'); + } + + if (!isset($parse_tree->children[1])) { + throw new TypeParseTreeException('Invalid return type'); + } + + $return_type = self::getTypeFromTree( + $parse_tree->children[1], + $codebase, + null, + $template_type_map, + $type_aliases + ); + + $callable_type->return_type = $return_type instanceof Union ? $return_type : new Union([$return_type]); + + return $callable_type; + } + + if ($parse_tree instanceof ParseTree\CallableTree) { + $params = array_map( + /** + * @return FunctionLikeParameter + */ + function (ParseTree $child_tree) use ( + $codebase, + $template_type_map, + $type_aliases + ): FunctionLikeParameter { + $is_variadic = false; + $is_optional = false; + + if ($child_tree instanceof ParseTree\CallableParamTree) { + if (isset($child_tree->children[0])) { + $tree_type = self::getTypeFromTree( + $child_tree->children[0], + $codebase, + null, + $template_type_map, + $type_aliases + ); + } else { + $tree_type = new TMixed(); + } + + $is_variadic = $child_tree->variadic; + $is_optional = $child_tree->has_default; + } else { + if ($child_tree instanceof ParseTree\Value && strpos($child_tree->value, '$') > 0) { + $child_tree->value = preg_replace('/(.+)\$.*/', '$1', $child_tree->value); + } + + $tree_type = self::getTypeFromTree( + $child_tree, + $codebase, + null, + $template_type_map, + $type_aliases + ); + } + + $tree_type = $tree_type instanceof Union ? $tree_type : new Union([$tree_type]); + + $param = new FunctionLikeParameter( + '', + false, + $tree_type, + null, + null, + $is_optional, + false, + $is_variadic + ); + + // type is not authoratative + $param->signature_type = null; + + return $param; + }, + $parse_tree->children + ); + $pure = strpos($parse_tree->value, 'pure-') === 0 ? true : null; + + if (in_array(strtolower($parse_tree->value), ['closure', '\closure'], true)) { + return new TClosure('Closure', $params, null, $pure); + } + + return new TCallable('callable', $params, null, $pure); + } + + if ($parse_tree instanceof ParseTree\EncapsulationTree) { + return self::getTypeFromTree( + $parse_tree->children[0], + $codebase, + null, + $template_type_map, + $type_aliases + ); + } + + if ($parse_tree instanceof ParseTree\NullableTree) { + if (!isset($parse_tree->children[0])) { + throw new TypeParseTreeException('Misplaced question mark'); + } + + $non_nullable_type = self::getTypeFromTree( + $parse_tree->children[0], + $codebase, + null, + $template_type_map, + $type_aliases + ); + + if ($non_nullable_type instanceof Union) { + $non_nullable_type->addType(new TNull); + + return $non_nullable_type; + } + + return TypeCombination::combineTypes([ + new TNull, + $non_nullable_type, + ]); + } + + if ($parse_tree instanceof ParseTree\MethodTree + || $parse_tree instanceof ParseTree\MethodWithReturnTypeTree + ) { + throw new TypeParseTreeException('Misplaced brackets'); + } + + if ($parse_tree instanceof ParseTree\IndexedAccessTree) { + if (!isset($parse_tree->children[0]) || !$parse_tree->children[0] instanceof ParseTree\Value) { + throw new TypeParseTreeException('Unrecognised indexed access'); + } + + $offset_param_name = $parse_tree->value; + $array_param_name = $parse_tree->children[0]->value; + + if (!isset($template_type_map[$offset_param_name])) { + throw new TypeParseTreeException('Unrecognised template param ' . $offset_param_name); + } + + if (!isset($template_type_map[$array_param_name])) { + throw new TypeParseTreeException('Unrecognised template param ' . $array_param_name); + } + + $offset_template_data = $template_type_map[$offset_param_name]; + + $offset_defining_class = array_keys($offset_template_data)[0]; + + if (!$offset_defining_class + && isset($offset_template_data['']) + && $offset_template_data[''][0]->isSingle() + ) { + $offset_template_type = array_values($offset_template_data[''][0]->getAtomicTypes())[0]; + + if ($offset_template_type instanceof Atomic\TTemplateKeyOf) { + $offset_defining_class = (string) $offset_template_type->defining_class; + } + } + + $array_defining_class = array_keys($template_type_map[$array_param_name])[0]; + + if ($offset_defining_class !== $array_defining_class + && substr($offset_defining_class, 0, 3) !== 'fn-' + ) { + throw new TypeParseTreeException('Template params are defined in different locations'); + } + + return new Atomic\TTemplateIndexedAccess( + $array_param_name, + $offset_param_name, + $array_defining_class + ); + } + + if ($parse_tree instanceof ParseTree\TemplateAsTree) { + return new Atomic\TTemplateParam( + $parse_tree->param_name, + new Union([new TNamedObject($parse_tree->as)]), + 'class-string-map' + ); + } + + if ($parse_tree instanceof ParseTree\ConditionalTree) { + $template_param_name = $parse_tree->condition->param_name; + + if (!isset($template_type_map[$template_param_name])) { + throw new TypeParseTreeException('Unrecognized template \'' . $template_param_name . '\''); + } + + if (count($parse_tree->children) !== 2) { + throw new TypeParseTreeException('Invalid conditional'); + } + + $first_class = array_keys($template_type_map[$template_param_name])[0]; + + $conditional_type = self::getTypeFromTree( + $parse_tree->condition->children[0], + $codebase, + null, + $template_type_map, + $type_aliases + ); + + $if_type = self::getTypeFromTree( + $parse_tree->children[0], + $codebase, + null, + $template_type_map, + $type_aliases + ); + + $else_type = self::getTypeFromTree( + $parse_tree->children[1], + $codebase, + null, + $template_type_map, + $type_aliases + ); + + if ($conditional_type instanceof Atomic) { + $conditional_type = new Union([$conditional_type]); + } + + if ($if_type instanceof Atomic) { + $if_type = new Union([$if_type]); + } + + if ($else_type instanceof Atomic) { + $else_type = new Union([$else_type]); + } + + return new Atomic\TConditional( + $template_param_name, + $first_class, + $template_type_map[$template_param_name][$first_class][0], + $conditional_type, + $if_type, + $else_type + ); + } + + if (!$parse_tree instanceof ParseTree\Value) { + throw new \InvalidArgumentException('Unrecognised parse tree type ' . get_class($parse_tree)); + } + + if ($parse_tree->value[0] === '"' || $parse_tree->value[0] === '\'') { + return new TLiteralString(substr($parse_tree->value, 1, -1)); + } + + if (strpos($parse_tree->value, '::')) { + [$fq_classlike_name, $const_name] = explode('::', $parse_tree->value); + + if (isset($template_type_map[$fq_classlike_name]) && $const_name === 'class') { + $first_class = array_keys($template_type_map[$fq_classlike_name])[0]; + + return self::getGenericParamClass( + $fq_classlike_name, + $template_type_map[$fq_classlike_name][$first_class][0], + $first_class + ); + } + + if ($const_name === 'class') { + return new Atomic\TLiteralClassString($fq_classlike_name); + } + + return new Atomic\TScalarClassConstant($fq_classlike_name, $const_name); + } + + if (preg_match('/^\-?(0|[1-9][0-9]*)(\.[0-9]{1,})$/', $parse_tree->value)) { + return new TLiteralFloat((float) $parse_tree->value); + } + + if (preg_match('/^\-?(0|[1-9][0-9]*)$/', $parse_tree->value)) { + return new TLiteralInt((int) $parse_tree->value); + } + + if (!preg_match('@^(\$this|\\\\?[a-zA-Z_\x7f-\xff][\\\\\-0-9a-zA-Z_\x7f-\xff]*)$@', $parse_tree->value)) { + throw new TypeParseTreeException('Invalid type \'' . $parse_tree->value . '\''); + } + + $atomic_type_string = TypeTokenizer::fixScalarTerms($parse_tree->value, $php_version); + + $atomic_type = Atomic::create($atomic_type_string, $php_version, $template_type_map, $type_aliases); + + $atomic_type->offset_start = $parse_tree->offset_start; + $atomic_type->offset_end = $parse_tree->offset_end; + + return $atomic_type; + } + + private static function getGenericParamClass( + string $param_name, + Union $as, + string $defining_class + ) : Atomic\TTemplateParamClass { + if ($as->hasMixed()) { + return new Atomic\TTemplateParamClass( + $param_name, + 'object', + null, + $defining_class + ); + } + + if (!$as->isSingle()) { + throw new TypeParseTreeException( + 'Invalid templated classname \'' . $as . '\'' + ); + } + + foreach ($as->getAtomicTypes() as $t) { + if ($t instanceof TObject) { + return new Atomic\TTemplateParamClass( + $param_name, + 'object', + null, + $defining_class + ); + } + + if ($t instanceof TIterable) { + $traversable = new TGenericObject( + 'Traversable', + $t->type_params + ); + + $as->substitute(new Union([$t]), new Union([$traversable])); + + return new Atomic\TTemplateParamClass( + $param_name, + $traversable->value, + $traversable, + $defining_class + ); + } + + if ($t instanceof Atomic\TTemplateParam) { + $t_atomic_types = $t->as->getAtomicTypes(); + $t_atomic_type = \count($t_atomic_types) === 1 ? \reset($t_atomic_types) : null; + + if (!$t_atomic_type instanceof TNamedObject) { + $t_atomic_type = null; + } + + return new Atomic\TTemplateParamClass( + $t->param_name, + $t_atomic_type ? $t_atomic_type->value : 'object', + $t_atomic_type, + $t->defining_class + ); + } + + if (!$t instanceof TNamedObject) { + throw new TypeParseTreeException( + 'Invalid templated classname \'' . $t->getId() . '\'' + ); + } + + return new Atomic\TTemplateParamClass( + $param_name, + $t->value, + $t, + $defining_class + ); + } + + throw new \LogicException('Should never get here'); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeTokenizer.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeTokenizer.php new file mode 100644 index 0000000000000000000000000000000000000000..60b4be3d104b69bedca4bd2f99dce18a5e3eb50a --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/TypeTokenizer.php @@ -0,0 +1,483 @@ + + */ + public const PSALM_RESERVED_WORDS = [ + 'int' => true, + 'string' => true, + 'float' => true, + 'bool' => true, + 'false' => true, + 'true' => true, + 'object' => true, + 'empty' => true, + 'callable' => true, + 'array' => true, + 'non-empty-array' => true, + 'non-empty-string' => true, + 'iterable' => true, + 'null' => true, + 'mixed' => true, + 'numeric-string' => true, + 'class-string' => true, + 'callable-string' => true, + 'callable-array' => true, + 'pure-callable' => true, + 'pure-Closure' => true, + 'trait-string' => true, + 'mysql-escaped-string' => true, + 'html-escaped-string' => true, + 'lowercase-string' => true, + 'non-empty-lowercase-string' => true, + 'positive-int' => true, + 'boolean' => true, + 'integer' => true, + 'double' => true, + 'real' => true, + 'resource' => true, + 'void' => true, + 'self' => true, + 'static' => true, + 'scalar' => true, + 'numeric' => true, + 'no-return' => true, + 'never-return' => true, + 'never-returns' => true, + 'array-key' => true, + 'key-of' => true, + 'value-of' => true, + 'non-empty-countable' => true, + 'list' => true, + 'non-empty-list' => true, + 'class-string-map' => true, + 'open-resource' => true, + 'closed-resource' => true, + 'associative-array' => true, + 'arraylike-object' => true, + ]; + + /** + * @var array> + */ + private static $memoized_tokens = []; + + /** + * @return list + */ + public static function tokenize(string $string_type, bool $ignore_space = true): array + { + $type_tokens = [['', 0]]; + $was_char = false; + $quote_char = null; + $escaped = false; + + if (isset(self::$memoized_tokens[$string_type])) { + return self::$memoized_tokens[$string_type]; + } + + // index of last type token + $rtc = 0; + + $chars = str_split($string_type); + $was_space = false; + + for ($i = 0, $c = count($chars); $i < $c; ++$i) { + $char = $chars[$i]; + + if (!$quote_char && $char === ' ' && $ignore_space) { + $was_space = true; + continue; + } + + if ($was_space + && ($char === '$' + || ($char === '.' + && ($chars[$i + 1] ?? null) === '.' + && ($chars[$i + 2] ?? null) === '.' + && ($chars[$i + 3] ?? null) === '$')) + ) { + $type_tokens[++$rtc] = [' ', $i - 1]; + $type_tokens[++$rtc] = ['', $i]; + } elseif ($was_space + && ($char === 'a' || $char === 'i') + && ($chars[$i + 1] ?? null) === 's' + && ($chars[$i + 2] ?? null) === ' ' + ) { + $type_tokens[++$rtc] = [$char . 's', $i - 1]; + $type_tokens[++$rtc] = ['', ++$i]; + continue; + } elseif ($was_char) { + $type_tokens[++$rtc] = ['', $i]; + } + + if ($quote_char) { + if ($char === $quote_char && $i > 0 && !$escaped) { + $quote_char = null; + + $type_tokens[$rtc][0] .= $char; + $was_char = true; + + continue; + } + + $was_char = false; + + if ($char === '\\' + && !$escaped + && $i < $c - 1 + && ($chars[$i + 1] === $quote_char || $chars[$i + 1] === '\\') + ) { + $escaped = true; + continue; + } + + $escaped = false; + + $type_tokens[$rtc][0] .= $char; + + continue; + } + + if ($char === '"' || $char === '\'') { + if ($type_tokens[$rtc][0] === '') { + $type_tokens[$rtc] = [$char, $i]; + } else { + $type_tokens[++$rtc] = [$char, $i]; + } + + $quote_char = $char; + + $was_char = false; + $was_space = false; + + continue; + } + + if ($char === '<' + || $char === '>' + || $char === '|' + || $char === '?' + || $char === ',' + || $char === '{' + || $char === '}' + || $char === '[' + || $char === ']' + || $char === '(' + || $char === ')' + || $char === ' ' + || $char === '&' + || $char === '=' + ) { + if ($char === '(' + && $type_tokens[$rtc][0] === 'func_num_args' + && isset($chars[$i + 1]) + && $chars[$i + 1] === ')' + ) { + $type_tokens[$rtc][0] = 'func_num_args()'; + ++$i; + + continue; + } + + if ($type_tokens[$rtc][0] === '') { + $type_tokens[$rtc] = [$char, $i]; + } else { + $type_tokens[++$rtc] = [$char, $i]; + } + + $was_char = true; + $was_space = false; + + continue; + } + + if ($char === ':') { + if ($i + 1 < $c && $chars[$i + 1] === ':') { + if ($type_tokens[$rtc][0] === '') { + $type_tokens[$rtc] = ['::', $i]; + } else { + $type_tokens[++$rtc] = ['::', $i]; + } + + $was_char = true; + $was_space = false; + + ++$i; + + continue; + } + + if ($type_tokens[$rtc][0] === '') { + $type_tokens[$rtc] = [':', $i]; + } else { + $type_tokens[++$rtc] = [':', $i]; + } + + $was_char = true; + $was_space = false; + + continue; + } + + if ($char === '.') { + if ($i + 1 < $c + && is_numeric($chars[$i + 1]) + && $i > 0 + && is_numeric($chars[$i - 1]) + ) { + $type_tokens[$rtc][0] .= $char; + $was_char = false; + $was_space = false; + + continue; + } + + if ($i + 2 > $c || $chars[$i + 1] !== '.' || $chars[$i + 2] !== '.') { + throw new TypeParseTreeException('Unexpected token ' . $char); + } + + if ($type_tokens[$rtc][0] === '') { + $type_tokens[$rtc] = ['...', $i]; + } else { + $type_tokens[++$rtc] = ['...', $i]; + } + + $was_char = true; + $was_space = false; + + $i += 2; + + continue; + } + + $type_tokens[$rtc][0] .= $char; + $was_char = false; + $was_space = false; + } + + /** @var list $type_tokens */ + self::$memoized_tokens[$string_type] = $type_tokens; + + return $type_tokens; + } + + /** + * @param array{int,int}|null $php_version + * + * + * @psalm-pure + */ + public static function fixScalarTerms( + string $type_string, + ?array $php_version = null + ) : string { + $type_string_lc = strtolower($type_string); + + switch ($type_string_lc) { + case 'int': + case 'void': + case 'float': + case 'string': + case 'bool': + case 'callable': + case 'iterable': + case 'array': + case 'object': + case 'true': + case 'false': + case 'null': + case 'mixed': + return $type_string_lc; + } + + switch ($type_string) { + case 'boolean': + return $php_version !== null ? $type_string : 'bool'; + + case 'integer': + return $php_version !== null ? $type_string : 'int'; + + case 'double': + case 'real': + return $php_version !== null ? $type_string : 'float'; + } + + return $type_string; + } + + /** + * @param array|null $template_type_map + * @param array|null $type_aliases + * + * @return list + */ + public static function getFullyQualifiedTokens( + string $string_type, + Aliases $aliases, + ?array $template_type_map = null, + ?array $type_aliases = null, + ?string $self_fqcln = null, + ?string $parent_fqcln = null, + bool $allow_assertions = false + ): array { + $type_tokens = self::tokenize($string_type); + + for ($i = 0, $l = count($type_tokens); $i < $l; ++$i) { + $string_type_token = $type_tokens[$i]; + + if (in_array( + $string_type_token[0], + [ + '<', '>', '|', '?', ',', '{', '}', ':', '::', '[', ']', '(', ')', '&', '=', '...', 'as', 'is', + ], + true + )) { + continue; + } + + if ($string_type_token[0][0] === '\\' + && strlen($string_type_token[0]) === 1 + ) { + throw new TypeParseTreeException("Backslash \"\\\" has to be part of class name."); + } + + if ($string_type_token[0][0] === '"' + || $string_type_token[0][0] === '\'' + || preg_match('/[0-9]/', $string_type_token[0][0]) + ) { + continue; + } + + if (isset($type_tokens[$i + 1]) + && $type_tokens[$i + 1][0] === ':' + && isset($type_tokens[$i - 1]) + && ($type_tokens[$i - 1][0] === '{' || $type_tokens[$i - 1][0] === ',') + ) { + continue; + } + + if ($i > 0 && $type_tokens[$i - 1][0] === '::') { + continue; + } + + if (strpos($string_type_token[0], '$')) { + $string_type_token[0] = preg_replace('/(.+)\$.*/', '$1', $string_type_token[0]); + } + + $fixed_token = !isset($type_tokens[$i + 1]) || $type_tokens[$i + 1][0] !== '(' + ? self::fixScalarTerms($string_type_token[0]) + : $string_type_token[0]; + + $type_tokens[$i][0] = $fixed_token; + $string_type_token[0] = $fixed_token; + + if ($string_type_token[0] === 'self' && $self_fqcln) { + $type_tokens[$i][0] = $self_fqcln; + continue; + } + + if ($string_type_token[0] === 'parent' && $parent_fqcln) { + $type_tokens[$i][0] = $parent_fqcln; + continue; + } + + if (isset(self::PSALM_RESERVED_WORDS[$string_type_token[0]])) { + continue; + } + + if (isset($template_type_map[$string_type_token[0]])) { + continue; + } + + if ($i > 1 + && ($type_tokens[$i - 2][0] === 'class-string-map') + && ($type_tokens[$i - 1][0] === '<') + ) { + $template_type_map[$string_type_token[0]] = true; + continue; + } + + if (isset($type_tokens[$i + 1]) + && isset($type_tokens[$i - 1]) + && ($type_tokens[$i - 1][0] === '{' || $type_tokens[$i - 1][0] === ',') + ) { + $next_char = $type_tokens[$i + 1][0]; + + if ($next_char === ':') { + continue; + } + + if ($next_char === '?' && isset($type_tokens[$i + 2]) && $type_tokens[$i + 2][0] === ':') { + continue; + } + } + + if ($string_type_token[0][0] === '$' || $string_type_token[0][0] === ' ') { + continue; + } + + if (isset($type_tokens[$i + 1]) && $type_tokens[$i + 1][0] === '(') { + continue; + } + + if ($allow_assertions && $string_type_token[0] === 'falsy') { + $type_tokens[$i][0] = 'false-y'; + continue; + } + + if ($string_type_token[0] === 'func_num_args()') { + continue; + } + + if (isset($type_aliases[$string_type_token[0]])) { + $type_alias = $type_aliases[$string_type_token[0]]; + + if ($type_alias instanceof TypeAlias\InlineTypeAlias) { + $replacement_tokens = $type_alias->replacement_tokens; + + array_unshift($replacement_tokens, ['(', $i]); + array_push($replacement_tokens, [')', $i]); + + $diff = count($replacement_tokens) - 1; + + array_splice($type_tokens, $i, 1, $replacement_tokens); + + $i += $diff; + $l += $diff; + } + } else { + $type_tokens[$i][0] = \Psalm\Type::getFQCLNFromString( + $string_type_token[0], + $aliases + ); + } + } + + /** @var list */ + return $type_tokens; + } + + public static function clearCache() : void + { + self::$memoized_tokens = []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/UnionTemplateHandler.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/UnionTemplateHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..4763696ad32c77671e7960edeff3e96d20edc378 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/Type/UnionTemplateHandler.php @@ -0,0 +1,1022 @@ +getAtomicTypes(); + + $had_template = false; + + foreach ($original_atomic_types as $key => $atomic_type) { + $atomic_types = array_merge( + $atomic_types, + self::handleAtomicStandin( + $atomic_type, + $key, + $template_result, + $codebase, + $statements_analyzer, + $input_type, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth, + count($original_atomic_types) === 1, + $union_type->isNullable(), + $had_template + ) + ); + } + + if ($replace) { + if (array_values($original_atomic_types) === $atomic_types) { + return $union_type; + } + + if (!$atomic_types) { + throw new \UnexpectedValueException('Cannot remove all keys'); + } + + $new_union_type = new Union($atomic_types); + $new_union_type->ignore_nullable_issues = $union_type->ignore_nullable_issues; + $new_union_type->ignore_falsable_issues = $union_type->ignore_falsable_issues; + $new_union_type->possibly_undefined = $union_type->possibly_undefined; + + if ($had_template) { + $new_union_type->had_template = true; + } + + return $new_union_type; + } + + return $union_type; + } + + /** + * @return list + */ + private static function handleAtomicStandin( + Atomic $atomic_type, + string $key, + TemplateResult $template_result, + ?Codebase $codebase, + ?StatementsAnalyzer $statements_analyzer, + ?Union $input_type, + ?int $input_arg_offset, + ?string $calling_class, + ?string $calling_function, + bool $replace, + bool $add_upper_bound, + int $depth, + bool $was_single, + bool $was_nullable, + bool &$had_template + ) : array { + if ($bracket_pos = strpos($key, '<')) { + $key = substr($key, 0, $bracket_pos); + } + + if ($atomic_type instanceof Atomic\TTemplateParam + && isset($template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class]) + ) { + $a = self::handleTemplateParamStandin( + $atomic_type, + $key, + $input_type, + $input_arg_offset, + $calling_class, + $calling_function, + $template_result, + $codebase, + $statements_analyzer, + $replace, + $add_upper_bound, + $depth, + $was_nullable, + $had_template + ); + + return $a; + } + + if ($atomic_type instanceof Atomic\TTemplateParamClass + && isset($template_result->template_types[$atomic_type->param_name]) + ) { + if ($replace) { + return self::handleTemplateParamClassStandin( + $atomic_type, + $input_type, + $input_arg_offset, + $template_result, + $depth, + $was_single + ); + } + } + + if ($atomic_type instanceof Atomic\TTemplateIndexedAccess) { + if ($replace) { + $atomic_types = []; + + $include_first = true; + + if (isset($template_result->template_types[$atomic_type->array_param_name][$atomic_type->defining_class]) + && !empty($template_result->upper_bounds[$atomic_type->offset_param_name]) + ) { + $array_template_type + = $template_result->template_types[$atomic_type->array_param_name][$atomic_type->defining_class][0]; + $offset_template_type + = array_values( + $template_result->upper_bounds[$atomic_type->offset_param_name] + )[0][0]; + + if ($array_template_type->isSingle() + && $offset_template_type->isSingle() + && !$array_template_type->isMixed() + && !$offset_template_type->isMixed() + ) { + $array_template_type = array_values($array_template_type->getAtomicTypes())[0]; + $offset_template_type = array_values($offset_template_type->getAtomicTypes())[0]; + + if ($array_template_type instanceof Atomic\TKeyedArray + && ($offset_template_type instanceof Atomic\TLiteralString + || $offset_template_type instanceof Atomic\TLiteralInt) + && isset($array_template_type->properties[$offset_template_type->value]) + ) { + $include_first = false; + + $replacement_type + = clone $array_template_type->properties[$offset_template_type->value]; + + foreach ($replacement_type->getAtomicTypes() as $replacement_atomic_type) { + $atomic_types[] = $replacement_atomic_type; + } + } + } + } + + if ($include_first) { + $atomic_types[] = $atomic_type; + } + + return $atomic_types; + } + + return [$atomic_type]; + } + + if ($atomic_type instanceof Atomic\TTemplateKeyOf) { + if ($replace) { + $atomic_types = []; + + $include_first = true; + + if (isset($template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class])) { + $template_type + = $template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class][0]; + + if ($template_type->isSingle()) { + $template_type = array_values($template_type->getAtomicTypes())[0]; + + if ($template_type instanceof Atomic\TKeyedArray + || $template_type instanceof Atomic\TArray + || $template_type instanceof Atomic\TList + ) { + if ($template_type instanceof Atomic\TKeyedArray) { + $key_type = $template_type->getGenericKeyType(); + } elseif ($template_type instanceof Atomic\TList) { + $key_type = \Psalm\Type::getInt(); + } else { + $key_type = clone $template_type->type_params[0]; + } + + $include_first = false; + + foreach ($key_type->getAtomicTypes() as $key_atomic_type) { + $atomic_types[] = $key_atomic_type; + } + } + } + } + + if ($include_first) { + $atomic_types[] = $atomic_type; + } + + return $atomic_types; + } + + return [$atomic_type]; + } + + $matching_atomic_types = []; + + if ($input_type && $codebase && !$input_type->hasMixed()) { + $matching_atomic_types = self::findMatchingAtomicTypesForTemplate( + $input_type, + $atomic_type, + $key, + $codebase, + $statements_analyzer + ); + } + + if (!$matching_atomic_types) { + $atomic_type = $atomic_type->replaceTemplateTypesWithStandins( + $template_result, + $codebase, + $statements_analyzer, + null, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth + 1 + ); + + return [$atomic_type]; + } + + $atomic_types = []; + + foreach ($matching_atomic_types as $matching_atomic_type) { + $atomic_types[] = $atomic_type->replaceTemplateTypesWithStandins( + $template_result, + $codebase, + $statements_analyzer, + $matching_atomic_type, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth + 1 + ); + } + + return $atomic_types; + } + + /** + * @return list + */ + private static function findMatchingAtomicTypesForTemplate( + Union $input_type, + Atomic $base_type, + string $key, + Codebase $codebase, + ?StatementsAnalyzer $statements_analyzer + ) : array { + $matching_atomic_types = []; + + foreach ($input_type->getAtomicTypes() as $input_key => $atomic_input_type) { + if ($bracket_pos = strpos($input_key, '<')) { + $input_key = substr($input_key, 0, $bracket_pos); + } + + if ($input_key === $key) { + $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; + continue; + } + + if ($atomic_input_type instanceof Atomic\TClosure && $base_type instanceof Atomic\TClosure) { + $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; + continue; + } + + if ($atomic_input_type instanceof Atomic\TCallable + && $base_type instanceof Atomic\TCallable + ) { + $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; + continue; + } + + if ($atomic_input_type instanceof Atomic\TClosure && $base_type instanceof Atomic\TCallable) { + $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; + continue; + } + + if (($atomic_input_type instanceof Atomic\TArray + || $atomic_input_type instanceof Atomic\TKeyedArray + || $atomic_input_type instanceof Atomic\TList) + && $key === 'iterable' + ) { + $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; + continue; + } + + if (strpos($input_key, $key . '&') === 0) { + $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; + continue; + } + + if ($atomic_input_type instanceof Atomic\TLiteralClassString + && $base_type instanceof Atomic\TClassString + && $base_type->as_type + ) { + try { + $classlike_storage = + $codebase->classlike_storage_provider->get($atomic_input_type->value); + + if (isset($classlike_storage->template_type_extends[$base_type->as_type->value])) { + $extends_list = $classlike_storage->template_type_extends[$base_type->as_type->value]; + + $new_generic_params = []; + + foreach ($extends_list as $extends_key => $value) { + if (is_string($extends_key)) { + $new_generic_params[] = $value; + } + } + + if ($new_generic_params) { + $atomic_input_type = new Atomic\TClassString( + $base_type->as_type->value, + new Atomic\TGenericObject( + $base_type->as_type->value, + $new_generic_params + ) + ); + } + + $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; + continue; + } + } catch (\InvalidArgumentException $e) { + // do nothing + } + } + + if ($base_type instanceof Atomic\TCallable) { + $matching_atomic_type = CallableTypeComparator::getCallableFromAtomic( + $codebase, + $atomic_input_type, + null, + $statements_analyzer + ); + + if ($matching_atomic_type) { + $matching_atomic_types[$matching_atomic_type->getId()] = $matching_atomic_type; + continue; + } + } + + if ($atomic_input_type instanceof Atomic\TNamedObject + && ($base_type instanceof Atomic\TNamedObject + || $base_type instanceof Atomic\TIterable) + ) { + if ($base_type instanceof Atomic\TIterable) { + if ($atomic_input_type->value === 'Traversable') { + $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; + continue; + } + + $base_type = new Atomic\TGenericObject( + 'Traversable', + $base_type->type_params + ); + } + + try { + $classlike_storage = + $codebase->classlike_storage_provider->get($atomic_input_type->value); + + if ($atomic_input_type instanceof Atomic\TGenericObject + && isset($classlike_storage->template_type_extends[$base_type->value]) + ) { + $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; + continue; + } + + if (isset($classlike_storage->template_type_extends[$base_type->value])) { + $extends_list = $classlike_storage->template_type_extends[$base_type->value]; + + $new_generic_params = []; + + foreach ($extends_list as $extends_key => $value) { + if (is_string($extends_key)) { + $new_generic_params[] = $value; + } + } + + if (!$new_generic_params) { + $atomic_input_type = new Atomic\TNamedObject($atomic_input_type->value); + } else { + $atomic_input_type = new Atomic\TGenericObject( + $atomic_input_type->value, + $new_generic_params + ); + } + + $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; + continue; + } + } catch (\InvalidArgumentException $e) { + // do nothing + } + } + } + + return array_values($matching_atomic_types); + } + + /** + * @return list + */ + private static function handleTemplateParamStandin( + Atomic\TTemplateParam $atomic_type, + string $key, + ?Union $input_type, + ?int $input_arg_offset, + ?string $calling_class, + ?string $calling_function, + TemplateResult $template_result, + ?Codebase $codebase, + ?StatementsAnalyzer $statements_analyzer, + bool $replace, + bool $add_upper_bound, + int $depth, + bool $was_nullable, + bool &$had_template + ) : array { + $template_type = $template_result->template_types + [$atomic_type->param_name] + [$atomic_type->defining_class] + [0]; + + if ($template_type->getId() === $key) { + return array_values($template_type->getAtomicTypes()); + } + + $replacement_type = $template_type; + + $param_name_key = $atomic_type->param_name; + + if (strpos($key, '&')) { + $param_name_key = $key; + } + + $extra_types = []; + + if ($atomic_type->extra_types) { + foreach ($atomic_type->extra_types as $extra_type) { + $extra_type = self::replaceTemplateTypesWithStandins( + new \Psalm\Type\Union([$extra_type]), + $template_result, + $codebase, + $statements_analyzer, + $input_type, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth + 1 + ); + + if ($extra_type->isSingle()) { + $extra_type = array_values($extra_type->getAtomicTypes())[0]; + + if ($extra_type instanceof Atomic\TNamedObject + || $extra_type instanceof Atomic\TTemplateParam + || $extra_type instanceof Atomic\TIterable + || $extra_type instanceof Atomic\TObjectWithProperties + ) { + $extra_types[$extra_type->getKey()] = $extra_type; + } + } + } + } + + if ($replace) { + $atomic_types = []; + + if ($replacement_type->hasMixed() + && !$atomic_type->as->hasMixed() + ) { + foreach ($atomic_type->as->getAtomicTypes() as $as_atomic_type) { + $atomic_types[] = clone $as_atomic_type; + } + } else { + if ($codebase) { + $replacement_type = TypeExpander::expandUnion( + $codebase, + $replacement_type, + $calling_class, + $calling_class, + null + ); + } + + if ($depth < 10) { + $replacement_type = self::replaceTemplateTypesWithStandins( + $replacement_type, + $template_result, + $codebase, + $statements_analyzer, + $input_type, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth + 1 + ); + } + + foreach ($replacement_type->getAtomicTypes() as $replacement_atomic_type) { + $replacements_found = false; + + // @codingStandardsIgnoreStart + if ($replacement_atomic_type instanceof Atomic\TTemplateKeyOf + && isset($template_result->template_types[$replacement_atomic_type->param_name][$replacement_atomic_type->defining_class][0]) + ) { + $keyed_template = $template_result->template_types[$replacement_atomic_type->param_name][$replacement_atomic_type->defining_class][0]; + + if ($keyed_template->isSingle()) { + $keyed_template = array_values($keyed_template->getAtomicTypes())[0]; + } + + if ($keyed_template instanceof Atomic\TKeyedArray + || $keyed_template instanceof Atomic\TArray + || $keyed_template instanceof Atomic\TList + ) { + if ($keyed_template instanceof Atomic\TKeyedArray) { + $key_type = $keyed_template->getGenericKeyType(); + } elseif ($keyed_template instanceof Atomic\TList) { + $key_type = \Psalm\Type::getInt(); + } else { + $key_type = $keyed_template->type_params[0]; + } + + $replacements_found = true; + + foreach ($key_type->getAtomicTypes() as $key_type_atomic) { + $atomic_types[] = clone $key_type_atomic; + } + + $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class][0] + = clone $key_type; + } + } + + if ($replacement_atomic_type instanceof Atomic\TTemplateParam + && $replacement_atomic_type->defining_class !== $calling_class + && $replacement_atomic_type->defining_class !== 'fn-' . $calling_function + ) { + foreach ($replacement_atomic_type->as->getAtomicTypes() as $nested_type_atomic) { + $replacements_found = true; + $atomic_types[] = clone $nested_type_atomic; + } + } + // @codingStandardsIgnoreEnd + + if (!$replacements_found) { + $atomic_types[] = clone $replacement_atomic_type; + } + + $had_template = true; + } + } + + $matching_input_keys = []; + + if ($codebase) { + $atomic_type->as = TypeExpander::expandUnion( + $codebase, + $atomic_type->as, + $calling_class, + $calling_class, + null + ); + } + + $atomic_type->as = self::replaceTemplateTypesWithStandins( + $atomic_type->as, + $template_result, + $codebase, + $statements_analyzer, + $input_type, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth + 1 + ); + + if ($input_type + && ( + $atomic_type->as->isMixed() + || !$codebase + || UnionTypeComparator::canBeContainedBy( + $codebase, + $input_type, + $atomic_type->as, + false, + false, + $matching_input_keys + ) + ) + ) { + $generic_param = clone $input_type; + + if ($matching_input_keys) { + $generic_param_keys = \array_keys($generic_param->getAtomicTypes()); + + foreach ($generic_param_keys as $atomic_key) { + if (!isset($matching_input_keys[$atomic_key])) { + $generic_param->removeType($atomic_key); + } + } + } + + if ($was_nullable && $generic_param->isNullable() && !$generic_param->isNull()) { + $generic_param->removeType('null'); + } + + if ($add_upper_bound) { + return array_values($generic_param->getAtomicTypes()); + } + + $generic_param->setFromDocblock(); + + if (isset( + $template_result->upper_bounds[$param_name_key][$atomic_type->defining_class][0] + )) { + $existing_generic_param = $template_result->upper_bounds + [$param_name_key] + [$atomic_type->defining_class]; + + $existing_depth = $existing_generic_param[1] ?? -1; + $existing_arg_offset = $existing_generic_param[2] ?? $input_arg_offset; + + if ($existing_depth > $depth && $input_arg_offset === $existing_arg_offset) { + return $atomic_types ?: [$atomic_type]; + } + + if ($existing_depth === $depth || $input_arg_offset !== $existing_arg_offset) { + $generic_param = \Psalm\Type::combineUnionTypes( + $template_result->upper_bounds + [$param_name_key] + [$atomic_type->defining_class] + [0], + $generic_param, + $codebase + ); + } + } + + $template_result->upper_bounds[$param_name_key][$atomic_type->defining_class] = [ + $generic_param, + $depth, + $input_arg_offset + ]; + } + + foreach ($atomic_types as $atomic_type) { + if ($atomic_type instanceof Atomic\TNamedObject + || $atomic_type instanceof Atomic\TTemplateParam + || $atomic_type instanceof Atomic\TIterable + || $atomic_type instanceof Atomic\TObjectWithProperties + ) { + $atomic_type->extra_types = $extra_types; + } + } + + return $atomic_types; + } + + if ($add_upper_bound && $input_type) { + $matching_input_keys = []; + + if ($codebase + && UnionTypeComparator::canBeContainedBy( + $codebase, + $input_type, + $replacement_type, + false, + false, + $matching_input_keys + ) + ) { + $generic_param = clone $input_type; + + if ($matching_input_keys) { + $generic_param_keys = \array_keys($generic_param->getAtomicTypes()); + + foreach ($generic_param_keys as $atomic_key) { + if (!isset($matching_input_keys[$atomic_key])) { + $generic_param->removeType($atomic_key); + } + } + } + + if (isset($template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0])) { + if (!UnionTypeComparator::isContainedBy( + $codebase, + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0], + $generic_param + ) || !UnionTypeComparator::isContainedBy( + $codebase, + $generic_param, + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0] + )) { + $intersection_type = \Psalm\Type::intersectUnionTypes( + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0], + $generic_param, + $codebase + ); + } else { + $intersection_type = $generic_param; + } + + if ($intersection_type) { + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0] + = $intersection_type; + } else { + $template_result->lower_bounds_unintersectable_types[] + = $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0]; + $template_result->lower_bounds_unintersectable_types[] = $generic_param; + + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0] + = \Psalm\Type::getMixed(); + } + } else { + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0] + = $generic_param; + } + } + } + + return [$atomic_type]; + } + + /** + * @return non-empty-list + */ + public static function handleTemplateParamClassStandin( + Atomic\TTemplateParamClass $atomic_type, + ?Union $input_type, + ?int $input_arg_offset, + TemplateResult $template_result, + int $depth, + bool $was_single + ) : array { + $class_string = new Atomic\TClassString($atomic_type->as, $atomic_type->as_type); + + $atomic_types = []; + + $atomic_types[] = $class_string; + + if ($input_type) { + $valid_input_atomic_types = []; + + foreach ($input_type->getAtomicTypes() as $input_atomic_type) { + if ($input_atomic_type instanceof Atomic\TLiteralClassString) { + $valid_input_atomic_types[] = new Atomic\TNamedObject( + $input_atomic_type->value + ); + } elseif ($input_atomic_type instanceof Atomic\TTemplateParamClass) { + $valid_input_atomic_types[] = new Atomic\TTemplateParam( + $input_atomic_type->param_name, + $input_atomic_type->as_type + ? new Union([$input_atomic_type->as_type]) + : ($input_atomic_type->as === 'object' + ? \Psalm\Type::getObject() + : \Psalm\Type::getMixed()), + $input_atomic_type->defining_class + ); + } elseif ($input_atomic_type instanceof Atomic\TClassString) { + if ($input_atomic_type->as_type) { + $valid_input_atomic_types[] = clone $input_atomic_type->as_type; + } elseif ($input_atomic_type->as !== 'object') { + $valid_input_atomic_types[] = new Atomic\TNamedObject( + $input_atomic_type->as + ); + } else { + $valid_input_atomic_types[] = new Atomic\TObject(); + } + } elseif ($input_atomic_type instanceof Atomic\TDependentGetClass) { + $valid_input_atomic_types[] = new Atomic\TObject(); + } + } + + $generic_param = null; + + if ($valid_input_atomic_types) { + $generic_param = new Union($valid_input_atomic_types); + $generic_param->setFromDocblock(); + } elseif ($was_single) { + $generic_param = \Psalm\Type::getMixed(); + } + + if ($generic_param) { + if (isset($template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class])) { + $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class] = [ + \Psalm\Type::combineUnionTypes( + $generic_param, + $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class][0] + ), + $depth + ]; + } else { + $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class] = [ + $generic_param, + $depth, + $input_arg_offset + ]; + } + } + } + + return $atomic_types; + } + + /** + * @param array> $template_types + * + * @return array{Union, 1?:int}|null + */ + public static function getRootTemplateType( + array $template_types, + string $param_name, + string $defining_class, + array $visited_classes = [] + ) : ?array { + if (isset($visited_classes[$defining_class])) { + return null; + } + + if (isset($template_types[$param_name][$defining_class])) { + $mapped_type = $template_types[$param_name][$defining_class][0]; + + $mapped_type_atomic_types = array_values($mapped_type->getAtomicTypes()); + + if (count($mapped_type_atomic_types) > 1 + || !$mapped_type_atomic_types[0] instanceof Atomic\TTemplateParam + ) { + return $template_types[$param_name][$defining_class]; + } + + $first_template = $mapped_type_atomic_types[0]; + + return self::getRootTemplateType( + $template_types, + $first_template->param_name, + $first_template->defining_class, + $visited_classes + [$defining_class => true] + ) ?? $template_types[$param_name][$defining_class]; + } + + return null; + } + + /** + * @param Atomic\TGenericObject|Atomic\TIterable $input_type_part + * @param Atomic\TGenericObject|Atomic\TIterable $container_type_part + * @return list + */ + public static function getMappedGenericTypeParams( + Codebase $codebase, + Atomic $input_type_part, + Atomic $container_type_part, + ?array &$container_type_params_covariant = null + ) : array { + $input_type_params = $input_type_part->type_params; + + try { + $input_class_storage = $codebase->classlike_storage_provider->get($input_type_part->value); + $container_class_storage = $codebase->classlike_storage_provider->get($container_type_part->value); + $container_type_params_covariant = $container_class_storage->template_covariants; + } catch (\Throwable $e) { + $input_class_storage = null; + } + + if ($input_type_part->value !== $container_type_part->value + && $input_class_storage + ) { + $input_template_types = $input_class_storage->template_types; + $i = 0; + + $replacement_templates = []; + + if ($input_template_types + && (!$input_type_part instanceof Atomic\TGenericObject || !$input_type_part->remapped_params) + && (!$container_type_part instanceof Atomic\TGenericObject || !$container_type_part->remapped_params) + ) { + foreach ($input_template_types as $template_name => $_) { + if (!isset($input_type_params[$i])) { + break; + } + + $replacement_templates[$template_name][$input_type_part->value] = [$input_type_params[$i]]; + + $i++; + } + } + + $template_extends = $input_class_storage->template_type_extends; + + if (isset($template_extends[$container_type_part->value])) { + $params = $template_extends[$container_type_part->value]; + + $new_input_params = []; + + foreach ($params as $key => $extended_input_param_type) { + if (is_string($key)) { + $new_input_param = null; + + foreach ($extended_input_param_type->getAtomicTypes() as $et) { + if ($et instanceof Atomic\TTemplateParam) { + $ets = \Psalm\Internal\Codebase\Methods::getExtendedTemplatedTypes( + $et, + $template_extends + ); + } else { + $ets = []; + } + + if ($ets + && $ets[0] instanceof Atomic\TTemplateParam + && isset( + $input_class_storage->template_types + [$ets[0]->param_name] + [$ets[0]->defining_class] + ) + ) { + $old_params_offset = (int) \array_search( + $ets[0]->param_name, + \array_keys($input_class_storage->template_types) + ); + + if (!isset($input_type_params[$old_params_offset])) { + $candidate_param_type = \Psalm\Type::getMixed(); + } else { + $candidate_param_type = $input_type_params[$old_params_offset]; + } + } else { + $candidate_param_type = new Union([clone $et]); + } + + $candidate_param_type->from_template_default = true; + + if (!$new_input_param) { + $new_input_param = $candidate_param_type; + } else { + $new_input_param = \Psalm\Type::combineUnionTypes( + $new_input_param, + $candidate_param_type + ); + } + } + + $new_input_param = clone $new_input_param; + $new_input_param->replaceTemplateTypesWithArgTypes( + new TemplateResult([], $replacement_templates), + $codebase + ); + + $new_input_params[] = $new_input_param; + } + } + + $input_type_params = $new_input_params; + } + } + + return $input_type_params; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..699647eabc7caa1ae95fa60ebd106848d2773520 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php @@ -0,0 +1,61 @@ +fq_classlike_name = $fq_classlike_name; + } + + protected function enterNode(TypeNode $type) : ?int + { + if ($type instanceof TNamedObject) { + if (strtolower($type->value) === $this->fq_classlike_name) { + $this->contains_classlike = true; + return NodeVisitor::STOP_TRAVERSAL; + } + } + + if ($type instanceof TScalarClassConstant) { + if (strtolower($type->fq_classlike_name) === $this->fq_classlike_name) { + $this->contains_classlike = true; + return NodeVisitor::STOP_TRAVERSAL; + } + } + + if ($type instanceof TLiteralClassString) { + if (strtolower($type->value) === $this->fq_classlike_name) { + $this->contains_classlike = true; + return NodeVisitor::STOP_TRAVERSAL; + } + } + + return null; + } + + public function matches() : bool + { + return $this->contains_classlike; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/FromDocblockSetter.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/FromDocblockSetter.php new file mode 100644 index 0000000000000000000000000000000000000000..3bfcc9e328e3d794240d24a149fb9582c53f4d48 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/FromDocblockSetter.php @@ -0,0 +1,26 @@ +from_docblock = true; + + if ($type instanceof \Psalm\Type\Atomic\TTemplateParam + && $type->as->isMixed() + ) { + return NodeVisitor::DONT_TRAVERSE_CHILDREN; + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php new file mode 100644 index 0000000000000000000000000000000000000000..be2f4c59f9777c9c1c8d978d0c47198be1c5123f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php @@ -0,0 +1,48 @@ + + */ + private $template_types = []; + + protected function enterNode(TypeNode $type) : ?int + { + if ($type instanceof TTemplateParam) { + $this->template_types[] = $type; + } elseif ($type instanceof TTemplateParamClass) { + $extends = $type->as_type; + + $this->template_types[] = new TTemplateParam( + $type->param_name, + $extends ? new Union([$extends]) : \Psalm\Type::getMixed(), + $type->defining_class + ); + } elseif ($type instanceof TConditional) { + $this->template_types[] = new TTemplateParam( + $type->param_name, + \Psalm\Type::getMixed(), + $type->defining_class + ); + } + + return null; + } + + /** + * @return list + */ + public function getTemplateTypes() : array + { + return $this->template_types; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TypeChecker.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TypeChecker.php new file mode 100644 index 0000000000000000000000000000000000000000..596ba67d568124d8947b8a72cb784fe0ae688c47 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TypeChecker.php @@ -0,0 +1,403 @@ + + */ + private $suppressed_issues; + + /** + * @var array + */ + private $phantom_classes; + + /** + * @var bool + */ + private $inferred; + + /** + * @var bool + */ + private $inherited; + + /** + * @var bool + */ + private $prevent_template_covariance; + + /** @var bool */ + private $has_errors = false; + + private $calling_method_id; + + /** + * @param array $suppressed_issues + * @param array $phantom_classes + */ + public function __construct( + StatementsSource $source, + CodeLocation $code_location, + array $suppressed_issues, + array $phantom_classes = [], + bool $inferred = true, + bool $inherited = false, + bool $prevent_template_covariance = false, + ?string $calling_method_id = null + ) { + $this->source = $source; + $this->code_location = $code_location; + $this->suppressed_issues = $suppressed_issues; + $this->phantom_classes = $phantom_classes; + $this->inferred = $inferred; + $this->inherited = $inherited; + $this->prevent_template_covariance = $prevent_template_covariance; + $this->calling_method_id = $calling_method_id; + } + + /** + * @psalm-suppress MoreSpecificImplementedParamType + * + * @param \Psalm\Type\Atomic|\Psalm\Type\Union $type + */ + protected function enterNode(TypeNode $type) : ?int + { + if ($type->checked) { + return NodeVisitor::DONT_TRAVERSE_CHILDREN; + } + + if ($type instanceof TNamedObject) { + $this->checkNamedObject($type); + } elseif ($type instanceof TScalarClassConstant) { + $this->checkScalarClassConstant($type); + } elseif ($type instanceof TTemplateParam) { + $this->checkTemplateParam($type); + } elseif ($type instanceof TResource) { + $this->checkResource($type); + } elseif ($type instanceof TArray) { + if (\count($type->type_params) > 2) { + if (IssueBuffer::accepts( + new TooManyTemplateParams( + $type->getId(). ' has too many template params, expecting 2', + $this->code_location + ), + $this->suppressed_issues + )) { + // fall through + } + } + } + + $type->checked = true; + + return null; + } + + public function hasErrors() : bool + { + return $this->has_errors; + } + + private function checkNamedObject(TNamedObject $atomic) : void + { + $codebase = $this->source->getCodebase(); + + if ($this->code_location instanceof CodeLocation\DocblockTypeLocation + && $codebase->store_node_types + && $atomic->offset_start !== null + && $atomic->offset_end !== null + ) { + $codebase->analyzer->addOffsetReference( + $this->source->getFilePath(), + $this->code_location->raw_file_start + $atomic->offset_start, + $this->code_location->raw_file_start + $atomic->offset_end, + $atomic->value + ); + } + + if (!isset($this->phantom_classes[\strtolower($atomic->value)]) && + ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $this->source, + $atomic->value, + $this->code_location, + $this->source->getFQCLN(), + $this->calling_method_id, + $this->suppressed_issues, + $this->inferred, + false, + true, + $atomic->from_docblock + ) === false + ) { + $this->has_errors = true; + return; + } + + $fq_class_name_lc = strtolower($atomic->value); + + if (!$this->inherited + && $codebase->classlike_storage_provider->has($fq_class_name_lc) + && $this->source->getFQCLN() !== $atomic->value + ) { + $class_storage = $codebase->classlike_storage_provider->get($fq_class_name_lc); + + if ($class_storage->deprecated) { + if (IssueBuffer::accepts( + new DeprecatedClass( + 'Class ' . $atomic->value . ' is marked as deprecated', + $this->code_location, + $atomic->value + ), + $this->source->getSuppressedIssues() + $this->suppressed_issues + )) { + // fall through + } + } + } + + if ($atomic instanceof TGenericObject) { + $this->checkGenericParams($atomic); + } + } + + private function checkGenericParams(TGenericObject $atomic) : void + { + $codebase = $this->source->getCodebase(); + + try { + $class_storage = $codebase->classlike_storage_provider->get(strtolower($atomic->value)); + } catch (\InvalidArgumentException $e) { + return; + } + + $expected_type_params = $class_storage->template_types ?: []; + $expected_param_covariants = $class_storage->template_covariants; + + $template_type_count = \count($expected_type_params); + $template_param_count = \count($atomic->type_params); + + if ($template_type_count > $template_param_count) { + if (IssueBuffer::accepts( + new MissingTemplateParam( + $atomic->value . ' has missing template params, expecting ' + . $template_type_count, + $this->code_location + ), + $this->suppressed_issues + )) { + // fall through + } + } elseif ($template_type_count < $template_param_count) { + if (IssueBuffer::accepts( + new TooManyTemplateParams( + $atomic->getId(). ' has too many template params, expecting ' + . $template_type_count, + $this->code_location + ), + $this->suppressed_issues + )) { + // fall through + } + } + + foreach ($atomic->type_params as $i => $type_param) { + $this->prevent_template_covariance = $this->source instanceof \Psalm\Internal\Analyzer\MethodAnalyzer + && $this->source->getMethodName() !== '__construct' + && empty($expected_param_covariants[$i]); + + if (isset(\array_values($expected_type_params)[$i])) { + $expected_type_param = \reset(\array_values($expected_type_params)[$i])[0]; + + $expected_type_param = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $expected_type_param, + $this->source->getFQCLN(), + $this->source->getFQCLN(), + $this->source->getParentFQCLN() + ); + + $template_name = \array_keys($expected_type_params)[$i]; + + $type_param = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $type_param, + $this->source->getFQCLN(), + $this->source->getFQCLN(), + $this->source->getParentFQCLN() + ); + + if (!UnionTypeComparator::isContainedBy($codebase, $type_param, $expected_type_param)) { + if (IssueBuffer::accepts( + new InvalidTemplateParam( + 'Extended template param ' . $template_name + . ' of ' . $atomic->getId() + . ' expects type ' + . $expected_type_param->getId() + . ', type ' . $type_param->getId() . ' given', + $this->code_location + ), + $this->suppressed_issues + )) { + // fall through + } + } + } + } + } + + public function checkScalarClassConstant(TScalarClassConstant $atomic) : void + { + $fq_classlike_name = $atomic->fq_classlike_name === 'self' + ? $this->source->getClassName() + : $atomic->fq_classlike_name; + + if (!$fq_classlike_name) { + return; + } + + if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( + $this->source, + $fq_classlike_name, + $this->code_location, + null, + null, + $this->suppressed_issues, + $this->inferred, + false, + true, + $atomic->from_docblock + ) === false + ) { + $this->has_errors = true; + return; + } + + $const_name = $atomic->const_name; + if (\strpos($const_name, '*') !== false) { + $expanded = TypeExpander::expandAtomic( + $this->source->getCodebase(), + $atomic, + $fq_classlike_name, + $fq_classlike_name, + null, + true, + true + ); + + $is_defined = \is_array($expanded) && \count($expanded) > 0; + } else { + $class_constant_type = $this->source->getCodebase()->classlikes->getClassConstantType( + $fq_classlike_name, + $atomic->const_name, + \ReflectionProperty::IS_PRIVATE, + null + ); + + $is_defined = null !== $class_constant_type; + } + + if (!$is_defined) { + if (\Psalm\IssueBuffer::accepts( + new UndefinedConstant( + 'Constant ' . $fq_classlike_name . '::' . $const_name . ' is not defined', + $this->code_location + ), + $this->source->getSuppressedIssues() + )) { + // fall through + } + } + } + + public function checkTemplateParam(\Psalm\Type\Atomic\TTemplateParam $atomic) : void + { + if ($this->prevent_template_covariance + && \substr($atomic->defining_class, 0, 3) !== 'fn-' + ) { + $codebase = $this->source->getCodebase(); + + $class_storage = $codebase->classlike_storage_provider->get($atomic->defining_class); + + $template_offset = $class_storage->template_types + ? \array_search($atomic->param_name, \array_keys($class_storage->template_types), true) + : false; + + if ($template_offset !== false + && isset($class_storage->template_covariants[$template_offset]) + && $class_storage->template_covariants[$template_offset] + ) { + $method_storage = $this->source instanceof \Psalm\Internal\Analyzer\MethodAnalyzer + ? $this->source->getFunctionLikeStorage() + : null; + + if ($method_storage instanceof MethodStorage + && $method_storage->mutation_free + && !$method_storage->mutation_free_inferred + ) { + // do nothing + } else { + if (\Psalm\IssueBuffer::accepts( + new \Psalm\Issue\InvalidTemplateParam( + 'Template param ' . $atomic->param_name . ' of ' + . $atomic->defining_class . ' is marked covariant and cannot be used here', + $this->code_location + ), + $this->source->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } + + public function checkResource(TResource $atomic) : void + { + if (!$atomic->from_docblock) { + if (\Psalm\IssueBuffer::accepts( + new \Psalm\Issue\ReservedWord( + '\'resource\' is a reserved word', + $this->code_location, + 'resource' + ), + $this->source->getSuppressedIssues() + )) { + // fall through + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TypeScanner.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TypeScanner.php new file mode 100644 index 0000000000000000000000000000000000000000..26022565fbecbe630fe95960118951e27cc69b74 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/TypeVisitor/TypeScanner.php @@ -0,0 +1,87 @@ + $phantom_classes + */ + public function __construct( + Scanner $scanner, + ?FileStorage $file_storage, + array $phantom_classes + ) { + $this->scanner = $scanner; + $this->file_storage = $file_storage; + $this->phantom_classes = $phantom_classes; + } + + protected function enterNode(TypeNode $type) : ?int + { + if ($type instanceof TNamedObject) { + $fq_classlike_name_lc = strtolower($type->value); + + if (!isset($this->phantom_classes[$type->value]) + && !isset($this->phantom_classes[$fq_classlike_name_lc]) + ) { + $this->scanner->queueClassLikeForScanning( + $type->value, + false, + !$type->from_docblock, + $this->phantom_classes + ); + + if ($this->file_storage) { + $this->file_storage->referenced_classlikes[$fq_classlike_name_lc] = $type->value; + } + } + } + + if ($type instanceof TScalarClassConstant) { + $this->scanner->queueClassLikeForScanning( + $type->fq_classlike_name, + false, + !$type->from_docblock, + $this->phantom_classes + ); + + if ($this->file_storage) { + $fq_classlike_name_lc = strtolower($type->fq_classlike_name); + + $this->file_storage->referenced_classlikes[$fq_classlike_name_lc] = $type->fq_classlike_name; + } + } + + if ($type instanceof TLiteralClassString) { + $this->scanner->queueClassLikeForScanning( + $type->value, + false, + !$type->from_docblock, + $this->phantom_classes + ); + + if ($this->file_storage) { + $fq_classlike_name_lc = strtolower($type->value); + + $this->file_storage->referenced_classlikes[$fq_classlike_name_lc] = $type->value; + } + } + + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Internal/exception_handler.php b/lib/composer/vimeo/psalm/src/Psalm/Internal/exception_handler.php new file mode 100644 index 0000000000000000000000000000000000000000..e1295a087f61cd3592b36535a9496cb83aeb76a7 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Internal/exception_handler.php @@ -0,0 +1,12 @@ +function_id = $function_id ? strtolower($function_id) : null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/ArgumentTypeCoercion.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/ArgumentTypeCoercion.php new file mode 100644 index 0000000000000000000000000000000000000000..68d51cf9a7d19ce9df21f9252571a6adcf3a6c48 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/ArgumentTypeCoercion.php @@ -0,0 +1,8 @@ +fq_classlike_name = $fq_classlike_name; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/CodeIssue.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/CodeIssue.php new file mode 100644 index 0000000000000000000000000000000000000000..2cbc554f718f581fd5e483b309013fb717bca658 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/CodeIssue.php @@ -0,0 +1,120 @@ +code_location = $code_location; + $this->message = $message; + } + + /** + * @deprecated + * @psalm-suppress PossiblyUnusedMethod + */ + public function getLocation(): CodeLocation + { + return $this->code_location; + } + + public function getShortLocationWithPrevious(): string + { + $previous_text = ''; + + if ($this->code_location->previous_location) { + $previous_location = $this->code_location->previous_location; + $previous_text = ' from ' . $previous_location->file_name . ':' . $previous_location->getLineNumber(); + } + + return $this->code_location->file_name . ':' . $this->code_location->getLineNumber() . $previous_text; + } + + public function getShortLocation(): string + { + return $this->code_location->file_name . ':' . $this->code_location->getLineNumber(); + } + + public function getFilePath(): string + { + return $this->code_location->file_path; + } + + /** + * @deprecated + * @psalm-suppress PossiblyUnusedMethod for convenience + */ + public function getFileName(): string + { + return $this->code_location->file_name; + } + + /** + * @deprecated + * @psalm-suppress PossiblyUnusedMethod + */ + public function getMessage(): string + { + return $this->message; + } + + public function toIssueData(string $severity = Config::REPORT_ERROR): \Psalm\Internal\Analyzer\IssueData + { + $location = $this->code_location; + $selection_bounds = $location->getSelectionBounds(); + $snippet_bounds = $location->getSnippetBounds(); + + $fqcn_parts = explode('\\', get_called_class()); + $issue_type = array_pop($fqcn_parts); + + return new \Psalm\Internal\Analyzer\IssueData( + $severity, + $location->getLineNumber(), + $location->getEndLineNumber(), + $issue_type, + $this->message, + $location->file_name, + $location->file_path, + $location->getSnippet(), + $location->getSelectedText(), + $selection_bounds[0], + $selection_bounds[1], + $snippet_bounds[0], + $snippet_bounds[1], + $location->getColumn(), + $location->getEndColumn(), + (int) static::SHORTCODE, + (int) static::ERROR_LEVEL, + $this instanceof TaintedInput ? $this->getTaintTrace() : null, + $this->dupe_key + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/ConflictingReferenceConstraint.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/ConflictingReferenceConstraint.php new file mode 100644 index 0000000000000000000000000000000000000000..5ad067b9586191a84ddc1e2d97f4ef2e2e3ef224 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/ConflictingReferenceConstraint.php @@ -0,0 +1,8 @@ +dupe_key = $dupe_key; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/DuplicateArrayKey.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/DuplicateArrayKey.php new file mode 100644 index 0000000000000000000000000000000000000000..e205747e027a9953fe81d1169a0979153e026303 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/DuplicateArrayKey.php @@ -0,0 +1,8 @@ +function_id = strtolower($function_id); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/ImplementationRequirementViolation.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/ImplementationRequirementViolation.php new file mode 100644 index 0000000000000000000000000000000000000000..1b6886f02c01814e09fa877cd0db00be691685b5 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/ImplementationRequirementViolation.php @@ -0,0 +1,9 @@ +method_id = strtolower($method_id); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/MethodSignatureMismatch.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/MethodSignatureMismatch.php new file mode 100644 index 0000000000000000000000000000000000000000..274436a7ca822378edfd2889efa4526d11d50b26 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/MethodSignatureMismatch.php @@ -0,0 +1,8 @@ +dupe_key = $dupe_key; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/MissingDependency.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/MissingDependency.php new file mode 100644 index 0000000000000000000000000000000000000000..f7bc3665997ff058a3c1c2255631bcdd34964ff5 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/MissingDependency.php @@ -0,0 +1,7 @@ +property_id = $property_id; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/PropertyNotSetInConstructor.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/PropertyNotSetInConstructor.php new file mode 100644 index 0000000000000000000000000000000000000000..ede8db651f85e234543fa62491e628326d35fc04 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/PropertyNotSetInConstructor.php @@ -0,0 +1,17 @@ +dupe_key = $property_id; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/PropertyTypeCoercion.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/PropertyTypeCoercion.php new file mode 100644 index 0000000000000000000000000000000000000000..e33c058d35ad5e8a37b9e451122d14b1f6bc56dd --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/PropertyTypeCoercion.php @@ -0,0 +1,8 @@ +dupe_key = $dupe_key; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/RedundantConditionGivenDocblockType.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/RedundantConditionGivenDocblockType.php new file mode 100644 index 0000000000000000000000000000000000000000..753f027bf5309c5182ef38c0f4ee46015f34f233 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/RedundantConditionGivenDocblockType.php @@ -0,0 +1,15 @@ +dupe_key = $dupe_key; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/RedundantIdentityWithTrue.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/RedundantIdentityWithTrue.php new file mode 100644 index 0000000000000000000000000000000000000000..5743131975705fef022c6250281d1e0fc1bfe6c2 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/RedundantIdentityWithTrue.php @@ -0,0 +1,8 @@ + + * @readonly + */ + public $journey = []; + + /** + * @param list $journey + */ + public function __construct( + string $message, + CodeLocation $code_location, + array $journey, + string $journey_text + ) { + parent::__construct($message, $code_location); + + $this->journey = $journey; + $this->journey_text = $journey_text; + } + + /** + * @return list + */ + public function getTaintTrace(): array + { + $nodes = []; + + foreach ($this->journey as ['location' => $location, 'label' => $label, 'entry_path_type' => $path_type]) { + if ($location) { + $nodes[] = self::nodeToDataFlowNodeData($location, $label, $path_type); + } else { + $nodes[] = ['label' => $label, 'entry_path_type' => $path_type]; + } + } + + return $nodes; + } + + private static function nodeToDataFlowNodeData( + CodeLocation $location, + string $label, + string $entry_path_type + ) : DataFlowNodeData { + $selection_bounds = $location->getSelectionBounds(); + $snippet_bounds = $location->getSnippetBounds(); + + return new DataFlowNodeData( + $label, + $entry_path_type, + null, + $location->getLineNumber(), + $location->getEndLineNumber(), + $location->file_name, + $location->file_path, + $location->getSnippet(), + $location->getSelectedText(), + $selection_bounds[0], + $selection_bounds[1], + $snippet_bounds[0], + $snippet_bounds[1], + $location->getColumn(), + $location->getEndColumn() + ); + } + + public function getJourneyMessage() : string + { + return $this->message . ' in path: ' . $this->journey_text; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/TooFewArguments.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/TooFewArguments.php new file mode 100644 index 0000000000000000000000000000000000000000..aa818f1c100fc14a5fd3710b1a1ce5adf8927821 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/TooFewArguments.php @@ -0,0 +1,8 @@ +dupe_key = $dupe_key; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/TypeDoesNotContainType.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/TypeDoesNotContainType.php new file mode 100644 index 0000000000000000000000000000000000000000..f45b4591cd4330578fa860e174af03c604114f94 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/TypeDoesNotContainType.php @@ -0,0 +1,14 @@ +dupe_key = $dupe_key; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Issue/UncaughtThrowInGlobalScope.php b/lib/composer/vimeo/psalm/src/Psalm/Issue/UncaughtThrowInGlobalScope.php new file mode 100644 index 0000000000000000000000000000000000000000..22553577da2478f9bd18d335b3be08e70c0b2054 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Issue/UncaughtThrowInGlobalScope.php @@ -0,0 +1,8 @@ +var_name = strtolower($var_name); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/IssueBuffer.php b/lib/composer/vimeo/psalm/src/Psalm/IssueBuffer.php new file mode 100644 index 0000000000000000000000000000000000000000..741a2a71a17763a2495ee286d5cd912a62a10976 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/IssueBuffer.php @@ -0,0 +1,846 @@ +> + */ + protected static $issues_data = []; + + /** + * @var array + */ + protected static $console_issues = []; + + /** + * @var array + */ + protected static $fixable_issue_counts = []; + + /** + * @var int + */ + protected static $error_count = 0; + + /** + * @var array + */ + protected static $emitted = []; + + /** @var int */ + protected static $recording_level = 0; + + /** @var array> */ + protected static $recorded_issues = []; + + /** + * @var array> + */ + protected static $unused_suppressions = []; + + /** + * @var array> + */ + protected static $used_suppressions = []; + + /** + * @param string[] $suppressed_issues + * + */ + public static function accepts(CodeIssue $e, array $suppressed_issues = [], bool $is_fixable = false): bool + { + if (self::isSuppressed($e, $suppressed_issues)) { + return false; + } + + return self::add($e, $is_fixable); + } + + public static function addUnusedSuppression(string $file_path, int $offset, string $issue_type) : void + { + if ($issue_type === 'TaintedInput') { + return; + } + + if (isset(self::$used_suppressions[$file_path][$offset])) { + return; + } + + if (!isset(self::$unused_suppressions[$file_path])) { + self::$unused_suppressions[$file_path] = []; + } + + self::$unused_suppressions[$file_path][$offset] = $offset + \strlen($issue_type) - 1; + } + + /** + * @param string[] $suppressed_issues + * + */ + public static function isSuppressed(CodeIssue $e, array $suppressed_issues = []) : bool + { + $config = Config::getInstance(); + + $fqcn_parts = explode('\\', get_class($e)); + $issue_type = array_pop($fqcn_parts); + $file_path = $e->getFilePath(); + + if (!$config->reportIssueInFile($issue_type, $file_path)) { + return true; + } + + $suppressed_issue_position = array_search($issue_type, $suppressed_issues); + + if ($suppressed_issue_position !== false) { + if (\is_int($suppressed_issue_position)) { + self::$used_suppressions[$file_path][$suppressed_issue_position] = true; + } + + return true; + } + + $parent_issue_type = Config::getParentIssueType($issue_type); + + if ($parent_issue_type) { + $suppressed_issue_position = array_search($parent_issue_type, $suppressed_issues); + + if ($suppressed_issue_position !== false) { + if (\is_int($suppressed_issue_position)) { + self::$used_suppressions[$file_path][$suppressed_issue_position] = true; + } + + return true; + } + } + + $suppress_all_position = array_search('all', $suppressed_issues); + + if ($suppress_all_position !== false) { + if (\is_int($suppress_all_position)) { + self::$used_suppressions[$file_path][$suppress_all_position] = true; + } + + return true; + } + + $reporting_level = $config->getReportingLevelForIssue($e); + + if ($reporting_level === Config::REPORT_SUPPRESS) { + return true; + } + + if ($e->code_location->getLineNumber() === -1) { + return true; + } + + if (self::$recording_level > 0) { + self::$recorded_issues[self::$recording_level][] = $e; + + return true; + } + + return false; + } + + /** + * @throws Exception\CodeException + */ + public static function add(CodeIssue $e, bool $is_fixable = false): bool + { + $config = Config::getInstance(); + + $fqcn_parts = explode('\\', get_class($e)); + $issue_type = array_pop($fqcn_parts); + + $project_analyzer = ProjectAnalyzer::getInstance(); + + if (!$project_analyzer->show_issues) { + return false; + } + + if ($project_analyzer->getCodebase()->taint_flow_graph && $issue_type !== 'TaintedInput') { + return false; + } + + $reporting_level = $config->getReportingLevelForIssue($e); + + if ($reporting_level === Config::REPORT_SUPPRESS) { + return false; + } + + if ($config->debug_emitted_issues) { + ob_start(); + debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $trace = ob_get_clean(); + fwrite(STDERR, "\nEmitting {$e->getShortLocation()} $issue_type {$e->message}\n$trace\n"); + } + + $emitted_key = $issue_type + . '-' . $e->getShortLocation() + . ':' . $e->code_location->getColumn() + . ' ' . $e->dupe_key; + + if ($reporting_level === Config::REPORT_INFO) { + if ($issue_type === 'TaintedInput' || !self::alreadyEmitted($emitted_key)) { + self::$issues_data[$e->getFilePath()][] = $e->toIssueData(Config::REPORT_INFO); + + if ($is_fixable) { + self::addFixableIssue($issue_type); + } + } + + return false; + } + + if ($config->throw_exception) { + \Psalm\Internal\Analyzer\FileAnalyzer::clearCache(); + + $message = $e instanceof \Psalm\Issue\TaintedInput + ? $e->getJourneyMessage() + : $e->message; + + throw new Exception\CodeException( + $issue_type + . ' - ' . $e->getShortLocationWithPrevious() + . ':' . $e->code_location->getColumn() + . ' - ' . $message + ); + } + + if ($issue_type === 'TaintedInput' || !self::alreadyEmitted($emitted_key)) { + ++self::$error_count; + self::$issues_data[$e->getFilePath()][] = $e->toIssueData(Config::REPORT_ERROR); + + if ($is_fixable) { + self::addFixableIssue($issue_type); + } + } + + return true; + } + + public static function remove(string $file_path, string $issue_type, int $file_offset) : void + { + if (!isset(self::$issues_data[$file_path])) { + return; + } + + $filtered_issues = []; + + foreach (self::$issues_data[$file_path] as $issue) { + if ($issue->type !== $issue_type || $issue->from !== $file_offset) { + $filtered_issues[] = $issue; + } + } + + if (empty($filtered_issues)) { + unset(self::$issues_data[$file_path]); + } else { + self::$issues_data[$file_path] = $filtered_issues; + } + } + + public static function addFixableIssue(string $issue_type) : void + { + if (isset(self::$fixable_issue_counts[$issue_type])) { + self::$fixable_issue_counts[$issue_type]++; + } else { + self::$fixable_issue_counts[$issue_type] = 1; + } + } + + /** + * @return array> + */ + public static function getIssuesData(): array + { + return self::$issues_data; + } + + /** + * @return list + */ + public static function getIssuesDataForFile(string $file_path): array + { + return self::$issues_data[$file_path] ?? []; + } + + /** + * @return array + */ + public static function getFixableIssues(): array + { + return self::$fixable_issue_counts; + } + + /** + * @param array $fixable_issue_counts + */ + public static function addFixableIssues(array $fixable_issue_counts) : void + { + foreach ($fixable_issue_counts as $issue_type => $count) { + if (isset(self::$fixable_issue_counts[$issue_type])) { + self::$fixable_issue_counts[$issue_type] += $count; + } else { + self::$fixable_issue_counts[$issue_type] = $count; + } + } + } + + /** + * @return array> + */ + public static function getUnusedSuppressions() : array + { + return self::$unused_suppressions; + } + + /** + * @return array> + */ + public static function getUsedSuppressions() : array + { + return self::$used_suppressions; + } + + /** + * @param array> $unused_suppressions + */ + public static function addUnusedSuppressions(array $unused_suppressions) : void + { + self::$unused_suppressions += $unused_suppressions; + } + + /** + * @param array> $used_suppressions + */ + public static function addUsedSuppressions(array $used_suppressions) : void + { + foreach ($used_suppressions as $file => $offsets) { + if (!isset(self::$used_suppressions[$file])) { + self::$used_suppressions[$file] = $offsets; + } else { + self::$used_suppressions[$file] += $offsets; + } + } + } + + public static function processUnusedSuppressions(\Psalm\Internal\Provider\FileProvider $file_provider) : void + { + $config = Config::getInstance(); + + foreach (self::$unused_suppressions as $file_path => $offsets) { + if (!$offsets) { + continue; + } + + $file_contents = $file_provider->getContents($file_path); + + foreach ($offsets as $start => $end) { + if (isset(self::$used_suppressions[$file_path][$start])) { + continue; + } + + self::add( + new UnusedPsalmSuppress( + 'This suppression is never used', + new CodeLocation\Raw( + $file_contents, + $file_path, + $config->shortenFileName($file_path), + $start, + $end + ) + ) + ); + } + } + } + + public static function getErrorCount(): int + { + return self::$error_count; + } + + /** + * @param array> $issues_data + * + */ + public static function addIssues(array $issues_data): void + { + foreach ($issues_data as $file_path => $file_issues) { + foreach ($file_issues as $issue) { + $emitted_key = $issue->type + . '-' . $issue->file_name + . ':' . $issue->line_from + . ':' . $issue->column_from + . ' ' . $issue->dupe_key; + + if (!self::alreadyEmitted($emitted_key)) { + self::$issues_data[$file_path][] = $issue; + } + } + } + } + + /** + * @param array}>> $issue_baseline + * + */ + public static function finish( + ProjectAnalyzer $project_analyzer, + bool $is_full, + float $start_time, + bool $add_stats = false, + array $issue_baseline = [] + ): void { + if (!$project_analyzer->stdout_report_options) { + throw new \UnexpectedValueException('Cannot finish without stdout report options'); + } + + $codebase = $project_analyzer->getCodebase(); + + $error_count = 0; + $info_count = 0; + + $issues_data = []; + + if (self::$issues_data) { + if (in_array( + $project_analyzer->stdout_report_options->format, + [\Psalm\Report::TYPE_CONSOLE, \Psalm\Report::TYPE_PHP_STORM] + )) { + echo "\n"; + } + + \ksort(self::$issues_data); + + foreach (self::$issues_data as $file_path => $file_issues) { + usort( + $file_issues, + function (IssueData $d1, IssueData $d2) : int { + if ($d1->file_path === $d2->file_path) { + if ($d1->line_from === $d2->line_from) { + if ($d1->column_from === $d2->column_from) { + return 0; + } + + return $d1->column_from > $d2->column_from ? 1 : -1; + } + + return $d1->line_from > $d2->line_from ? 1 : -1; + } + + return $d1->file_path > $d2->file_path ? 1 : -1; + } + ); + self::$issues_data[$file_path] = $file_issues; + } + + // make a copy so what gets saved in cache is unaffected by baseline + $issues_data = self::$issues_data; + + if (!empty($issue_baseline)) { + // Set severity for issues in baseline to INFO + foreach ($issues_data as $file_path => $file_issues) { + foreach ($file_issues as $key => $issue_data) { + $file = $issue_data->file_name; + $file = str_replace('\\', '/', $file); + $type = $issue_data->type; + + if (isset($issue_baseline[$file][$type]) && $issue_baseline[$file][$type]['o'] > 0) { + if ($issue_baseline[$file][$type]['o'] === count($issue_baseline[$file][$type]['s'])) { + $position = array_search( + $issue_data->selected_text, + $issue_baseline[$file][$type]['s'], + true + ); + + if ($position !== false) { + $issue_data->severity = Config::REPORT_INFO; + array_splice($issue_baseline[$file][$type]['s'], $position, 1); + $issue_baseline[$file][$type]['o'] = $issue_baseline[$file][$type]['o'] - 1; + } + } else { + $issue_baseline[$file][$type]['s'] = []; + $issue_data->severity = Config::REPORT_INFO; + $issue_baseline[$file][$type]['o'] = $issue_baseline[$file][$type]['o'] - 1; + } + } + + /** @psalm-suppress PropertyTypeCoercion due to Psalm bug */ + $issues_data[$file_path][$key] = $issue_data; + } + } + } + } + + echo self::getOutput( + $issues_data, + $project_analyzer->stdout_report_options, + $codebase->analyzer->getTotalTypeCoverage($codebase) + ); + + foreach ($issues_data as $file_issues) { + foreach ($file_issues as $issue_data) { + if ($issue_data->severity === Config::REPORT_ERROR) { + ++$error_count; + } else { + ++$info_count; + } + } + } + + $after_analysis_hooks = $codebase->config->after_analysis; + + if ($after_analysis_hooks) { + $source_control_info = null; + $build_info = (new \Psalm\Internal\ExecutionEnvironment\BuildInfoCollector($_SERVER))->collect(); + + try { + $source_control_info = (new \Psalm\Internal\ExecutionEnvironment\GitInfoCollector())->collect(); + } catch (\RuntimeException $e) { + // do nothing + } + + foreach ($after_analysis_hooks as $after_analysis_hook) { + /** @psalm-suppress ArgumentTypeCoercion due to Psalm bug */ + $after_analysis_hook::afterAnalysis( + $codebase, + $issues_data, + $build_info, + $source_control_info + ); + } + } + + foreach ($project_analyzer->generated_report_options as $report_options) { + if (!$report_options->output_path) { + throw new \UnexpectedValueException('Output path should not be null here'); + } + + $folder = dirname($report_options->output_path); + if (!is_dir($folder) && !mkdir($folder, 0777, true) && !is_dir($folder)) { + throw new \RuntimeException(sprintf('Directory "%s" was not created', $folder)); + } + file_put_contents( + $report_options->output_path, + self::getOutput( + $issues_data, + $report_options, + $codebase->analyzer->getTotalTypeCoverage($codebase) + ) + ); + } + + if (in_array( + $project_analyzer->stdout_report_options->format, + [\Psalm\Report::TYPE_CONSOLE, \Psalm\Report::TYPE_PHP_STORM] + )) { + echo str_repeat('-', 30) . "\n"; + + if ($error_count) { + echo($project_analyzer->stdout_report_options->use_color + ? "\e[0;31m" . $error_count . " errors\e[0m" + : $error_count . ' errors' + ) . ' found' . "\n"; + } else { + echo 'No errors found!' . "\n"; + } + + $show_info = $project_analyzer->stdout_report_options->show_info; + $show_suggestions = $project_analyzer->stdout_report_options->show_suggestions; + + if ($info_count && ($show_info || $show_suggestions)) { + echo str_repeat('-', 30) . "\n"; + + echo $info_count . ' other issues found.' . "\n"; + + if (!$show_info) { + echo 'You can display them with ' . + ($project_analyzer->stdout_report_options->use_color + ? "\e[30;48;5;195m--show-info=true\e[0m" + : '--show-info=true') . "\n"; + } + } + + if (self::$fixable_issue_counts && $show_suggestions && !$codebase->taint_flow_graph) { + echo str_repeat('-', 30) . "\n"; + + $total_count = \array_sum(self::$fixable_issue_counts); + $command = '--alter --issues=' . \implode(',', \array_keys(self::$fixable_issue_counts)); + $command .= ' --dry-run'; + + echo 'Psalm can automatically fix ' . $total_count + . ($show_info ? ' issues' : ' of these issues') . ".\n" + . 'Run Psalm again with ' . "\n" + . ($project_analyzer->stdout_report_options->use_color + ? "\e[30;48;5;195m" . $command . "\e[0m" + : $command) . "\n" + . 'to see what it can fix.' . "\n"; + } + + echo str_repeat('-', 30) . "\n" . "\n"; + + if ($start_time) { + echo 'Checks took ' . number_format(microtime(true) - $start_time, 2) . ' seconds'; + echo ' and used ' . number_format(memory_get_peak_usage() / (1024 * 1024), 3) . 'MB of memory' . "\n"; + + $analysis_summary = $codebase->analyzer->getTypeInferenceSummary($codebase); + echo $analysis_summary . "\n"; + + if ($add_stats) { + echo '-----------------' . "\n"; + echo $codebase->analyzer->getNonMixedStats(); + echo "\n"; + } + + if ($project_analyzer->debug_performance) { + echo '-----------------' . "\n"; + echo 'Slow-to-analyze functions' . "\n"; + echo '-----------------' . "\n\n"; + + $function_timings = $codebase->analyzer->getFunctionTimings(); + + \arsort($function_timings); + + $i = 0; + + foreach ($function_timings as $function_id => $time) { + if (++$i > 10) { + break; + } + + echo $function_id . ': ' . \round(1000 * $time, 2) . 'ms per node' . "\n"; + } + + echo "\n"; + } + } + } + + if ($is_full && $start_time) { + $codebase->file_reference_provider->removeDeletedFilesFromReferences(); + + if ($project_analyzer->project_cache_provider) { + $project_analyzer->project_cache_provider->processSuccessfulRun($start_time); + } + + if ($codebase->statements_provider->parser_cache_provider) { + $codebase->statements_provider->parser_cache_provider->processSuccessfulRun(); + } + } + + if ($error_count) { + exit(1); + } + } + + /** + * @param array> $issues_data + * @param array{int, int} $mixed_counts + * + */ + public static function getOutput( + array $issues_data, + \Psalm\Report\ReportOptions $report_options, + array $mixed_counts = [0, 0] + ): string { + $total_expression_count = $mixed_counts[0] + $mixed_counts[1]; + $mixed_expression_count = $mixed_counts[0]; + + $normalized_data = $issues_data === [] ? [] : array_merge(...array_values($issues_data)); + + switch ($report_options->format) { + case Report::TYPE_COMPACT: + $output = new CompactReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + case Report::TYPE_EMACS: + $output = new EmacsReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + case Report::TYPE_TEXT: + $output = new TextReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + case Report::TYPE_JSON: + $output = new JsonReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + case Report::TYPE_JSON_SUMMARY: + $output = new JsonSummaryReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + $mixed_expression_count, + $total_expression_count + ); + break; + + case Report::TYPE_SONARQUBE: + $output = new SonarqubeReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + case Report::TYPE_PYLINT: + $output = new PylintReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + case Report::TYPE_CHECKSTYLE: + $output = new CheckstyleReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + case Report::TYPE_XML: + $output = new XmlReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + case Report::TYPE_JUNIT: + $output = new JunitReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + case Report::TYPE_CONSOLE: + $output = new ConsoleReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + case Report::TYPE_GITHUB_ACTIONS: + $output = new GithubActionsReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + + case Report::TYPE_PHP_STORM: + $output = new PhpStormReport($normalized_data, self::$fixable_issue_counts, $report_options); + break; + } + + return $output->create(); + } + + protected static function alreadyEmitted(string $message): bool + { + $sham = sha1($message); + + if (isset(self::$emitted[$sham])) { + return true; + } + + self::$emitted[$sham] = true; + + return false; + } + + public static function clearCache(): void + { + self::$issues_data = []; + self::$emitted = []; + self::$error_count = 0; + self::$recording_level = 0; + self::$recorded_issues = []; + self::$console_issues = []; + self::$unused_suppressions = []; + self::$used_suppressions = []; + } + + /** + * @return array> + */ + public static function clear(): array + { + $current_data = self::$issues_data; + self::$issues_data = []; + self::$emitted = []; + + return $current_data; + } + + public static function isRecording(): bool + { + return self::$recording_level > 0; + } + + public static function startRecording(): void + { + ++self::$recording_level; + self::$recorded_issues[self::$recording_level] = []; + } + + public static function stopRecording(): void + { + if (self::$recording_level === 0) { + throw new \UnexpectedValueException('Cannot stop recording - already at base level'); + } + + --self::$recording_level; + } + + /** + * @return array + */ + public static function clearRecordingLevel(): array + { + if (self::$recording_level === 0) { + throw new \UnexpectedValueException('Not currently recording'); + } + + $recorded_issues = self::$recorded_issues[self::$recording_level]; + + self::$recorded_issues[self::$recording_level] = []; + + return $recorded_issues; + } + + public static function bubbleUp(CodeIssue $e): void + { + if (self::$recording_level === 0) { + self::add($e); + + return; + } + + self::$recorded_issues[self::$recording_level][] = $e; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/NodeTypeProvider.php b/lib/composer/vimeo/psalm/src/Psalm/NodeTypeProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..503a863f220df1726f588e5da5e60e695d49e0fc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/NodeTypeProvider.php @@ -0,0 +1,18 @@ +> $issues + */ + public static function afterAnalysis( + Codebase $codebase, + array $issues, + array $build_info, + ?SourceControlInfo $source_control_info = null + ): void; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/AfterClassLikeAnalysisInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/AfterClassLikeAnalysisInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..6c2cf483b7401220dcbe32e0001def3e456bfac8 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/AfterClassLikeAnalysisInterface.php @@ -0,0 +1,26 @@ + + */ + public static function getFunctionIds() : array; + + /** + * Use this hook for informing whether or not a global function exists. If you know the function does + * not exist, return false. If you aren't sure if it exists or not, return null and the default analysis + * will continue to determine if the function actually exists. + * + */ + public static function doesFunctionExist( + StatementsSource $statements_source, + string $function_id + ): ?bool; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionParamsProviderInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionParamsProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c334e33f0d3a0ade2fbe14aaf3b213c6f6dfbf5d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionParamsProviderInterface.php @@ -0,0 +1,28 @@ + + */ + public static function getFunctionIds() : array; + + /** + * @param array $call_args + * + * @return ?array + */ + public static function getFunctionParams( + StatementsSource $statements_source, + string $function_id, + array $call_args, + ?Context $context = null, + ?CodeLocation $code_location = null + ): ?array; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionReturnTypeProviderInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionReturnTypeProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..afc8bfcdb11e4e38ac9e11e89f46dd3310b2605d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/FunctionReturnTypeProviderInterface.php @@ -0,0 +1,31 @@ + + */ + public static function getFunctionIds() : array; + + /** + * Use this hook for providing custom return type logic. If this plugin does not know what a function should + * return but another plugin may be able to determine the type, return null. Otherwise return a mixed union type + * if something should be returned, but can't be more specific. + * + * @param array $call_args + */ + public static function getFunctionReturnType( + StatementsSource $statements_source, + string $function_id, + array $call_args, + Context $context, + CodeLocation $code_location + ): ?Type\Union; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodExistenceProviderInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodExistenceProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..5aa742591ae3071f70049910d396780d4a076730 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodExistenceProviderInterface.php @@ -0,0 +1,25 @@ + + */ + public static function getClassLikeNames() : array; + + /** + * Use this hook for informing whether or not a method exists on a given object. If you know the method does + * not exist, return false. If you aren't sure if it exists or not, return null and the default analysis will + * continue to determine if the method actually exists. + */ + public static function doesMethodExist( + string $fq_classlike_name, + string $method_name_lowercase, + ?StatementsSource $source = null, + ?CodeLocation $code_location = null + ): ?bool; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodParamsProviderInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodParamsProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..af1b7ec8a624e7bd1ac6bdcfa782b1ee5a57f455 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodParamsProviderInterface.php @@ -0,0 +1,29 @@ + + */ + public static function getClassLikeNames() : array; + + /** + * @param array $call_args + * + * @return ?array + */ + public static function getMethodParams( + string $fq_classlike_name, + string $method_name_lowercase, + ?array $call_args = null, + ?StatementsSource $statements_source = null, + ?Context $context = null, + ?CodeLocation $code_location = null + ): ?array; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodReturnTypeProviderInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodReturnTypeProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..326840f1539fd10581a1639e141f6e7f865bbfd1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodReturnTypeProviderInterface.php @@ -0,0 +1,38 @@ + + */ + public static function getClassLikeNames() : array; + + /** + * Use this hook for providing custom return type logic. If this plugin does not know what a method should return + * but another plugin may be able to determine the type, return null. Otherwise return a mixed union type if + * something should be returned, but can't be more specific. + * + * @param array $call_args + * @param ?array $template_type_parameters + * @param lowercase-string $method_name_lowercase + * @param lowercase-string $called_method_name_lowercase + */ + public static function getMethodReturnType( + StatementsSource $source, + string $fq_classlike_name, + string $method_name_lowercase, + array $call_args, + Context $context, + CodeLocation $code_location, + ?array $template_type_parameters = null, + ?string $called_fq_classlike_name = null, + ?string $called_method_name_lowercase = null + ): ?Type\Union; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodVisibilityProviderInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodVisibilityProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..ff74b48d68a6c967b25db2b434dab3683f0d4f8f --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/MethodVisibilityProviderInterface.php @@ -0,0 +1,22 @@ + + */ + public static function getClassLikeNames() : array; + + public static function isMethodVisible( + StatementsSource $source, + string $fq_classlike_name, + string $method_name_lowercase, + Context $context, + ?CodeLocation $code_location = null + ): ?bool; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyExistenceProviderInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyExistenceProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..57b06e2033aa9a0ec836d1d67bff01fe4e081e2a --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyExistenceProviderInterface.php @@ -0,0 +1,29 @@ + + */ + public static function getClassLikeNames() : array; + + /** + * Use this hook for informing whether or not a property exists on a given object. If you know the property does + * not exist, return false. If you aren't sure if it exists or not, return null and the default analysis will + * continue to determine if the property actually exists. + * + */ + public static function doesPropertyExist( + string $fq_classlike_name, + string $property_name, + bool $read_mode, + ?StatementsSource $source = null, + ?Context $context = null, + ?CodeLocation $code_location = null + ): ?bool; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyTypeProviderInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyTypeProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8f7e8e38a89158b657db369c1fd9ce2828052e1a --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyTypeProviderInterface.php @@ -0,0 +1,27 @@ + + */ + public static function getClassLikeNames() : array; + + /** + * @param array $call_args + * + */ + public static function getPropertyType( + string $fq_classlike_name, + string $property_name, + bool $read_mode, + ?StatementsSource $source = null, + ?Context $context = null + ): ?Type\Union; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyVisibilityProviderInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyVisibilityProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a4c5ff9a0b152bd4c7d617e2d71cedb55720cb13 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/PropertyVisibilityProviderInterface.php @@ -0,0 +1,23 @@ + + */ + public static function getClassLikeNames() : array; + + public static function isPropertyVisible( + StatementsSource $source, + string $fq_classlike_name, + string $property_name, + bool $read_mode, + Context $context, + CodeLocation $code_location + ): ?bool; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/StringInterpreterInterface.php b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/StringInterpreterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..39394c217f55a033b52d72aba5a44a0a214a8862 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Plugin/Hook/StringInterpreterInterface.php @@ -0,0 +1,14 @@ +> $issues + */ + public static function afterAnalysis( + Codebase $codebase, + array $issues, + array $build_info, + ?SourceControlInfo $source_control_info = null + ): void { + if (!function_exists('curl_init')) { + fwrite(STDERR, 'No curl found, cannot send data to ' . $codebase->config->shepherd_host . PHP_EOL); + + return; + } + + $source_control_data = $source_control_info ? $source_control_info->toArray() : []; + + if (!$source_control_data && isset($build_info['git']) && \is_array($build_info['git'])) { + $source_control_data = $build_info['git']; + } + + unset($build_info['git']); + + if ($build_info) { + $normalized_data = $issues === [] ? [] : array_filter( + array_merge(...array_values($issues)), + static function (IssueData $i) : bool { + return $i->severity === 'error'; + } + ); + + $data = [ + 'build' => $build_info, + 'git' => $source_control_data, + 'issues' => $normalized_data, + 'coverage' => $codebase->analyzer->getTotalTypeCoverage($codebase), + ]; + + $payload = json_encode($data); + + $base_address = $codebase->config->shepherd_host; + + if (parse_url($base_address, PHP_URL_SCHEME) === null) { + $base_address = 'https://' . $base_address; + } + + // Prepare new cURL resource + $ch = curl_init($base_address . '/hooks/psalm'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLINFO_HEADER_OUT, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + + // Set HTTP Header for POST request + curl_setopt( + $ch, + CURLOPT_HTTPHEADER, + [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($payload), + ] + ); + + // Submit the POST request + $return = curl_exec($ch); + + if ($return !== '') { + fwrite(STDERR, 'Error with Psalm Shepherd:' . PHP_EOL); + + if ($return === false) { + fwrite(STDERR, self::getCurlErrorMessage($ch) . PHP_EOL); + } else { + echo $return . PHP_EOL; + echo 'Git args: ' + . var_export($source_control_data, true) + . PHP_EOL; + echo 'CI args: ' + . var_export($build_info, true) + . PHP_EOL; + } + } else { + $short_address = \str_replace('https://', '', $base_address); + + fwrite(STDERR, "🐑 results sent to $short_address 🐑" . PHP_EOL); + } + + // Close cURL session handle + curl_close($ch); + } + } + + /** + * @param mixed $ch + * + * @psalm-pure + */ + public static function getCurlErrorMessage($ch) : string + { + /** + * @psalm-suppress MixedArgument + * @var array + */ + $curl_info = curl_getinfo($ch); + + if (isset($curl_info['ssl_verify_result']) + && $curl_info['ssl_verify_result'] !== 0 + ) { + switch ($curl_info['ssl_verify_result']) { + case 2: + return 'unable to get issuer certificate'; + case 3: + return 'unable to get certificate CRL'; + case 4: + return 'unable to decrypt certificate’s signature'; + case 5: + return 'unable to decrypt CRL’s signature'; + case 6: + return 'unable to decode issuer public key'; + case 7: + return 'certificate signature failure'; + case 8: + return 'CRL signature failure'; + case 9: + return 'certificate is not yet valid'; + case 10: + return 'certificate has expired'; + case 11: + return 'CRL is not yet valid'; + case 12: + return 'CRL has expired'; + case 13: + return 'format error in certificate’s notBefore field'; + case 14: + return 'format error in certificate’s notAfter field'; + case 15: + return 'format error in CRL’s lastUpdate field'; + case 16: + return 'format error in CRL’s nextUpdate field'; + case 17: + return 'out of memory'; + case 18: + return 'self signed certificate'; + case 19: + return 'self signed certificate in certificate chain'; + case 20: + return 'unable to get local issuer certificate'; + case 21: + return 'unable to verify the first certificate'; + case 22: + return 'certificate chain too long'; + case 23: + return 'certificate revoked'; + case 24: + return 'invalid CA certificate'; + case 25: + return 'path length constraint exceeded'; + case 26: + return 'unsupported certificate purpose'; + case 27: + return 'certificate not trusted'; + case 28: + return 'certificate rejected'; + case 29: + return 'subject issuer mismatch'; + case 30: + return 'authority and subject key identifier mismatch'; + case 31: + return 'authority and issuer serial number mismatch'; + case 32: + return 'key usage does not include certificate signing'; + case 50: + return 'application verification failure'; + } + + return ''; + } + + /** + * @psalm-suppress MixedArgument + */ + return var_export(curl_getinfo($ch), true); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/PluginRegistrationSocket.php b/lib/composer/vimeo/psalm/src/Psalm/PluginRegistrationSocket.php new file mode 100644 index 0000000000000000000000000000000000000000..7c5cf26cf8ce5985e45bbdc4476e85a5a772c587 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/PluginRegistrationSocket.php @@ -0,0 +1,133 @@ +config = $config; + $this->codebase = $codebase; + } + + public function addStubFile(string $file_name): void + { + $this->config->addStubFile($file_name); + } + + public function registerHooksFromClass(string $handler): void + { + if (!class_exists($handler, false)) { + throw new \InvalidArgumentException('Plugins must be loaded before registration'); + } + + if (is_subclass_of($handler, Hook\AfterFileAnalysisInterface::class)) { + $this->config->after_file_checks[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\AfterMethodCallAnalysisInterface::class)) { + $this->config->after_method_checks[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\AfterFunctionCallAnalysisInterface::class)) { + $this->config->after_function_checks[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\AfterEveryFunctionCallAnalysisInterface::class)) { + $this->config->after_every_function_checks[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\AfterExpressionAnalysisInterface::class)) { + $this->config->after_expression_checks[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\AfterStatementAnalysisInterface::class)) { + $this->config->after_statement_checks[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\AfterClassLikeExistenceCheckInterface::class)) { + $this->config->after_classlike_exists_checks[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\AfterClassLikeAnalysisInterface::class)) { + $this->config->after_classlike_checks[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\AfterClassLikeVisitInterface::class)) { + $this->config->after_visit_classlikes[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\AfterCodebasePopulatedInterface::class)) { + $this->config->after_codebase_populated[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\BeforeFileAnalysisInterface::class)) { + $this->config->before_file_checks[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\PropertyExistenceProviderInterface::class)) { + $this->codebase->properties->property_existence_provider->registerClass($handler); + } + + if (is_subclass_of($handler, Hook\PropertyVisibilityProviderInterface::class)) { + $this->codebase->properties->property_visibility_provider->registerClass($handler); + } + + if (is_subclass_of($handler, Hook\PropertyTypeProviderInterface::class)) { + $this->codebase->properties->property_type_provider->registerClass($handler); + } + + if (is_subclass_of($handler, Hook\MethodExistenceProviderInterface::class)) { + $this->codebase->methods->existence_provider->registerClass($handler); + } + + if (is_subclass_of($handler, Hook\MethodVisibilityProviderInterface::class)) { + $this->codebase->methods->visibility_provider->registerClass($handler); + } + + if (is_subclass_of($handler, Hook\MethodReturnTypeProviderInterface::class)) { + $this->codebase->methods->return_type_provider->registerClass($handler); + } + + if (is_subclass_of($handler, Hook\MethodParamsProviderInterface::class)) { + $this->codebase->methods->params_provider->registerClass($handler); + } + + if (is_subclass_of($handler, Hook\FunctionExistenceProviderInterface::class)) { + $this->codebase->functions->existence_provider->registerClass($handler); + } + + if (is_subclass_of($handler, Hook\FunctionParamsProviderInterface::class)) { + $this->codebase->functions->params_provider->registerClass($handler); + } + + if (is_subclass_of($handler, Hook\FunctionReturnTypeProviderInterface::class)) { + $this->codebase->functions->return_type_provider->registerClass($handler); + } + + if (is_subclass_of($handler, Hook\AfterAnalysisInterface::class)) { + $this->config->after_analysis[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\StringInterpreterInterface::class)) { + $this->config->string_interpreters[$handler] = $handler; + } + + if (is_subclass_of($handler, Hook\AfterFunctionLikeAnalysisInterface::class)) { + $this->config->after_functionlike_checks[$handler] = $handler; + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Progress/DebugProgress.php b/lib/composer/vimeo/psalm/src/Psalm/Progress/DebugProgress.php new file mode 100644 index 0000000000000000000000000000000000000000..4b3a3cd526924752efc565b1962b046425ac8f5b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Progress/DebugProgress.php @@ -0,0 +1,38 @@ +write($message); + } + + public function startScanningFiles(): void + { + $this->write('Scanning files...' . "\n"); + } + + public function startAnalyzingFiles(): void + { + $this->write('Analyzing files...' . "\n"); + } + + public function startAlteringFiles(): void + { + $this->write('Updating files...' . "\n"); + } + + public function alterFileDone(string $file_name) : void + { + $this->write('Altered ' . $file_name . "\n"); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Progress/DefaultProgress.php b/lib/composer/vimeo/psalm/src/Psalm/Progress/DefaultProgress.php new file mode 100644 index 0000000000000000000000000000000000000000..0470b7d06b189f2a72caf5a581b6eb91bfc37e3c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Progress/DefaultProgress.php @@ -0,0 +1,101 @@ +number_of_tasks > self::TOO_MANY_FILES) { + ++$this->progress; + + // Source for rate limiting: + // https://github.com/phan/phan/blob/9a788581ee1a4e1c35bebf89c435fd8a238c1d17/src/Phan/CLI.php + $time = \microtime(true); + + // If not enough time has elapsed, then don't update the progress bar. + // Making the update frequency based on time (instead of the number of files) + // prevents the terminal from rapidly flickering while processing small/empty files, + // and reduces the time spent writing to stderr. + if ($time - $this->previous_update_time < self::PROGRESS_BAR_SAMPLE_INTERVAL) { + // Make sure to output the section for 100% completion regardless of limits, to avoid confusion. + if ($this->progress !== $this->number_of_tasks) { + return; + } + } + $this->previous_update_time = $time; + + $inner_progress = self::renderInnerProgressBar( + self::NUMBER_OF_COLUMNS, + $this->progress / $this->number_of_tasks + ); + + $this->write($inner_progress . ' ' . $this->getOverview() . "\r"); + } else { + parent::taskDone($level); + } + } + + /** + * Fully stolen from + * https://github.com/phan/phan/blob/d61a624b1384ea220f39927d53fd656a65a75fac/src/Phan/CLI.php + * Renders a unicode progress bar that goes from light (left) to dark (right) + * The length in the console is the positive integer $length + * + * @see https://en.wikipedia.org/wiki/Block_Elements + */ + private static function renderInnerProgressBar(int $length, float $p) : string + { + $current_float = $p * $length; + $current = (int)$current_float; + $rest = \max($length - $current, 0); + + if (!self::doesTerminalSupportUtf8()) { + // Show a progress bar of "XXXX>------" in Windows when utf-8 is unsupported. + $progress_bar = str_repeat('X', $current); + $delta = $current_float - $current; + if ($delta > 0.5) { + $progress_bar .= '>' . str_repeat('-', $rest - 1); + } else { + $progress_bar .= str_repeat('-', $rest); + } + + return $progress_bar; + } + + // The left-most characters are "Light shade" + $progress_bar = str_repeat("\u{2588}", $current); + $delta = $current_float - $current; + if ($delta > 3.0 / 4) { + $progress_bar .= "\u{258A}" . str_repeat("\u{2591}", $rest - 1); + } elseif ($delta > 2.0 / 4) { + $progress_bar .= "\u{258C}" . str_repeat("\u{2591}", $rest - 1); + } elseif ($delta > 1.0 / 4) { + $progress_bar .= "\u{258E}" . str_repeat("\u{2591}", $rest - 1); + } else { + $progress_bar .= str_repeat("\u{2591}", $rest); + } + + return $progress_bar; + } + + public function finish(): void + { + if ($this->number_of_tasks > self::TOO_MANY_FILES) { + $this->write(str_repeat(' ', self::NUMBER_OF_COLUMNS + strlen($this->getOverview()) + 1) . "\r"); + } else { + parent::finish(); + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Progress/LongProgress.php b/lib/composer/vimeo/psalm/src/Psalm/Progress/LongProgress.php new file mode 100644 index 0000000000000000000000000000000000000000..daf8b9ce4ef4e4a365910e3eaf156efe9c100873 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Progress/LongProgress.php @@ -0,0 +1,106 @@ +print_errors = $print_errors; + $this->print_infos = $print_infos; + } + + public function startScanningFiles(): void + { + $this->write('Scanning files...' . "\n"); + } + + public function startAnalyzingFiles(): void + { + $this->write('Analyzing files...' . "\n\n"); + } + + public function startAlteringFiles(): void + { + $this->write('Altering files...' . "\n"); + } + + public function alterFileDone(string $file_name) : void + { + $this->write('Altered ' . $file_name . "\n"); + } + + public function start(int $number_of_tasks): void + { + $this->number_of_tasks = $number_of_tasks; + $this->progress = 0; + } + + public function taskDone(int $level): void + { + if ($level === 0 || ($level === 1 && !$this->print_infos) || !$this->print_errors) { + $this->write(self::doesTerminalSupportUtf8() ? '░' : '_'); + } elseif ($level === 1) { + $this->write('I'); + } else { + $this->write('E'); + } + + ++$this->progress; + + if (($this->progress % self::NUMBER_OF_COLUMNS) !== 0) { + return; + } + + $this->printOverview(); + $this->write(PHP_EOL); + } + + public function finish(): void + { + $this->write(PHP_EOL); + } + + protected function getOverview() : string + { + if ($this->number_of_tasks === null) { + throw new \LogicException('Progress::start() should be called before Progress::startDone()'); + } + + $leadingSpaces = 1 + strlen((string) $this->number_of_tasks) - strlen((string) $this->progress); + // Don't show 100% unless this is the last line of the progress bar. + $percentage = floor($this->progress / $this->number_of_tasks * 100); + + return sprintf( + '%s%s / %s (%s%%)', + str_repeat(' ', $leadingSpaces), + $this->progress, + $this->number_of_tasks, + $percentage + ); + } + + private function printOverview(): void + { + $this->write($this->getOverview()); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Progress/Progress.php b/lib/composer/vimeo/psalm/src/Psalm/Progress/Progress.php new file mode 100644 index 0000000000000000000000000000000000000000..d0eff87da9851dd77708e124b2d4bb92eea85fab --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Progress/Progress.php @@ -0,0 +1,64 @@ + + */ + protected $issues_data; + + /** @var array */ + protected $fixable_issue_counts; + + /** @var bool */ + protected $use_color; + + /** @var bool */ + protected $show_snippet; + + /** @var bool */ + protected $show_info; + + /** @var bool */ + protected $pretty; + + /** @var int */ + protected $mixed_expression_count; + + /** @var int */ + protected $total_expression_count; + + /** + * @param array $issues_data + * @param array $fixable_issue_counts + * @param bool $use_color + * @param bool $show_snippet + * @param bool $show_info + */ + public function __construct( + array $issues_data, + array $fixable_issue_counts, + Report\ReportOptions $report_options, + int $mixed_expression_count = 1, + int $total_expression_count = 1 + ) { + if (!$report_options->show_info) { + $this->issues_data = array_filter( + $issues_data, + function ($issue_data) : bool { + return $issue_data->severity !== Config::REPORT_INFO; + } + ); + } else { + $this->issues_data = $issues_data; + } + $this->fixable_issue_counts = $fixable_issue_counts; + + $this->use_color = $report_options->use_color; + $this->show_snippet = $report_options->show_snippet; + $this->show_info = $report_options->show_info; + $this->pretty = $report_options->pretty; + + $this->mixed_expression_count = $mixed_expression_count; + $this->total_expression_count = $total_expression_count; + } + + abstract public function create(): string; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/CheckstyleReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/CheckstyleReport.php new file mode 100644 index 0000000000000000000000000000000000000000..b0a9fa0e2b126ffb15ea33fe6c5c4bb8642271ba --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/CheckstyleReport.php @@ -0,0 +1,38 @@ +' . "\n"; + + $output .= '' . "\n"; + + foreach ($this->issues_data as $issue_data) { + $message = sprintf( + '%s: %s', + $issue_data->type, + $issue_data->message + ); + + $output .= '' . "\n"; + $output .= ' '; + $output .= 'line_from . '"'; + $output .= ' column="' . $issue_data->column_from . '"'; + $output .= ' severity="' . $issue_data->severity . '"'; + $output .= ' message="' . htmlspecialchars($message) . '"'; + $output .= '/>' . "\n"; + $output .= '' . "\n"; + } + + $output .= '' . "\n"; + + return $output; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/CompactReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/CompactReport.php new file mode 100644 index 0000000000000000000000000000000000000000..366f53eee3719fc57adcf9fa2c613f6de752ff80 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/CompactReport.php @@ -0,0 +1,80 @@ +issues_data as $i => $issue_data) { + if (!$this->show_info && $issue_data->severity === Config::REPORT_INFO) { + continue; + } elseif ($current_file === null || $current_file !== $issue_data->file_name) { + // If we're processing a new file, then wrap up the last table and render it out. + if ($buffer !== null) { + $table->render(); + $output[] = $buffer->fetch(); + } + + $output[] = 'FILE: ' . $issue_data->file_name . "\n"; + + $buffer = new BufferedOutput(); + $table = new Table($buffer); + $table->setHeaders(['SEVERITY', 'LINE', 'ISSUE', 'DESCRIPTION']); + } + + $is_error = $issue_data->severity === Config::REPORT_ERROR; + if ($is_error) { + $severity = ($this->use_color ? "\e[0;31mERROR\e[0m" : 'ERROR'); + } else { + $severity = strtoupper($issue_data->severity); + } + + // Since `Table::setColumnMaxWidth` is only available in symfony/console 4.2+ we need do something similar + // so we have clean tables. + $message = $issue_data->message; + if (strlen($message) > 70) { + $message = implode("\n", str_split($message, 70)); + } + + $table->addRow([ + $severity, + $issue_data->line_from, + $issue_data->type, + $message, + ]); + + $current_file = $issue_data->file_name; + + // If we're at the end of the issue sets, then wrap up the last table and render it out. + if ($i === count($this->issues_data) - 1) { + $table->render(); + $output[] = $buffer->fetch(); + } + } + + return implode("\n", $output); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/ConsoleReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/ConsoleReport.php new file mode 100644 index 0000000000000000000000000000000000000000..2656a5ef607aae8dcee1eb81a8d75c05cd36caf3 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/ConsoleReport.php @@ -0,0 +1,96 @@ +issues_data as $issue_data) { + $output .= $this->format($issue_data) . "\n" . "\n"; + } + + return $output; + } + + private function format(\Psalm\Internal\Analyzer\IssueData $issue_data): string + { + $issue_string = ''; + + $is_error = $issue_data->severity === Config::REPORT_ERROR; + + if ($is_error) { + $issue_string .= ($this->use_color ? "\e[0;31mERROR\e[0m" : 'ERROR'); + } else { + $issue_string .= 'INFO'; + } + + $issue_reference = ' (see ' . $issue_data->link . ')'; + + $issue_string .= ': ' . $issue_data->type + . ' - ' . $issue_data->file_name . ':' . $issue_data->line_from . ':' . $issue_data->column_from + . ' - ' . $issue_data->message . $issue_reference . "\n"; + + + if ($issue_data->taint_trace) { + $issue_string .= $this->getTaintSnippets($issue_data->taint_trace); + } elseif ($this->show_snippet) { + $snippet = $issue_data->snippet; + + if (!$this->use_color) { + $issue_string .= $snippet; + } else { + $selection_start = $issue_data->from - $issue_data->snippet_from; + $selection_length = $issue_data->to - $issue_data->from; + + $issue_string .= substr($snippet, 0, $selection_start) + . ($is_error ? "\e[97;41m" : "\e[30;47m") . substr($snippet, $selection_start, $selection_length) + . "\e[0m" . substr($snippet, $selection_length + $selection_start) . "\n"; + } + } + + return $issue_string; + } + + /** + * @param non-empty-list $taint_trace + */ + private function getTaintSnippets(array $taint_trace) : string + { + $snippets = ''; + + foreach ($taint_trace as $node_data) { + if ($node_data instanceof DataFlowNodeData) { + $snippets .= ' ' . $node_data->label + . ' - ' . $node_data->file_name + . ':' . $node_data->line_from + . ':' . $node_data->column_from . "\n"; + + if ($this->show_snippet) { + $snippet = $node_data->snippet; + + if (!$this->use_color) { + $snippets .= $snippet . "\n\n"; + } else { + $selection_start = $node_data->from - $node_data->snippet_from; + $selection_length = $node_data->to - $node_data->from; + + $snippets .= substr($snippet, 0, $selection_start) + . "\e[30;47m" . substr($snippet, $selection_start, $selection_length) + . "\e[0m" . substr($snippet, $selection_length + $selection_start) . "\n\n"; + } + } + } else { + $snippets .= ' ' . $node_data['label'] . "\n"; + $snippets .= ' ' . "\n\n"; + } + } + + return $snippets; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/EmacsReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/EmacsReport.php new file mode 100644 index 0000000000000000000000000000000000000000..e950c1e57b86a20694b93494e5df1bedf5eaf7fe --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/EmacsReport.php @@ -0,0 +1,26 @@ +issues_data as $issue_data) { + $output .= sprintf( + '%s:%s:%s:%s - %s', + $issue_data->file_path, + $issue_data->line_from, + $issue_data->column_from, + ($issue_data->severity === Config::REPORT_ERROR ? 'error' : 'warning'), + $issue_data->message + ) . "\n"; + } + + return $output; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/GithubActionsReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/GithubActionsReport.php new file mode 100644 index 0000000000000000000000000000000000000000..e442d8e62e8b5692a66cae3143a5dc471a37f6a8 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/GithubActionsReport.php @@ -0,0 +1,26 @@ +issues_data as $issue_data) { + $output .= sprintf( + '::%s file=%s,line=%s,col=%s::%s', + ($issue_data->severity === Config::REPORT_ERROR ? 'error' : 'warning'), + $issue_data->file_name, + $issue_data->line_from, + $issue_data->column_from, + $issue_data->message + ) . "\n"; + } + + return $output; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/JsonReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/JsonReport.php new file mode 100644 index 0000000000000000000000000000000000000000..fdca5b25dde07d6bf1a5c53540d148107624e1c6 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/JsonReport.php @@ -0,0 +1,27 @@ +pretty ? Json::PRETTY : Json::DEFAULT; + + $issues_data = \array_map( + function ($issue_data): array { + $issue_data = (array) $issue_data; + unset($issue_data['dupe_key']); + return $issue_data; + }, + $this->issues_data + ); + + return Json::encode(array_values($issues_data), $options) . "\n"; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/JsonSummaryReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/JsonSummaryReport.php new file mode 100644 index 0000000000000000000000000000000000000000..a64ca38b08c852a98f5845903cee6a49b534df92 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/JsonSummaryReport.php @@ -0,0 +1,31 @@ +issues_data as $issue_data) { + $type = $issue_data->type; + + if (!isset($type_counts[$type])) { + $type_counts[$type] = 0; + } + + ++$type_counts[$type]; + } + + $options = $this->pretty ? Json::PRETTY : Json::DEFAULT; + + return Json::encode([ + 'issue_counts' => $type_counts, + 'mixed_expression_count' => $this->mixed_expression_count, + 'total_expression_count' => $this->total_expression_count, + ], $options) . "\n"; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/JunitReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/JunitReport.php new file mode 100644 index 0000000000000000000000000000000000000000..6f545634ecb504bcbdbbc2c97de0245429274944 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/JunitReport.php @@ -0,0 +1,176 @@ +issues_data as $error) { + $is_error = $error->severity === Config::REPORT_ERROR; + $is_warning = $error->severity === Config::REPORT_INFO; + + if (!$is_error && !$is_warning) { + continue; + } + + if ($is_error) { + $errors++; + } + + $tests++; + + $fname = $error->file_name; + + if (!isset($ndata[$fname])) { + $ndata[$fname] = [ + 'errors' => $is_error ? 1 : 0, + 'warnings' => $is_warning ? 1 : 0, + 'failures' => [], + ]; + } else { + if ($is_error) { + $ndata[$fname]['errors']++; + } else { + $ndata[$fname]['warnings']++; + } + } + + $ndata[$fname]['failures'][] = $error; + } + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + + $schema = 'https://raw.githubusercontent.com/junit-team/'. + 'junit5/r5.5.1/platform-tests/src/test/resources/jenkins-junit.xsd'; + + $suites = $dom->createElement('testsuites'); + + $suites->setAttribute('failures', (string) $errors); + $suites->setAttribute('errors', '0'); + $suites->setAttribute('name', 'psalm'); + $suites->setAttribute('tests', (string) $tests); + $suites->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $suites->setAttribute('xsi:noNamespaceSchemaLocation', $schema); + $dom->appendChild($suites); + + if (!count($ndata)) { + $suites->setAttribute('tests', '1'); + + $testsuite = $dom->createElement('testsuite'); + $testsuite->setAttribute('name', 'psalm'); + $testsuite->setAttribute('failures', '0'); + $testsuite->setAttribute('errors', '0'); + $testsuite->setAttribute('tests', '1'); + + $testcase = $dom->createElement('testcase'); + $testcase->setAttribute('name', 'psalm'); + $testsuite->appendChild($testcase); + + $suites->appendChild($testsuite); + } else { + foreach ($ndata as $file => $report) { + $this->createTestSuite($dom, $suites, $file, $report); + } + } + + + + return $dom->saveXML(); + } + + /** + * @param array{ + * errors: int, + * warnings: int, + * failures: list + * } $report + */ + private function createTestSuite(DOMDocument $dom, DOMElement $parent, string $file, array $report): void + { + $totalTests = $report['errors'] + $report['warnings']; + if ($totalTests < 1) { + $totalTests = 1; + } + + $testsuite = $dom->createElement('testsuite'); + $testsuite->setAttribute('name', $file); + $testsuite->setAttribute('failures', (string) $report['errors']); + $testsuite->setAttribute('errors', '0'); + $testsuite->setAttribute('tests', (string) $totalTests); + + $failuresByType = $this->groupByType($report['failures']); + + foreach ($failuresByType as $type => $data) { + foreach ($data as $d) { + $testcase = $dom->createElement('testcase'); + $testcase->setAttribute('name', "{$file}:{$d->line_from}"); + $testcase->setAttribute('classname', $type); + $testcase->setAttribute('assertions', (string) count($data)); + + if ($d->severity === Config::REPORT_ERROR) { + $issue = $dom->createElement('failure'); + $issue->setAttribute('type', $type); + } else { + $issue = $dom->createElement('skipped'); + } + $issue->nodeValue = $this->dataToOutput($d); + + $testcase->appendChild($issue); + $testsuite->appendChild($testcase); + } + } + $parent->appendChild($testsuite); + } + + /** + * @param list $failures + * + * @return array> + */ + private function groupByType(array $failures): array + { + $nfailures = []; + + foreach ($failures as $failure) { + $nfailures[$failure->type][] = $failure; + } + + return $nfailures; + } + + private function dataToOutput(IssueData $data): string + { + $ret = 'message: ' . htmlspecialchars(trim($data->message), ENT_XML1 | ENT_QUOTES) . "\n"; + $ret .= 'type: ' . trim($data->type) . "\n"; + $ret .= 'snippet: ' . htmlspecialchars(trim($data->snippet), ENT_XML1 | ENT_QUOTES) . "\n"; + $ret .= 'selected_text: ' . htmlspecialchars(trim($data->selected_text)) . "\n"; + $ret .= 'line: ' . $data->line_from . "\n"; + $ret .= 'column_from: ' . $data->column_from . "\n"; + $ret .= 'column_to: ' . $data->column_to . "\n"; + + return $ret; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/PhpStormReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/PhpStormReport.php new file mode 100644 index 0000000000000000000000000000000000000000..4ef4c5d9e102e0fcc0a9c8ab406704f9d8d53cbd --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/PhpStormReport.php @@ -0,0 +1,96 @@ +issues_data as $issue_data) { + $output .= $this->format($issue_data) . "\n" . "\n"; + } + + return $output; + } + + private function format(\Psalm\Internal\Analyzer\IssueData $issue_data): string + { + $issue_string = ''; + + $is_error = $issue_data->severity === Config::REPORT_ERROR; + + if ($is_error) { + $issue_string .= ($this->use_color ? "\e[0;31mERROR\e[0m" : 'ERROR'); + } else { + $issue_string .= 'INFO'; + } + + $issue_reference = ' (see ' . $issue_data->link . ')'; + + $issue_string .= ': ' . $issue_data->type + . "\nat " . $issue_data->file_path . ':' . $issue_data->line_from . ':' . $issue_data->column_from + . "\n" . $issue_data->message . $issue_reference . "\n"; + + + if ($issue_data->taint_trace) { + $issue_string .= $this->getTaintSnippets($issue_data->taint_trace); + } elseif ($this->show_snippet) { + $snippet = $issue_data->snippet; + + if (!$this->use_color) { + $issue_string .= $snippet; + } else { + $selection_start = $issue_data->from - $issue_data->snippet_from; + $selection_length = $issue_data->to - $issue_data->from; + + $issue_string .= substr($snippet, 0, $selection_start) + . ($is_error ? "\e[97;41m" : "\e[30;47m") . substr($snippet, $selection_start, $selection_length) + . "\e[0m" . substr($snippet, $selection_length + $selection_start) . "\n"; + } + } + + return $issue_string; + } + + /** + * @param non-empty-list $taint_trace + */ + private function getTaintSnippets(array $taint_trace) : string + { + $snippets = ''; + + foreach ($taint_trace as $node_data) { + if ($node_data instanceof DataFlowNodeData) { + $snippets .= ' ' . $node_data->label + . ' - ' . $node_data->file_name + . ':' . $node_data->line_from + . ':' . $node_data->column_from . "\n"; + + if ($this->show_snippet) { + $snippet = $node_data->snippet; + + if (!$this->use_color) { + $snippets .= $snippet . "\n\n"; + } else { + $selection_start = $node_data->from - $node_data->snippet_from; + $selection_length = $node_data->to - $node_data->from; + + $snippets .= substr($snippet, 0, $selection_start) + . "\e[30;47m" . substr($snippet, $selection_start, $selection_length) + . "\e[0m" . substr($snippet, $selection_length + $selection_start) . "\n\n"; + } + } + } else { + $snippets .= ' ' . $node_data['label'] . "\n"; + $snippets .= ' ' . "\n\n"; + } + } + + return $snippets; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/PylintReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/PylintReport.php new file mode 100644 index 0000000000000000000000000000000000000000..44530c62696db471e6e23e7f5736689edb0ee8ca --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/PylintReport.php @@ -0,0 +1,48 @@ +issues_data as $issue_data) { + $output .= $this->format($issue_data) . "\n"; + } + + return $output; + } + + private function format(\Psalm\Internal\Analyzer\IssueData $issue_data): string + { + $message = sprintf( + '%s: %s', + $issue_data->type, + $issue_data->message + ); + + if ($issue_data->severity === Config::REPORT_ERROR) { + $code = 'E0001'; + } else { + $code = 'W0001'; + } + + // https://docs.pylint.org/en/1.6.0/output.html doesn't mention what to do about 'column', + // but it's still useful for users. + // E.g. jenkins can't parse %s:%d:%d. + $message = sprintf('%s (column %d)', $message, $issue_data->column_from); + $issue_string = sprintf( + '%s:%d: [%s] %s', + $issue_data->file_name, + $issue_data->line_from, + $code, + $message + ); + + return $issue_string; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/ReportOptions.php b/lib/composer/vimeo/psalm/src/Psalm/Report/ReportOptions.php new file mode 100644 index 0000000000000000000000000000000000000000..2901ff406f71fa59cb3bc7566c5b81f82303f118 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/ReportOptions.php @@ -0,0 +1,42 @@ + + */ + public $format = Report::TYPE_CONSOLE; + + /** + * @var bool + */ + public $pretty = false; + + /** + * @var ?string + */ + public $output_path; + + /** + * @var bool + */ + public $show_suggestions = true; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/SonarqubeReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/SonarqubeReport.php new file mode 100644 index 0000000000000000000000000000000000000000..05e47ac17f7b1e6213a91428d31715e0c6fc0a36 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/SonarqubeReport.php @@ -0,0 +1,45 @@ + []]; + + foreach ($this->issues_data as $issue_data) { + $report['issues'][] = [ + 'engineId' => 'Psalm', + 'ruleId' => $issue_data->type, + 'primaryLocation' => [ + 'message' => $issue_data->message, + 'filePath' => $issue_data->file_name, + 'textRange' => [ + 'startLine' => $issue_data->line_from, + 'endLine' => $issue_data->line_to, + // Columns in external issue reports are indexed from 0 + 'startColumn' => max(0, $issue_data->column_from - 1), + 'endColumn' => max(0, $issue_data->column_to - 1), + ], + ], + 'type' => 'CODE_SMELL', + 'severity' => $issue_data->severity === Config::REPORT_ERROR ? 'CRITICAL' : 'MINOR', + ]; + } + + $options = $this->pretty ? Json::PRETTY : Json::DEFAULT; + + return Json::encode($report, $options) . "\n"; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/TextReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/TextReport.php new file mode 100644 index 0000000000000000000000000000000000000000..2fdad0db535d2447d59e6443c7b83df7f737161c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/TextReport.php @@ -0,0 +1,27 @@ +issues_data as $issue_data) { + $output .= sprintf( + '%s:%s:%s:%s - %s: %s', + $issue_data->file_path, + $issue_data->line_from, + $issue_data->column_from, + ($issue_data->severity === Config::REPORT_ERROR ? 'error' : 'warning'), + $issue_data->type, + $issue_data->message + ) . "\n"; + } + + return $output; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Report/XmlReport.php b/lib/composer/vimeo/psalm/src/Psalm/Report/XmlReport.php new file mode 100644 index 0000000000000000000000000000000000000000..24cc6fc2687317b0dd9d116322b28524a588bdaa --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Report/XmlReport.php @@ -0,0 +1,29 @@ + array_map( + function (IssueData $issue_data): array { + $issue_data = (array) $issue_data; + unset($issue_data['dupe_key']); + return $issue_data; + }, + $this->issues_data + ) + ] + ); + + return $xml->saveXML(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/SourceControl/Git/CommitInfo.php b/lib/composer/vimeo/psalm/src/Psalm/SourceControl/Git/CommitInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..e6597f4cea73791305477aa4f0698017b6bad10c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/SourceControl/Git/CommitInfo.php @@ -0,0 +1,208 @@ + + */ +class CommitInfo +{ + /** + * Commit ID. + * + * @var null|string + */ + protected $id; + + /** + * Author name. + * + * @var null|string + */ + protected $author_name; + + /** + * Author email. + * + * @var null|string + */ + protected $author_email; + + /** + * Committer name. + * + * @var null|string + */ + protected $committer_name; + + /** + * Committer email. + * + * @var null|string + */ + protected $committer_email; + + /** + * Commit message. + * + * @var null|string + */ + protected $message; + + /** + * Commit message. + * + * @var null|int + */ + protected $date; + + public function toArray() : array + { + return [ + 'id' => $this->id, + 'author_name' => $this->author_name, + 'author_email' => $this->author_email, + 'committer_name' => $this->committer_name, + 'committer_email' => $this->committer_email, + 'message' => $this->message, + 'date' => $this->date, + ]; + } + + // accessor + + /** + * Set commit ID. + */ + public function setId(string $id) : self + { + $this->id = $id; + + return $this; + } + + /** + * Return commit ID. + * + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * Set author name. + */ + public function setAuthorName(string $author_name) : self + { + $this->author_name = $author_name; + + return $this; + } + + /** + * Return author name. + * + */ + public function getAuthorName(): ?string + { + return $this->author_name; + } + + /** + * Set author email. + * + */ + public function setAuthorEmail(string $author_email) : self + { + $this->author_email = $author_email; + + return $this; + } + + /** + * Return author email. + * + */ + public function getAuthorEmail(): ?string + { + return $this->author_email; + } + + /** + * Set committer name. + */ + public function setCommitterName(string $committer_name) : self + { + $this->committer_name = $committer_name; + + return $this; + } + + /** + * Return committer name. + * + */ + public function getCommitterName(): ?string + { + return $this->committer_name; + } + + /** + * Set committer email. + */ + public function setCommitterEmail(string $committer_email) : self + { + $this->committer_email = $committer_email; + + return $this; + } + + /** + * Return committer email. + * + */ + public function getCommitterEmail(): ?string + { + return $this->committer_email; + } + + /** + * Set commit message. + */ + public function setMessage(string $message) : self + { + $this->message = $message; + + return $this; + } + + /** + * Return commit message. + * + */ + public function getMessage(): ?string + { + return $this->message; + } + + /** + * Set commit date + */ + public function setDate(int $date) : self + { + $this->date = $date; + + return $this; + } + + /** + * Return commit date. + * + */ + public function getDate(): ?int + { + return $this->date; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/SourceControl/Git/GitInfo.php b/lib/composer/vimeo/psalm/src/Psalm/SourceControl/Git/GitInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..6dd2566cf8c0126289f9708ff4a01f292b099226 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/SourceControl/Git/GitInfo.php @@ -0,0 +1,110 @@ + + */ +class GitInfo extends SourceControlInfo +{ + /** + * Branch name. + * + * @var string + */ + protected $branch; + + /** + * Head. + * + * @var CommitInfo + */ + protected $head; + + /** + * Remote. + * + * @var RemoteInfo[] + */ + protected $remotes; + + /** + * Constructor. + * + * @param string $branch branch name + * @param CommitInfo $head hEAD commit + * @param RemoteInfo[] $remotes remote repositories + */ + public function __construct(string $branch, CommitInfo $head, array $remotes) + { + $this->branch = $branch; + $this->head = $head; + $this->remotes = $remotes; + } + + public function toArray() : array + { + $remotes = []; + + foreach ($this->remotes as $remote) { + $remotes[] = $remote->toArray(); + } + + return [ + 'branch' => $this->branch, + 'head' => $this->head->toArray(), + 'remotes' => $remotes, + ]; + } + + // accessor + + /** + * Return branch name. + * + */ + public function getBranch() : string + { + return $this->branch; + } + + /** + * Return HEAD commit. + * + */ + public function getHead() : CommitInfo + { + return $this->head; + } + + /** + * Return remote repositories. + * + * @return RemoteInfo[] + */ + public function getRemotes() : array + { + return $this->remotes; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/SourceControl/Git/RemoteInfo.php b/lib/composer/vimeo/psalm/src/Psalm/SourceControl/Git/RemoteInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..2b5ae9c4f6a7b9a09ccac3deb8b931b8fb48bdcc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/SourceControl/Git/RemoteInfo.php @@ -0,0 +1,80 @@ + + */ +class RemoteInfo +{ + /** + * Remote name. + * + * @var null|string + */ + protected $name; + + /** + * Remote URL. + * + * @var null|string + */ + protected $url; + + public function toArray() : array + { + return [ + 'name' => $this->name, + 'url' => $this->url, + ]; + } + + // accessor + + /** + * Set remote name. + * + * @param string $name remote name + * + * @return $this + */ + public function setName(string $name): RemoteInfo + { + $this->name = $name; + + return $this; + } + + /** + * Return remote name. + * + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * Set remote URL. + * + * @param string $url remote URL + * + * @return $this + */ + public function setUrl(string $url): RemoteInfo + { + $this->url = $url; + + return $this; + } + + /** + * Return remote URL. + * + */ + public function getUrl(): ?string + { + return $this->url; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/SourceControl/SourceControlInfo.php b/lib/composer/vimeo/psalm/src/Psalm/SourceControl/SourceControlInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..ed60dc87d47b563c9cf2d3bcb02f25ecbf71fcd1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/SourceControl/SourceControlInfo.php @@ -0,0 +1,7 @@ + + */ + public function getAliasedClassesFlipped(): array; + + /** + * @return array + */ + public function getAliasedClassesFlippedReplaceable(): array; + + public function getFQCLN(): ?string; + + public function getClassName(): ?string; + + public function getParentFQCLN(): ?string; + + /** + * @return array>|null + */ + public function getTemplateTypeMap(): ?array; + + public function setRootFilePath(string $file_path, string $file_name): void; + + public function hasParentFilePath(string $file_path): bool; + + public function hasAlreadyRequiredFilePath(string $file_path): bool; + + public function getRequireNesting(): int; + + public function isStatic(): bool; + + public function getSource(): StatementsSource; + + public function getCodebase() : Codebase; + + /** + * Get a list of suppressed issues + * + * @return array + */ + public function getSuppressedIssues(): array; + + /** + * @param list $new_issues + */ + public function addSuppressedIssues(array $new_issues): void; + + /** + * @param list $new_issues + */ + public function removeSuppressedIssues(array $new_issues): void; + + public function getNodeTypeProvider() : NodeTypeProvider; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Storage/Assertion.php b/lib/composer/vimeo/psalm/src/Psalm/Storage/Assertion.php new file mode 100644 index 0000000000000000000000000000000000000000..02a0ca55f3b2a3d3b7088e20f429d7c2e6d4fe8e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Storage/Assertion.php @@ -0,0 +1,88 @@ +> the rule being asserted + */ + public $rule; + + /** + * @var int|string the id of the property/variable, or + * the parameter offset of the affected arg + */ + public $var_id; + + /** + * @param string|int $var_id + * @param array> $rule + */ + public function __construct($var_id, array $rule) + { + $this->rule = $rule; + $this->var_id = $var_id; + } + + /** + * @param array> $template_type_map + */ + public function getUntemplatedCopy(array $template_type_map, ?string $this_var_id) : self + { + return new Assertion( + \is_string($this->var_id) && $this_var_id + ? \str_replace('$this->', $this_var_id . '->', $this->var_id) + : $this->var_id, + array_map( + /** + * @param array $rules + * + * @return array{0: string} + */ + function (array $rules) use ($template_type_map) : array { + $first_rule = $rules[0]; + + if ($template_type_map) { + $rule_tokens = \Psalm\Internal\Type\TypeTokenizer::tokenize($first_rule); + + $substitute = false; + + foreach ($rule_tokens as &$rule_token) { + if (isset($template_type_map[$rule_token[0]])) { + foreach ($template_type_map[$rule_token[0]] as [$type]) { + $substitute = true; + + $first_type = \array_values($type->getAtomicTypes())[0]; + + if ($first_type instanceof \Psalm\Type\Atomic\TTemplateParam) { + $rule_token[0] = $first_type->param_name; + } else { + $rule_token[0] = $first_type->getKey(); + } + } + } + } + + if ($substitute) { + return [implode( + '', + array_map( + function ($f) { + return $f[0]; + }, + $rule_tokens + ) + )]; + } + } + + return [$first_rule]; + }, + $this->rule + ) + ); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Storage/ClassConstantStorage.php b/lib/composer/vimeo/psalm/src/Psalm/Storage/ClassConstantStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..8301041d6a066d72edcf0433e774d32732561196 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Storage/ClassConstantStorage.php @@ -0,0 +1,49 @@ +visibility = $visibility; + $this->location = $location; + $this->type = $type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Storage/ClassLikeStorage.php b/lib/composer/vimeo/psalm/src/Psalm/Storage/ClassLikeStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..fb8802bbf6f10b1f6c8e44efea13cc4c4d7d86d0 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Storage/ClassLikeStorage.php @@ -0,0 +1,385 @@ + + */ + public $constants = []; + + /** + * Aliases to help Psalm understand constant refs + * + * @var ?\Psalm\Aliases + */ + public $aliases; + + /** + * @var bool + */ + public $populated = false; + + /** + * @var bool + */ + public $stubbed = false; + + /** + * @var bool + */ + public $deprecated = false; + + /** + * @var string + */ + public $internal = ''; + + /** + * @var null|Type\Atomic\TTemplateParam|Type\Atomic\TNamedObject + * @deprecated + */ + public $mixin = null; + + /** + * @var Type\Atomic\TTemplateParam[] + */ + public $templatedMixins = []; + + /** + * @var Type\Atomic\TNamedObject[] + */ + public $namedMixins = []; + + /** + * @var ?string + */ + public $mixin_declaring_fqcln = null; + + /** + * @var bool + */ + public $sealed_properties = false; + + /** + * @var bool + */ + public $sealed_methods = false; + + /** + * @var bool + */ + public $override_property_visibility = false; + + /** + * @var bool + */ + public $override_method_visibility = false; + + /** + * @var array + */ + public $suppressed_issues = []; + + /** + * @var string + */ + public $name; + + /** + * Is this class user-defined + * + * @var bool + */ + public $user_defined = false; + + /** + * Interfaces this class implements directly + * + * @var array + */ + public $direct_class_interfaces = []; + + /** + * Interfaces this class implements explicitly and implicitly + * + * @var array + */ + public $class_implements = []; + + /** + * Parent interfaces listed explicitly + * + * @var array + */ + public $direct_interface_parents = []; + + /** + * Parent interfaces + * + * @var array + */ + public $parent_interfaces = []; + + /** + * There can only be one direct parent class + * + * @var ?string + */ + public $parent_class; + + /** + * Parent classes + * + * @var array + */ + public $parent_classes = []; + + /** + * @var CodeLocation|null + */ + public $location; + + /** + * @var CodeLocation|null + */ + public $stmt_location; + + /** + * @var CodeLocation|null + */ + public $namespace_name_location; + + /** + * @var bool + */ + public $abstract = false; + + /** + * @var bool + */ + public $final = false; + + /** + * @var array + */ + public $used_traits = []; + + /** + * @var array + */ + public $trait_alias_map = []; + + /** + * @var array + */ + public $trait_final_map = []; + + /** + * @var array + */ + public $trait_visibility_map = []; + + /** + * @var bool + */ + public $is_trait = false; + + /** + * @var bool + */ + public $is_interface = false; + + /** + * @var bool + */ + public $external_mutation_free = false; + + /** + * @var bool + */ + public $mutation_free = false; + + /** + * @var bool + */ + public $specialize_instance = false; + + /** + * @var array + */ + public $methods = []; + + /** + * @var array + */ + public $pseudo_methods = []; + + /** + * @var array + */ + public $pseudo_static_methods = []; + + /** + * @var array + */ + public $declaring_method_ids = []; + + /** + * @var array + */ + public $appearing_method_ids = []; + + /** + * @var array> + */ + public $overridden_method_ids = []; + + /** + * @var array + */ + public $documenting_method_ids = []; + + /** + * @var array + */ + public $inheritable_method_ids = []; + + /** + * @var array> + */ + public $potential_declaring_method_ids = []; + + /** + * @var array + */ + public $properties = []; + + /** + * @var array + */ + public $pseudo_property_set_types = []; + + /** + * @var array + */ + public $pseudo_property_get_types = []; + + /** + * @var array + */ + public $declaring_property_ids = []; + + /** + * @var array + */ + public $appearing_property_ids = []; + + /** + * @var array + */ + public $inheritable_property_ids = []; + + /** + * @var array> + */ + public $overridden_property_ids = []; + + /** + * @var array>|null + */ + public $template_types; + + /** + * @var array|null + */ + public $template_covariants; + + /** + * @var array>|null + */ + public $template_type_extends; + + /** + * @var ?int + */ + public $template_type_extends_count; + + /** + * @var array|null + */ + public $template_type_implements_count; + + /** + * @var ?Type\Union + */ + public $yield; + + /** + * @var array|null + */ + public $template_type_uses_count; + + /** + * @var array + */ + public $initialized_properties = []; + + /** + * @var array + */ + public $invalid_dependencies = []; + + /** + * @var array + */ + public $dependent_classlikes = []; + + /** + * A hash of the source file's name, contents, and this file's modified on date + * + * @var string + */ + public $hash = ''; + + /** + * @var bool + */ + public $has_visitor_issues = false; + + /** + * @var list<\Psalm\Issue\CodeIssue> + */ + public $docblock_issues = []; + + /** + * @var array + */ + public $type_aliases = []; + + /** + * @var bool + */ + public $preserve_constructor_signature = false; + + /** + * @var null|string + */ + public $extension_requirement; + + /** + * @var array + */ + public $implementation_requirements = []; + + public function __construct(string $name) + { + $this->name = $name; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Storage/CustomMetadataTrait.php b/lib/composer/vimeo/psalm/src/Psalm/Storage/CustomMetadataTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..a2d2bd00b922892c7e9e4f46cf4e4d6b72f32182 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Storage/CustomMetadataTrait.php @@ -0,0 +1,11 @@ + */ + public $custom_metadata = []; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Storage/FileStorage.php b/lib/composer/vimeo/psalm/src/Psalm/Storage/FileStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..5e47ece37025bb02f6dbc503a99cc4e179acabb8 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Storage/FileStorage.php @@ -0,0 +1,104 @@ + + */ + public $classlikes_in_file = []; + + /** + * @var array + */ + public $referenced_classlikes = []; + + /** + * @var array + */ + public $required_classes = []; + + /** + * @var array + */ + public $required_interfaces = []; + + /** + * @var bool + */ + public $has_trait = false; + + /** @var string */ + public $file_path; + + /** + * @var array + */ + public $functions = []; + + /** @var array */ + public $declaring_function_ids = []; + + /** + * @var array + */ + public $constants = []; + + /** @var array */ + public $declaring_constants = []; + + /** @var array */ + public $required_file_paths = []; + + /** @var array */ + public $required_by_file_paths = []; + + /** @var bool */ + public $populated = false; + + /** @var bool */ + public $deep_scan = false; + + /** @var bool */ + public $has_extra_statements = false; + + /** + * @var string + */ + public $hash = ''; + + /** + * @var bool + */ + public $has_visitor_issues = false; + + /** + * @var list<\Psalm\Issue\CodeIssue> + */ + public $docblock_issues = []; + + /** + * @var array + */ + public $type_aliases = []; + + /** + * @var array + */ + public $classlike_aliases = []; + + /** @var ?Aliases */ + public $aliases; + + /** @var Aliases[] */ + public $namespace_aliases = []; + + public function __construct(string $file_path) + { + $this->file_path = $file_path; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Storage/FunctionLikeParameter.php b/lib/composer/vimeo/psalm/src/Psalm/Storage/FunctionLikeParameter.php new file mode 100644 index 0000000000000000000000000000000000000000..cfe70df87f5c6e2762d9472ead3cb805d3d49049 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Storage/FunctionLikeParameter.php @@ -0,0 +1,133 @@ +|null + */ + public $sinks; + + /** + * @var bool + */ + public $assert_untainted = false; + + /** + * @var bool + */ + public $type_inferred = false; + + /** + * @var bool + */ + public $expect_variable = false; + + public function __construct( + string $name, + bool $by_ref, + ?Type\Union $type = null, + ?CodeLocation $location = null, + ?CodeLocation $type_location = null, + bool $is_optional = true, + bool $is_nullable = false, + bool $is_variadic = false, + ?Type\Union $default_type = null + ) { + $this->name = $name; + $this->by_ref = $by_ref; + $this->type = $type; + $this->signature_type = $type; + $this->is_optional = $is_optional; + $this->is_nullable = $is_nullable; + $this->is_variadic = $is_variadic; + $this->location = $location; + $this->type_location = $type_location; + $this->signature_type_location = $type_location; + $this->default_type = $default_type; + } + + public function getId() : string + { + return ($this->type ? $this->type->getId() : 'mixed') + . ($this->is_variadic ? '...' : '') + . ($this->is_optional ? '=' : ''); + } + + public function __clone() + { + if ($this->type) { + $this->type = clone $this->type; + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Storage/FunctionLikeStorage.php b/lib/composer/vimeo/psalm/src/Psalm/Storage/FunctionLikeStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..2880943c8ec9f6b80e3b9fc5fe4f575c1d723995 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Storage/FunctionLikeStorage.php @@ -0,0 +1,245 @@ + + */ + public $params = []; + + /** + * @var array + */ + public $param_lookup = []; + + /** + * @var Type\Union|null + */ + public $return_type; + + /** + * @var CodeLocation|null + */ + public $return_type_location; + + /** + * @var Type\Union|null + */ + public $signature_return_type; + + /** + * @var CodeLocation|null + */ + public $signature_return_type_location; + + /** + * @var ?string + */ + public $cased_name; + + /** + * @var array + */ + public $suppressed_issues = []; + + /** + * @var ?bool + */ + public $deprecated; + + /** + * @var string + */ + public $internal = ''; + + /** + * @var bool + */ + public $variadic = false; + + /** + * @var bool + */ + public $returns_by_ref = false; + + /** + * @var ?int + */ + public $required_param_count; + + /** + * @var array + */ + public $defined_constants = []; + + /** + * @var array + */ + public $global_variables = []; + + /** + * @var array + */ + public $global_types = []; + + /** + * @var array>|null + */ + public $template_types; + + /** + * @var array|null + */ + public $template_covariants; + + /** + * @var array + */ + public $assertions = []; + + /** + * @var array + */ + public $if_true_assertions = []; + + /** + * @var array + */ + public $if_false_assertions = []; + + /** + * @var bool + */ + public $has_visitor_issues = false; + + /** + * @var list<\Psalm\Issue\CodeIssue> + */ + public $docblock_issues = []; + + /** + * @var array + */ + public $throws = []; + + /** + * @var array + */ + public $throw_locations = []; + + /** + * @var bool + */ + public $has_yield = false; + + /** + * @var bool + */ + public $mutation_free = false; + + /** + * @var string|null + */ + public $return_type_description; + + /** + * @var array|null + */ + public $unused_docblock_params; + + /** + * @var bool + */ + public $pure = false; + + /** + * Whether or not the function output is dependent solely on input - a function can be + * impure but still have this property (e.g. var_export). Useful for taint analysis. + * + * @var bool + */ + public $specialize_call = false; + + /** + * @var array + */ + public $taint_source_types = []; + + /** + * @var array + */ + public $added_taints = []; + + /** + * @var array + */ + public $removed_taints = []; + + /** + * @var array + */ + public $return_source_params = []; + + /** + * @var bool + */ + public $allow_named_arg_calls = true; + + public function __toString(): string + { + return $this->getSignature(false); + } + + public function getSignature(bool $allow_newlines = false): string + { + $newlines = $allow_newlines && !empty($this->params); + + $symbol_text = 'function ' . $this->cased_name . '(' . ($newlines ? "\n" : '') . implode( + ',' . ($newlines ? "\n" : ' '), + array_map( + function (FunctionLikeParameter $param) use ($newlines) : string { + return ($newlines ? ' ' : '') . ($param->type ?: 'mixed') . ' $' . $param->name; + }, + $this->params + ) + ) . ($newlines ? "\n" : '') . ') : ' . ($this->return_type ?: 'mixed'); + + if (!$this instanceof MethodStorage) { + return $symbol_text; + } + + switch ($this->visibility) { + case ClassLikeAnalyzer::VISIBILITY_PRIVATE: + $visibility_text = 'private'; + break; + + case ClassLikeAnalyzer::VISIBILITY_PROTECTED: + $visibility_text = 'protected'; + break; + + default: + $visibility_text = 'public'; + } + + return $visibility_text . ' ' . $symbol_text; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Storage/FunctionStorage.php b/lib/composer/vimeo/psalm/src/Psalm/Storage/FunctionStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..f7e2ff283f54c97241964d441b746be25a58ea43 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Storage/FunctionStorage.php @@ -0,0 +1,8 @@ + */ + public $byref_uses = []; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Storage/MethodStorage.php b/lib/composer/vimeo/psalm/src/Psalm/Storage/MethodStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..af3ae973bb40cdf5ef42633438defe85199a761c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Storage/MethodStorage.php @@ -0,0 +1,77 @@ + + */ + public $this_property_mutations = null; + + /** + * @var Type\Union|null + */ + public $self_out_type = null; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Storage/PropertyStorage.php b/lib/composer/vimeo/psalm/src/Psalm/Storage/PropertyStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..66bf4f95279040f2c3ce733327d46f565a7fb3ff --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Storage/PropertyStorage.php @@ -0,0 +1,106 @@ +visibility) { + case ClassLikeAnalyzer::VISIBILITY_PRIVATE: + $visibility_text = 'private'; + break; + + case ClassLikeAnalyzer::VISIBILITY_PROTECTED: + $visibility_text = 'protected'; + break; + + default: + $visibility_text = 'public'; + } + + return $visibility_text . ' ' . ($this->type ? $this->type->getId() : 'mixed'); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type.php b/lib/composer/vimeo/psalm/src/Psalm/Type.php new file mode 100644 index 0000000000000000000000000000000000000000..ee5445a5e86b7f9cb6fc89c7ed3609b6e6984abc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type.php @@ -0,0 +1,639 @@ +> $template_type_map + */ + public static function parseString( + string $type_string, + ?array $php_version = null, + array $template_type_map = [] + ): Union { + return TypeParser::parseTokens( + TypeTokenizer::tokenize( + $type_string + ), + $php_version, + $template_type_map + ); + } + + public static function getFQCLNFromString( + string $class, + Aliases $aliases + ) : string { + if ($class === '') { + throw new \InvalidArgumentException('$class cannot be empty'); + } + + if ($class[0] === '\\') { + return substr($class, 1); + } + + $imported_namespaces = $aliases->uses; + + if (strpos($class, '\\') !== false) { + $class_parts = explode('\\', $class); + $first_namespace = array_shift($class_parts); + + if (isset($imported_namespaces[strtolower($first_namespace)])) { + return $imported_namespaces[strtolower($first_namespace)] . '\\' . implode('\\', $class_parts); + } + } elseif (isset($imported_namespaces[strtolower($class)])) { + return $imported_namespaces[strtolower($class)]; + } + + $namespace = $aliases->namespace; + + return ($namespace ? $namespace . '\\' : '') . $class; + } + + /** + * @param array $aliased_classes + * + * @psalm-pure + */ + public static function getStringFromFQCLN( + string $value, + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $allow_self = false + ) : string { + if ($allow_self && $value === $this_class) { + return 'self'; + } + + if (isset($aliased_classes[strtolower($value)])) { + return $aliased_classes[strtolower($value)]; + } + + if ($namespace && stripos($value, $namespace . '\\') === 0) { + $candidate = preg_replace( + '/^' . preg_quote($namespace . '\\') . '/i', + '', + $value + ); + + $candidate_parts = explode('\\', $candidate); + + if (!isset($aliased_classes[strtolower($candidate_parts[0])])) { + return $candidate; + } + } elseif (!$namespace && strpos($value, '\\') === false) { + return $value; + } + + if (strpos($value, '\\')) { + $parts = explode('\\', $value); + + $suffix = array_pop($parts); + + while ($parts) { + $left = implode('\\', $parts); + + if (isset($aliased_classes[strtolower($left)])) { + return $aliased_classes[strtolower($left)] . '\\' . $suffix; + } + + $suffix = array_pop($parts) . '\\' . $suffix; + } + } + + return '\\' . $value; + } + + public static function getInt(bool $from_calculation = false, ?int $value = null): Union + { + if ($value !== null) { + $union = new Union([new TLiteralInt($value)]); + } else { + $union = new Union([new TInt()]); + } + + $union->from_calculation = $from_calculation; + + return $union; + } + + /** + * @param int|null $value + * + */ + public static function getPositiveInt(bool $from_calculation = false): Union + { + $union = new Union([new Type\Atomic\TPositiveInt()]); + $union->from_calculation = $from_calculation; + + return $union; + } + + public static function getNumeric(): Union + { + $type = new TNumeric; + + return new Union([$type]); + } + + public static function getString(?string $value = null): Union + { + $type = null; + + if ($value !== null) { + $config = \Psalm\Config::getInstance(); + + if ($config->string_interpreters) { + foreach ($config->string_interpreters as $string_interpreter) { + if ($type = $string_interpreter::getTypeFromValue($value)) { + break; + } + } + } + + if (!$type) { + if (strlen($value) < $config->max_string_length) { + $type = new TLiteralString($value); + } else { + $type = new Type\Atomic\TNonEmptyString(); + } + } + } + + if (!$type) { + $type = new TString(); + } + + return new Union([$type]); + } + + public static function getSingleLetter(): Union + { + $type = new TSingleLetter; + + return new Union([$type]); + } + + public static function getClassString(string $extends = 'object'): Union + { + return new Union([ + new TClassString( + $extends, + $extends === 'object' + ? null + : new TNamedObject($extends) + ), + ]); + } + + public static function getLiteralClassString(string $class_type): Union + { + $type = new TLiteralClassString($class_type); + + return new Union([$type]); + } + + public static function getNull(): Union + { + $type = new TNull; + + return new Union([$type]); + } + + public static function getMixed(bool $from_loop_isset = false): Union + { + $type = new TMixed($from_loop_isset); + + return new Union([$type]); + } + + public static function getScalar(): Union + { + $type = new TScalar(); + + return new Union([$type]); + } + + public static function getEmpty(): Union + { + $type = new TEmpty(); + + return new Union([$type]); + } + + public static function getBool(): Union + { + $type = new TBool; + + return new Union([$type]); + } + + public static function getFloat(?float $value = null): Union + { + if ($value !== null) { + $type = new TLiteralFloat($value); + } else { + $type = new TFloat(); + } + + return new Union([$type]); + } + + public static function getObject(): Union + { + $type = new TObject; + + return new Union([$type]); + } + + public static function getClosure(): Union + { + $type = new Type\Atomic\TClosure('Closure'); + + return new Union([$type]); + } + + public static function getArrayKey(): Union + { + $type = new TArrayKey(); + + return new Union([$type]); + } + + public static function getArray(): Union + { + $type = new TArray( + [ + new Type\Union([new TArrayKey]), + new Type\Union([new TMixed]), + ] + ); + + return new Union([$type]); + } + + public static function getEmptyArray(): Union + { + $array_type = new TArray( + [ + new Type\Union([new TEmpty]), + new Type\Union([new TEmpty]), + ] + ); + + return new Type\Union([ + $array_type, + ]); + } + + public static function getList(): Union + { + $type = new TList(new Type\Union([new TMixed])); + + return new Union([$type]); + } + + public static function getNonEmptyList(): Union + { + $type = new Type\Atomic\TNonEmptyList(new Type\Union([new TMixed])); + + return new Union([$type]); + } + + public static function getVoid(): Union + { + $type = new TVoid; + + return new Union([$type]); + } + + public static function getFalse(): Union + { + $type = new TFalse; + + return new Union([$type]); + } + + public static function getTrue(): Union + { + $type = new TTrue; + + return new Union([$type]); + } + + public static function getResource(): Union + { + return new Union([new TResource]); + } + + /** + * @param non-empty-list $union_types + */ + public static function combineUnionTypeArray(array $union_types, ?Codebase $codebase) : Type\Union + { + $first_type = array_pop($union_types); + + foreach ($union_types as $type) { + $first_type = self::combineUnionTypes($first_type, $type, $codebase); + } + + return $first_type; + } + + /** + * Combines two union types into one + * + * @param int $literal_limit any greater number of literal types than this + * will be merged to a scalar + * + */ + public static function combineUnionTypes( + Union $type_1, + Union $type_2, + ?Codebase $codebase = null, + bool $overwrite_empty_array = false, + bool $allow_mixed_union = true, + int $literal_limit = 500 + ): Union { + if ($type_1 === $type_2) { + return $type_1; + } + + if ($type_1->isVanillaMixed() && $type_2->isVanillaMixed()) { + $combined_type = Type::getMixed(); + } else { + $both_failed_reconciliation = false; + + if ($type_1->failed_reconciliation) { + if ($type_2->failed_reconciliation) { + $both_failed_reconciliation = true; + } else { + $type_2 = clone $type_2; + $type_2->parent_nodes += $type_1->parent_nodes; + + return $type_2; + } + } elseif ($type_2->failed_reconciliation) { + $type_1 = clone $type_1; + $type_1->parent_nodes += $type_2->parent_nodes; + + return $type_1; + } + + $combined_type = TypeCombination::combineTypes( + array_merge( + array_values($type_1->getAtomicTypes()), + array_values($type_2->getAtomicTypes()) + ), + $codebase, + $overwrite_empty_array, + $allow_mixed_union, + $literal_limit + ); + + if (!$type_1->initialized || !$type_2->initialized) { + $combined_type->initialized = false; + } + + if ($type_1->possibly_undefined_from_try || $type_2->possibly_undefined_from_try) { + $combined_type->possibly_undefined_from_try = true; + } + + if ($type_1->from_docblock || $type_2->from_docblock) { + $combined_type->from_docblock = true; + } + + if ($type_1->from_calculation || $type_2->from_calculation) { + $combined_type->from_calculation = true; + } + + if ($type_1->ignore_nullable_issues || $type_2->ignore_nullable_issues) { + $combined_type->ignore_nullable_issues = true; + } + + if ($type_1->ignore_falsable_issues || $type_2->ignore_falsable_issues) { + $combined_type->ignore_falsable_issues = true; + } + + if ($type_1->had_template && $type_2->had_template) { + $combined_type->had_template = true; + } + + if ($type_1->reference_free && $type_2->reference_free) { + $combined_type->reference_free = true; + } + + if ($both_failed_reconciliation) { + $combined_type->failed_reconciliation = true; + } + } + + if ($type_1->possibly_undefined || $type_2->possibly_undefined) { + $combined_type->possibly_undefined = true; + } + + if ($type_1->parent_nodes || $type_2->parent_nodes) { + $combined_type->parent_nodes = $type_1->parent_nodes + $type_2->parent_nodes; + } + + if ($type_1->by_ref || $type_2->by_ref) { + $combined_type->by_ref = true; + } + + return $combined_type; + } + + /** + * Combines two union types into one via an intersection + * + * + */ + public static function intersectUnionTypes( + Union $type_1, + Union $type_2, + Codebase $codebase + ): ?Union { + $intersection_performed = false; + + if ($type_1->isMixed() && $type_2->isMixed()) { + $combined_type = Type::getMixed(); + } else { + $both_failed_reconciliation = false; + + if ($type_1->failed_reconciliation) { + if ($type_2->failed_reconciliation) { + $both_failed_reconciliation = true; + } else { + return $type_2; + } + } elseif ($type_2->failed_reconciliation) { + return $type_1; + } + + if ($type_1->isMixed() && !$type_2->isMixed()) { + $combined_type = clone $type_2; + $intersection_performed = true; + } elseif (!$type_1->isMixed() && $type_2->isMixed()) { + $combined_type = clone $type_1; + $intersection_performed = true; + } else { + $combined_type = clone $type_1; + + foreach ($combined_type->getAtomicTypes() as $t1_key => $type_1_atomic) { + foreach ($type_2->getAtomicTypes() as $t2_key => $type_2_atomic) { + if ($type_1_atomic instanceof TNamedObject + && $type_2_atomic instanceof TNamedObject + ) { + if (AtomicTypeComparator::isContainedBy( + $codebase, + $type_2_atomic, + $type_1_atomic + )) { + $combined_type->removeType($t1_key); + $combined_type->addType(clone $type_2_atomic); + $intersection_performed = true; + } elseif (AtomicTypeComparator::isContainedBy( + $codebase, + $type_1_atomic, + $type_2_atomic + )) { + $combined_type->removeType($t2_key); + $combined_type->addType(clone $type_1_atomic); + $intersection_performed = true; + } + } + + if (($type_1_atomic instanceof TIterable + || $type_1_atomic instanceof TNamedObject + || $type_1_atomic instanceof TTemplateParam + || $type_1_atomic instanceof TObjectWithProperties) + && ($type_2_atomic instanceof TIterable + || $type_2_atomic instanceof TNamedObject + || $type_2_atomic instanceof TTemplateParam + || $type_2_atomic instanceof TObjectWithProperties) + ) { + if (!$type_1_atomic->extra_types) { + $type_1_atomic->extra_types = []; + } + + $intersection_performed = true; + + $type_2_atomic_clone = clone $type_2_atomic; + + $type_2_atomic_clone->extra_types = []; + + $type_1_atomic->extra_types[$type_2_atomic_clone->getKey()] = $type_2_atomic_clone; + + $type_2_atomic_intersection_types = $type_2_atomic->getIntersectionTypes(); + + if ($type_2_atomic_intersection_types) { + foreach ($type_2_atomic_intersection_types as $type_2_intersection_type) { + $type_1_atomic->extra_types[$type_2_intersection_type->getKey()] + = clone $type_2_intersection_type; + } + } + } + + if ($type_1_atomic instanceof TObject && $type_2_atomic instanceof TNamedObject) { + $combined_type->removeType($t1_key); + $combined_type->addType(clone $type_2_atomic); + $intersection_performed = true; + } elseif ($type_2_atomic instanceof TObject && $type_1_atomic instanceof TNamedObject) { + $combined_type->removeType($t2_key); + $combined_type->addType(clone $type_1_atomic); + $intersection_performed = true; + } + } + } + } + + if (!$type_1->initialized && !$type_2->initialized) { + $combined_type->initialized = false; + } + + if ($type_1->possibly_undefined_from_try && $type_2->possibly_undefined_from_try) { + $combined_type->possibly_undefined_from_try = true; + } + + if ($type_1->from_docblock && $type_2->from_docblock) { + $combined_type->from_docblock = true; + } + + if ($type_1->from_calculation && $type_2->from_calculation) { + $combined_type->from_calculation = true; + } + + if ($type_1->ignore_nullable_issues && $type_2->ignore_nullable_issues) { + $combined_type->ignore_nullable_issues = true; + } + + if ($type_1->ignore_falsable_issues && $type_2->ignore_falsable_issues) { + $combined_type->ignore_falsable_issues = true; + } + + if ($both_failed_reconciliation) { + $combined_type->failed_reconciliation = true; + } + } + + if (!$intersection_performed && $type_1->getId() !== $type_2->getId()) { + return null; + } + + if ($type_1->possibly_undefined && $type_2->possibly_undefined) { + $combined_type->possibly_undefined = true; + } + + return $combined_type; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Algebra.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Algebra.php new file mode 100644 index 0000000000000000000000000000000000000000..39c6c72df39f8dd283ba41a77584130695225033 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Algebra.php @@ -0,0 +1,841 @@ +>> $all_types + * + * @return array>> + * + * @psalm-pure + */ + public static function negateTypes(array $all_types): array + { + return array_filter( + array_map( + /** + * @param non-empty-list> $anded_types + * + * @return list> + */ + function (array $anded_types): array { + if (count($anded_types) > 1) { + $new_anded_types = []; + + foreach ($anded_types as $orred_types) { + if (count($orred_types) > 1) { + return []; + } + + $new_anded_types[] = self::negateType($orred_types[0]); + } + + return [$new_anded_types]; + } + + $new_orred_types = []; + + foreach ($anded_types[0] as $orred_type) { + $new_orred_types[] = [self::negateType($orred_type)]; + } + + return $new_orred_types; + }, + $all_types + ) + ); + } + + /** + * @psalm-pure + */ + public static function negateType(string $type): string + { + if ($type === 'mixed') { + return $type; + } + + return $type[0] === '!' ? substr($type, 1) : '!' . $type; + } + + /** + * @return list + */ + public static function getFormula( + int $conditional_object_id, + int $creating_object_id, + PhpParser\Node\Expr $conditional, + ?string $this_class_name, + FileSource $source, + ?Codebase $codebase = null, + bool $inside_negation = false, + bool $cache = true + ): array { + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd || + $conditional instanceof PhpParser\Node\Expr\BinaryOp\LogicalAnd + ) { + $left_assertions = self::getFormula( + $conditional_object_id, + \spl_object_id($conditional->left), + $conditional->left, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + + $right_assertions = self::getFormula( + $conditional_object_id, + \spl_object_id($conditional->right), + $conditional->right, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + + return array_merge( + $left_assertions, + $right_assertions + ); + } + + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr || + $conditional instanceof PhpParser\Node\Expr\BinaryOp\LogicalOr + ) { + $left_clauses = self::getFormula( + $conditional_object_id, + \spl_object_id($conditional->left), + $conditional->left, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + + $right_clauses = self::getFormula( + $conditional_object_id, + \spl_object_id($conditional->right), + $conditional->right, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + + return self::combineOredClauses($left_clauses, $right_clauses, $conditional_object_id); + } + + if ($conditional instanceof PhpParser\Node\Expr\BooleanNot) { + if ($conditional->expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) { + $and_expr = new PhpParser\Node\Expr\BinaryOp\BooleanAnd( + new PhpParser\Node\Expr\BooleanNot( + $conditional->expr->left, + $conditional->getAttributes() + ), + new PhpParser\Node\Expr\BooleanNot( + $conditional->expr->right, + $conditional->getAttributes() + ), + $conditional->expr->getAttributes() + ); + + return self::getFormula( + $conditional_object_id, + $conditional_object_id, + $and_expr, + $this_class_name, + $source, + $codebase, + $inside_negation, + false + ); + } + + if ($conditional->expr instanceof PhpParser\Node\Expr\Isset_ + && count($conditional->expr->vars) > 1 + ) { + $assertions = null; + + if ($cache && $source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + $assertions = $source->node_data->getAssertions($conditional->expr); + } + + if ($assertions === null) { + $assertions = AssertionFinder::scrapeAssertions( + $conditional->expr, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + + if ($cache && $source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + $source->node_data->setAssertions($conditional->expr, $assertions); + } + } + + $clauses = []; + + foreach ($assertions as $var => $anded_types) { + $redefined = false; + + if ($var[0] === '=') { + /** @var string */ + $var = substr($var, 1); + $redefined = true; + } + + foreach ($anded_types as $orred_types) { + $clauses[] = new Clause( + [$var => $orred_types], + $conditional_object_id, + \spl_object_id($conditional->expr), + false, + true, + $orred_types[0][0] === '=' + || $orred_types[0][0] === '~' + || (strlen($orred_types[0]) > 1 + && ($orred_types[0][1] === '=' + || $orred_types[0][1] === '~')), + $redefined ? [$var => true] : [] + ); + } + } + + return self::negateFormula($clauses); + } + + if ($conditional->expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd) { + $and_expr = new PhpParser\Node\Expr\BinaryOp\BooleanOr( + new PhpParser\Node\Expr\BooleanNot( + $conditional->expr->left, + $conditional->getAttributes() + ), + new PhpParser\Node\Expr\BooleanNot( + $conditional->expr->right, + $conditional->getAttributes() + ), + $conditional->expr->getAttributes() + ); + + return self::getFormula( + $conditional_object_id, + \spl_object_id($conditional->expr), + $and_expr, + $this_class_name, + $source, + $codebase, + $inside_negation, + false + ); + } + + return self::negateFormula( + self::getFormula( + $conditional_object_id, + \spl_object_id($conditional->expr), + $conditional->expr, + $this_class_name, + $source, + $codebase, + !$inside_negation + ) + ); + } + + if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical + || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal + ) { + $false_pos = AssertionFinder::hasFalseVariable($conditional); + $true_pos = AssertionFinder::hasTrueVariable($conditional); + + if ($false_pos === AssertionFinder::ASSIGNMENT_TO_RIGHT + && ($conditional->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd + || $conditional->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) + ) { + $inside_negation = !$inside_negation; + + return self::getFormula( + $conditional_object_id, + \spl_object_id($conditional->left), + $conditional->left, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + } + + if ($false_pos === AssertionFinder::ASSIGNMENT_TO_LEFT + && ($conditional->right instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd + || $conditional->right instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) + ) { + $inside_negation = !$inside_negation; + + return self::getFormula( + $conditional_object_id, + \spl_object_id($conditional->right), + $conditional->right, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + } + + if ($true_pos === AssertionFinder::ASSIGNMENT_TO_RIGHT + && ($conditional->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd + || $conditional->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) + ) { + return self::getFormula( + $conditional_object_id, + \spl_object_id($conditional->left), + $conditional->left, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + } + + if ($true_pos === AssertionFinder::ASSIGNMENT_TO_LEFT + && ($conditional->right instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd + || $conditional->right instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) + ) { + return self::getFormula( + $conditional_object_id, + \spl_object_id($conditional->right), + $conditional->right, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + } + } + + if ($conditional instanceof PhpParser\Node\Expr\Cast\Bool_) { + return self::getFormula( + $conditional_object_id, + \spl_object_id($conditional->expr), + $conditional->expr, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + } + + $assertions = null; + + if ($cache && $source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + $assertions = $source->node_data->getAssertions($conditional); + } + + if ($assertions === null) { + $assertions = AssertionFinder::scrapeAssertions( + $conditional, + $this_class_name, + $source, + $codebase, + $inside_negation, + $cache + ); + + if ($cache && $source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { + $source->node_data->setAssertions($conditional, $assertions); + } + } + + if ($assertions) { + $clauses = []; + + foreach ($assertions as $var => $anded_types) { + $redefined = false; + + if ($var[0] === '=') { + /** @var string */ + $var = substr($var, 1); + $redefined = true; + } + + foreach ($anded_types as $orred_types) { + $clauses[] = new Clause( + [$var => $orred_types], + $conditional_object_id, + $creating_object_id, + false, + true, + $orred_types[0][0] === '=' + || $orred_types[0][0] === '~' + || (strlen($orred_types[0]) > 1 + && ($orred_types[0][1] === '=' + || $orred_types[0][1] === '~')), + $redefined ? [$var => true] : [] + ); + } + } + + return $clauses; + } + + return [new Clause([], $conditional_object_id, $creating_object_id, true)]; + } + + /** + * This is a very simple simplification heuristic + * for CNF formulae. + * + * It simplifies formulae: + * ($a) && ($a || $b) => $a + * (!$a) && (!$b) && ($a || $b || $c) => $c + * + * @param list $clauses + * + * @return list + * + * @psalm-pure + */ + public static function simplifyCNF(array $clauses): array + { + $cloned_clauses = []; + + // avoid strict duplicates + foreach ($clauses as $clause) { + $unique_clause = $clause->makeUnique(); + $cloned_clauses[$unique_clause->hash] = $unique_clause; + } + + // remove impossible types + foreach ($cloned_clauses as $clause_a) { + if (count($clause_a->possibilities) !== 1 || count(array_values($clause_a->possibilities)[0]) !== 1) { + continue; + } + + if (!$clause_a->reconcilable || $clause_a->wedge) { + continue; + } + + $clause_var = array_keys($clause_a->possibilities)[0]; + $only_type = array_pop(array_values($clause_a->possibilities)[0]); + $negated_clause_type = self::negateType($only_type); + + foreach ($cloned_clauses as $clause_hash => $clause_b) { + if ($clause_a === $clause_b || !$clause_b->reconcilable || $clause_b->wedge) { + continue; + } + + if (isset($clause_b->possibilities[$clause_var]) && + in_array($negated_clause_type, $clause_b->possibilities[$clause_var], true) + ) { + $clause_var_possibilities = array_values( + array_filter( + $clause_b->possibilities[$clause_var], + function (string $possible_type) use ($negated_clause_type): bool { + return $possible_type !== $negated_clause_type; + } + ) + ); + + unset($cloned_clauses[$clause_hash]); + + if (!$clause_var_possibilities) { + $updated_clause = $clause_b->removePossibilities($clause_var); + + if ($updated_clause) { + $cloned_clauses[$updated_clause->hash] = $updated_clause; + } + } else { + $updated_clause = $clause_b->addPossibilities( + $clause_var, + $clause_var_possibilities + ); + + $cloned_clauses[$updated_clause->hash] = $updated_clause; + } + } + } + } + + $simplified_clauses = []; + + foreach ($cloned_clauses as $clause_a) { + $is_redundant = false; + + foreach ($cloned_clauses as $clause_b) { + if ($clause_a === $clause_b + || !$clause_b->reconcilable + || $clause_b->wedge + || $clause_a->wedge + ) { + continue; + } + + if ($clause_a->contains($clause_b)) { + $is_redundant = true; + break; + } + } + + if (!$is_redundant) { + $simplified_clauses[] = $clause_a; + } + } + + return $simplified_clauses; + } + + /** + * Look for clauses with only one possible value + * + * @param list $clauses + * @param array $cond_referenced_var_ids + * @param array>> $active_truths + * + * @return array>> + */ + public static function getTruthsFromFormula( + array $clauses, + ?int $creating_conditional_id = null, + array &$cond_referenced_var_ids = [], + array &$active_truths = [] + ): array { + $truths = []; + $active_truths = []; + + if ($clauses === []) { + return []; + } + + foreach ($clauses as $clause) { + if (!$clause->reconcilable) { + continue; + } + + foreach ($clause->possibilities as $var => $possible_types) { + // if there's only one possible type, return it + if (count($clause->possibilities) === 1 && count($possible_types) === 1) { + $possible_type = array_pop($possible_types); + + if (isset($truths[$var]) && !isset($clause->redefined_vars[$var])) { + $truths[$var][] = [$possible_type]; + } else { + $truths[$var] = [[$possible_type]]; + } + + if ($creating_conditional_id && $creating_conditional_id === $clause->creating_conditional_id) { + if (!isset($active_truths[$var])) { + $active_truths[$var] = []; + } + + $active_truths[$var][count($truths[$var]) - 1] = [$possible_type]; + } + } elseif (count($clause->possibilities) === 1) { + // if there's only one active clause, return all the non-negation clause members ORed together + $things_that_can_be_said = array_filter( + $possible_types, + function (string $possible_type): bool { + return $possible_type[0] !== '!'; + } + ); + + if ($things_that_can_be_said && count($things_that_can_be_said) === count($possible_types)) { + $things_that_can_be_said = array_unique($things_that_can_be_said); + + if ($clause->generated && count($possible_types) > 1) { + unset($cond_referenced_var_ids[$var]); + } + + /** @var array $things_that_can_be_said */ + $truths[$var] = [$things_that_can_be_said]; + + if ($creating_conditional_id && $creating_conditional_id === $clause->creating_conditional_id) { + $active_truths[$var] = [$things_that_can_be_said]; + } + } + } + } + } + + return $truths; + } + + /** + * @param non-empty-list $clauses + * + * @return list + * + * @psalm-pure + */ + public static function groupImpossibilities(array $clauses): array + { + $complexity = 1; + + $seed_clauses = []; + + $clause = array_pop($clauses); + + if (!$clause->wedge) { + if ($clause->impossibilities === null) { + throw new \UnexpectedValueException('$clause->impossibilities should not be null'); + } + + foreach ($clause->impossibilities as $var => $impossible_types) { + foreach ($impossible_types as $impossible_type) { + $seed_clause = new Clause( + [$var => [$impossible_type]], + $clause->creating_conditional_id, + $clause->creating_object_id + ); + + $seed_clauses[] = $seed_clause; + + ++$complexity; + } + } + } + + if (!$clauses || !$seed_clauses) { + return $seed_clauses; + } + + while ($clauses) { + $clause = array_pop($clauses); + + $new_clauses = []; + + foreach ($seed_clauses as $grouped_clause) { + if ($clause->impossibilities === null) { + throw new \UnexpectedValueException('$clause->impossibilities should not be null'); + } + + foreach ($clause->impossibilities as $var => $impossible_types) { + foreach ($impossible_types as $impossible_type) { + $new_clause_possibilities = $grouped_clause->possibilities; + + if (isset($grouped_clause->possibilities[$var])) { + $new_clause_possibilities[$var][] = $impossible_type; + } else { + $new_clause_possibilities[$var] = [$impossible_type]; + } + + $new_clause = new Clause( + $new_clause_possibilities, + $grouped_clause->creating_conditional_id, + $clause->creating_object_id, + false, + true, + true, + [] + ); + + $new_clauses[] = $new_clause; + + ++$complexity; + + if ($complexity > 20000) { + throw new ComplicatedExpressionException(); + } + } + } + } + + $seed_clauses = $new_clauses; + } + + return $seed_clauses; + } + + /** + * @param list $left_clauses + * @param list $right_clauses + * + * @return list + * + * @psalm-pure + */ + public static function combineOredClauses( + array $left_clauses, + array $right_clauses, + int $conditional_object_id + ): array { + $clauses = []; + + $all_wedges = true; + $has_wedge = false; + + foreach ($left_clauses as $left_clause) { + foreach ($right_clauses as $right_clause) { + $all_wedges = $all_wedges && ($left_clause->wedge && $right_clause->wedge); + $has_wedge = $has_wedge || ($left_clause->wedge && $right_clause->wedge); + } + } + + if ($all_wedges) { + return [new Clause([], $conditional_object_id, $conditional_object_id, true)]; + } + + foreach ($left_clauses as $left_clause) { + foreach ($right_clauses as $right_clause) { + if ($left_clause->wedge && $right_clause->wedge) { + // handled below + continue; + } + + /** @var array> */ + $possibilities = []; + + $can_reconcile = true; + + if ($left_clause->wedge || + $right_clause->wedge || + !$left_clause->reconcilable || + !$right_clause->reconcilable + ) { + $can_reconcile = false; + } + + foreach ($left_clause->possibilities as $var => $possible_types) { + if (isset($right_clause->redefined_vars[$var])) { + continue; + } + + if (isset($possibilities[$var])) { + $possibilities[$var] = array_merge($possibilities[$var], $possible_types); + } else { + $possibilities[$var] = $possible_types; + } + } + + foreach ($right_clause->possibilities as $var => $possible_types) { + if (isset($possibilities[$var])) { + $possibilities[$var] = array_merge($possibilities[$var], $possible_types); + } else { + $possibilities[$var] = $possible_types; + } + } + + if (count($left_clauses) > 1 || count($right_clauses) > 1) { + foreach ($possibilities as $var => $p) { + $possibilities[$var] = array_values(array_unique($p)); + } + } + + foreach ($possibilities as $var_possibilities) { + if (count($var_possibilities) === 2) { + if ($var_possibilities[0] === '!' . $var_possibilities[1] + || $var_possibilities[1] === '!' . $var_possibilities[0] + ) { + continue 2; + } + } + } + + $creating_conditional_id = + $right_clause->creating_conditional_id === $left_clause->creating_conditional_id + ? $right_clause->creating_conditional_id + : $conditional_object_id; + + $clauses[] = new Clause( + $possibilities, + $creating_conditional_id, + $creating_conditional_id, + false, + $can_reconcile, + $right_clause->generated + || $left_clause->generated + || count($left_clauses) > 1 + || count($right_clauses) > 1, + [] + ); + } + } + + if ($has_wedge) { + $clauses[] = new Clause([], $conditional_object_id, $conditional_object_id, true); + } + + return $clauses; + } + + /** + * Negates a set of clauses + * negateClauses([$a || $b]) => !$a && !$b + * negateClauses([$a, $b]) => !$a || !$b + * negateClauses([$a, $b || $c]) => + * (!$a || !$b) && + * (!$a || !$c) + * negateClauses([$a, $b || $c, $d || $e || $f]) => + * (!$a || !$b || !$d) && + * (!$a || !$b || !$e) && + * (!$a || !$b || !$f) && + * (!$a || !$c || !$d) && + * (!$a || !$c || !$e) && + * (!$a || !$c || !$f) + * + * @param list $clauses + * + * @return non-empty-list + */ + public static function negateFormula(array $clauses): array + { + if (!$clauses) { + $cond_id = \mt_rand(0, 100000000); + return [new Clause([], $cond_id, $cond_id, true)]; + } + + $clauses_with_impossibilities = []; + + foreach ($clauses as $clause) { + $clauses_with_impossibilities[] = $clause->calculateNegation(); + } + + unset($clauses); + + $impossible_clauses = self::groupImpossibilities($clauses_with_impossibilities); + + if (!$impossible_clauses) { + $cond_id = \mt_rand(0, 100000000); + return [new Clause([], $cond_id, $cond_id, true)]; + } + + $negated = self::simplifyCNF($impossible_clauses); + + if (!$negated) { + $cond_id = \mt_rand(0, 100000000); + return [new Clause([], $cond_id, $cond_id, true)]; + } + + return $negated; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic.php new file mode 100644 index 0000000000000000000000000000000000000000..57ceda58d026a20c0ef9e8864ef151e545641648 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic.php @@ -0,0 +1,596 @@ +> $template_type_map + * @param array $type_aliases + */ + public static function create( + string $value, + ?array $php_version = null, + array $template_type_map = [], + array $type_aliases = [] + ): Atomic { + switch ($value) { + case 'int': + return new TInt(); + + case 'float': + return new TFloat(); + + case 'string': + return new TString(); + + case 'bool': + return new TBool(); + + case 'void': + if ($php_version === null + || ($php_version[0] > 7) + || ($php_version[0] === 7 && $php_version[1] >= 1) + ) { + return new TVoid(); + } + + break; + + case 'array-key': + return new TArrayKey(); + + case 'iterable': + if ($php_version === null + || ($php_version[0] > 7) + || ($php_version[0] === 7 && $php_version[1] >= 1) + ) { + return new TIterable(); + } + + break; + + case 'never-return': + case 'never-returns': + case 'no-return': + return new TNever(); + + case 'object': + if ($php_version === null + || ($php_version[0] > 7) + || ($php_version[0] === 7 && $php_version[1] >= 2) + ) { + return new TObject(); + } + + break; + + case 'callable': + return new TCallable(); + case 'pure-callable': + $type = new TCallable(); + $type->is_pure = true; + + return $type; + + case 'array': + case 'associative-array': + return new TArray([new Union([new TArrayKey]), new Union([new TMixed])]); + + case 'non-empty-array': + return new TNonEmptyArray([new Union([new TArrayKey]), new Union([new TMixed])]); + + case 'callable-array': + return new Type\Atomic\TCallableArray([new Union([new TArrayKey]), new Union([new TMixed])]); + + case 'list': + return new TList(Type::getMixed()); + + case 'non-empty-list': + return new TNonEmptyList(Type::getMixed()); + + case 'non-empty-string': + return new Type\Atomic\TNonEmptyString(); + + case 'lowercase-string': + return new Type\Atomic\TLowercaseString(); + + case 'non-empty-lowercase-string': + return new Type\Atomic\TNonEmptyLowercaseString(); + + case 'resource': + return $php_version !== null ? new TNamedObject($value) : new TResource(); + + case 'resource (closed)': + case 'closed-resource': + return new Type\Atomic\TClosedResource(); + + case 'positive-int': + return new TPositiveInt(); + + case 'numeric': + return $php_version !== null ? new TNamedObject($value) : new TNumeric(); + + case 'true': + return $php_version !== null ? new TNamedObject($value) : new TTrue(); + + case 'false': + if ($php_version === null || $php_version[0] >= 8) { + return new TFalse(); + } + + return new TNamedObject($value); + + case 'empty': + return $php_version !== null ? new TNamedObject($value) : new TEmpty(); + + case 'scalar': + return $php_version !== null ? new TNamedObject($value) : new TScalar(); + + case 'null': + if ($php_version === null || $php_version[0] >= 8) { + return new TNull(); + } + + return new TNamedObject($value); + + case 'mixed': + if ($php_version === null || $php_version[0] >= 8) { + return new TMixed(); + } + + return new TNamedObject($value); + + case 'callable-object': + return new TCallableObject(); + + case 'class-string': + case 'interface-string': + return new TClassString(); + + case 'trait-string': + return new TTraitString(); + + case 'callable-string': + return new TCallableString(); + + case 'numeric-string': + return new TNumericString(); + + case 'html-escaped-string': + return new THtmlEscapedString(); + + case 'false-y': + return new TAssertionFalsy(); + + case '$this': + return new TNamedObject('static'); + } + + if (strpos($value, '-') && substr($value, 0, 4) !== 'OCI-') { + throw new \Psalm\Exception\TypeParseTreeException('Unrecognized type ' . $value); + } + + if (is_numeric($value[0])) { + throw new \Psalm\Exception\TypeParseTreeException('First character of type cannot be numeric'); + } + + if (isset($template_type_map[$value])) { + $first_class = array_keys($template_type_map[$value])[0]; + + return new TTemplateParam( + $value, + $template_type_map[$value][$first_class][0], + $first_class + ); + } + + if (isset($type_aliases[$value])) { + $type_alias = $type_aliases[$value]; + + if ($type_alias instanceof TypeAlias\LinkableTypeAlias) { + return new TTypeAlias($type_alias->declaring_fq_classlike_name, $type_alias->alias_name); + } + + throw new \UnexpectedValueException('This should never happen'); + } + + return new TNamedObject($value); + } + + abstract public function getKey(bool $include_extra = true) : string; + + public function isNumericType(): bool + { + return $this instanceof TInt + || $this instanceof TFloat + || $this instanceof TNumericString + || $this instanceof TNumeric + || ($this instanceof TLiteralString && \is_numeric($this->value)); + } + + public function isObjectType(): bool + { + return $this instanceof TObject + || $this instanceof TNamedObject + || ($this instanceof TTemplateParam + && $this->as->hasObjectType()); + } + + public function isNamedObjectType(): bool + { + return $this instanceof TNamedObject + || ($this instanceof TTemplateParam + && ($this->as->hasNamedObjectType() + || array_filter( + $this->extra_types ?: [], + function ($extra_type) { + return $extra_type->isNamedObjectType(); + } + ) + ) + ); + } + + public function isCallableType(): bool + { + return $this instanceof TCallable + || $this instanceof TCallableObject + || $this instanceof TCallableString + || $this instanceof TCallableArray + || $this instanceof TCallableList + || $this instanceof TCallableKeyedArray; + } + + public function isIterable(Codebase $codebase): bool + { + return $this instanceof TIterable + || $this->hasTraversableInterface($codebase) + || $this instanceof TArray + || $this instanceof TKeyedArray + || $this instanceof TList; + } + + public function isCountable(Codebase $codebase): bool + { + return $this->hasCountableInterface($codebase) + || $this instanceof TArray + || $this instanceof TKeyedArray + || $this instanceof TList; + } + + public function hasTraversableInterface(Codebase $codebase): bool + { + return $this instanceof TNamedObject + && ( + strtolower($this->value) === 'traversable' + || ($codebase->classOrInterfaceExists($this->value) + && ($codebase->classExtendsOrImplements( + $this->value, + 'Traversable' + ) || $codebase->interfaceExtends( + $this->value, + 'Traversable' + ))) + || ( + $this->extra_types + && array_filter( + $this->extra_types, + function (Atomic $a) use ($codebase) : bool { + return $a->hasTraversableInterface($codebase); + } + ) + ) + ); + } + + public function hasCountableInterface(Codebase $codebase): bool + { + return $this instanceof TNamedObject + && ( + strtolower($this->value) === 'countable' + || ($codebase->classOrInterfaceExists($this->value) + && ($codebase->classExtendsOrImplements( + $this->value, + 'Countable' + ) || $codebase->interfaceExtends( + $this->value, + 'Countable' + ))) + || ( + $this->extra_types + && array_filter( + $this->extra_types, + function (Atomic $a) use ($codebase) : bool { + return $a->hasCountableInterface($codebase); + } + ) + ) + ); + } + + public function isArrayAccessibleWithStringKey(Codebase $codebase): bool + { + return $this instanceof TArray + || $this instanceof TKeyedArray + || $this instanceof TList + || $this instanceof Atomic\TClassStringMap + || $this->hasArrayAccessInterface($codebase) + || ($this instanceof TNamedObject && $this->value === 'SimpleXMLElement'); + } + + public function isArrayAccessibleWithIntOrStringKey(Codebase $codebase): bool + { + return $this instanceof TString + || $this->isArrayAccessibleWithStringKey($codebase); + } + + public function hasArrayAccessInterface(Codebase $codebase): bool + { + return $this instanceof TNamedObject + && ( + strtolower($this->value) === 'arrayaccess' + || ($codebase->classOrInterfaceExists($this->value) + && ($codebase->classExtendsOrImplements( + $this->value, + 'ArrayAccess' + ) || $codebase->interfaceExtends( + $this->value, + 'ArrayAccess' + ))) + || ( + $this->extra_types + && array_filter( + $this->extra_types, + function (Atomic $a) use ($codebase) : bool { + return $a->hasArrayAccessInterface($codebase); + } + ) + ) + ); + } + + public function getChildNodes() : array + { + return []; + } + + public function replaceClassLike(string $old, string $new) : void + { + if ($this instanceof TNamedObject) { + if (strtolower($this->value) === $old) { + $this->value = $new; + } + } + + if ($this instanceof TNamedObject + || $this instanceof TIterable + || $this instanceof TTemplateParam + ) { + if ($this->extra_types) { + foreach ($this->extra_types as $extra_type) { + $extra_type->replaceClassLike($old, $new); + } + } + } + + if ($this instanceof TScalarClassConstant) { + if (strtolower($this->fq_classlike_name) === $old) { + $this->fq_classlike_name = $new; + } + } + + if ($this instanceof TClassString && $this->as !== 'object') { + if (strtolower($this->as) === $old) { + $this->as = $new; + } + } + + if ($this instanceof TTemplateParam) { + $this->as->replaceClassLike($old, $new); + } + + if ($this instanceof TLiteralClassString) { + if (strtolower($this->value) === $old) { + $this->value = $new; + } + } + + if ($this instanceof Type\Atomic\TArray + || $this instanceof Type\Atomic\TGenericObject + || $this instanceof Type\Atomic\TIterable + ) { + foreach ($this->type_params as $type_param) { + $type_param->replaceClassLike($old, $new); + } + } + + if ($this instanceof Type\Atomic\TKeyedArray) { + foreach ($this->properties as $property_type) { + $property_type->replaceClassLike($old, $new); + } + } + + if ($this instanceof Type\Atomic\TClosure + || $this instanceof Type\Atomic\TCallable + ) { + if ($this->params) { + foreach ($this->params as $param) { + if ($param->type) { + $param->type->replaceClassLike($old, $new); + } + } + } + + if ($this->return_type) { + $this->return_type->replaceClassLike($old, $new); + } + } + } + + public function __toString(): string + { + return ''; + } + + public function __clone() + { + if ($this instanceof TNamedObject + || $this instanceof TTemplateParam + || $this instanceof TIterable + || $this instanceof Type\Atomic\TObjectWithProperties + ) { + if ($this->extra_types) { + foreach ($this->extra_types as &$type) { + $type = clone $type; + } + } + } + + if ($this instanceof TTemplateParam) { + $this->as = clone $this->as; + } + } + + public function getId(bool $nested = false): string + { + return $this->__toString(); + } + + public function getAssertionString(): string + { + return $this->getId(); + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return $this->getKey(); + } + + /** + * @param array $aliased_classes + */ + abstract public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string; + + abstract public function canBeFullyExpressedInPhp(): bool; + + public function replaceTemplateTypesWithStandins( + TemplateResult $template_result, + ?Codebase $codebase = null, + ?StatementsAnalyzer $statements_analyzer = null, + Type\Atomic $input_type = null, + ?int $input_arg_offset = null, + ?string $calling_class = null, + ?string $calling_function = null, + bool $replace = true, + bool $add_upper_bound = false, + int $depth = 0 + ) : self { + return $this; + } + + public function replaceTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + // do nothing + } + + public function equals(Atomic $other_type): bool + { + return get_class($other_type) === get_class($this); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/CallableTrait.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/CallableTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..36bf87ff4d098d864ae58725bc094ff3861d7655 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/CallableTrait.php @@ -0,0 +1,285 @@ +|null + */ + public $params = []; + + /** + * @var Union|null + */ + public $return_type; + + /** + * @var ?bool + */ + public $is_pure; + + /** + * Constructs a new instance of a generic type + * + * @param array $params + */ + public function __construct( + string $value = 'callable', + ?array $params = null, + ?Union $return_type = null, + ?bool $is_pure = null + ) { + $this->value = $value; + $this->params = $params; + $this->return_type = $return_type; + $this->is_pure = $is_pure; + } + + public function __clone() + { + if ($this->params) { + foreach ($this->params as &$param) { + $param = clone $param; + } + } + + $this->return_type = $this->return_type ? clone $this->return_type : null; + } + + public function getKey(bool $include_extra = true): string + { + return $this->__toString(); + } + + /** + * @param array $aliased_classes + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($use_phpdoc_format) { + if ($this instanceof TNamedObject) { + return parent::toNamespacedString($namespace, $aliased_classes, $this_class, true); + } + + return $this->value; + } + + $param_string = ''; + $return_type_string = ''; + + if ($this->params !== null) { + $param_string = '(' . implode( + ', ', + array_map( + /** + * @return string + */ + function (FunctionLikeParameter $param) use ($namespace, $aliased_classes, $this_class): string { + if (!$param->type) { + $type_string = 'mixed'; + } else { + $type_string = $param->type->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + false + ); + } + + return ($param->is_variadic ? '...' : '') . $type_string . ($param->is_optional ? '=' : ''); + }, + $this->params + ) + ) . ')'; + } + + if ($this->return_type !== null) { + $return_type_multiple = count($this->return_type->getAtomicTypes()) > 1; + + $return_type_string = ':' . ($return_type_multiple ? '(' : '') . $this->return_type->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + false + ) . ($return_type_multiple ? ')' : ''); + } + + if ($this instanceof TNamedObject) { + return parent::toNamespacedString($namespace, $aliased_classes, $this_class, true) + . $param_string . $return_type_string; + } + + return ($this->is_pure ? 'pure-' : '') . 'callable' . $param_string . $return_type_string; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): string { + if ($this instanceof TNamedObject) { + return parent::toNamespacedString($namespace, $aliased_classes, $this_class, true); + } + + return $this->value; + } + + public function getId(bool $nested = false): string + { + $param_string = ''; + $return_type_string = ''; + + if ($this->params !== null) { + $param_string .= '('; + foreach ($this->params as $i => $param) { + if ($i) { + $param_string .= ', '; + } + + $param_string .= $param->getId(); + } + + $param_string .= ')'; + } + + if ($this->return_type !== null) { + $return_type_multiple = count($this->return_type->getAtomicTypes()) > 1; + $return_type_string = ':' . ($return_type_multiple ? '(' : '') + . $this->return_type->getId() . ($return_type_multiple ? ')' : ''); + } + + return ($this->is_pure ? 'pure-' : ($this->is_pure === null ? '' : 'impure-')) + . $this->value . $param_string . $return_type_string; + } + + public function __toString(): string + { + return $this->getId(); + } + + public function replaceTemplateTypesWithStandins( + TemplateResult $template_result, + ?Codebase $codebase = null, + ?StatementsAnalyzer $statements_analyzer = null, + ?Atomic $input_type = null, + ?int $input_arg_offset = null, + ?string $calling_class = null, + ?string $calling_function = null, + bool $replace = true, + bool $add_upper_bound = false, + int $depth = 0 + ) : Atomic { + $callable = clone $this; + + if ($callable->params) { + foreach ($callable->params as $offset => $param) { + $input_param_type = null; + + if (($input_type instanceof Atomic\TClosure || $input_type instanceof Atomic\TCallable) + && isset($input_type->params[$offset]) + ) { + $input_param_type = $input_type->params[$offset]->type; + } + + if (!$param->type) { + continue; + } + + $param->type = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $param->type, + $template_result, + $codebase, + $statements_analyzer, + $input_param_type, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + !$add_upper_bound, + $depth + ); + } + } + + if (($input_type instanceof Atomic\TCallable || $input_type instanceof Atomic\TClosure) + && $callable->return_type + && $input_type->return_type + ) { + $callable->return_type = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $callable->return_type, + $template_result, + $codebase, + $statements_analyzer, + $input_type->return_type, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound + ); + } + + return $callable; + } + + public function replaceTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + if ($this->params) { + foreach ($this->params as $param) { + if (!$param->type) { + continue; + } + + $param->type->replaceTemplateTypesWithArgTypes($template_result, $codebase); + } + } + + if ($this->return_type) { + $this->return_type->replaceTemplateTypesWithArgTypes($template_result, $codebase); + } + } + + /** + * @return list<\Psalm\Type\TypeNode> + */ + public function getChildNodes() : array + { + $child_nodes = []; + + if ($this->params) { + foreach ($this->params as $param) { + if ($param->type) { + $child_nodes[] = $param->type; + } + } + } + + if ($this->return_type) { + $child_nodes[] = $this->return_type; + } + + return $child_nodes; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/GenericTrait.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/GenericTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..f2e48849ccbe91ef0692b764574fcc3659989f77 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/GenericTrait.php @@ -0,0 +1,242 @@ + + */ + public $type_params; + + public function __toString(): string + { + $s = ''; + foreach ($this->type_params as $type_param) { + $s .= $type_param . ', '; + } + + $extra_types = ''; + + if ($this instanceof TNamedObject && $this->extra_types) { + $extra_types = '&' . implode('&', $this->extra_types); + } + + return $this->value . '<' . substr($s, 0, -2) . '>' . $extra_types; + } + + public function getId(bool $nested = false): string + { + $s = ''; + foreach ($this->type_params as $type_param) { + $s .= $type_param->getId() . ', '; + } + + $extra_types = ''; + + if ($this instanceof TNamedObject && $this->extra_types) { + $extra_types = '&' . implode( + '&', + array_map( + function ($type) { + return $type->getId(true); + }, + $this->extra_types + ) + ); + } + + return $this->value . '<' . substr($s, 0, -2) . '>' . $extra_types; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + $base_value = $this instanceof TNamedObject + ? parent::toNamespacedString($namespace, $aliased_classes, $this_class, $use_phpdoc_format) + : $this->value; + + if ($base_value === 'non-empty-array') { + $base_value = 'array'; + } + + if ($use_phpdoc_format) { + if ($this instanceof TNamedObject || $this instanceof TIterable) { + return $base_value; + } + + $value_type = $this->type_params[1]; + + if ($value_type->isMixed() || $value_type->isEmpty()) { + return $base_value; + } + + $value_type_string = $value_type->toNamespacedString($namespace, $aliased_classes, $this_class, true); + + if (!$value_type->isSingle()) { + return '(' . $value_type_string . ')[]'; + } + + return $value_type_string . '[]'; + } + + $extra_types = ''; + + if ($this instanceof TNamedObject && $this->extra_types) { + $extra_types = '&' . implode( + '&', + array_map( + /** + * @return string + */ + function (Atomic $extra_type) use ($namespace, $aliased_classes, $this_class): string { + return $extra_type->toNamespacedString($namespace, $aliased_classes, $this_class, false); + }, + $this->extra_types + ) + ); + } + + return $base_value . + '<' . + implode( + ', ', + array_map( + /** + * @return string + */ + function (Union $type_param) use ($namespace, $aliased_classes, $this_class): string { + return $type_param->toNamespacedString($namespace, $aliased_classes, $this_class, false); + }, + $this->type_params + ) + ) . + '>' . $extra_types; + } + + public function __clone() + { + foreach ($this->type_params as &$type_param) { + $type_param = clone $type_param; + } + } + + /** + * @return array<\Psalm\Type\TypeNode> + */ + public function getChildNodes() : array + { + return $this->type_params; + } + + public function replaceTemplateTypesWithStandins( + TemplateResult $template_result, + ?Codebase $codebase = null, + ?StatementsAnalyzer $statements_analyzer = null, + ?Atomic $input_type = null, + ?int $input_arg_offset = null, + ?string $calling_class = null, + ?string $calling_function = null, + bool $replace = true, + bool $add_upper_bound = false, + int $depth = 0 + ) : Atomic { + if ($input_type instanceof Atomic\TList) { + $input_type = new Atomic\TArray([Type::getInt(), $input_type->type_param]); + } + + $input_object_type_params = []; + + if ($input_type instanceof Atomic\TGenericObject + && ($this instanceof Atomic\TGenericObject || $this instanceof Atomic\TIterable) + && $codebase + ) { + $input_object_type_params = UnionTemplateHandler::getMappedGenericTypeParams( + $codebase, + $input_type, + $this + ); + } + + $atomic = clone $this; + + foreach ($atomic->type_params as $offset => $type_param) { + $input_type_param = null; + + if (($input_type instanceof Atomic\TIterable + || $input_type instanceof Atomic\TArray) + && + isset($input_type->type_params[$offset]) + ) { + $input_type_param = $input_type->type_params[$offset]; + } elseif ($input_type instanceof Atomic\TKeyedArray) { + if ($offset === 0) { + $input_type_param = $input_type->getGenericKeyType(); + } elseif ($offset === 1) { + $input_type_param = $input_type->getGenericValueType(); + } else { + throw new \UnexpectedValueException('Not expecting offset of ' . $offset); + } + } elseif ($input_type instanceof Atomic\TNamedObject + && isset($input_object_type_params[$offset]) + ) { + $input_type_param = $input_object_type_params[$offset]; + } + + /** @psalm-suppress PropertyTypeCoercion */ + $atomic->type_params[$offset] = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $type_param, + $template_result, + $codebase, + $statements_analyzer, + $input_type_param, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth + 1 + ); + } + + return $atomic; + } + + public function replaceTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + foreach ($this->type_params as $offset => $type_param) { + $type_param->replaceTemplateTypesWithArgTypes($template_result, $codebase); + + if ($this instanceof Atomic\TArray && $offset === 0 && $type_param->isMixed()) { + $this->type_params[0] = \Psalm\Type::getArrayKey(); + } + } + + if ($this instanceof TGenericObject) { + $this->remapped_params = true; + } + + if ($this instanceof TGenericObject || $this instanceof TIterable) { + $this->replaceIntersectionTemplateTypesWithArgTypes($template_result, $codebase); + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/HasIntersectionTrait.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/HasIntersectionTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..24652be2c60bfb14c77b570a4bd86a42686c837a --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/HasIntersectionTrait.php @@ -0,0 +1,105 @@ +|null + */ + public $extra_types; + + /** + * @param array $aliased_classes + */ + private function getNamespacedIntersectionTypes( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ) : string { + if (!$this->extra_types) { + return ''; + } + + return '&' . implode( + '&', + array_map( + /** + * @param TNamedObject|TTemplateParam|TIterable|TObjectWithProperties $extra_type + * + * @return string + */ + function (Atomic $extra_type) use ( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ): string { + return $extra_type->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ); + }, + $this->extra_types + ) + ); + } + + /** + * @param TNamedObject|TTemplateParam|TIterable|TObjectWithProperties $type + */ + public function addIntersectionType(Type\Atomic $type) : void + { + $this->extra_types[$type->getKey()] = $type; + } + + /** + * @return array|null + */ + public function getIntersectionTypes() : ?array + { + return $this->extra_types; + } + + public function replaceIntersectionTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + if (!$this->extra_types) { + return; + } + + $new_types = []; + + foreach ($this->extra_types as $extra_type) { + if ($extra_type instanceof TTemplateParam + && isset($template_result->upper_bounds[$extra_type->param_name][$extra_type->defining_class]) + ) { + $template_type = clone $template_result->upper_bounds + [$extra_type->param_name][$extra_type->defining_class][0]; + + foreach ($template_type->getAtomicTypes() as $template_type_part) { + if ($template_type_part instanceof TNamedObject) { + $new_types[$template_type_part->getKey()] = $template_type_part; + } elseif ($template_type_part instanceof TTemplateParam) { + $new_types[$template_type_part->getKey()] = $template_type_part; + } + } + } else { + $extra_type->replaceTemplateTypesWithArgTypes($template_result, $codebase); + $new_types[$extra_type->getKey()] = $extra_type; + } + } + + $this->extra_types = $new_types; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/Scalar.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/Scalar.php new file mode 100644 index 0000000000000000000000000000000000000000..fcf2f582374ff18c5093c65c700548ebe51c2fa4 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/Scalar.php @@ -0,0 +1,10 @@ + 7 + || ($php_major_version === 7 && $php_minor_version >= 2) + ? 'object' : null; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return 'object'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TArray.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TArray.php new file mode 100644 index 0000000000000000000000000000000000000000..603012cca6140db5b9903398084a002af68e6cf1 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TArray.php @@ -0,0 +1,83 @@ + $type_params + */ + public function __construct(array $type_params) + { + $this->type_params = $type_params; + } + + public function getKey(bool $include_extra = true): string + { + return 'array'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): string { + return $this->getKey(); + } + + public function canBeFullyExpressedInPhp(): bool + { + return $this->type_params[0]->isArrayKey() && $this->type_params[1]->isMixed(); + } + + public function equals(Atomic $other_type): bool + { + if (get_class($other_type) !== static::class) { + return false; + } + + if ($this instanceof TNonEmptyArray + && $other_type instanceof TNonEmptyArray + && $this->count !== $other_type->count + ) { + return false; + } + + if (count($this->type_params) !== count($other_type->type_params)) { + return false; + } + + foreach ($this->type_params as $i => $type_param) { + if (!$type_param->equals($other_type->type_params[$i])) { + return false; + } + } + + return true; + } + + public function getAssertionString(): string + { + return $this->getKey(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TArrayKey.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TArrayKey.php new file mode 100644 index 0000000000000000000000000000000000000000..d4741a11859d57d8089ea9a97294af0dcffe8f48 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TArrayKey.php @@ -0,0 +1,33 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TAssertionFalsy.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TAssertionFalsy.php new file mode 100644 index 0000000000000000000000000000000000000000..7f66c9768a31bef09050907a4adb3901f997ebb5 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TAssertionFalsy.php @@ -0,0 +1,38 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TBool.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TBool.php new file mode 100644 index 0000000000000000000000000000000000000000..fc8f1830622f301630617362564bae8ade1989ff --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TBool.php @@ -0,0 +1,28 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return $php_major_version >= 7 ? 'bool' : null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TCallable.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TCallable.php new file mode 100644 index 0000000000000000000000000000000000000000..2b583cb6e184019adb71f4a8ace8510319bc3857 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TCallable.php @@ -0,0 +1,30 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): string { + return 'callable'; + } + + public function canBeFullyExpressedInPhp(): bool + { + return $this->params === null && $this->return_type === null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TCallableArray.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TCallableArray.php new file mode 100644 index 0000000000000000000000000000000000000000..1ca772031d99f77b4a857856ee421940e7c66a4d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TCallableArray.php @@ -0,0 +1,18 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return $php_major_version > 7 + || ($php_major_version === 7 && $php_minor_version >= 2) + ? 'object' : null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function getAssertionString(): string + { + return 'object'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TCallableString.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TCallableString.php new file mode 100644 index 0000000000000000000000000000000000000000..961264c2777885d12932e0a5262c47f301ad26ec --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TCallableString.php @@ -0,0 +1,26 @@ +getKey(); + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function getAssertionString(): string + { + return 'string'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClassString.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClassString.php new file mode 100644 index 0000000000000000000000000000000000000000..da8267fb91b10251a0904512814b6b31ecfe66dc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClassString.php @@ -0,0 +1,162 @@ +as = $as; + $this->as_type = $as_type; + } + + public function getKey(bool $include_extra = true): string + { + return 'class-string' . ($this->as === 'object' ? '' : '<' . $this->as_type . '>'); + } + + public function __toString(): string + { + return $this->getKey(); + } + + public function getId(bool $nested = false): string + { + return $this->getKey(); + } + + public function getAssertionString(): string + { + return 'class-string'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return 'string'; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($this->as === 'object') { + return 'class-string'; + } + + if ($namespace && stripos($this->as, $namespace . '\\') === 0) { + return 'class-string<' . preg_replace( + '/^' . preg_quote($namespace . '\\') . '/i', + '', + $this->as + ) . '>'; + } + + if (!$namespace && strpos($this->as, '\\') === false) { + return 'class-string<' . $this->as . '>'; + } + + if (isset($aliased_classes[strtolower($this->as)])) { + return 'class-string<' . $aliased_classes[strtolower($this->as)] . '>'; + } + + return 'class-string<\\' . $this->as . '>'; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function getChildNodes() : array + { + return $this->as_type ? [$this->as_type] : []; + } + + public function replaceTemplateTypesWithStandins( + TemplateResult $template_result, + ?Codebase $codebase = null, + ?StatementsAnalyzer $statements_analyzer = null, + ?Atomic $input_type = null, + ?int $input_arg_offset = null, + ?string $calling_class = null, + ?string $calling_function = null, + bool $replace = true, + bool $add_upper_bound = false, + int $depth = 0 + ) : Atomic { + $class_string = clone $this; + + if (!$class_string->as_type) { + return $class_string; + } + + if ($input_type instanceof TLiteralClassString) { + $input_object_type = new TNamedObject($input_type->value); + } elseif ($input_type instanceof TClassString && $input_type->as_type) { + $input_object_type = $input_type->as_type; + } else { + $input_object_type = new TObject(); + } + + $as_type = UnionTemplateHandler::replaceTemplateTypesWithStandins( + new \Psalm\Type\Union([$class_string->as_type]), + $template_result, + $codebase, + $statements_analyzer, + new \Psalm\Type\Union([$input_object_type]), + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth + ); + + $as_type_types = \array_values($as_type->getAtomicTypes()); + + $class_string->as_type = \count($as_type_types) === 1 + && $as_type_types[0] instanceof TNamedObject + ? $as_type_types[0] + : null; + + if (!$class_string->as_type) { + $class_string->as = 'object'; + } + + return $class_string; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClassStringMap.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClassStringMap.php new file mode 100644 index 0000000000000000000000000000000000000000..0ffc96266574e2514e118547ecf3dbd83d6f6073 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClassStringMap.php @@ -0,0 +1,236 @@ +value_param = $value_param; + $this->param_name = $param_name; + $this->as_type = $as_type; + } + + public function __toString(): string + { + /** @psalm-suppress MixedOperand */ + return static::KEY + . '<' + . $this->param_name + . ' as ' + . ($this->as_type ? (string) $this->as_type : 'object') + . ', ' + . ((string) $this->value_param) + . '>'; + } + + public function getId(bool $nested = false): string + { + /** @psalm-suppress MixedOperand */ + return static::KEY + . '<' + . $this->param_name + . ' as ' + . ($this->as_type ? (string) $this->as_type : 'object') + . ', ' + . $this->value_param->getId() + . '>'; + } + + public function __clone() + { + $this->value_param = clone $this->value_param; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($use_phpdoc_format) { + return (new TArray([Type::getString(), $this->value_param])) + ->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ); + } + + /** @psalm-suppress MixedOperand */ + return static::KEY + . '<' + . $this->param_name + . ($this->as_type ? ' as ' . $this->as_type : '') + . ', ' + . $this->value_param->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ) + . '>'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): string { + return 'array'; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function getKey(bool $include_extra = true): string + { + return 'array'; + } + + public function replaceTemplateTypesWithStandins( + TemplateResult $template_result, + ?Codebase $codebase = null, + ?StatementsAnalyzer $statements_analyzer = null, + ?Atomic $input_type = null, + ?int $input_arg_offset = null, + ?string $calling_class = null, + ?string $calling_function = null, + bool $replace = true, + bool $add_upper_bound = false, + int $depth = 0 + ) : Atomic { + $map = clone $this; + + foreach ([Type::getString(), $map->value_param] as $offset => $type_param) { + $input_type_param = null; + + if (($input_type instanceof Atomic\TGenericObject + || $input_type instanceof Atomic\TIterable + || $input_type instanceof Atomic\TArray) + && + isset($input_type->type_params[$offset]) + ) { + $input_type_param = clone $input_type->type_params[$offset]; + } elseif ($input_type instanceof Atomic\TKeyedArray) { + if ($offset === 0) { + $input_type_param = $input_type->getGenericKeyType(); + } else { + $input_type_param = $input_type->getGenericValueType(); + } + } elseif ($input_type instanceof Atomic\TList) { + if ($offset === 0) { + continue; + } + + $input_type_param = clone $input_type->type_param; + } + + $value_param = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $type_param, + $template_result, + $codebase, + $statements_analyzer, + $input_type_param, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth + 1 + ); + + if ($offset === 1) { + $map->value_param = $value_param; + } + } + + return $map; + } + + public function replaceTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + $this->value_param->replaceTemplateTypesWithArgTypes($template_result, $codebase); + } + + public function getChildNodes() : array + { + return [$this->value_param]; + } + + public function equals(Atomic $other_type): bool + { + if (get_class($other_type) !== static::class) { + return false; + } + + if (!$this->value_param->equals($other_type->value_param)) { + return false; + } + + return true; + } + + public function getAssertionString(): string + { + return $this->getKey(); + } + + public function getStandinKeyParam() : Type\Union + { + return new Type\Union([ + new TTemplateParamClass( + $this->param_name, + $this->as_type ? $this->as_type->value : 'object', + $this->as_type, + 'class-string-map' + ) + ]); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClosedResource.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClosedResource.php new file mode 100644 index 0000000000000000000000000000000000000000..f5688f5300896b65f15b3b812cf5a9591daeeda7 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClosedResource.php @@ -0,0 +1,38 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClosure.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClosure.php new file mode 100644 index 0000000000000000000000000000000000000000..2a6bebb263a9f7f20633de30f453030e9770bb15 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TClosure.php @@ -0,0 +1,18 @@ + */ + public $byref_uses = []; + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TConditional.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TConditional.php new file mode 100644 index 0000000000000000000000000000000000000000..f710efdc8329f7b1cd25a844c07a4646edcac6dc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TConditional.php @@ -0,0 +1,138 @@ +param_name = $param_name; + $this->defining_class = $defining_class; + $this->as_type = $as_type; + $this->conditional_type = $conditional_type; + $this->if_type = $if_type; + $this->else_type = $else_type; + } + + public function __toString(): string + { + return '(' + . $this->param_name + . ' is ' . $this->conditional_type + . ' ? ' . $this->if_type + . ' : ' . $this->else_type + . ')'; + } + + public function __clone() + { + $this->conditional_type = clone $this->conditional_type; + $this->if_type = clone $this->if_type; + $this->else_type = clone $this->else_type; + $this->as_type = clone $this->as_type; + } + + public function getKey(bool $include_extra = true): string + { + return $this->__toString(); + } + + public function getAssertionString(): string + { + return ''; + } + + public function getId(bool $nested = false): string + { + return '(' + . $this->param_name . ':' . $this->defining_class + . ' is ' . $this->conditional_type->getId() + . ' ? ' . $this->if_type->getId() + . ' : ' . $this->else_type->getId() + . ')'; + } + + /** + * @param array $aliased_classes + * + * @return null + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return ''; + } + + public function getChildNodes() : array + { + return [$this->conditional_type, $this->if_type, $this->else_type]; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function replaceTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + $this->conditional_type->replaceTemplateTypesWithArgTypes($template_result, $codebase); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetClass.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetClass.php new file mode 100644 index 0000000000000000000000000000000000000000..8eb7327bc0752e00aa0235114deeda816fded93e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetClass.php @@ -0,0 +1,44 @@ +typeof = $typeof; + $this->as_type = $as_type; + } + + public function getId(bool $nested = false): string + { + return $this->as_type->isMixed() + || $this->as_type->hasObject() + ? 'class-string' + : 'class-string<' . $this->as_type->getId() . '>'; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetDebugType.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetDebugType.php new file mode 100644 index 0000000000000000000000000000000000000000..9245315c316f4c2f7a1c52a96a4ae0e58456511c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetDebugType.php @@ -0,0 +1,28 @@ +typeof = $typeof; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetType.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetType.php new file mode 100644 index 0000000000000000000000000000000000000000..f1de269a83fe206592754046a4b2fb13652613cd --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TDependentGetType.php @@ -0,0 +1,28 @@ +typeof = $typeof; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TEmpty.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TEmpty.php new file mode 100644 index 0000000000000000000000000000000000000000..bc80b6acef7bf7cbb3a607c21c10bd8937bca645 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TEmpty.php @@ -0,0 +1,28 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TEmptyMixed.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TEmptyMixed.php new file mode 100644 index 0000000000000000000000000000000000000000..eb6638237ac0dfb75d28372421e1325223000fb7 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TEmptyMixed.php @@ -0,0 +1,10 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return $php_major_version >= 7 ? 'float' : null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TGenericObject.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TGenericObject.php new file mode 100644 index 0000000000000000000000000000000000000000..27f628a3de460cf2a831bcb4156fe99229f69724 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TGenericObject.php @@ -0,0 +1,94 @@ + $type_params + */ + public function __construct(string $value, array $type_params) + { + if ($value[0] === '\\') { + $value = substr($value, 1); + } + + $this->value = $value; + $this->type_params = $type_params; + } + + public function getKey(bool $include_extra = true): string + { + $s = ''; + + foreach ($this->type_params as $type_param) { + $s .= $type_param->getKey() . ', '; + } + + $extra_types = ''; + + if ($include_extra && $this->extra_types) { + $extra_types = '&' . implode('&', $this->extra_types); + } + + return $this->value . '<' . substr($s, 0, -2) . '>' . $extra_types; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return parent::toNamespacedString($namespace, $aliased_classes, $this_class, false); + } + + public function equals(Atomic $other_type): bool + { + if (!$other_type instanceof self) { + return false; + } + + if (count($this->type_params) !== count($other_type->type_params)) { + return false; + } + + foreach ($this->type_params as $i => $type_param) { + if (!$type_param->equals($other_type->type_params[$i])) { + return false; + } + } + + return true; + } + + public function getAssertionString(): string + { + return $this->value; + } + + public function getChildNodes() : array + { + return array_merge($this->type_params, $this->extra_types !== null ? $this->extra_types : []); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/THtmlEscapedString.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/THtmlEscapedString.php new file mode 100644 index 0000000000000000000000000000000000000000..53c3680603d68a4f1f82202d4e35534a10cefec5 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/THtmlEscapedString.php @@ -0,0 +1,20 @@ +getKey(); + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TInt.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TInt.php new file mode 100644 index 0000000000000000000000000000000000000000..b4538b6deeab1da6c01c5ea049fc0176cf2e30ff --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TInt.php @@ -0,0 +1,28 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return $php_major_version >= 7 ? 'int' : null; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TIterable.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TIterable.php new file mode 100644 index 0000000000000000000000000000000000000000..24418ccc3e7225c6ac96e815b744adf307261acc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TIterable.php @@ -0,0 +1,117 @@ + $type_params + */ + public function __construct(array $type_params = []) + { + if ($type_params) { + $this->has_docblock_params = true; + $this->type_params = $type_params; + } else { + $this->type_params = [\Psalm\Type::getMixed(), \Psalm\Type::getMixed()]; + } + } + + public function getKey(bool $include_extra = true): string + { + if ($include_extra && $this->extra_types) { + // do nothing + } + + return 'iterable'; + } + + public function getAssertionString(): string + { + return 'iterable'; + } + + public function getId(bool $nested = false): string + { + $s = ''; + foreach ($this->type_params as $type_param) { + $s .= $type_param->getId() . ', '; + } + + $extra_types = ''; + + if ($this->extra_types) { + $extra_types = '&' . implode('&', $this->extra_types); + } + + return $this->value . '<' . substr($s, 0, -2) . '>' . $extra_types; + } + + public function __toString(): string + { + return $this->getId(); + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return $php_major_version > 7 + || ($php_major_version === 7 && $php_minor_version >= 1) + ? 'iterable' + : null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return $this->type_params[0]->isMixed() && $this->type_params[1]->isMixed(); + } + + public function equals(Atomic $other_type): bool + { + if (!$other_type instanceof self) { + return false; + } + + if (count($this->type_params) !== count($other_type->type_params)) { + return false; + } + + foreach ($this->type_params as $i => $type_param) { + if (!$type_param->equals($other_type->type_params[$i])) { + return false; + } + } + + return true; + } + + public function getChildNodes() : array + { + return array_merge($this->type_params, $this->extra_types !== null ? $this->extra_types : []); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TKeyOfClassConstant.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TKeyOfClassConstant.php new file mode 100644 index 0000000000000000000000000000000000000000..d077027eb8f138334e38308dd8dbf14764789571 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TKeyOfClassConstant.php @@ -0,0 +1,102 @@ +fq_classlike_name = $fq_classlike_name; + $this->const_name = $const_name; + } + + public function getKey(bool $include_extra = true): string + { + return 'key-of<' . $this->fq_classlike_name . '::' . $this->const_name . '>'; + } + + public function __toString(): string + { + return 'key-of<' . $this->fq_classlike_name . '::' . $this->const_name . '>'; + } + + public function getId(bool $nested = false): string + { + return $this->getKey(); + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($this->fq_classlike_name === 'static') { + return 'key-ofconst_name . '>'; + } + + if ($this->fq_classlike_name === $this_class) { + return 'key-ofconst_name . '>'; + } + + if ($namespace && stripos($this->fq_classlike_name, $namespace . '\\') === 0) { + return 'key-of<' . preg_replace( + '/^' . preg_quote($namespace . '\\') . '/i', + '', + $this->fq_classlike_name + ) . '::' . $this->const_name . '>'; + } + + if (!$namespace && strpos($this->fq_classlike_name, '\\') === false) { + return 'key-of<' . $this->fq_classlike_name . '::' . $this->const_name . '>'; + } + + if (isset($aliased_classes[strtolower($this->fq_classlike_name)])) { + return 'key-of<' + . $aliased_classes[strtolower($this->fq_classlike_name)] + . '::' + . $this->const_name + . '>'; + } + + return 'key-of<\\' . $this->fq_classlike_name . '::' . $this->const_name . '>'; + } + + public function getAssertionString(): string + { + return 'mixed'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TKeyedArray.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TKeyedArray.php new file mode 100644 index 0000000000000000000000000000000000000000..5772d0748f7763dbe36423612f721223ba3b0331 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TKeyedArray.php @@ -0,0 +1,407 @@ + + */ + public $properties; + + /** + * @var array|null + */ + public $class_strings = null; + + /** + * @var bool - whether or not the objectlike has been created from an explicit array + */ + public $sealed = false; + + /** + * Whether or not the previous array had an unknown key type + * + * @var ?Union + */ + public $previous_key_type = null; + + /** + * Whether or not to allow new properties to be asserted on the given array + * + * @var ?Union + */ + public $previous_value_type = null; + + /** + * @var bool - if this is a list of sequential elements + */ + public $is_list = false; + + public const KEY = 'array'; + + /** + * Constructs a new instance of a generic type + * + * @param non-empty-array $properties + * @param array $class_strings + */ + public function __construct(array $properties, ?array $class_strings = null) + { + $this->properties = $properties; + $this->class_strings = $class_strings; + } + + public function __toString(): string + { + $property_strings = array_map( + function ($name, Union $type): string { + if ($this->is_list && $this->sealed) { + return (string) $type; + } + + if (\is_string($name) && \preg_match('/[ "\'\\\\.\n:]/', $name)) { + $name = '\'' . \str_replace("\n", '\n', \addslashes($name)) . '\''; + } + + return $name . ($type->possibly_undefined ? '?' : '') . ': ' . $type; + }, + array_keys($this->properties), + $this->properties + ); + + if (!$this->is_list) { + sort($property_strings); + } + + /** @psalm-suppress MixedOperand */ + return static::KEY . '{' . implode(', ', $property_strings) . '}'; + } + + public function getId(bool $nested = false): string + { + $property_strings = array_map( + function ($name, Union $type): string { + if ($this->is_list && $this->sealed) { + return $type->getId(); + } + + if (\is_string($name) && \preg_match('/[ "\'\\\\.\n:]/', $name)) { + $name = '\'' . \str_replace("\n", '\n', \addslashes($name)) . '\''; + } + + return $name . ($type->possibly_undefined ? '?' : '') . ': ' . $type->getId(); + }, + array_keys($this->properties), + $this->properties + ); + + if (!$this->is_list) { + sort($property_strings); + } + + /** @psalm-suppress MixedOperand */ + return static::KEY . '{' . + implode(', ', $property_strings) . + '}' + . ($this->previous_value_type + && (!$this->previous_value_type->isMixed() + || ($this->previous_key_type && !$this->previous_key_type->isArrayKey())) + ? '<' . ($this->previous_key_type ? $this->previous_key_type->getId() . ', ' : '') + . $this->previous_value_type->getId() . '>' + : ''); + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($use_phpdoc_format) { + return $this->getGenericArrayType()->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ); + } + + /** @psalm-suppress MixedOperand */ + return static::KEY . '{' . + implode( + ', ', + array_map( + function ( + $name, + Union $type + ) use ( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ): string { + if (\is_string($name) && \preg_match('/[ "\'\\\\.\n:]/', $name)) { + $name = '\'' . \str_replace("\n", '\n', \addslashes($name)) . '\''; + } + + return $name . ($type->possibly_undefined ? '?' : '') . ': ' . $type->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ); + }, + array_keys($this->properties), + $this->properties + ) + ) . + '}'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): string { + return $this->getKey(); + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function getGenericKeyType(): Union + { + $key_types = []; + + foreach ($this->properties as $key => $_) { + if (is_int($key)) { + $key_types[] = new Type\Atomic\TLiteralInt($key); + } elseif (isset($this->class_strings[$key])) { + $key_types[] = new Type\Atomic\TLiteralClassString($key); + } else { + $key_types[] = new Type\Atomic\TLiteralString($key); + } + } + + $key_type = TypeCombination::combineTypes($key_types); + + $key_type->possibly_undefined = false; + + if ($this->previous_key_type) { + $key_type = Type::combineUnionTypes($this->previous_key_type, $key_type); + } + + return $key_type; + } + + public function getGenericValueType(): Union + { + $value_type = null; + + foreach ($this->properties as $property) { + if ($value_type === null) { + $value_type = clone $property; + } else { + $value_type = Type::combineUnionTypes($property, $value_type); + } + } + + if ($this->previous_value_type) { + $value_type = Type::combineUnionTypes($this->previous_value_type, $value_type); + } + + $value_type->possibly_undefined = false; + + return $value_type; + } + + public function getGenericArrayType(): TArray + { + $key_types = []; + $value_type = null; + + $has_defined_keys = false; + + foreach ($this->properties as $key => $property) { + if (is_int($key)) { + $key_types[] = new Type\Atomic\TLiteralInt($key); + } elseif (isset($this->class_strings[$key])) { + $key_types[] = new Type\Atomic\TLiteralClassString($key); + } else { + $key_types[] = new Type\Atomic\TLiteralString($key); + } + + if ($value_type === null) { + $value_type = clone $property; + } else { + $value_type = Type::combineUnionTypes($property, $value_type); + } + + if (!$value_type->possibly_undefined) { + $has_defined_keys = true; + } + } + + $key_type = TypeCombination::combineTypes($key_types); + + if ($this->previous_value_type) { + $value_type = Type::combineUnionTypes($this->previous_value_type, $value_type); + } + + if ($this->previous_key_type) { + $key_type = Type::combineUnionTypes($this->previous_key_type, $key_type); + } + + $value_type->possibly_undefined = false; + + if ($this->previous_value_type || $has_defined_keys) { + $array_type = new TNonEmptyArray([$key_type, $value_type]); + } else { + $array_type = new TArray([$key_type, $value_type]); + } + + return $array_type; + } + + public function __clone() + { + foreach ($this->properties as &$property) { + $property = clone $property; + } + } + + public function getKey(bool $include_extra = true): string + { + /** @var string */ + return static::KEY; + } + + public function replaceTemplateTypesWithStandins( + TemplateResult $template_result, + ?Codebase $codebase = null, + ?StatementsAnalyzer $statements_analyzer = null, + ?Atomic $input_type = null, + ?int $input_arg_offset = null, + ?string $calling_class = null, + ?string $calling_function = null, + bool $replace = true, + bool $add_upper_bound = false, + int $depth = 0 + ) : Atomic { + $object_like = clone $this; + + foreach ($this->properties as $offset => $property) { + $input_type_param = null; + + if ($input_type instanceof Atomic\TKeyedArray + && isset($input_type->properties[$offset]) + ) { + $input_type_param = $input_type->properties[$offset]; + } + + $object_like->properties[$offset] = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $property, + $template_result, + $codebase, + $statements_analyzer, + $input_type_param, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth + ); + } + + return $object_like; + } + + public function replaceTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + foreach ($this->properties as $property) { + $property->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + } + } + + public function getChildNodes() : array + { + return $this->properties; + } + + public function equals(Atomic $other_type): bool + { + if (get_class($other_type) !== static::class) { + return false; + } + + if (count($this->properties) !== count($other_type->properties)) { + return false; + } + + if ($this->sealed !== $other_type->sealed) { + return false; + } + + foreach ($this->properties as $property_name => $property_type) { + if (!isset($other_type->properties[$property_name])) { + return false; + } + + if (!$property_type->equals($other_type->properties[$property_name])) { + return false; + } + } + + return true; + } + + public function getAssertionString(): string + { + return $this->getKey(); + } + + public function getList() : TNonEmptyList + { + if (!$this->is_list) { + throw new \UnexpectedValueException('Object-like array must be a list for conversion'); + } + + return new TNonEmptyList($this->getGenericValueType()); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TList.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TList.php new file mode 100644 index 0000000000000000000000000000000000000000..8d3ad6280dfe99e38689d57b78f779540317e5d8 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TList.php @@ -0,0 +1,194 @@ +type_param = $type_param; + } + + public function __toString(): string + { + /** @psalm-suppress MixedOperand */ + return static::KEY . '<' . $this->type_param . '>'; + } + + public function getId(bool $nested = false): string + { + /** @psalm-suppress MixedOperand */ + return static::KEY . '<' . $this->type_param->getId() . '>'; + } + + public function __clone() + { + $this->type_param = clone $this->type_param; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($use_phpdoc_format) { + return (new TArray([Type::getInt(), $this->type_param])) + ->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ); + } + + /** @psalm-suppress MixedOperand */ + return static::KEY + . '<' + . $this->type_param->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ) + . '>'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): string { + return 'array'; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function getKey(bool $include_extra = true): string + { + return 'array'; + } + + public function replaceTemplateTypesWithStandins( + TemplateResult $template_result, + ?Codebase $codebase = null, + ?StatementsAnalyzer $statements_analyzer = null, + ?Atomic $input_type = null, + ?int $input_arg_offset = null, + ?string $calling_class = null, + ?string $calling_function = null, + bool $replace = true, + bool $add_upper_bound = false, + int $depth = 0 + ) : Atomic { + $list = clone $this; + + foreach ([Type::getInt(), $list->type_param] as $offset => $type_param) { + $input_type_param = null; + + if (($input_type instanceof Atomic\TGenericObject + || $input_type instanceof Atomic\TIterable + || $input_type instanceof Atomic\TArray) + && + isset($input_type->type_params[$offset]) + ) { + $input_type_param = clone $input_type->type_params[$offset]; + } elseif ($input_type instanceof Atomic\TKeyedArray) { + if ($offset === 0) { + $input_type_param = $input_type->getGenericKeyType(); + } else { + $input_type_param = $input_type->getGenericValueType(); + } + } elseif ($input_type instanceof Atomic\TList) { + if ($offset === 0) { + continue; + } + + $input_type_param = clone $input_type->type_param; + } + + $type_param = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $type_param, + $template_result, + $codebase, + $statements_analyzer, + $input_type_param, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth + 1 + ); + + if ($offset === 1) { + $list->type_param = $type_param; + } + } + + return $list; + } + + public function replaceTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + $this->type_param->replaceTemplateTypesWithArgTypes($template_result, $codebase); + } + + public function equals(Atomic $other_type): bool + { + if (get_class($other_type) !== static::class) { + return false; + } + + if (!$this->type_param->equals($other_type->type_param)) { + return false; + } + + return true; + } + + public function getAssertionString(): string + { + return 'list'; + } + + public function getChildNodes() : array + { + return [$this->type_param]; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralClassString.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralClassString.php new file mode 100644 index 0000000000000000000000000000000000000000..836ee2d68139d6ab3226bbfcc3c09038fe55dfb2 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralClassString.php @@ -0,0 +1,94 @@ +value = $value; + } + + public function __toString(): string + { + return 'class-string'; + } + + public function getKey(bool $include_extra = true): string + { + return 'class-string(' . $this->value . ')'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): string { + return 'string'; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function getId(bool $nested = false): string + { + return $this->value . '::class'; + } + + public function getAssertionString(): string + { + return $this->getKey(); + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($this->value === 'static') { + return 'static::class'; + } + + if ($this->value === $this_class) { + return 'self::class'; + } + + if ($namespace && stripos($this->value, $namespace . '\\') === 0) { + return preg_replace( + '/^' . preg_quote($namespace . '\\') . '/i', + '', + $this->value + ) . '::class'; + } + + if (!$namespace && strpos($this->value, '\\') === false) { + return $this->value . '::class'; + } + + if (isset($aliased_classes[strtolower($this->value)])) { + return $aliased_classes[strtolower($this->value)] . '::class'; + } + + return '\\' . $this->value . '::class'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralFloat.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralFloat.php new file mode 100644 index 0000000000000000000000000000000000000000..64df97379980806834c6c9147453d42860c8e675 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralFloat.php @@ -0,0 +1,49 @@ +value = $value; + } + + public function getKey(bool $include_extra = true): string + { + return 'float(' . $this->value . ')'; + } + + public function getId(bool $nested = false): string + { + return 'float(' . $this->value . ')'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return $php_major_version >= 7 ? 'float' : null; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return 'float'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralInt.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralInt.php new file mode 100644 index 0000000000000000000000000000000000000000..b030e182ab80d674a569595831a3ca3d53aa90f4 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralInt.php @@ -0,0 +1,49 @@ +value = $value; + } + + public function getKey(bool $include_extra = true): string + { + return 'int(' . $this->value . ')'; + } + + public function getId(bool $nested = false): string + { + return 'int(' . $this->value . ')'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return $php_major_version >= 7 ? 'int' : null; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return $use_phpdoc_format ? 'int' : (string) $this->value; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralString.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralString.php new file mode 100644 index 0000000000000000000000000000000000000000..aeec57b2a7b623d7076b9107e89b765a3fcfcb11 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLiteralString.php @@ -0,0 +1,63 @@ +value = $value; + } + + public function getKey(bool $include_extra = true) : string + { + return $this->getId(); + } + + public function __toString(): string + { + return 'string'; + } + + public function getId(bool $nested = false): string + { + $no_newline_value = preg_replace("/\n/m", '\n', $this->value); + if (strlen($this->value) > 80) { + return 'string(' . substr($no_newline_value, 0, 80) . '...' . ')'; + } + + return 'string(' . $no_newline_value . ')'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return $php_major_version >= 7 ? 'string' : null; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return 'string'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLowercaseString.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLowercaseString.php new file mode 100644 index 0000000000000000000000000000000000000000..be3ebee6fbc1da23d4b51063a2ae597da849745c --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TLowercaseString.php @@ -0,0 +1,20 @@ +from_loop_isset = $from_loop_isset; + } + + public function __toString(): string + { + return 'mixed'; + } + + public function getKey(bool $include_extra = true): string + { + return 'mixed'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function getAssertionString(): string + { + return 'mixed'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNamedObject.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNamedObject.php new file mode 100644 index 0000000000000000000000000000000000000000..2367ee369db33592b66be9dcebed7fcb36d55e63 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNamedObject.php @@ -0,0 +1,128 @@ +value = $value; + $this->was_static = $was_static; + } + + public function __toString(): string + { + return $this->getKey(); + } + + public function getKey(bool $include_extra = true): string + { + if ($include_extra && $this->extra_types) { + return $this->value . '&' . implode('&', $this->extra_types); + } + + return $this->value; + } + + public function getId(bool $nested = false): string + { + if ($this->extra_types) { + return $this->value . '&' . implode( + '&', + array_map( + function ($type) { + return $type->getId(true); + }, + $this->extra_types + ) + ); + } + + return $this->was_static ? $this->value . '&static' : $this->value; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($this->value === 'static') { + return 'static'; + } + + $intersection_types = $this->getNamespacedIntersectionTypes( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ); + + return Type::getStringFromFQCLN($this->value, $namespace, $aliased_classes, $this_class, true) + . $intersection_types; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + if ($this->value === 'static') { + return null; + } + + return $this->toNamespacedString($namespace, $aliased_classes, $this_class, false); + } + + public function canBeFullyExpressedInPhp(): bool + { + return $this->value !== 'static'; + } + + public function replaceTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + $this->replaceIntersectionTemplateTypesWithArgTypes($template_result, $codebase); + } + + public function getChildNodes() : array + { + return $this->extra_types !== null ? $this->extra_types : []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNever.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNever.php new file mode 100644 index 0000000000000000000000000000000000000000..c8e31668989cf4f1e0622eaf55d694f1bebb91ad --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNever.php @@ -0,0 +1,33 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyArray.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyArray.php new file mode 100644 index 0000000000000000000000000000000000000000..bccc82c76eaa54fd7ddbc1178ddff6832a43058d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNonEmptyArray.php @@ -0,0 +1,18 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNumeric.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNumeric.php new file mode 100644 index 0000000000000000000000000000000000000000..70ae163c64bbc866a3193a96f8dd90c51c45cb43 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNumeric.php @@ -0,0 +1,33 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNumericString.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNumericString.php new file mode 100644 index 0000000000000000000000000000000000000000..6adf65d16b5cbf1822d5a9f511ee88792446db3e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TNumericString.php @@ -0,0 +1,30 @@ +getKey(); + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function getAssertionString(): string + { + return 'string'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TObject.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TObject.php new file mode 100644 index 0000000000000000000000000000000000000000..901995914e22886ee6070a6bf384c5b43d00f315 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TObject.php @@ -0,0 +1,36 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return $php_major_version > 7 + || ($php_major_version === 7 && $php_minor_version >= 2) + ? $this->getKey() + : null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TObjectWithProperties.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TObjectWithProperties.php new file mode 100644 index 0000000000000000000000000000000000000000..52783832f035442bf6d18241d04a31601c42b7d9 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TObjectWithProperties.php @@ -0,0 +1,277 @@ + + */ + public $properties; + + /** + * @var array + */ + public $methods; + + /** + * Constructs a new instance of a generic type + * + * @param array $properties + * @param array $methods + */ + public function __construct(array $properties, array $methods = []) + { + $this->properties = $properties; + $this->methods = $methods; + } + + public function __toString(): string + { + $extra_types = ''; + + if ($this->extra_types) { + $extra_types = '&' . implode('&', $this->extra_types); + } + + $properties_string = implode( + ', ', + array_map( + /** + * @param string|int $name + */ + function ($name, Union $type): string { + return $name . ($type->possibly_undefined ? '?' : '') . ':' . $type; + }, + array_keys($this->properties), + $this->properties + ) + ); + + $methods_string = implode( + ', ', + array_map( + function (string $name): string { + return $name . '()'; + }, + array_keys($this->methods) + ) + ); + + return 'object{' + . $properties_string . ($methods_string && $properties_string ? ', ' : '') + . $methods_string + . '}' . $extra_types; + } + + public function getId(bool $nested = false): string + { + $extra_types = ''; + + if ($this->extra_types) { + $extra_types = '&' . implode('&', $this->extra_types); + } + + $properties_string = implode( + ', ', + array_map( + /** + * @param string|int $name + */ + function ($name, Union $type): string { + return $name . ($type->possibly_undefined ? '?' : '') . ':' . $type->getId(); + }, + array_keys($this->properties), + $this->properties + ) + ); + + $methods_string = implode( + ', ', + array_map( + function (string $name): string { + return $name . '()'; + }, + array_keys($this->methods) + ) + ); + + return 'object{' + . $properties_string . ($methods_string && $properties_string ? ', ' : '') + . $methods_string + . '}' . $extra_types; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($use_phpdoc_format) { + return 'object'; + } + + return 'object{' . + implode( + ', ', + array_map( + /** + * @param string|int $name + */ + function ( + $name, + Union $type + ) use ( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ): string { + return $name . ($type->possibly_undefined ? '?' : '') . ':' . $type->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ); + }, + array_keys($this->properties), + $this->properties + ) + ) . + '}'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): string { + return $this->getKey(); + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function __clone() + { + foreach ($this->properties as &$property) { + $property = clone $property; + } + } + + public function equals(Atomic $other_type): bool + { + if (!$other_type instanceof self) { + return false; + } + + if (count($this->properties) !== count($other_type->properties)) { + return false; + } + + if ($this->methods !== $other_type->methods) { + return false; + } + + foreach ($this->properties as $property_name => $property_type) { + if (!isset($other_type->properties[$property_name])) { + return false; + } + + if (!$property_type->equals($other_type->properties[$property_name])) { + return false; + } + } + + return true; + } + + public function replaceTemplateTypesWithStandins( + TemplateResult $template_result, + ?Codebase $codebase = null, + ?StatementsAnalyzer $statements_analyzer = null, + ?Atomic $input_type = null, + ?int $input_arg_offset = null, + ?string $calling_class = null, + ?string $calling_function = null, + bool $replace = true, + bool $add_upper_bound = false, + int $depth = 0 + ) : Atomic { + $object_like = clone $this; + + foreach ($this->properties as $offset => $property) { + $input_type_param = null; + + if ($input_type instanceof Atomic\TKeyedArray + && isset($input_type->properties[$offset]) + ) { + $input_type_param = $input_type->properties[$offset]; + } + + $object_like->properties[$offset] = UnionTemplateHandler::replaceTemplateTypesWithStandins( + $property, + $template_result, + $codebase, + $statements_analyzer, + $input_type_param, + $input_arg_offset, + $calling_class, + $calling_function, + $replace, + $add_upper_bound, + $depth + ); + } + + return $object_like; + } + + public function replaceTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + foreach ($this->properties as $property) { + $property->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + } + } + + public function getChildNodes() : array + { + return array_merge($this->properties, $this->extra_types !== null ? array_values($this->extra_types) : []); + } + + public function getAssertionString(): string + { + return $this->getKey(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TPositiveInt.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TPositiveInt.php new file mode 100644 index 0000000000000000000000000000000000000000..6f9fd7cf4886a2c278d07f7d9ecac7e91c36caaf --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TPositiveInt.php @@ -0,0 +1,36 @@ + $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return $use_phpdoc_format ? 'int' : 'positive-int'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TResource.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TResource.php new file mode 100644 index 0000000000000000000000000000000000000000..59042a25f1fd988c175b6429b46fb7e25ebf1655 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TResource.php @@ -0,0 +1,33 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TScalar.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TScalar.php new file mode 100644 index 0000000000000000000000000000000000000000..9f0f1f7a75db2036e90c8ba8a575fd6b815740cc --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TScalar.php @@ -0,0 +1,38 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function getAssertionString(): string + { + return 'scalar'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TScalarClassConstant.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TScalarClassConstant.php new file mode 100644 index 0000000000000000000000000000000000000000..fc76416245591c3ff74dd330adb94e3b8f4a2d22 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TScalarClassConstant.php @@ -0,0 +1,74 @@ +fq_classlike_name = $fq_classlike_name; + $this->const_name = $const_name; + } + + public function getKey(bool $include_extra = true): string + { + return 'scalar-class-constant(' . $this->fq_classlike_name . '::' . $this->const_name . ')'; + } + + public function __toString(): string + { + return $this->fq_classlike_name . '::' . $this->const_name; + } + + public function getId(bool $nested = false): string + { + return $this->getKey(); + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($this->fq_classlike_name === 'static') { + return 'static::' . $this->const_name; + } + + return \Psalm\Type::getStringFromFQCLN($this->fq_classlike_name, $namespace, $aliased_classes, $this_class) + . '::' + . $this->const_name; + } + + public function getAssertionString(): string + { + return 'mixed'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TSingleLetter.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TSingleLetter.php new file mode 100644 index 0000000000000000000000000000000000000000..b72ab6082ad7dce35281a1b8802d0edc068d0a23 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TSingleLetter.php @@ -0,0 +1,6 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return $php_major_version >= 7 ? 'string' : null; + } + + public function __toString(): string + { + return 'string'; + } + + public function getKey(bool $include_extra = true) : string + { + return 'string'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php new file mode 100644 index 0000000000000000000000000000000000000000..a1bcbf55f3ccf9732874843731d72a1701c3e676 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php @@ -0,0 +1,76 @@ +array_param_name = $array_param_name; + $this->offset_param_name = $offset_param_name; + $this->defining_class = $defining_class; + } + + public function getKey(bool $include_extra = true): string + { + return $this->array_param_name . '[' . $this->offset_param_name . ']'; + } + + public function __toString(): string + { + return $this->getKey(); + } + + public function getId(bool $nested = false): string + { + return $this->getKey(); + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return $this->getKey(); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateKeyOf.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateKeyOf.php new file mode 100644 index 0000000000000000000000000000000000000000..d4e672fe909b9cc8ab7ea13e338bafbe11d8a42d --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateKeyOf.php @@ -0,0 +1,81 @@ +param_name = $param_name; + $this->defining_class = $defining_class; + $this->as = $as; + } + + public function getKey(bool $include_extra = true): string + { + return 'key-of<' . $this->param_name . '>'; + } + + public function __toString(): string + { + return 'key-of<' . $this->param_name . '>'; + } + + public function getId(bool $nested = false): string + { + return 'key-of<' . $this->param_name . ':' . $this->defining_class . ' as ' . $this->as->getId() . '>'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + /** + * @return false + */ + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return 'key-of<' . $this->param_name . '>'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateParam.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateParam.php new file mode 100644 index 0000000000000000000000000000000000000000..b21dd62bd636732ac5f13cb68b7bf6aec68babad --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateParam.php @@ -0,0 +1,129 @@ +param_name = $param_name; + $this->as = $extends; + $this->defining_class = $defining_class; + } + + public function __toString(): string + { + return $this->param_name; + } + + public function getKey(bool $include_extra = true): string + { + if ($include_extra && $this->extra_types) { + return $this->param_name . ':' . $this->defining_class . '&' . implode('&', $this->extra_types); + } + + return $this->param_name . ':' . $this->defining_class; + } + + public function getAssertionString(): string + { + return $this->as->getId(); + } + + public function getId(bool $nested = false): string + { + if ($this->extra_types) { + return '(' . $this->param_name . ':' . $this->defining_class . ' as ' . $this->as->getId() + . ')&' . implode('&', array_map(function ($type) { + return $type->getId(true); + }, $this->extra_types)); + } + + return ($nested ? '(' : '') . $this->param_name + . ':' . $this->defining_class + . ' as ' . $this->as->getId() . ($nested ? ')' : ''); + } + + /** + * @param array $aliased_classes + * + * @return null + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($use_phpdoc_format) { + return $this->as->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ); + } + + $intersection_types = $this->getNamespacedIntersectionTypes( + $namespace, + $aliased_classes, + $this_class, + $use_phpdoc_format + ); + + return $this->param_name . $intersection_types; + } + + public function getChildNodes() : array + { + return [$this->as]; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + public function replaceTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + $this->replaceIntersectionTemplateTypesWithArgTypes($template_result, $codebase); + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateParamClass.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateParamClass.php new file mode 100644 index 0000000000000000000000000000000000000000..7ec7c5f0193eb925906526efc003c6bd6cdba48e --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTemplateParamClass.php @@ -0,0 +1,83 @@ +param_name = $param_name; + $this->as = $as; + $this->as_type = $as_type; + $this->defining_class = $defining_class; + } + + public function getKey(bool $include_extra = true): string + { + return 'class-string<' . $this->param_name . '>'; + } + + public function __toString(): string + { + return 'class-string<' . $this->param_name . '>'; + } + + public function getId(bool $nested = false): string + { + return 'class-string<' . $this->param_name . ':' . $this->defining_class . ' as ' . $this->as . '>'; + } + + public function getAssertionString(): string + { + return 'class-string<' . $this->param_name . '>'; + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return 'string'; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return $this->param_name . '::class'; + } + + public function getChildNodes() : array + { + return $this->as_type ? [$this->as_type] : []; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTraitString.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTraitString.php new file mode 100644 index 0000000000000000000000000000000000000000..ee93794b73d287cca6b4dbf0857a2fd1821b7add --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTraitString.php @@ -0,0 +1,51 @@ +getKey(); + } + + public function getId(bool $nested = false): string + { + return $this->getKey(); + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return 'string'; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return 'trait-string'; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTrue.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTrue.php new file mode 100644 index 0000000000000000000000000000000000000000..0e44f3af6977051b3ebbf6d346518581cb59c1f8 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TTrue.php @@ -0,0 +1,20 @@ +|null + */ + public $extra_types; + + /** @var string */ + public $declaring_fq_classlike_name; + + /** @var string */ + public $alias_name; + + public function __construct(string $declaring_fq_classlike_name, string $alias_name) + { + $this->declaring_fq_classlike_name = $declaring_fq_classlike_name; + $this->alias_name = $alias_name; + } + + public function getKey(bool $include_extra = true): string + { + return 'type-alias(' . $this->declaring_fq_classlike_name . '::' . $this->alias_name . ')'; + } + + public function __toString(): string + { + if ($this->extra_types) { + return $this->getKey() . '&' . implode( + '&', + array_map( + 'strval', + $this->extra_types + ) + ); + } + + return $this->getKey(); + } + + public function getId(bool $nested = false): string + { + if ($this->extra_types) { + return $this->getKey() . '&' . implode( + '&', + array_map( + function ($type) { + return $type->getId(true); + }, + $this->extra_types + ) + ); + } + + return $this->getKey(); + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + return $this->getKey(); + } + + public function getAssertionString(): string + { + return 'mixed'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TValueOfClassConstant.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TValueOfClassConstant.php new file mode 100644 index 0000000000000000000000000000000000000000..398a576b9a4bce36bf2524c341a4dd81ca082fac --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TValueOfClassConstant.php @@ -0,0 +1,74 @@ +fq_classlike_name = $fq_classlike_name; + $this->const_name = $const_name; + } + + public function getKey(bool $include_extra = true): string + { + return 'value-of<' . $this->fq_classlike_name . '::' . $this->const_name . '>'; + } + + public function __toString(): string + { + return 'value-of<' . $this->fq_classlike_name . '::' . $this->const_name . '>'; + } + + public function getId(bool $nested = false): string + { + return $this->getKey(); + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return false; + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + if ($this->fq_classlike_name === 'static') { + return 'value-ofconst_name . '>'; + } + + return 'value-of<' + . \Psalm\Type::getStringFromFQCLN($this->fq_classlike_name, $namespace, $aliased_classes, $this_class) + . '>::' . $this->const_name . '>'; + } + + public function getAssertionString(): string + { + return 'mixed'; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TVoid.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TVoid.php new file mode 100644 index 0000000000000000000000000000000000000000..c8f30eb0378503162f150c19c8f39ff09cb41637 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Atomic/TVoid.php @@ -0,0 +1,35 @@ + $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + return $php_major_version > 7 + || ($php_major_version === 7 && $php_minor_version >= 1) + ? $this->getKey() : null; + } + + public function canBeFullyExpressedInPhp(): bool + { + return true; + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/NodeVisitor.php b/lib/composer/vimeo/psalm/src/Psalm/Type/NodeVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..19db1cab066bc054beaf1e225114d9e86babd011 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/NodeVisitor.php @@ -0,0 +1,49 @@ +enterNode($node); + + if ($visitor_result === self::DONT_TRAVERSE_CHILDREN) { + return true; + } + + if ($visitor_result === self::STOP_TRAVERSAL) { + return false; + } + + foreach ($node->getChildNodes() as $child_node) { + if ($this->traverse($child_node) === false) { + return false; + } + } + + return true; + } + + /** + * @param array $nodes + */ + public function traverseArray(array $nodes) : void + { + foreach ($nodes as $node) { + if ($this->traverse($node) === false) { + return; + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Reconciler.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Reconciler.php new file mode 100644 index 0000000000000000000000000000000000000000..f21b965306990b2acafb706515914b8b785cd6fe --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Reconciler.php @@ -0,0 +1,998 @@ +> */ + private static $broken_paths = []; + + /** + * Takes two arrays and consolidates them, removing null values from existing types where applicable + * + * @param array $new_types + * @param array $active_new_types - types we can complain about + * @param array $existing_types + * @param array $changed_var_ids + * @param array $referenced_var_ids + * @param array> $template_type_map + * + * @return array + */ + public static function reconcileKeyedTypes( + array $new_types, + array $active_new_types, + array $existing_types, + array &$changed_var_ids, + array $referenced_var_ids, + StatementsAnalyzer $statements_analyzer, + array $template_type_map = [], + bool $inside_loop = false, + ?CodeLocation $code_location = null, + bool $negated = false + ): array { + if (!$new_types) { + return $existing_types; + } + + $suppressed_issues = $statements_analyzer->getSuppressedIssues(); + + $old_new_types = $new_types; + + foreach ($new_types as $nk => $type) { + if (strpos($nk, '[') || strpos($nk, '->')) { + if ($type[0][0] === '=isset' + || $type[0][0] === '!=empty' + || $type[0][0] === 'isset' + || $type[0][0] === '!empty' + ) { + $isset_or_empty = $type[0][0] === 'isset' || $type[0][0] === '=isset' + ? '=isset' + : '!=empty'; + + $key_parts = Reconciler::breakUpPathIntoParts($nk); + + if (!$key_parts) { + throw new \UnexpectedValueException('There should be some key parts'); + } + + $base_key = array_shift($key_parts); + + if ($base_key[0] !== '$' && count($key_parts) > 2 && $key_parts[0] === '::$') { + $base_key .= array_shift($key_parts); + $base_key .= array_shift($key_parts); + } + + if (!isset($existing_types[$base_key]) || $existing_types[$base_key]->isNullable()) { + if (!isset($new_types[$base_key])) { + $new_types[$base_key] = [['=isset']]; + } else { + $new_types[$base_key][] = ['=isset']; + } + } + + while ($key_parts) { + $divider = array_shift($key_parts); + + if ($divider === '[') { + $array_key = array_shift($key_parts); + array_shift($key_parts); + + $new_base_key = $base_key . '[' . $array_key . ']'; + + if (strpos($array_key, '\'') !== false) { + $new_types[$base_key][] = ['=string-array-access']; + } else { + $new_types[$base_key][] = ['=int-or-string-array-access']; + } + + $base_key = $new_base_key; + + continue; + } + + if ($divider === '->') { + $property_name = array_shift($key_parts); + $new_base_key = $base_key . '->' . $property_name; + + $base_key = $new_base_key; + } else { + break; + } + + if (!$key_parts) { + break; + } + + if (!isset($new_types[$base_key])) { + $new_types[$base_key] = [['!~bool'], ['!~int'], ['=isset']]; + } else { + $new_types[$base_key][] = ['!~bool']; + $new_types[$base_key][] = ['!~int']; + $new_types[$base_key][] = ['=isset']; + } + } + + // replace with a less specific check + $new_types[$nk][0][0] = $isset_or_empty; + } + + if ($type[0][0] === 'array-key-exists') { + $key_parts = Reconciler::breakUpPathIntoParts($nk); + + if (count($key_parts) === 4 && $key_parts[1] === '[') { + if (isset($new_types[$key_parts[2]])) { + $new_types[$key_parts[2]][] = ['=in-array-' . $key_parts[0]]; + } else { + $new_types[$key_parts[2]] = [['=in-array-' . $key_parts[0]]]; + } + + if ($key_parts[0][0] === '$') { + if (isset($new_types[$key_parts[0]])) { + $new_types[$key_parts[0]][] = ['=has-array-key-' . $key_parts[2]]; + } else { + $new_types[$key_parts[0]] = [['=has-array-key-' . $key_parts[2]]]; + } + } + } + } + } + } + + // make sure array keys come after base keys + ksort($new_types); + + $codebase = $statements_analyzer->getCodebase(); + + foreach ($new_types as $key => $new_type_parts) { + $has_negation = false; + $has_isset = false; + $has_inverted_isset = false; + $has_falsyish = false; + $has_empty = false; + $has_count_check = false; + $is_equality = ($old_new_types[$key] ?? null) === $new_type_parts; + + foreach ($new_type_parts as $new_type_part_parts) { + foreach ($new_type_part_parts as $new_type_part_part) { + switch ($new_type_part_part[0]) { + case '!': + $has_negation = true; + break; + } + + $has_isset = $has_isset + || $new_type_part_part === 'isset' + || $new_type_part_part === '=isset' + || $new_type_part_part === 'array-key-exists' + || $new_type_part_part === '=string-array-access'; + + $has_empty = $has_empty || $new_type_part_part === 'empty'; + + $has_falsyish = $has_falsyish + || $new_type_part_part === 'empty' + || $new_type_part_part === 'falsy'; + + $is_equality = $is_equality + && $new_type_part_part[0] === '=' + && $new_type_part_part !== '=isset'; + + $has_inverted_isset = $has_inverted_isset || $new_type_part_part === '!isset'; + + $has_count_check = $has_count_check + || $new_type_part_part === 'non-empty-countable'; + } + } + + $did_type_exist = isset($existing_types[$key]); + + $has_object_array_access = false; + + $result_type = isset($existing_types[$key]) + ? clone $existing_types[$key] + : self::getValueForKey( + $codebase, + $key, + $existing_types, + $new_types, + $code_location, + $has_isset, + $has_inverted_isset, + $has_empty, + $inside_loop, + $has_object_array_access + ); + + if ($result_type && empty($result_type->getAtomicTypes())) { + throw new \InvalidArgumentException('Union::$types cannot be empty after get value for ' . $key); + } + + $before_adjustment = $result_type ? clone $result_type : null; + + $failed_reconciliation = 0; + + foreach ($new_type_parts as $offset => $new_type_part_parts) { + $orred_type = null; + + foreach ($new_type_part_parts as $new_type_part_part) { + if ($new_type_part_part[0] === '>') { + /** @var array>> */ + $data = \json_decode(substr($new_type_part_part, 1), true); + + $existing_types = self::reconcileKeyedTypes( + $data, + $data, + $existing_types, + $changed_var_ids, + $referenced_var_ids, + $statements_analyzer, + $template_type_map, + $inside_loop, + $code_location, + $negated + ); + + $new_type_part_part = '!falsy'; + } + + $result_type_candidate = AssertionReconciler::reconcile( + $new_type_part_part, + $result_type ? clone $result_type : null, + $key, + $statements_analyzer, + $inside_loop, + $template_type_map, + $code_location + && isset($referenced_var_ids[$key]) + && isset($active_new_types[$key][$offset]) + ? $code_location + : null, + $suppressed_issues, + $failed_reconciliation, + $negated + ); + + if (!$result_type_candidate->getAtomicTypes()) { + $result_type_candidate->addType(new TEmpty); + } + + $orred_type = $orred_type + ? Type::combineUnionTypes( + $result_type_candidate, + $orred_type, + $codebase + ) + : $result_type_candidate; + } + + $result_type = $orred_type; + } + + if (!$result_type) { + throw new \UnexpectedValueException('$result_type should not be null'); + } + + if (!$did_type_exist && $result_type->isEmpty()) { + continue; + } + + if (($statements_analyzer->data_flow_graph instanceof \Psalm\Internal\Codebase\TaintFlowGraph + && $result_type->hasString()) + || $statements_analyzer->data_flow_graph instanceof \Psalm\Internal\Codebase\VariableUseGraph + ) { + if ($before_adjustment && $before_adjustment->parent_nodes) { + $result_type->parent_nodes = $before_adjustment->parent_nodes; + } elseif (!$did_type_exist && $code_location) { + $result_type->parent_nodes = $statements_analyzer->getParentNodesForPossiblyUndefinedVariable( + $key + ); + } + } + + if ($before_adjustment && $before_adjustment->by_ref) { + $result_type->by_ref = true; + } + + $type_changed = !$before_adjustment || !$result_type->equals($before_adjustment); + + if ($type_changed || $failed_reconciliation) { + $changed_var_ids[$key] = true; + + if (substr($key, -1) === ']' && !$has_inverted_isset && !$has_empty && !$is_equality) { + $key_parts = self::breakUpPathIntoParts($key); + self::adjustTKeyedArrayType( + $key_parts, + $existing_types, + $changed_var_ids, + $result_type + ); + } elseif ($key !== '$this') { + foreach ($existing_types as $new_key => $_) { + if ($new_key === $key) { + continue; + } + + if (!isset($new_types[$new_key]) + && preg_match('/' . preg_quote($key, '/') . '[\]\[\-]/', $new_key) + ) { + unset($existing_types[$new_key]); + } + } + } + } elseif (!$has_negation && !$has_falsyish && !$has_isset) { + $changed_var_ids[$key] = true; + } + + if ($failed_reconciliation === 2) { + $result_type->failed_reconciliation = true; + } + + if (!$has_object_array_access) { + $existing_types[$key] = $result_type; + } + } + + return $existing_types; + } + + /** + * @return array + */ + public static function breakUpPathIntoParts(string $path): array + { + if (isset(self::$broken_paths[$path])) { + return self::$broken_paths[$path]; + } + + $chars = str_split($path); + + $string_char = null; + $escape_char = false; + $brackets = 0; + + $parts = ['']; + $parts_offset = 0; + + for ($i = 0, $char_count = count($chars); $i < $char_count; ++$i) { + $char = $chars[$i]; + + if ($string_char) { + if ($char === $string_char && !$escape_char) { + $string_char = null; + } + + if ($char === '\\') { + $escape_char = !$escape_char; + } + + $parts[$parts_offset] .= $char; + continue; + } + + switch ($char) { + case '[': + case ']': + $parts_offset++; + $parts[$parts_offset] = $char; + ++$parts_offset; + + if ($char === '[') { + $brackets++; + } else { + $brackets--; + } + + continue 2; + + case '\'': + case '"': + if (!isset($parts[$parts_offset])) { + $parts[$parts_offset] = ''; + } + $parts[$parts_offset] .= $char; + $string_char = $char; + + continue 2; + + case ':': + if (!$brackets + && $i < $char_count - 2 + && $chars[$i + 1] === ':' + && $chars[$i + 2] === '$' + ) { + ++$i; + ++$i; + + ++$parts_offset; + $parts[$parts_offset] = '::$'; + ++$parts_offset; + continue 2; + } + // fall through + + case '-': + if (!$brackets + && $i < $char_count - 1 + && $chars[$i + 1] === '>' + ) { + ++$i; + + ++$parts_offset; + $parts[$parts_offset] = '->'; + ++$parts_offset; + continue 2; + } + // fall through + + // no break + default: + if (!isset($parts[$parts_offset])) { + $parts[$parts_offset] = ''; + } + $parts[$parts_offset] .= $char; + } + } + + $parts = \array_values($parts); + + self::$broken_paths[$path] = $parts; + + return $parts; + } + + /** + * Gets the type for a given (non-existent key) based on the passed keys + * + * @param array $existing_keys + * @param array $new_assertions + * @param string[][] $new_type_parts + * + */ + private static function getValueForKey( + Codebase $codebase, + string $key, + array &$existing_keys, + array $new_assertions, + ?CodeLocation $code_location, + bool $has_isset, + bool $has_inverted_isset, + bool $has_empty, + bool $inside_loop, + bool &$has_object_array_access + ): ?Union { + $key_parts = self::breakUpPathIntoParts($key); + + if (count($key_parts) === 1) { + return isset($existing_keys[$key_parts[0]]) ? clone $existing_keys[$key_parts[0]] : null; + } + + $base_key = array_shift($key_parts); + + if ($base_key[0] !== '$' && count($key_parts) > 2 && $key_parts[0] === '::$') { + $base_key .= array_shift($key_parts); + $base_key .= array_shift($key_parts); + } + + if (!isset($existing_keys[$base_key])) { + if (strpos($base_key, '::')) { + [$fq_class_name, $const_name] = explode('::', $base_key); + + if (!$codebase->classlikes->classOrInterfaceExists($fq_class_name)) { + return null; + } + + $class_constant = $codebase->classlikes->getClassConstantType( + $fq_class_name, + $const_name, + \ReflectionProperty::IS_PRIVATE, + null + ); + + if ($class_constant) { + $existing_keys[$base_key] = clone $class_constant; + } else { + return null; + } + } else { + return null; + } + } + + while ($key_parts) { + $divider = array_shift($key_parts); + + if ($divider === '[') { + $array_key = array_shift($key_parts); + array_shift($key_parts); + + $new_base_key = $base_key . '[' . $array_key . ']'; + + if (!isset($existing_keys[$new_base_key])) { + $new_base_type = null; + + foreach ($existing_keys[$base_key]->getAtomicTypes() as $existing_key_type_part) { + if ($existing_key_type_part instanceof Type\Atomic\TArray) { + if ($has_empty) { + return null; + } + + $new_base_type_candidate = clone $existing_key_type_part->type_params[1]; + + if ($new_base_type_candidate->isMixed() && !$has_isset && !$has_inverted_isset) { + return $new_base_type_candidate; + } + + if (($has_isset || $has_inverted_isset) && isset($new_assertions[$new_base_key])) { + if ($has_inverted_isset && $new_base_key === $key) { + $new_base_type_candidate->addType(new Type\Atomic\TNull); + } + + $new_base_type_candidate->possibly_undefined = true; + } + } elseif ($existing_key_type_part instanceof Type\Atomic\TList) { + if ($has_empty) { + return null; + } + + $new_base_type_candidate = clone $existing_key_type_part->type_param; + + if (($has_isset || $has_inverted_isset) && isset($new_assertions[$new_base_key])) { + if ($has_inverted_isset && $new_base_key === $key) { + $new_base_type_candidate->addType(new Type\Atomic\TNull); + } + + $new_base_type_candidate->possibly_undefined = true; + } + } elseif ($existing_key_type_part instanceof Type\Atomic\TNull + || $existing_key_type_part instanceof Type\Atomic\TFalse + ) { + $new_base_type_candidate = Type::getNull(); + + if ($existing_keys[$base_key]->ignore_nullable_issues) { + $new_base_type_candidate->ignore_nullable_issues = true; + } + } elseif ($existing_key_type_part instanceof Type\Atomic\TClassStringMap) { + return Type::getMixed(); + } elseif ($existing_key_type_part instanceof Type\Atomic\TEmpty + || ($existing_key_type_part instanceof Type\Atomic\TMixed + && $existing_key_type_part->from_loop_isset) + ) { + return Type::getMixed($inside_loop); + } elseif ($existing_key_type_part instanceof TString) { + $new_base_type_candidate = Type::getString(); + } elseif ($existing_key_type_part instanceof Type\Atomic\TNamedObject + && ($has_isset || $has_inverted_isset) + ) { + $has_object_array_access = true; + return null; + } elseif (!$existing_key_type_part instanceof Type\Atomic\TKeyedArray) { + return Type::getMixed(); + } elseif ($array_key[0] === '$' || ($array_key[0] !== '\'' && !\is_numeric($array_key[0]))) { + if ($has_empty) { + return null; + } + + $new_base_type_candidate = $existing_key_type_part->getGenericValueType(); + } else { + $array_properties = $existing_key_type_part->properties; + + $key_parts_key = str_replace('\'', '', $array_key); + + if (!isset($array_properties[$key_parts_key])) { + if ($existing_key_type_part->previous_value_type) { + $new_base_type_candidate = clone $existing_key_type_part->previous_value_type; + $new_base_type_candidate->different = true; + } else { + return null; + } + } else { + $new_base_type_candidate = clone $array_properties[$key_parts_key]; + } + } + + if (!$new_base_type) { + $new_base_type = $new_base_type_candidate; + } else { + $new_base_type = Type::combineUnionTypes( + $new_base_type, + $new_base_type_candidate, + $codebase + ); + } + } + + $existing_keys[$new_base_key] = $new_base_type; + } + + $base_key = $new_base_key; + } elseif ($divider === '->' || $divider === '::$') { + $property_name = array_shift($key_parts); + $new_base_key = $base_key . $divider . $property_name; + + if (!isset($existing_keys[$new_base_key])) { + $new_base_type = null; + + foreach ($existing_keys[$base_key]->getAtomicTypes() as $existing_key_type_part) { + if ($existing_key_type_part instanceof TNull) { + $class_property_type = Type::getNull(); + } elseif ($existing_key_type_part instanceof TMixed + || $existing_key_type_part instanceof TTemplateParam + || $existing_key_type_part instanceof TObject + || ($existing_key_type_part instanceof TNamedObject + && strtolower($existing_key_type_part->value) === 'stdclass') + ) { + $class_property_type = Type::getMixed(); + } elseif ($existing_key_type_part instanceof TNamedObject) { + if (!$codebase->classOrInterfaceExists($existing_key_type_part->value)) { + $class_property_type = Type::getMixed(); + } else { + if (substr($property_name, -2) === '()') { + $method_id = new \Psalm\Internal\MethodIdentifier( + $existing_key_type_part->value, + strtolower(substr($property_name, 0, -2)) + ); + + if (!$codebase->methods->methodExists($method_id)) { + return null; + } + + $declaring_method_id = $codebase->methods->getDeclaringMethodId( + $method_id + ); + + if ($declaring_method_id === null) { + return null; + } + + $declaring_class = $declaring_method_id->fq_class_name; + + $method_return_type = $codebase->methods->getMethodReturnType( + $method_id, + $declaring_class, + null, + null + ); + + if ($method_return_type) { + $class_property_type = \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + clone $method_return_type, + $declaring_class, + $declaring_class, + null + ); + } else { + $class_property_type = Type::getMixed(); + } + } else { + $class_property_type = self::getPropertyType( + $codebase, + $existing_key_type_part->value, + $property_name + ); + + if (!$class_property_type) { + return null; + } + } + } + } else { + $class_property_type = Type::getMixed(); + } + + if ($new_base_type instanceof Type\Union) { + $new_base_type = Type::combineUnionTypes( + $new_base_type, + $class_property_type, + $codebase + ); + } else { + $new_base_type = $class_property_type; + } + + $existing_keys[$new_base_key] = $new_base_type; + } + } + + $base_key = $new_base_key; + } else { + return null; + } + } + + if (!isset($existing_keys[$base_key])) { + if ($code_location) { + IssueBuffer::add( + new PsalmInternalError( + 'Unknown key ' . $base_key, + $code_location + ) + ); + } + + return null; + } + + return $existing_keys[$base_key]; + } + + private static function getPropertyType( + Codebase $codebase, + string $fq_class_name, + string $property_name + ) : ?Type\Union { + $property_id = $fq_class_name . '::$' . $property_name; + + if (!$codebase->properties->propertyExists($property_id, true)) { + $declaring_class_storage = $codebase->classlike_storage_provider->get( + $fq_class_name + ); + + if (isset($declaring_class_storage->pseudo_property_get_types['$' . $property_name])) { + return clone $declaring_class_storage->pseudo_property_get_types['$' . $property_name]; + } + + return null; + } + + $declaring_property_class = $codebase->properties->getDeclaringClassForProperty( + $property_id, + true + ); + + if ($declaring_property_class === null) { + return null; + } + + $class_property_type = $codebase->properties->getPropertyType( + $property_id, + false, + null, + null + ); + + $declaring_class_storage = $codebase->classlike_storage_provider->get( + $declaring_property_class + ); + + if ($class_property_type) { + return \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + clone $class_property_type, + $declaring_class_storage->name, + $declaring_class_storage->name, + null + ); + } + + return Type::getMixed(); + } + + /** + * @param string[] $suppressed_issues + * + */ + protected static function triggerIssueForImpossible( + Union $existing_var_type, + string $old_var_type_string, + string $key, + string $assertion, + bool $redundant, + bool $negated, + CodeLocation $code_location, + array $suppressed_issues + ): void { + $not = $assertion[0] === '!'; + + if ($not) { + $assertion = substr($assertion, 1); + } + + if ($negated) { + $redundant = !$redundant; + $not = !$not; + } + + $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); + + $from_docblock = $existing_var_type->from_docblock + || (isset($existing_var_atomic_types[$assertion]) + && $existing_var_atomic_types[$assertion]->from_docblock); + + if ($redundant) { + if ($from_docblock) { + if (IssueBuffer::accepts( + new RedundantConditionGivenDocblockType( + 'Docblock-defined type ' . $old_var_type_string + . ' for ' . $key + . ' is ' . ($not ? 'never ' : 'always ') . $assertion, + $code_location, + $old_var_type_string . ' ' . $assertion + ), + $suppressed_issues + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new RedundantCondition( + 'Type ' . $old_var_type_string + . ' for ' . $key + . ' is ' . ($not ? 'never ' : 'always ') . $assertion, + $code_location, + $old_var_type_string . ' ' . $assertion + ), + $suppressed_issues + )) { + // fall through + } + } + } else { + if ($from_docblock) { + if (IssueBuffer::accepts( + new DocblockTypeContradiction( + 'Docblock-defined type ' . $old_var_type_string + . ' for ' . $key + . ' is ' . ($not ? 'always ' : 'never ') . $assertion, + $code_location, + $old_var_type_string . ' ' . $assertion + ), + $suppressed_issues + )) { + // fall through + } + } else { + if ($assertion === 'null' && !$not) { + $issue = new TypeDoesNotContainNull( + 'Type ' . $old_var_type_string + . ' for ' . $key + . ' is never ' . $assertion, + $code_location, + $old_var_type_string . ' ' . $assertion + ); + } else { + $issue = new TypeDoesNotContainType( + 'Type ' . $old_var_type_string + . ' for ' . $key + . ' is ' . ($not ? 'always ' : 'never ') . $assertion, + $code_location, + $old_var_type_string . ' ' . $assertion + ); + } + + if (IssueBuffer::accepts( + $issue, + $suppressed_issues + )) { + // fall through + } + } + } + } + + /** + * @param string[] $key_parts + * @param array $existing_types + * @param array $changed_var_ids + */ + private static function adjustTKeyedArrayType( + array $key_parts, + array &$existing_types, + array &$changed_var_ids, + Type\Union $result_type + ): void { + array_pop($key_parts); + $array_key = array_pop($key_parts); + array_pop($key_parts); + + if ($array_key === null) { + throw new \UnexpectedValueException('Not expecting null array key'); + } + + if ($array_key[0] === '$') { + return; + } + + $array_key_offset = $array_key[0] === '\'' || $array_key[0] === '"' ? substr($array_key, 1, -1) : $array_key; + + $base_key = implode($key_parts); + + if (isset($existing_types[$base_key]) && $array_key_offset !== false) { + foreach ($existing_types[$base_key]->getAtomicTypes() as $base_atomic_type) { + if ($base_atomic_type instanceof Type\Atomic\TKeyedArray + || ($base_atomic_type instanceof Type\Atomic\TArray + && !$base_atomic_type->type_params[1]->isEmpty()) + || $base_atomic_type instanceof Type\Atomic\TList + || $base_atomic_type instanceof Type\Atomic\TClassStringMap + ) { + $new_base_type = clone $existing_types[$base_key]; + + if ($base_atomic_type instanceof Type\Atomic\TArray) { + $previous_key_type = clone $base_atomic_type->type_params[0]; + $previous_value_type = clone $base_atomic_type->type_params[1]; + + $base_atomic_type = new Type\Atomic\TKeyedArray( + [ + $array_key_offset => clone $result_type, + ], + null + ); + + if (!$previous_key_type->isEmpty()) { + $base_atomic_type->previous_key_type = $previous_key_type; + } + $base_atomic_type->previous_value_type = $previous_value_type; + } elseif ($base_atomic_type instanceof Type\Atomic\TList) { + $previous_key_type = Type::getInt(); + $previous_value_type = clone $base_atomic_type->type_param; + + $base_atomic_type = new Type\Atomic\TKeyedArray( + [ + $array_key_offset => clone $result_type, + ], + null + ); + + $base_atomic_type->is_list = true; + + $base_atomic_type->previous_key_type = $previous_key_type; + $base_atomic_type->previous_value_type = $previous_value_type; + } elseif ($base_atomic_type instanceof Type\Atomic\TClassStringMap) { + // do nothing + } else { + $base_atomic_type = clone $base_atomic_type; + $base_atomic_type->properties[$array_key_offset] = clone $result_type; + } + + $new_base_type->addType($base_atomic_type); + + $changed_var_ids[$base_key . '[' . $array_key . ']'] = true; + + if ($key_parts[count($key_parts) - 1] === ']') { + self::adjustTKeyedArrayType( + $key_parts, + $existing_types, + $changed_var_ids, + $new_base_type + ); + } + + $existing_types[$base_key] = $new_base_type; + break; + } + } + } + } +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/TaintKind.php b/lib/composer/vimeo/psalm/src/Psalm/Type/TaintKind.php new file mode 100644 index 0000000000000000000000000000000000000000..7257b63ba30c8aade4fe3900b4589694a3c62da0 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/TaintKind.php @@ -0,0 +1,16 @@ + + */ + public function getChildNodes() : array; +} diff --git a/lib/composer/vimeo/psalm/src/Psalm/Type/Union.php b/lib/composer/vimeo/psalm/src/Psalm/Type/Union.php new file mode 100644 index 0000000000000000000000000000000000000000..24ff822f0ca7e19315c3f39a8156eb9060a599ab --- /dev/null +++ b/lib/composer/vimeo/psalm/src/Psalm/Type/Union.php @@ -0,0 +1,1679 @@ + + */ + private $types; + + /** + * Whether the type originated in a docblock + * + * @var bool + */ + public $from_docblock = false; + + /** + * Whether the type originated from integer calculation + * + * @var bool + */ + public $from_calculation = false; + + /** + * Whether the property that this type has been derived from has been initialized in a constructor + * + * @var bool + */ + public $initialized = true; + + /** + * Which class the type was initialised in + * + * @var ?string + */ + public $initialized_class = null; + + /** + * Whether or not the type has been checked yet + * + * @var bool + */ + public $checked = false; + + /** + * @var bool + */ + public $failed_reconciliation = false; + + /** + * Whether or not to ignore issues with possibly-null values + * + * @var bool + */ + public $ignore_nullable_issues = false; + + /** + * Whether or not to ignore issues with possibly-false values + * + * @var bool + */ + public $ignore_falsable_issues = false; + + /** + * Whether or not this variable is possibly undefined + * + * @var bool + */ + public $possibly_undefined = false; + + /** + * Whether or not this variable is possibly undefined + * + * @var bool + */ + public $possibly_undefined_from_try = false; + + /** + * Whether or not this union had a template, since replaced + * + * @var bool + */ + public $had_template = false; + + /** + * Whether or not this union comes from a template "as" default + * + * @var bool + */ + public $from_template_default = false; + + /** + * @var array + */ + private $literal_string_types = []; + + /** + * @var array + */ + private $typed_class_strings = []; + + /** + * @var array + */ + private $literal_int_types = []; + + /** + * @var array + */ + private $literal_float_types = []; + + /** + * Whether or not the type was passed by reference + * + * @var bool + */ + public $by_ref = false; + + /** + * @var bool + */ + public $reference_free = false; + + /** + * @var bool + */ + public $allow_mutations = true; + + /** @var null|string */ + private $id; + + /** + * @var array + */ + public $parent_nodes = []; + + /** + * @var bool + */ + public $different = false; + + /** + * Constructs an Union instance + * + * @param non-empty-array $types + */ + public function __construct(array $types) + { + $from_docblock = false; + + $keyed_types = []; + + foreach ($types as $type) { + $key = $type->getKey(); + $keyed_types[$key] = $type; + + if ($type instanceof TLiteralInt) { + $this->literal_int_types[$key] = $type; + } elseif ($type instanceof TLiteralString) { + $this->literal_string_types[$key] = $type; + } elseif ($type instanceof TLiteralFloat) { + $this->literal_float_types[$key] = $type; + } elseif ($type instanceof Type\Atomic\TClassString + && ($type->as_type || $type instanceof Type\Atomic\TTemplateParamClass) + ) { + $this->typed_class_strings[$key] = $type; + } + + $from_docblock = $from_docblock || $type->from_docblock; + } + + $this->types = $keyed_types; + + $this->from_docblock = $from_docblock; + } + + /** + * @return array + * @deprecated in favour of getAtomicTypes() + * @psalm-suppress PossiblyUnusedMethod + */ + public function getTypes(): array + { + return $this->types; + } + + /** + * @return non-empty-array + */ + public function getAtomicTypes(): array + { + return $this->types; + } + + public function addType(Atomic $type): void + { + $this->types[$type->getKey()] = $type; + + if ($type instanceof TLiteralString) { + $this->literal_string_types[$type->getKey()] = $type; + } elseif ($type instanceof TLiteralInt) { + $this->literal_int_types[$type->getKey()] = $type; + } elseif ($type instanceof TLiteralFloat) { + $this->literal_float_types[$type->getKey()] = $type; + } elseif ($type instanceof TString && $this->literal_string_types) { + foreach ($this->literal_string_types as $key => $_) { + unset($this->literal_string_types[$key], $this->types[$key]); + } + if (!$type instanceof Type\Atomic\TClassString + || (!$type->as_type && !$type instanceof Type\Atomic\TTemplateParamClass) + ) { + foreach ($this->typed_class_strings as $key => $_) { + unset($this->typed_class_strings[$key], $this->types[$key]); + } + } + } elseif ($type instanceof TInt && $this->literal_int_types) { + foreach ($this->literal_int_types as $key => $_) { + unset($this->literal_int_types[$key], $this->types[$key]); + } + } elseif ($type instanceof TFloat && $this->literal_float_types) { + foreach ($this->literal_float_types as $key => $_) { + unset($this->literal_float_types[$key], $this->types[$key]); + } + } + + $this->id = null; + } + + public function __clone() + { + $this->literal_string_types = []; + $this->literal_int_types = []; + $this->literal_float_types = []; + $this->typed_class_strings = []; + + foreach ($this->types as $key => &$type) { + $type = clone $type; + + if ($type instanceof TLiteralInt) { + $this->literal_int_types[$key] = $type; + } elseif ($type instanceof TLiteralString) { + $this->literal_string_types[$key] = $type; + } elseif ($type instanceof TLiteralFloat) { + $this->literal_float_types[$key] = $type; + } elseif ($type instanceof Type\Atomic\TClassString + && ($type->as_type || $type instanceof Type\Atomic\TTemplateParamClass) + ) { + $this->typed_class_strings[$key] = $type; + } + } + } + + public function __toString(): string + { + $types = []; + + $printed_int = false; + $printed_float = false; + $printed_string = false; + + foreach ($this->types as $type) { + if ($type instanceof TLiteralFloat) { + if ($printed_float) { + continue; + } + + $printed_float = true; + } elseif ($type instanceof TLiteralString) { + if ($printed_string) { + continue; + } + + $printed_string = true; + } elseif ($type instanceof TLiteralInt) { + if ($printed_int) { + continue; + } + + $printed_int = true; + } + + $types[] = strval($type); + } + + sort($types); + return implode('|', $types); + } + + public function getKey() : string + { + $types = []; + + $printed_int = false; + $printed_float = false; + $printed_string = false; + + foreach ($this->types as $type) { + if ($type instanceof TLiteralFloat) { + if ($printed_float) { + continue; + } + + $types[] = 'float'; + $printed_float = true; + } elseif ($type instanceof TLiteralString) { + if ($printed_string) { + continue; + } + + $types[] = 'string'; + $printed_string = true; + } elseif ($type instanceof TLiteralInt) { + if ($printed_int) { + continue; + } + + $types[] = 'int'; + $printed_int = true; + } else { + $types[] = strval($type->getKey()); + } + } + + sort($types); + return implode('|', $types); + } + + public function getId(): string + { + if ($this->id) { + return $this->id; + } + + $types = []; + foreach ($this->types as $type) { + $types[] = strval($type->getId()); + } + sort($types); + + if (\count($types) > 1) { + foreach ($types as $i => $type) { + if (strpos($type, ' as ') && strpos($type, '(') === false) { + $types[$i] = '(' . $type . ')'; + } + } + } + + $id = implode('|', $types); + + $this->id = $id; + + return $id; + } + + public function getAssertionString(): string + { + foreach ($this->types as $type) { + return $type->getAssertionString(); + } + + throw new \UnexpectedValueException('Should only be one type per assertion'); + } + + /** + * @param array $aliased_classes + * + */ + public function toNamespacedString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + bool $use_phpdoc_format + ): string { + $types = []; + + $multi_ints = count($this->literal_int_types) > 1 + || $this->hasPositiveInt(); + $multi_strings = count($this->literal_string_types) > 1; + $multi_floats = count($this->literal_float_types) > 1; + + foreach ($this->types as $type) { + $type_string = $type->toNamespacedString($namespace, $aliased_classes, $this_class, $use_phpdoc_format); + + if ($type instanceof TLiteralInt && !$multi_ints) { + $type_string = 'int'; + } elseif ($type instanceof TLiteralFloat && !$multi_floats) { + $type_string = 'float'; + } elseif ($type instanceof TLiteralString && !$multi_strings) { + $type_string = 'string'; + } + + $types[] = $type_string; + } + + sort($types); + return implode('|', \array_unique($types)); + } + + /** + * @param array $aliased_classes + */ + public function toPhpString( + ?string $namespace, + array $aliased_classes, + ?string $this_class, + int $php_major_version, + int $php_minor_version + ): ?string { + $nullable = false; + + if (!$this->isSingleAndMaybeNullable() + || $php_major_version < 7 + || (isset($this->types['null']) && $php_major_version === 7 && $php_minor_version < 1) + ) { + return null; + } + + $types = $this->types; + + if (isset($types['null'])) { + if (count($types) === 1) { + return null; + } + + unset($types['null']); + + $nullable = true; + } + + $atomic_type = array_values($types)[0]; + + $atomic_type_string = $atomic_type->toPhpString( + $namespace, + $aliased_classes, + $this_class, + $php_major_version, + $php_minor_version + ); + + if ($atomic_type_string) { + return ($nullable ? '?' : '') . $atomic_type_string; + } + + return null; + } + + public function canBeFullyExpressedInPhp(): bool + { + if (!$this->isSingleAndMaybeNullable()) { + return false; + } + + $types = $this->types; + + if (isset($types['null'])) { + if (count($types) > 1) { + unset($types['null']); + } else { + return false; + } + } + + $atomic_type = array_values($types)[0]; + + return $atomic_type->canBeFullyExpressedInPhp(); + } + + public function removeType(string $type_string): bool + { + if (isset($this->types[$type_string])) { + unset($this->types[$type_string]); + + if (strpos($type_string, '(')) { + unset( + $this->literal_string_types[$type_string], + $this->literal_int_types[$type_string], + $this->literal_float_types[$type_string] + ); + } + + $this->id = null; + + return true; + } + + if ($type_string === 'string') { + if ($this->literal_string_types) { + foreach ($this->literal_string_types as $literal_key => $_) { + unset($this->types[$literal_key]); + } + $this->literal_string_types = []; + } + + if ($this->typed_class_strings) { + foreach ($this->typed_class_strings as $typed_class_key => $_) { + unset($this->types[$typed_class_key]); + } + $this->typed_class_strings = []; + } + + unset($this->types['class-string'], $this->types['trait-string']); + } elseif ($type_string === 'int' && $this->literal_int_types) { + foreach ($this->literal_int_types as $literal_key => $_) { + unset($this->types[$literal_key]); + } + $this->literal_int_types = []; + } elseif ($type_string === 'float' && $this->literal_float_types) { + foreach ($this->literal_float_types as $literal_key => $_) { + unset($this->types[$literal_key]); + } + $this->literal_float_types = []; + } + + return false; + } + + public function bustCache(): void + { + $this->id = null; + } + + public function hasType(string $type_string): bool + { + return isset($this->types[$type_string]); + } + + public function hasArray(): bool + { + return isset($this->types['array']); + } + + public function hasList(): bool + { + return isset($this->types['array']) && $this->types['array'] instanceof Atomic\TList; + } + + public function hasClassStringMap(): bool + { + return isset($this->types['array']) && $this->types['array'] instanceof Atomic\TClassStringMap; + } + + public function isTemplatedClassString() : bool + { + return $this->isSingle() + && count( + array_filter( + $this->types, + function ($type): bool { + return $type instanceof Atomic\TTemplateParamClass; + } + ) + ) === 1; + } + + public function hasEmptyArray(): bool + { + return isset($this->types['array']) + && $this->types['array'] instanceof Atomic\TArray + && $this->types['array']->type_params[1]->isEmpty(); + } + + public function hasArrayAccessInterface(Codebase $codebase) : bool + { + return !!array_filter( + $this->types, + function ($type) use ($codebase) { + return $type->hasArrayAccessInterface($codebase); + } + ); + } + + public function hasCallableType(): bool + { + return $this->getCallableTypes() || $this->getClosureTypes(); + } + + /** + * @return array + */ + private function getCallableTypes(): array + { + return array_filter( + $this->types, + function ($type): bool { + return $type instanceof Atomic\TCallable; + } + ); + } + + /** + * @return array + */ + public function getClosureTypes(): array + { + return array_filter( + $this->types, + function ($type): bool { + return $type instanceof Atomic\TClosure; + } + ); + } + + public function hasObject(): bool + { + return isset($this->types['object']); + } + + public function hasObjectType(): bool + { + foreach ($this->types as $type) { + if ($type->isObjectType()) { + return true; + } + } + + return false; + } + + public function isObjectType(): bool + { + foreach ($this->types as $type) { + if (!$type->isObjectType()) { + return false; + } + } + + return true; + } + + public function hasNamedObjectType(): bool + { + foreach ($this->types as $type) { + if ($type->isNamedObjectType()) { + return true; + } + } + + return false; + } + + public function isFormerStaticObject(): bool + { + foreach ($this->types as $type) { + if (!$type instanceof TNamedObject + || !$type->was_static + ) { + return false; + } + } + + return true; + } + + public function hasFormerStaticObject(): bool + { + foreach ($this->types as $type) { + if ($type instanceof TNamedObject + && $type->was_static + ) { + return true; + } + } + + return false; + } + + public function isNullable(): bool + { + if (isset($this->types['null'])) { + return true; + } + + foreach ($this->types as $type) { + if ($type instanceof TTemplateParam && $type->as->isNullable()) { + return true; + } + } + + return false; + } + + public function isFalsable(): bool + { + if (isset($this->types['false'])) { + return true; + } + + foreach ($this->types as $type) { + if ($type instanceof TTemplateParam && $type->as->isFalsable()) { + return true; + } + } + + return false; + } + + public function hasBool(): bool + { + return isset($this->types['bool']) || isset($this->types['false']) || isset($this->types['true']); + } + + public function hasString(): bool + { + return isset($this->types['string']) + || isset($this->types['class-string']) + || isset($this->types['trait-string']) + || isset($this->types['numeric-string']) + || $this->literal_string_types + || $this->typed_class_strings; + } + + public function hasLowercaseString(): bool + { + return isset($this->types['string']) + && ($this->types['string'] instanceof Atomic\TLowercaseString + || $this->types['string'] instanceof Atomic\TNonEmptyLowercaseString); + } + + public function hasLiteralClassString(): bool + { + return count($this->typed_class_strings) > 0; + } + + public function hasInt(): bool + { + return isset($this->types['int']) || isset($this->types['array-key']) || $this->literal_int_types; + } + + public function hasPositiveInt(): bool + { + return isset($this->types['int']) && $this->types['int'] instanceof Type\Atomic\TPositiveInt; + } + + public function hasArrayKey(): bool + { + return isset($this->types['array-key']); + } + + public function hasFloat(): bool + { + return isset($this->types['float']) || $this->literal_float_types; + } + + public function hasDefinitelyNumericType(bool $include_literal_int = true): bool + { + return isset($this->types['int']) + || isset($this->types['float']) + || isset($this->types['numeric-string']) + || ($include_literal_int && $this->literal_int_types) + || $this->literal_float_types; + } + + public function hasPossiblyNumericType(): bool + { + return isset($this->types['int']) + || isset($this->types['float']) + || isset($this->types['string']) + || isset($this->types['numeric-string']) + || $this->literal_int_types + || $this->literal_float_types + || $this->literal_string_types; + } + + public function hasScalar(): bool + { + return isset($this->types['scalar']); + } + + public function hasNumeric(): bool + { + return isset($this->types['numeric']); + } + + public function hasScalarType(): bool + { + return isset($this->types['int']) + || isset($this->types['float']) + || isset($this->types['string']) + || isset($this->types['class-string']) + || isset($this->types['trait-string']) + || isset($this->types['bool']) + || isset($this->types['false']) + || isset($this->types['true']) + || isset($this->types['numeric']) + || isset($this->types['numeric-string']) + || $this->literal_int_types + || $this->literal_float_types + || $this->literal_string_types + || $this->typed_class_strings; + } + + public function hasTemplate(): bool + { + return (bool) array_filter( + $this->types, + function (Atomic $type) : bool { + return $type instanceof Type\Atomic\TTemplateParam + || ($type instanceof Type\Atomic\TNamedObject + && $type->extra_types + && array_filter( + $type->extra_types, + function ($t): bool { + return $t instanceof Type\Atomic\TTemplateParam; + } + ) + ); + } + ); + } + + public function hasConditional(): bool + { + return (bool) array_filter( + $this->types, + function (Atomic $type) : bool { + return $type instanceof Type\Atomic\TConditional; + } + ); + } + + public function hasTemplateOrStatic(): bool + { + return (bool) array_filter( + $this->types, + function (Atomic $type) : bool { + return $type instanceof Type\Atomic\TTemplateParam + || ($type instanceof Type\Atomic\TNamedObject + && ($type->was_static + || ($type->extra_types + && array_filter( + $type->extra_types, + function ($t): bool { + return $t instanceof Type\Atomic\TTemplateParam; + } + ) + ) + ) + ); + } + ); + } + + public function hasMixed(): bool + { + return isset($this->types['mixed']); + } + + public function isMixed(): bool + { + return isset($this->types['mixed']) && count($this->types) === 1; + } + + public function isEmptyMixed(): bool + { + return isset($this->types['mixed']) + && $this->types['mixed'] instanceof Type\Atomic\TEmptyMixed; + } + + public function isVanillaMixed(): bool + { + /** + * @psalm-suppress UndefinedPropertyFetch + */ + return isset($this->types['mixed']) + && !$this->types['mixed']->from_loop_isset + && get_class($this->types['mixed']) === Type\Atomic\TMixed::class + && !$this->types['mixed']->from_loop_isset + && count($this->types) === 1; + } + + public function isArrayKey(): bool + { + return isset($this->types['array-key']) && count($this->types) === 1; + } + + public function isNull(): bool + { + return count($this->types) === 1 && isset($this->types['null']); + } + + public function isFalse(): bool + { + return count($this->types) === 1 && isset($this->types['false']); + } + + public function isTrue(): bool + { + return count($this->types) === 1 && isset($this->types['true']); + } + + public function isVoid(): bool + { + return isset($this->types['void']); + } + + public function isNever(): bool + { + return isset($this->types['never-return']); + } + + public function isGenerator(): bool + { + return count($this->types) === 1 + && (($single_type = reset($this->types)) instanceof TNamedObject) + && ($single_type->value === 'Generator'); + } + + public function isEmpty(): bool + { + return isset($this->types['empty']); + } + + public function substitute(Union $old_type, ?Union $new_type = null): void + { + if ($this->hasMixed() && !$this->isEmptyMixed()) { + return; + } + + if ($new_type && $new_type->ignore_nullable_issues) { + $this->ignore_nullable_issues = true; + } + + if ($new_type && $new_type->ignore_falsable_issues) { + $this->ignore_falsable_issues = true; + } + + foreach ($old_type->types as $old_type_part) { + if (!$this->removeType($old_type_part->getKey())) { + if ($old_type_part instanceof Type\Atomic\TFalse + && isset($this->types['bool']) + && !isset($this->types['true']) + ) { + $this->removeType('bool'); + $this->types['true'] = new Type\Atomic\TTrue; + } elseif ($old_type_part instanceof Type\Atomic\TTrue + && isset($this->types['bool']) + && !isset($this->types['false']) + ) { + $this->removeType('bool'); + $this->types['false'] = new Type\Atomic\TFalse; + } elseif (isset($this->types['iterable'])) { + if ($old_type_part instanceof Type\Atomic\TNamedObject + && $old_type_part->value === 'Traversable' + && !isset($this->types['array']) + ) { + $this->removeType('iterable'); + $this->types['array'] = new Type\Atomic\TArray([Type::getArrayKey(), Type::getMixed()]); + } + + if ($old_type_part instanceof Type\Atomic\TArray + && !isset($this->types['traversable']) + ) { + $this->removeType('iterable'); + $this->types['traversable'] = new Type\Atomic\TNamedObject('Traversable'); + } + } elseif (isset($this->types['array-key'])) { + if ($old_type_part instanceof Type\Atomic\TString + && !isset($this->types['int']) + ) { + $this->removeType('array-key'); + $this->types['int'] = new Type\Atomic\TInt(); + } + + if ($old_type_part instanceof Type\Atomic\TInt + && !isset($this->types['string']) + ) { + $this->removeType('array-key'); + $this->types['string'] = new Type\Atomic\TString(); + } + } + } + } + + if ($new_type) { + foreach ($new_type->types as $key => $new_type_part) { + if (!isset($this->types[$key]) + || ($new_type_part instanceof Type\Atomic\Scalar + && get_class($new_type_part) === get_class($this->types[$key])) + ) { + $this->types[$key] = $new_type_part; + } else { + $combined = TypeCombination::combineTypes([$new_type_part, $this->types[$key]]); + $this->types[$key] = array_values($combined->types)[0]; + } + } + } elseif (count($this->types) === 0) { + $this->types['mixed'] = new Atomic\TMixed(); + } + + $this->id = null; + } + + public function replaceTemplateTypesWithArgTypes( + TemplateResult $template_result, + ?Codebase $codebase + ) : void { + $keys_to_unset = []; + + $new_types = []; + + $is_mixed = false; + + $found_generic_params = $template_result->upper_bounds ?: []; + + foreach ($this->types as $key => $atomic_type) { + $atomic_type->replaceTemplateTypesWithArgTypes($template_result, $codebase); + + if ($atomic_type instanceof Type\Atomic\TTemplateParam) { + $template_type = null; + + $traversed_type = \Psalm\Internal\Type\UnionTemplateHandler::getRootTemplateType( + $found_generic_params, + $atomic_type->param_name, + $atomic_type->defining_class + ); + + if ($traversed_type) { + $template_type = $traversed_type[0]; + + if (!$atomic_type->as->isMixed() && $template_type->isMixed()) { + $template_type = clone $atomic_type->as; + } else { + $template_type = clone $template_type; + } + + if ($atomic_type->extra_types) { + foreach ($template_type->getAtomicTypes() as $template_type_key => $atomic_template_type) { + if ($atomic_template_type instanceof TNamedObject + || $atomic_template_type instanceof TTemplateParam + || $atomic_template_type instanceof TIterable + || $atomic_template_type instanceof Type\Atomic\TObjectWithProperties + ) { + $atomic_template_type->extra_types = array_merge( + $atomic_type->extra_types, + $atomic_template_type->extra_types ?: [] + ); + } elseif ($atomic_template_type instanceof Type\Atomic\TObject) { + $first_atomic_type = array_shift($atomic_type->extra_types); + + if ($atomic_type->extra_types) { + $first_atomic_type->extra_types = $atomic_type->extra_types; + } + + $template_type->removeType($template_type_key); + $template_type->addType($first_atomic_type); + } + } + } + } elseif ($codebase) { + foreach ($found_generic_params as $template_type_map) { + foreach ($template_type_map as $template_class => $_) { + if (substr($template_class, 0, 3) === 'fn-') { + continue; + } + + try { + $classlike_storage = $codebase->classlike_storage_provider->get($template_class); + + if ($classlike_storage->template_type_extends) { + $defining_class = $atomic_type->defining_class; + + if (isset($classlike_storage->template_type_extends[$defining_class])) { + $param_map = $classlike_storage->template_type_extends[$defining_class]; + + if (isset($param_map[$key]) + && isset($found_generic_params[(string) $param_map[$key]][$template_class]) + ) { + $template_type + = clone $found_generic_params + [(string) $param_map[$key]][$template_class][0]; + } + } + } + } catch (\InvalidArgumentException $e) { + } + } + } + } + + if ($template_type) { + $keys_to_unset[] = $key; + + foreach ($template_type->types as $template_type_part) { + if ($template_type_part instanceof Type\Atomic\TMixed) { + $is_mixed = true; + } + + $new_types[$template_type_part->getKey()] = $template_type_part; + } + } + } elseif ($atomic_type instanceof Type\Atomic\TTemplateParamClass) { + $template_type = isset($found_generic_params[$atomic_type->param_name][$atomic_type->defining_class]) + ? clone $found_generic_params[$atomic_type->param_name][$atomic_type->defining_class][0] + : null; + + $class_template_type = null; + + if ($template_type) { + foreach ($template_type->types as $template_type_part) { + if ($template_type_part instanceof Type\Atomic\TMixed + || $template_type_part instanceof Type\Atomic\TObject + ) { + $class_template_type = new Type\Atomic\TClassString(); + } elseif ($template_type_part instanceof Type\Atomic\TNamedObject) { + $class_template_type = new Type\Atomic\TClassString( + $template_type_part->value, + $template_type_part + ); + } elseif ($template_type_part instanceof Type\Atomic\TTemplateParam) { + $first_atomic_type = array_values($template_type_part->as->types)[0]; + + $class_template_type = new Type\Atomic\TTemplateParamClass( + $template_type_part->param_name, + $template_type_part->as->getId(), + $first_atomic_type instanceof TNamedObject ? $first_atomic_type : null, + $template_type_part->defining_class + ); + } + } + } + + if ($class_template_type) { + $keys_to_unset[] = $key; + $new_types[$class_template_type->getKey()] = $class_template_type; + } + } elseif ($atomic_type instanceof Type\Atomic\TTemplateIndexedAccess) { + $keys_to_unset[] = $key; + + $template_type = null; + + if (isset($found_generic_params[$atomic_type->array_param_name][$atomic_type->defining_class]) + && !empty($found_generic_params[$atomic_type->offset_param_name]) + ) { + $array_template_type + = $found_generic_params[$atomic_type->array_param_name][$atomic_type->defining_class][0]; + $offset_template_type + = array_values( + $found_generic_params[$atomic_type->offset_param_name] + )[0][0]; + + if ($array_template_type->isSingle() + && $offset_template_type->isSingle() + && !$array_template_type->isMixed() + && !$offset_template_type->isMixed() + ) { + $array_template_type = array_values($array_template_type->types)[0]; + $offset_template_type = array_values($offset_template_type->types)[0]; + + if ($array_template_type instanceof Type\Atomic\TKeyedArray + && ($offset_template_type instanceof Type\Atomic\TLiteralString + || $offset_template_type instanceof Type\Atomic\TLiteralInt) + && isset($array_template_type->properties[$offset_template_type->value]) + ) { + $template_type = clone $array_template_type->properties[$offset_template_type->value]; + } + } + } + + if ($template_type) { + foreach ($template_type->types as $template_type_part) { + if ($template_type_part instanceof Type\Atomic\TMixed) { + $is_mixed = true; + } + + $new_types[$template_type_part->getKey()] = $template_type_part; + } + } else { + $new_types[$key] = new Type\Atomic\TMixed(); + } + } elseif ($atomic_type instanceof Type\Atomic\TConditional + && $codebase + ) { + $template_type = isset($found_generic_params[$atomic_type->param_name][$atomic_type->defining_class]) + ? clone $found_generic_params[$atomic_type->param_name][$atomic_type->defining_class][0] + : null; + + $class_template_type = null; + + $atomic_type = clone $atomic_type; + + if ($template_type) { + $atomic_type->as_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + + if ($atomic_type->as_type->isNullable() && $template_type->isVoid()) { + $template_type = Type::getNull(); + } + + if (UnionTypeComparator::isContainedBy( + $codebase, + $template_type, + $atomic_type->conditional_type + )) { + $class_template_type = clone $atomic_type->if_type; + $class_template_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + } elseif (UnionTypeComparator::isContainedBy( + $codebase, + $template_type, + $atomic_type->as_type + ) + && !UnionTypeComparator::isContainedBy( + $codebase, + $atomic_type->as_type, + $template_type + ) + ) { + $class_template_type = clone $atomic_type->else_type; + $class_template_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + } + } + + if (!$class_template_type) { + $atomic_type->if_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + + $atomic_type->else_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + + $class_template_type = Type::combineUnionTypes( + $atomic_type->if_type, + $atomic_type->else_type, + $codebase + ); + } + + $keys_to_unset[] = $key; + + foreach ($class_template_type->getAtomicTypes() as $class_template_atomic_type) { + $new_types[$class_template_atomic_type->getKey()] = $class_template_atomic_type; + } + } + } + + $this->id = null; + + if ($is_mixed) { + if (!$new_types) { + throw new \UnexpectedValueException('This array should be full'); + } + + $this->types = $new_types; + + return; + } + + foreach ($keys_to_unset as $key) { + unset($this->types[$key]); + } + + foreach ($new_types as $type) { + if ($type instanceof TLiteralString) { + $this->literal_string_types[$type->getKey()] = $type; + } elseif ($type instanceof TLiteralInt) { + $this->literal_int_types[$type->getKey()] = $type; + } elseif ($type instanceof TLiteralFloat) { + $this->literal_float_types[$type->getKey()] = $type; + } + } + + $atomic_types = array_values(array_merge($this->types, $new_types)); + + if ($atomic_types) { + $this->types = TypeCombination::combineTypes( + $atomic_types, + $codebase + )->getAtomicTypes(); + + $this->id = null; + } + } + + public function isSingle(): bool + { + $type_count = count($this->types); + + $int_literal_count = count($this->literal_int_types); + $string_literal_count = count($this->literal_string_types); + $float_literal_count = count($this->literal_float_types); + + if (($int_literal_count && $string_literal_count) + || ($int_literal_count && $float_literal_count) + || ($string_literal_count && $float_literal_count) + ) { + return false; + } + + if ($int_literal_count || $string_literal_count || $float_literal_count) { + $type_count -= $int_literal_count + $string_literal_count + $float_literal_count - 1; + } + + return $type_count === 1; + } + + public function isSingleAndMaybeNullable(): bool + { + $is_nullable = isset($this->types['null']); + + $type_count = count($this->types); + + if ($type_count === 1 && $is_nullable) { + return false; + } + + $int_literal_count = count($this->literal_int_types); + $string_literal_count = count($this->literal_string_types); + $float_literal_count = count($this->literal_float_types); + + if (($int_literal_count && $string_literal_count) + || ($int_literal_count && $float_literal_count) + || ($string_literal_count && $float_literal_count) + ) { + return false; + } + + if ($int_literal_count || $string_literal_count || $float_literal_count) { + $type_count -= $int_literal_count + $string_literal_count + $float_literal_count - 1; + } + + return ($type_count - (int) $is_nullable) === 1; + } + + /** + * @return bool true if this is an int + */ + public function isInt(bool $check_templates = false): bool + { + return count( + array_filter( + $this->types, + function ($type) use ($check_templates): bool { + return $type instanceof TInt + || ($check_templates + && $type instanceof TTemplateParam + && $type->as->isInt() + ); + } + ) + ) === count($this->types); + } + + /** + * @return bool true if this is a float + */ + public function isFloat(): bool + { + if (!$this->isSingle()) { + return false; + } + + return isset($this->types['float']) || $this->literal_float_types; + } + + /** + * @return bool true if this is a string + */ + public function isString(bool $check_templates = false): bool + { + return count( + array_filter( + $this->types, + function ($type) use ($check_templates): bool { + return $type instanceof TString + || ($check_templates + && $type instanceof TTemplateParam + && $type->as->isString() + ); + } + ) + ) === count($this->types); + } + + /** + * @return bool true if this is a string literal with only one possible value + */ + public function isSingleStringLiteral(): bool + { + return count($this->types) === 1 && count($this->literal_string_types) === 1; + } + + /** + * @throws \InvalidArgumentException if isSingleStringLiteral is false + * + * @return TLiteralString the only string literal represented by this union type + */ + public function getSingleStringLiteral(): TLiteralString + { + if (count($this->types) !== 1 || count($this->literal_string_types) !== 1) { + throw new \InvalidArgumentException('Not a string literal'); + } + + return reset($this->literal_string_types); + } + + public function allStringLiterals() : bool + { + foreach ($this->types as $atomic_key_type) { + if (!$atomic_key_type instanceof TLiteralString) { + return false; + } + } + + return true; + } + + public function hasLiteralValue() : bool + { + return $this->literal_int_types + || $this->literal_string_types + || $this->literal_float_types + || isset($this->types['false']) + || isset($this->types['true']); + } + + public function hasLiteralString(): bool + { + return count($this->literal_string_types) > 0; + } + + public function hasLiteralInt(): bool + { + return count($this->literal_int_types) > 0; + } + + /** + * @return bool true if this is a int literal with only one possible value + */ + public function isSingleIntLiteral(): bool + { + return count($this->types) === 1 && count($this->literal_int_types) === 1; + } + + /** + * @throws \InvalidArgumentException if isSingleIntLiteral is false + * + * @return TLiteralInt the only int literal represented by this union type + */ + public function getSingleIntLiteral(): TLiteralInt + { + if (count($this->types) !== 1 || count($this->literal_int_types) !== 1) { + throw new \InvalidArgumentException('Not an int literal'); + } + + return reset($this->literal_int_types); + } + + /** + * @param array $suppressed_issues + * @param array $phantom_classes + * + */ + public function check( + StatementsSource $source, + CodeLocation $code_location, + array $suppressed_issues, + array $phantom_classes = [], + bool $inferred = true, + bool $inherited = false, + bool $prevent_template_covariance = false, + ?string $calling_method_id = null + ) : bool { + if ($this->checked) { + return true; + } + + $checker = new \Psalm\Internal\TypeVisitor\TypeChecker( + $source, + $code_location, + $suppressed_issues, + $phantom_classes, + $inferred, + $inherited, + $prevent_template_covariance, + $calling_method_id + ); + + $checker->traverseArray($this->types); + + $this->checked = true; + + return !$checker->hasErrors(); + } + + /** + * @param array $phantom_classes + * + */ + public function queueClassLikesForScanning( + Codebase $codebase, + ?FileStorage $file_storage = null, + array $phantom_classes = [] + ): void { + $scanner_visitor = new \Psalm\Internal\TypeVisitor\TypeScanner( + $codebase->scanner, + $file_storage, + $phantom_classes + ); + + $scanner_visitor->traverseArray($this->types); + } + + /** + * @param lowercase-string $fq_class_like_name + */ + public function containsClassLike(string $fq_class_like_name) : bool + { + $classlike_visitor = new \Psalm\Internal\TypeVisitor\ContainsClassLikeVisitor($fq_class_like_name); + + $classlike_visitor->traverseArray($this->types); + + return $classlike_visitor->matches(); + } + + /** + * @return list + */ + public function getTemplateTypes(): array + { + $template_type_collector = new \Psalm\Internal\TypeVisitor\TemplateTypeCollector(); + + $template_type_collector->traverseArray($this->types); + + return $template_type_collector->getTemplateTypes(); + } + + public function setFromDocblock(): void + { + $this->from_docblock = true; + + (new \Psalm\Internal\TypeVisitor\FromDocblockSetter())->traverseArray($this->types); + } + + public function replaceClassLike(string $old, string $new) : void + { + foreach ($this->types as $key => $atomic_type) { + $atomic_type->replaceClassLike($old, $new); + + $this->removeType($key); + $this->addType($atomic_type); + } + } + + public function equals(Union $other_type): bool + { + if ($other_type === $this) { + return true; + } + + if ($other_type->id && $this->id && $other_type->id !== $this->id) { + return false; + } + + if ($this->possibly_undefined !== $other_type->possibly_undefined) { + return false; + } + + if ($this->had_template !== $other_type->had_template) { + return false; + } + + if ($this->possibly_undefined_from_try !== $other_type->possibly_undefined_from_try) { + return false; + } + + if ($this->from_calculation !== $other_type->from_calculation) { + return false; + } + + if ($this->initialized !== $other_type->initialized) { + return false; + } + + if ($this->from_docblock !== $other_type->from_docblock) { + return false; + } + + if (count($this->types) !== count($other_type->types)) { + return false; + } + + if ($this->parent_nodes !== $other_type->parent_nodes) { + return false; + } + + if ($this->different || $other_type->different) { + return false; + } + + $other_atomic_types = $other_type->types; + + foreach ($this->types as $key => $atomic_type) { + if (!isset($other_atomic_types[$key])) { + return false; + } + + if (!$atomic_type->equals($other_atomic_types[$key])) { + return false; + } + } + + return true; + } + + /** + * @return array + */ + public function getLiteralStrings(): array + { + return $this->literal_string_types; + } + + /** + * @return array + */ + public function getLiteralInts(): array + { + return $this->literal_int_types; + } + + /** + * @return array + */ + public function getLiteralFloats(): array + { + return $this->literal_float_types; + } + + /** + * @return array + */ + public function getChildNodes() : array + { + return $this->types; + } +} diff --git a/lib/composer/vimeo/psalm/src/command_functions.php b/lib/composer/vimeo/psalm/src/command_functions.php new file mode 100644 index 0000000000000000000000000000000000000000..01fa3892aeeb275b47fecaf1a0ffe2996805dcda --- /dev/null +++ b/lib/composer/vimeo/psalm/src/command_functions.php @@ -0,0 +1,571 @@ + + */ +function getArguments() : array +{ + global $argv; + + if (!$argv) { + return []; + } + + $filtered_input_paths = []; + + for ($i = 0, $iMax = count($argv); $i < $iMax; ++$i) { + $input_path = $argv[$i]; + + if (realpath($input_path) !== false) { + continue; + } + + if ($input_path[0] === '-' && strlen($input_path) === 2) { + if ($input_path[1] === 'c' || $input_path[1] === 'f') { + ++$i; + } + continue; + } + + if ($input_path[0] === '-' && $input_path[2] === '=') { + continue; + } + + $filtered_input_paths[] = $input_path; + } + + return $filtered_input_paths; +} + +/** + * @param string|array|null|false $f_paths + * + * @return list|null + */ +function getPathsToCheck($f_paths): ?array +{ + global $argv; + + $paths_to_check = []; + + if ($f_paths) { + $input_paths = is_array($f_paths) ? $f_paths : [$f_paths]; + } else { + $input_paths = $argv ? $argv : null; + } + + if ($input_paths) { + $filtered_input_paths = []; + + for ($i = 0, $iMax = count($input_paths); $i < $iMax; ++$i) { + /** @var string */ + $input_path = $input_paths[$i]; + + if (realpath($input_path) === realpath(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'psalm') + || realpath($input_path) === realpath(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'psalter') + || realpath($input_path) === realpath(Phar::running(false)) + ) { + continue; + } + + if ($input_path[0] === '-' && strlen($input_path) === 2) { + if ($input_path[1] === 'c' || $input_path[1] === 'f') { + ++$i; + } + continue; + } + + if ($input_path[0] === '-' && $input_path[2] === '=') { + continue; + } + + if (substr($input_path, 0, 2) === '--' && strlen($input_path) > 2) { + continue; + } + + $filtered_input_paths[] = $input_path; + } + + if ($filtered_input_paths === ['-']) { + $meta = stream_get_meta_data(STDIN); + stream_set_blocking(STDIN, false); + if ($stdin = fgets(STDIN)) { + $filtered_input_paths = preg_split('/\s+/', trim($stdin)); + } + $blocked = $meta['blocked']; + stream_set_blocking(STDIN, $blocked); + } + + foreach ($filtered_input_paths as $path_to_check) { + if ($path_to_check[0] === '-') { + fwrite(STDERR, 'Invalid usage, expecting psalm [options] [file...]' . PHP_EOL); + exit(1); + } + + if (!file_exists($path_to_check)) { + fwrite(STDERR, 'Cannot locate ' . $path_to_check . PHP_EOL); + exit(1); + } + + $path_to_check = realpath($path_to_check); + + if (!$path_to_check) { + fwrite(STDERR, 'Error getting realpath for file' . PHP_EOL); + exit(1); + } + + $paths_to_check[] = $path_to_check; + } + + if (!$paths_to_check) { + $paths_to_check = null; + } + } + + return $paths_to_check; +} + +/** + * @psalm-pure + */ +function getPsalmHelpText(): string +{ + return <<getMessage() . PHP_EOL); + exit(1); + } + + $config->setComposerClassLoader($first_autoloader); + + return $config; +} + +function update_config_file(Config $config, string $config_file_path, string $baseline_path) : void +{ + if ($config->error_baseline === $baseline_path) { + return; + } + + $configFile = $config_file_path; + + if (is_dir($config_file_path)) { + $configFile = Config::locateConfigFile($config_file_path); + } + + if (!$configFile) { + fwrite(STDERR, "Don't forget to set errorBaseline=\"{$baseline_path}\" to your config."); + + return; + } + + $configFileContents = file_get_contents($configFile); + + if ($config->error_baseline) { + $amendedConfigFileContents = preg_replace( + '/errorBaseline=".*?"/', + "errorBaseline=\"{$baseline_path}\"", + $configFileContents + ); + } else { + $endPsalmOpenTag = strpos($configFileContents, '>', (int)strpos($configFileContents, '", + $endPsalmOpenTag, + 1 + ); + } else { + $amendedConfigFileContents = substr_replace( + $configFileContents, + " errorBaseline=\"{$baseline_path}\">", + $endPsalmOpenTag, + 1 + ); + } + } + + file_put_contents($configFile, $amendedConfigFileContents); +} + +function get_path_to_config(array $options): ?string +{ + $path_to_config = isset($options['c']) && is_string($options['c']) ? realpath($options['c']) : null; + + if ($path_to_config === false) { + fwrite(STDERR, 'Could not resolve path to config ' . (string) ($options['c'] ?? '') . PHP_EOL); + exit(1); + } + return $path_to_config; +} + +/** + * @psalm-pure + */ +function getMemoryLimitInBytes(): int +{ + $limit = ini_get('memory_limit'); + // for unlimited = -1 + if ($limit < 0) { + return -1; + } + + if (preg_match('/^(\d+)(\D?)$/', $limit, $matches)) { + $limit = (int)$matches[1]; + switch (strtoupper($matches[2] ?? '')) { + case 'G': + $limit *= 1024 * 1024 * 1024; + break; + case 'M': + $limit *= 1024 * 1024; + break; + case 'K': + $limit *= 1024; + break; + } + } + + return (int)$limit; +} diff --git a/lib/composer/vimeo/psalm/src/functions.php b/lib/composer/vimeo/psalm/src/functions.php new file mode 100644 index 0000000000000000000000000000000000000000..c1907a02d295b48cbbd97a59c715b7cf70f63aea --- /dev/null +++ b/lib/composer/vimeo/psalm/src/functions.php @@ -0,0 +1,14 @@ +runAndCollect( + function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\ClassLoader { + return \Psalm\requireAutoloaders($current_dir, isset($options['r']), $vendor_dir); + } +); + +$ini_handler = new \Psalm\Internal\Fork\PsalmRestarter('PSALM'); + +$ini_handler->disableExtension('grpc'); + +// If Xdebug is enabled, restart without it +$ini_handler->check(); + +setlocale(LC_CTYPE, 'C'); + +$path_to_config = \Psalm\get_path_to_config($options); + +if (isset($options['tcp'])) { + if (!is_string($options['tcp'])) { + fwrite(STDERR, 'tcp url should be a string' . PHP_EOL); + exit(1); + } +} + +$find_unused_code = isset($options['find-dead-code']) ? 'auto' : null; + +$config = \Psalm\initialiseConfig($path_to_config, $current_dir, \Psalm\Report::TYPE_CONSOLE, $first_autoloader); +$config->setIncludeCollector($include_collector); + +if ($config->resolve_from_config_file) { + $current_dir = $config->base_dir; + chdir($current_dir); +} + +$config->setServerMode(); + +if (isset($options['clear-cache'])) { + $cache_directory = $config->getCacheDirectory(); + + if ($cache_directory !== null) { + Config::removeCacheDirectory($cache_directory); + } + echo 'Cache directory deleted' . PHP_EOL; + exit; +} + +$providers = new \Psalm\Internal\Provider\Providers( + new \Psalm\Internal\Provider\FileProvider, + new \Psalm\Internal\Provider\ParserCacheProvider($config), + new \Psalm\Internal\Provider\FileStorageCacheProvider($config), + new \Psalm\Internal\Provider\ClassLikeStorageCacheProvider($config), + new \Psalm\Internal\Provider\FileReferenceCacheProvider($config), + new \Psalm\Internal\Provider\ProjectCacheProvider(Composer::getLockFilePath($current_dir)) +); + +$project_analyzer = new ProjectAnalyzer( + $config, + $providers +); + +if ($config->find_unused_variables) { + $project_analyzer->getCodebase()->reportUnusedVariables(); +} + +if ($config->find_unused_code) { + $find_unused_code = 'auto'; +} + +if (isset($options['disable-on-change'])) { + $project_analyzer->onchange_line_limit = (int) $options['disable-on-change']; +} + +$project_analyzer->provide_completion = !isset($options['enable-autocomplete']) + || !is_string($options['enable-autocomplete']) + || strtolower($options['enable-autocomplete']) !== 'false'; + +$config->visitComposerAutoloadFiles($project_analyzer); + +if ($find_unused_code) { + $project_analyzer->getCodebase()->reportUnusedCode($find_unused_code); +} + +if (isset($options['use-extended-diagnostic-codes'])) { + $project_analyzer->language_server_use_extended_diagnostic_codes = true; +} + +if (isset($options['verbose'])) { + $project_analyzer->language_server_verbose = true; +} + +$project_analyzer->server($options['tcp'] ?? null, isset($options['tcp-server']) ? true : false); diff --git a/lib/composer/vimeo/psalm/src/psalm-refactor.php b/lib/composer/vimeo/psalm/src/psalm-refactor.php new file mode 100644 index 0000000000000000000000000000000000000000..f1733e831eec05c6ae7ebd25bb1d9f2ffb83963b --- /dev/null +++ b/lib/composer/vimeo/psalm/src/psalm-refactor.php @@ -0,0 +1,304 @@ +runAndCollect( + function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\ClassLoader { + return requireAutoloaders($current_dir, isset($options['r']), $vendor_dir); + } +); + +// If Xdebug is enabled, restart without it +(new \Composer\XdebugHandler\XdebugHandler('PSALTER'))->check(); + +$path_to_config = get_path_to_config($options); + +$args = getArguments(); + +$operation = null; +$last_arg = null; + +$to_refactor = []; + +foreach ($args as $arg) { + if ($arg === '--move') { + $operation = 'move'; + continue; + } + + if ($arg === '--into') { + if ($operation !== 'move' || !$last_arg) { + die('--into is not expected here' . PHP_EOL); + } + + $operation = 'move_into'; + continue; + } + + if ($arg === '--rename') { + $operation = 'rename'; + continue; + } + + if ($arg === '--to') { + if ($operation !== 'rename' || !$last_arg) { + die('--to is not expected here' . PHP_EOL); + } + + $operation = 'rename_to'; + + continue; + } + + if ($arg[0] === '-') { + $operation = null; + continue; + } + + if ($operation === 'move_into' || $operation === 'rename_to') { + if (!$last_arg) { + die('Expecting a previous argument' . PHP_EOL); + } + + if ($operation === 'move_into') { + $last_arg_parts = preg_split('/, ?/', $last_arg); + + foreach ($last_arg_parts as $last_arg_part) { + if (strpos($last_arg_part, '::')) { + [, $identifier_name] = explode('::', $last_arg_part); + $to_refactor[$last_arg_part] = $arg . '::' . $identifier_name; + } else { + $namespace_parts = explode('\\', $last_arg_part); + $class_name = end($namespace_parts); + $to_refactor[$last_arg_part] = $arg . '\\' . $class_name; + } + } + } else { + $to_refactor[$last_arg] = $arg; + } + + $last_arg = null; + $operation = null; + continue; + } + + if ($operation === 'move' || $operation === 'rename') { + $last_arg = $arg; + + continue; + } + + die('Unexpected argument "' . $arg . '"' . PHP_EOL); +} + +if (!$to_refactor) { + die('No --move or --rename arguments supplied' . PHP_EOL); +} + +$config = initialiseConfig($path_to_config, $current_dir, \Psalm\Report::TYPE_CONSOLE, $first_autoloader); +$config->setIncludeCollector($include_collector); + +if ($config->resolve_from_config_file) { + $current_dir = $config->base_dir; + chdir($current_dir); +} + +$threads = isset($options['threads']) + ? (int)$options['threads'] + : max(1, ProjectAnalyzer::getCpuCount() - 2); + +$providers = new \Psalm\Internal\Provider\Providers( + new \Psalm\Internal\Provider\FileProvider(), + new \Psalm\Internal\Provider\ParserCacheProvider($config, false), + new \Psalm\Internal\Provider\FileStorageCacheProvider($config), + new \Psalm\Internal\Provider\ClassLikeStorageCacheProvider($config), + null, + new \Psalm\Internal\Provider\ProjectCacheProvider(Composer::getLockFilePath($current_dir)) +); + +$debug = array_key_exists('debug', $options) || array_key_exists('debug-by-line', $options); +$progress = $debug + ? new DebugProgress() + : new DefaultProgress(); + +if (array_key_exists('debug-emitted-issues', $options)) { + $config->debug_emitted_issues = true; +} + +$project_analyzer = new ProjectAnalyzer( + $config, + $providers, + new \Psalm\Report\ReportOptions(), + [], + $threads, + $progress +); + +if (array_key_exists('debug-by-line', $options)) { + $project_analyzer->debug_lines = true; +} + +$config->visitComposerAutoloadFiles($project_analyzer); + +$project_analyzer->refactorCodeAfterCompletion($to_refactor); + +$start_time = microtime(true); + +$project_analyzer->check($current_dir); + +IssueBuffer::finish($project_analyzer, false, $start_time); diff --git a/lib/composer/vimeo/psalm/src/psalm.php b/lib/composer/vimeo/psalm/src/psalm.php new file mode 100644 index 0000000000000000000000000000000000000000..7e066b14ffe426564a346dadde104d43a8c24229 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/psalm.php @@ -0,0 +1,845 @@ +runAndCollect( + function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\ClassLoader { + return requireAutoloaders($current_dir, isset($options['r']), $vendor_dir); + } +); + + +if (array_key_exists('v', $options)) { + echo 'Psalm ' . PSALM_VERSION . PHP_EOL; + exit; +} + +$output_format = isset($options['output-format']) && is_string($options['output-format']) + ? $options['output-format'] + : \Psalm\Report::TYPE_CONSOLE; + +$init_level = null; +$init_source_dir = null; + +if (isset($options['i'])) { + if (file_exists($current_dir . 'psalm.xml')) { + die('A config file already exists in the current directory' . PHP_EOL); + } + + $args = array_values(array_filter( + $args, + function (string $arg): bool { + return $arg !== '--ansi' + && $arg !== '--no-ansi' + && $arg !== '-i' + && $arg !== '--init' + && $arg !== '--debug' + && $arg !== '--debug-by-line' + && $arg !== '--debug-emitted-issues' + && strpos($arg, '--disable-extension=') !== 0 + && strpos($arg, '--root=') !== 0 + && strpos($arg, '--r=') !== 0; + } + )); + + if (count($args)) { + if (count($args) > 2) { + die('Too many arguments provided for psalm --init' . PHP_EOL); + } + + if (isset($args[1])) { + if (!preg_match('/^[1-8]$/', $args[1])) { + die('Config strictness must be a number between 1 and 8 inclusive' . PHP_EOL); + } + + $init_level = (int)$args[1]; + } + + $init_source_dir = $args[0]; + } + + $vendor_dir = \Psalm\getVendorDir($current_dir); + + if ($init_level === null) { + echo "Calculating best config level based on project files\n"; + \Psalm\Config\Creator::createBareConfig($current_dir, $init_source_dir, $vendor_dir); + $config = \Psalm\Config::getInstance(); + } else { + try { + $template_contents = \Psalm\Config\Creator::getContents( + $current_dir, + $init_source_dir, + $init_level, + $vendor_dir + ); + } catch (\Psalm\Exception\ConfigCreationException $e) { + die($e->getMessage() . PHP_EOL); + } + + if (!file_put_contents($current_dir . 'psalm.xml', $template_contents)) { + die('Could not write to psalm.xml' . PHP_EOL); + } + + exit('Config file created successfully. Please re-run psalm.' . PHP_EOL); + } +} else { + $config = initialiseConfig($path_to_config, $current_dir, $output_format, $first_autoloader); + + if (isset($options['error-level']) + && is_numeric($options['error-level']) + ) { + $config_level = (int) $options['error-level']; + + if (!in_array($config_level, [1, 2, 3, 4, 5, 6, 7, 8], true)) { + throw new \Psalm\Exception\ConfigException( + 'Invalid error level ' . $config_level + ); + } + + $config->level = $config_level; + } +} + +$config->setIncludeCollector($include_collector); + +if ($config->resolve_from_config_file) { + $current_dir = $config->base_dir; + chdir($current_dir); +} + +$in_ci = isset($_SERVER['TRAVIS']) + || isset($_SERVER['CIRCLECI']) + || isset($_SERVER['APPVEYOR']) + || isset($_SERVER['JENKINS_URL']) + || isset($_SERVER['SCRUTINIZER']) + || isset($_SERVER['GITLAB_CI']) + || isset($_SERVER['GITHUB_WORKFLOW']); + +// disable progressbar on CI +if ($in_ci) { + $options['long-progress'] = true; +} + +if (isset($options['threads'])) { + $threads = (int)$options['threads']; +} elseif (isset($options['debug']) || $in_ci) { + $threads = 1; +} else { + $threads = max(1, ProjectAnalyzer::getCpuCount() - 1); +} + +if (!isset($options['threads']) + && !isset($options['debug']) + && $threads === 1 + && ini_get('pcre.jit') === '1' + && PHP_OS === 'Darwin' + && version_compare(PHP_VERSION, '7.3.0') >= 0 + && version_compare(PHP_VERSION, '7.4.0') < 0 +) { + echo( + 'If you want to run Psalm as a language server, or run Psalm with' . PHP_EOL + . 'multiple processes (--threads=4), beware:' . PHP_EOL + . \Psalm\Internal\Fork\Pool::MAC_PCRE_MESSAGE . PHP_EOL . PHP_EOL + ); +} + +$ini_handler = new \Psalm\Internal\Fork\PsalmRestarter('PSALM'); + +if (isset($options['disable-extension'])) { + if (is_array($options['disable-extension'])) { + /** @psalm-suppress MixedAssignment */ + foreach ($options['disable-extension'] as $extension) { + if (is_string($extension)) { + $ini_handler->disableExtension($extension); + } + } + } elseif (is_string($options['disable-extension'])) { + $ini_handler->disableExtension($options['disable-extension']); + } +} + +if ($threads > 1) { + $ini_handler->disableExtension('grpc'); +} + +$ini_handler->disableExtension('uopz'); + +$type_map_location = null; + +if (isset($options['generate-json-map']) && is_string($options['generate-json-map'])) { + $type_map_location = $options['generate-json-map']; +} + +$stubs_location = null; + +if (isset($options['generate-stubs']) && is_string($options['generate-stubs'])) { + $stubs_location = $options['generate-stubs']; +} + +// If Xdebug is enabled, restart without it +$ini_handler->check(); + +if ($config->load_xdebug_stub === null && '' !== $ini_handler->getSkippedVersion()) { + $config->load_xdebug_stub = true; +} + +if (isset($options['debug-emitted-issues'])) { + $config->debug_emitted_issues = true; +} + +setlocale(LC_CTYPE, 'C'); + +if (isset($options['set-baseline'])) { + if (is_array($options['set-baseline'])) { + die('Only one baseline file can be created at a time' . PHP_EOL); + } +} + +$paths_to_check = \Psalm\getPathsToCheck(isset($options['f']) ? $options['f'] : null); + +$plugins = []; + +if (isset($options['plugin'])) { + $plugins = $options['plugin']; + + if (!is_array($plugins)) { + $plugins = [$plugins]; + } +} + + + +$show_info = isset($options['show-info']) + ? $options['show-info'] === 'true' || $options['show-info'] === '1' + : false; + +$is_diff = !isset($options['no-diff']) + && !isset($options['set-baseline']) + && !isset($options['update-baseline']); + +/** @var false|'always'|'auto' $find_unused_code */ +$find_unused_code = false; +if (isset($options['find-dead-code'])) { + $options['find-unused-code'] = $options['find-dead-code']; +} + +if (isset($options['find-unused-code'])) { + if ($options['find-unused-code'] === 'always') { + $find_unused_code = 'always'; + } else { + $find_unused_code = 'auto'; + } +} + +$find_unused_variables = isset($options['find-unused-variables']); + +$find_references_to = isset($options['find-references-to']) && is_string($options['find-references-to']) + ? $options['find-references-to'] + : null; + +if (isset($options['shepherd'])) { + if (is_string($options['shepherd'])) { + $config->shepherd_host = $options['shepherd']; + } + $shepherd_plugin = __DIR__ . '/Psalm/Plugin/Shepherd.php'; + + if (!file_exists($shepherd_plugin)) { + die('Could not find Shepherd plugin location ' . $shepherd_plugin . PHP_EOL); + } + + $plugins[] = $shepherd_plugin; +} + +if (isset($options['clear-cache'])) { + $cache_directory = $config->getCacheDirectory(); + + if ($cache_directory !== null) { + Config::removeCacheDirectory($cache_directory); + } + echo 'Cache directory deleted' . PHP_EOL; + exit; +} + +if (isset($options['clear-global-cache'])) { + $cache_directory = $config->getGlobalCacheDirectory(); + + if ($cache_directory) { + Config::removeCacheDirectory($cache_directory); + echo 'Global cache directory deleted' . PHP_EOL; + } + + exit; +} + +$debug = array_key_exists('debug', $options) || array_key_exists('debug-by-line', $options); + +if ($debug) { + $progress = new DebugProgress(); +} elseif (isset($options['no-progress'])) { + $progress = new VoidProgress(); +} else { + $show_errors = !$config->error_baseline || isset($options['ignore-baseline']); + if (isset($options['long-progress'])) { + $progress = new LongProgress($show_errors, $show_info); + } else { + $progress = new DefaultProgress($show_errors, $show_info); + } +} + +if (isset($options['no-cache']) || isset($options['i'])) { + $providers = new Provider\Providers( + new Provider\FileProvider + ); +} else { + $no_reflection_cache = isset($options['no-reflection-cache']); + $no_file_cache = isset($options['no-file-cache']); + + $file_storage_cache_provider = $no_reflection_cache + ? null + : new Provider\FileStorageCacheProvider($config); + + $classlike_storage_cache_provider = $no_reflection_cache + ? null + : new Provider\ClassLikeStorageCacheProvider($config); + + $providers = new Provider\Providers( + new Provider\FileProvider, + new Provider\ParserCacheProvider($config, !$no_file_cache), + $file_storage_cache_provider, + $classlike_storage_cache_provider, + new Provider\FileReferenceCacheProvider($config), + new Provider\ProjectCacheProvider(Composer::getLockFilePath($current_dir)) + ); +} + +$stdout_report_options = new \Psalm\Report\ReportOptions(); +$stdout_report_options->use_color = !array_key_exists('m', $options); +$stdout_report_options->show_info = $show_info; +$stdout_report_options->show_suggestions = !array_key_exists('no-suggestions', $options); +/** + * @psalm-suppress PropertyTypeCoercion + */ +$stdout_report_options->format = $output_format; +$stdout_report_options->show_snippet = !isset($options['show-snippet']) || $options['show-snippet'] !== "false"; +$stdout_report_options->pretty = isset($options['pretty-print']) && $options['pretty-print'] !== "false"; + +/** @var list|string $report_file_paths type guaranteed by argument to getopt() */ +$report_file_paths = $options['report'] ?? []; +if (is_string($report_file_paths)) { + $report_file_paths = [$report_file_paths]; +} +$project_analyzer = new ProjectAnalyzer( + $config, + $providers, + $stdout_report_options, + ProjectAnalyzer::getFileReportOptions( + $report_file_paths, + isset($options['report-show-info']) + ? $options['report-show-info'] !== 'false' && $options['report-show-info'] !== '0' + : true + ), + $threads, + $progress +); + +if (!isset($options['php-version'])) { + $options['php-version'] = $config->getPhpVersion(); +} + +if (isset($options['php-version'])) { + if (!is_string($options['php-version'])) { + die('Expecting a version number in the format x.y' . PHP_EOL); + } + + $project_analyzer->setPhpVersion($options['php-version']); +} + +if ($type_map_location) { + $project_analyzer->getCodebase()->store_node_types = true; +} + +$start_time = microtime(true); + +if (array_key_exists('debug-by-line', $options)) { + $project_analyzer->debug_lines = true; +} + +if (array_key_exists('debug-performance', $options)) { + $project_analyzer->debug_performance = true; +} + +if ($config->find_unused_code) { + $find_unused_code = 'auto'; +} + +if ($find_references_to !== null) { + $project_analyzer->getCodebase()->collectLocations(); + $project_analyzer->show_issues = false; +} + +if ($find_unused_code) { + $project_analyzer->getCodebase()->reportUnusedCode($find_unused_code); +} + +if ($config->find_unused_variables || $find_unused_variables) { + $project_analyzer->getCodebase()->reportUnusedVariables(); +} + +if ($config->run_taint_analysis || (isset($options['track-tainted-input']) + || isset($options['security-analysis']) + || isset($options['taint-analysis'])) +) { + $project_analyzer->trackTaintedInputs(); +} + +if ($config->find_unused_psalm_suppress || isset($options['find-unused-psalm-suppress'])) { + $project_analyzer->trackUnusedSuppressions(); +} + +/** @var string $plugin_path */ +foreach ($plugins as $plugin_path) { + $config->addPluginPath($plugin_path); +} + +if ($paths_to_check === null) { + $project_analyzer->check($current_dir, $is_diff); +} elseif ($paths_to_check) { + $project_analyzer->checkPaths($paths_to_check); +} + +if ($find_references_to) { + $project_analyzer->findReferencesTo($find_references_to); +} + +if (isset($options['set-baseline']) && is_string($options['set-baseline'])) { + fwrite(STDERR, 'Writing error baseline to file...' . PHP_EOL); + + ErrorBaseline::create( + new \Psalm\Internal\Provider\FileProvider, + $options['set-baseline'], + IssueBuffer::getIssuesData(), + $config->include_php_versions_in_error_baseline || isset($options['include-php-versions']) + ); + + fwrite(STDERR, "Baseline saved to {$options['set-baseline']}."); + + update_config_file( + $config, + $path_to_config ?? $current_dir, + $options['set-baseline'] + ); + + fwrite(STDERR, PHP_EOL); +} + +$issue_baseline = []; + +if (isset($options['update-baseline'])) { + $baselineFile = Config::getInstance()->error_baseline; + + if (empty($baselineFile)) { + die('Cannot update baseline, because no baseline file is configured.' . PHP_EOL); + } + + try { + $issue_current_baseline = ErrorBaseline::read( + new \Psalm\Internal\Provider\FileProvider, + $baselineFile + ); + $total_issues_current_baseline = ErrorBaseline::countTotalIssues($issue_current_baseline); + + $issue_baseline = ErrorBaseline::update( + new \Psalm\Internal\Provider\FileProvider, + $baselineFile, + IssueBuffer::getIssuesData(), + $config->include_php_versions_in_error_baseline || isset($options['include-php-versions']) + ); + $total_issues_updated_baseline = ErrorBaseline::countTotalIssues($issue_baseline); + + $total_fixed_issues = $total_issues_current_baseline - $total_issues_updated_baseline; + + if ($total_fixed_issues > 0) { + echo str_repeat('-', 30) . "\n"; + echo $total_fixed_issues . ' errors fixed' . "\n"; + } + } catch (\Psalm\Exception\ConfigException $exception) { + fwrite(STDERR, 'Could not update baseline file: ' . $exception->getMessage() . PHP_EOL); + exit(1); + } +} + +if (isset($options['use-baseline'])) { + if (!is_string($options['use-baseline'])) { + fwrite(STDERR, '--use-baseline must be a string' . PHP_EOL); + exit(1); + } + + $baseline_file_path = $options['use-baseline']; +} else { + $baseline_file_path = Config::getInstance()->error_baseline; +} + +if ($baseline_file_path && !isset($options['ignore-baseline'])) { + try { + $issue_baseline = ErrorBaseline::read( + new \Psalm\Internal\Provider\FileProvider, + $baseline_file_path + ); + } catch (\Psalm\Exception\ConfigException $exception) { + fwrite(STDERR, 'Error while reading baseline: ' . $exception->getMessage() . PHP_EOL); + exit(1); + } +} + +if ($type_map_location) { + $file_map = $providers->file_reference_provider->getFileMaps(); + + $name_file_map = []; + + $expected_references = []; + + foreach ($file_map as $file_path => $map) { + $file_name = $config->shortenFileName($file_path); + foreach ($map[0] as $map_parts) { + $expected_references[$map_parts[1]] = true; + } + $map[2] = []; + $name_file_map[$file_name] = $map; + } + + $reference_dictionary = \Psalm\Internal\Codebase\ReferenceMapGenerator::getReferenceMap( + $providers->classlike_storage_provider, + $expected_references + ); + + $type_map_string = json_encode(['files' => $name_file_map, 'references' => $reference_dictionary]); + + $providers->file_provider->setContents( + $type_map_location, + $type_map_string + ); +} + +if ($stubs_location) { + $providers->file_provider->setContents( + $stubs_location, + \Psalm\Internal\Stubs\Generator\StubsGenerator::getAll( + $project_analyzer->getCodebase(), + $providers->classlike_storage_provider, + $providers->file_storage_provider + ) + ); +} + +if (!isset($options['i'])) { + IssueBuffer::finish( + $project_analyzer, + !$paths_to_check, + $start_time, + isset($options['stats']), + $issue_baseline + ); +} else { + $issues_by_file = IssueBuffer::getIssuesData(); + + if (!$issues_by_file) { + $init_level = 1; + } else { + $codebase = $project_analyzer->getCodebase(); + $mixed_counts = $codebase->analyzer->getTotalTypeCoverage($codebase); + + $init_level = \Psalm\Config\Creator::getLevel( + array_merge(...array_values($issues_by_file)), + (int) array_sum($mixed_counts) + ); + } + + echo "\n" . 'Detected level ' . $init_level . ' as a suitable initial default' . "\n"; + + try { + $template_contents = \Psalm\Config\Creator::getContents( + $current_dir, + $init_source_dir, + $init_level, + $vendor_dir + ); + } catch (\Psalm\Exception\ConfigCreationException $e) { + die($e->getMessage() . PHP_EOL); + } + + if (!file_put_contents($current_dir . 'psalm.xml', $template_contents)) { + die('Could not write to psalm.xml' . PHP_EOL); + } + + exit('Config file created successfully. Please re-run psalm.' . PHP_EOL); +} diff --git a/lib/composer/vimeo/psalm/src/psalm_plugin.php b/lib/composer/vimeo/psalm/src/psalm_plugin.php new file mode 100644 index 0000000000000000000000000000000000000000..ecbf66a651d02b47e2870fad49d8300f724024a0 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/psalm_plugin.php @@ -0,0 +1,40 @@ +addCommands([ + new ShowCommand($plugin_list_factory), + new EnableCommand($plugin_list_factory), + new DisableCommand($plugin_list_factory), +]); + +$app->getDefinition()->addOption( + new InputOption('config', 'c', InputOption::VALUE_REQUIRED, 'Path to Psalm config file') +); + +$app->setDefaultCommand('show'); +$app->run(); diff --git a/lib/composer/vimeo/psalm/src/psalter.php b/lib/composer/vimeo/psalm/src/psalter.php new file mode 100644 index 0000000000000000000000000000000000000000..47e7c67763b48096600a56cca51905790dcef260 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/psalter.php @@ -0,0 +1,505 @@ + 0 && $memLimit < 8 * 1024 * 1024 * 1024) { + ini_set('memory_limit', (string) (8 * 1024 * 1024 * 1024)); +} + +gc_collect_cycles(); +gc_disable(); + +require_once __DIR__ . '/Psalm/Internal/exception_handler.php'; + +$args = array_slice($argv, 1); + +$valid_short_options = ['f:', 'm', 'h', 'r:', 'c:']; +$valid_long_options = [ + 'help', 'debug', 'debug-by-line', 'debug-emitted-issues', 'config:', 'file:', 'root:', + 'plugin:', 'issues:', 'list-supported-issues', 'php-version:', 'dry-run', 'safe-types', + 'find-unused-code', 'threads:', 'codeowner:', + 'allow-backwards-incompatible-changes:', + 'add-newline-between-docblock-annotations:', + 'no-cache' +]; + +// get options from command line +$options = getopt(implode('', $valid_short_options), $valid_long_options); + +array_map( + /** + * @param string $arg + */ + function ($arg) use ($valid_long_options, $valid_short_options): void { + if (substr($arg, 0, 2) === '--' && $arg !== '--') { + $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); + + if ($arg_name === 'alter') { + // valid option for psalm, ignored by psalter + return; + } + + if (!in_array($arg_name, $valid_long_options) + && !in_array($arg_name . ':', $valid_long_options) + && !in_array($arg_name . '::', $valid_long_options) + ) { + fwrite( + STDERR, + 'Unrecognised argument "--' . $arg_name . '"' . PHP_EOL + . 'Type --help to see a list of supported arguments'. PHP_EOL + ); + exit(1); + } + } + }, + $args +); + +if (array_key_exists('help', $options)) { + $options['h'] = false; +} + +if (array_key_exists('monochrome', $options)) { + $options['m'] = false; +} + +if (isset($options['config'])) { + $options['c'] = $options['config']; +} + +if (isset($options['c']) && is_array($options['c'])) { + die('Too many config files provided' . PHP_EOL); +} + +if (array_key_exists('h', $options)) { + echo <<< HELP +Usage: + psalter [options] [file...] + +Options: + -h, --help + Display this help message + + --debug, --debug-by-line, --debug-emitted-issues + Debug information + + -c, --config=psalm.xml + Path to a psalm.xml configuration file. Run psalm --init to create one. + + -m, --monochrome + Enable monochrome output + + -r, --root + If running Psalm globally you'll need to specify a project root. Defaults to cwd + + --plugin=PATH + Executes a plugin, an alternative to using the Psalm config + + --dry-run + Shows a diff of all the changes, without making them + + --safe-types + Only update PHP types when the new type information comes from other PHP types, + as opposed to type information that just comes from docblocks + + --php-version=PHP_MAJOR_VERSION.PHP_MINOR_VERSION + + --issues=IssueType1,IssueType2 + If any issues can be fixed automatically, Psalm will update the codebase. To fix as many issues as possible, + use --issues=all + + --list-supported-issues + Display the list of issues that psalter knows how to fix + + --find-unused-code + Include unused code as a candidate for removal + + --threads=INT + If greater than one, Psalm will run analysis on multiple threads, speeding things up. + + --codeowner=[codeowner] + You can specify a GitHub code ownership group, and only that owner's code will be updated. + + --allow-backwards-incompatible-changes=BOOL + Allow Psalm modify method signatures that could break code outside the project. Defaults to true. + + --add-newline-between-docblock-annotations=BOOL + Whether to add or not add a new line between docblock annotations. Defaults to true. + + --no-cache + Runs Psalm without using cache +HELP; + + exit; +} + +if (!isset($options['issues']) && + !isset($options['list-supported-issues']) && + (!isset($options['plugin']) || $options['plugin'] === false) +) { + fwrite(STDERR, 'Please specify the issues you want to fix with --issues=IssueOne,IssueTwo or --issues=all, ' . + 'or provide a plugin that has its own manipulations with --plugin=path/to/plugin.php' . PHP_EOL); + exit(1); +} + +if (isset($options['root'])) { + $options['r'] = $options['root']; +} + +$current_dir = (string)getcwd() . DIRECTORY_SEPARATOR; + +if (isset($options['r']) && is_string($options['r'])) { + $root_path = realpath($options['r']); + + if (!$root_path) { + die('Could not locate root directory ' . $current_dir . DIRECTORY_SEPARATOR . $options['r'] . PHP_EOL); + } + + $current_dir = $root_path . DIRECTORY_SEPARATOR; +} + +$vendor_dir = \Psalm\getVendorDir($current_dir); + +require_once __DIR__ . '/Psalm/Internal/IncludeCollector.php'; +$include_collector = new IncludeCollector(); +$first_autoloader = $include_collector->runAndCollect( + function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\ClassLoader { + return requireAutoloaders($current_dir, isset($options['r']), $vendor_dir); + } +); + + +// If Xdebug is enabled, restart without it +(new \Composer\XdebugHandler\XdebugHandler('PSALTER'))->check(); + +$paths_to_check = getPathsToCheck(isset($options['f']) ? $options['f'] : null); + +$path_to_config = get_path_to_config($options); + +$config = initialiseConfig($path_to_config, $current_dir, \Psalm\Report::TYPE_CONSOLE, $first_autoloader); +$config->setIncludeCollector($include_collector); + +if ($config->resolve_from_config_file) { + $current_dir = $config->base_dir; + chdir($current_dir); +} + +$threads = isset($options['threads']) ? (int)$options['threads'] : 1; + +if (isset($options['no-cache'])) { + $providers = new \Psalm\Internal\Provider\Providers( + new \Psalm\Internal\Provider\FileProvider() + ); +} else { + $providers = new \Psalm\Internal\Provider\Providers( + new \Psalm\Internal\Provider\FileProvider(), + new \Psalm\Internal\Provider\ParserCacheProvider($config, false), + new \Psalm\Internal\Provider\FileStorageCacheProvider($config), + new \Psalm\Internal\Provider\ClassLikeStorageCacheProvider($config), + null, + new \Psalm\Internal\Provider\ProjectCacheProvider(Composer::getLockFilePath($current_dir)) + ); +} + +if (array_key_exists('list-supported-issues', $options)) { + echo implode(',', ProjectAnalyzer::getSupportedIssuesToFix()) . PHP_EOL; + exit(); +} + +$debug = array_key_exists('debug', $options) || array_key_exists('debug-by-line', $options); +$progress = $debug + ? new DebugProgress() + : new DefaultProgress(); + +$stdout_report_options = new \Psalm\Report\ReportOptions(); +$stdout_report_options->use_color = !array_key_exists('m', $options); + +$project_analyzer = new ProjectAnalyzer( + $config, + $providers, + $stdout_report_options, + [], + $threads, + $progress +); + +if (array_key_exists('debug-by-line', $options)) { + $project_analyzer->debug_lines = true; +} + +if (array_key_exists('debug-emitted-issues', $options)) { + $config->debug_emitted_issues = true; +} + +$config->visitComposerAutoloadFiles($project_analyzer); + +if (array_key_exists('issues', $options)) { + if (!is_string($options['issues']) || !$options['issues']) { + die('Expecting a comma-separated list of issues' . PHP_EOL); + } + + $issues = explode(',', $options['issues']); + + $keyed_issues = []; + + foreach ($issues as $issue) { + $keyed_issues[$issue] = true; + } +} else { + $keyed_issues = []; +} + +if (isset($options['php-version'])) { + if (!is_string($options['php-version'])) { + die('Expecting a version number in the format x.y' . PHP_EOL); + } + + $project_analyzer->setPhpVersion($options['php-version']); +} + +if (isset($options['codeowner'])) { + if (file_exists('CODEOWNERS')) { + $codeowners_file_path = realpath('CODEOWNERS'); + } elseif (file_exists('.github/CODEOWNERS')) { + $codeowners_file_path = realpath('.github/CODEOWNERS'); + } elseif (file_exists('docs/CODEOWNERS')) { + $codeowners_file_path = realpath('docs/CODEOWNERS'); + } else { + die('Cannot use --codeowner without a CODEOWNERS file' . PHP_EOL); + } + + $codeowners_file = file_get_contents($codeowners_file_path); + + $codeowner_lines = array_map( + function (string $line) : array { + $line_parts = preg_split('/\s+/', $line); + + $file_selector = substr(array_shift($line_parts), 1); + return [$file_selector, $line_parts]; + }, + array_filter( + explode("\n", $codeowners_file), + function (string $line) : bool { + $line = trim($line); + + // currently we don’t match wildcard files or files that could appear anywhere + // in the repo + return $line && $line[0] === '/' && strpos($line, '*') === false; + } + ) + ); + + $codeowner_files = []; + + foreach ($codeowner_lines as [$path, $owners]) { + if (!file_exists($path)) { + continue; + } + + foreach ($owners as $i => $owner) { + $owners[$i] = strtolower($owner); + } + + if (!is_dir($path)) { + if (pathinfo($path, PATHINFO_EXTENSION) === 'php') { + $codeowner_files[$path] = $owners; + } + } else { + foreach ($providers->file_provider->getFilesInDir($path, ['php']) as $php_file_path) { + $codeowner_files[$php_file_path] = $owners; + } + } + } + + if (!$codeowner_files) { + die('Could not find any available entries in CODEOWNERS' . PHP_EOL); + } + + $desired_codeowners = is_array($options['codeowner']) ? $options['codeowner'] : [$options['codeowner']]; + + /** @psalm-suppress MixedAssignment */ + foreach ($desired_codeowners as $desired_codeowner) { + if (!is_string($desired_codeowner)) { + die('Invalid --codeowner ' . (string)$desired_codeowner . PHP_EOL); + } + + if ($desired_codeowner[0] !== '@') { + die('--codeowner option must start with @' . PHP_EOL); + } + + $matched_file = false; + + foreach ($codeowner_files as $file_path => $owners) { + if (in_array(strtolower($desired_codeowner), $owners)) { + $paths_to_check[] = $file_path; + $matched_file = true; + } + } + + if (!$matched_file) { + die('User/group ' . $desired_codeowner . ' does not own any PHP files' . PHP_EOL); + } + } +} + +if (isset($options['allow-backwards-incompatible-changes'])) { + $allow_backwards_incompatible_changes = filter_var( + $options['allow-backwards-incompatible-changes'], + FILTER_VALIDATE_BOOLEAN, + ['flags' => FILTER_NULL_ON_FAILURE] + ); + + if ($allow_backwards_incompatible_changes === null) { + die('--allow-backwards-incompatible-changes expects a boolean value [true|false|1|0]' . PHP_EOL); + } + + $project_analyzer->getCodebase()->allow_backwards_incompatible_changes = $allow_backwards_incompatible_changes; +} + +if (isset($options['add-newline-between-docblock-annotations'])) { + $doc_block_add_new_line_before_return = filter_var( + $options['add-newline-between-docblock-annotations'], + FILTER_VALIDATE_BOOLEAN, + ['flags' => FILTER_NULL_ON_FAILURE] + ); + + if ($doc_block_add_new_line_before_return === null) { + die('--add-newline-between-docblock-annotations expects a boolean value [true|false|1|0]' . PHP_EOL); + } + + \Psalm\Internal\Scanner\ParsedDocblock::addNewLineBetweenAnnotations($doc_block_add_new_line_before_return); +} + +$plugins = []; + +if (isset($options['plugin'])) { + $plugins = $options['plugin']; + + if (!is_array($plugins)) { + $plugins = [$plugins]; + } +} + +/** @var string $plugin_path */ +foreach ($plugins as $plugin_path) { + Config::getInstance()->addPluginPath($current_dir . $plugin_path); +} + +$find_unused_code = array_key_exists('find-unused-code', $options); + +foreach ($keyed_issues as $issue_name => $_) { + // MissingParamType requires the scanning of all files to inform possible params + if (strpos($issue_name, 'Unused') !== false + || $issue_name === 'MissingParamType' + || $issue_name === 'UnnecessaryVarAnnotation' + || $issue_name === 'all' + ) { + $find_unused_code = true; + break; + } +} + +if ($find_unused_code) { + $project_analyzer->getCodebase()->reportUnusedCode(); +} + +$project_analyzer->alterCodeAfterCompletion( + array_key_exists('dry-run', $options), + array_key_exists('safe-types', $options) +); + +if ($keyed_issues === ['all' => true]) { + $project_analyzer->setAllIssuesToFix(); +} else { + try { + $project_analyzer->setIssuesToFix($keyed_issues); + } catch (\Psalm\Exception\UnsupportedIssueToFixException $e) { + fwrite(STDERR, $e->getMessage() . PHP_EOL); + exit(1); + } +} + +$start_time = microtime(true); + +if ($paths_to_check === null || count($paths_to_check) > 1 || $find_unused_code) { + if ($paths_to_check) { + $files_to_update = []; + + foreach ($paths_to_check as $path_to_check) { + if (!is_dir($path_to_check)) { + $files_to_update[] = (string) realpath($path_to_check); + } else { + foreach ($providers->file_provider->getFilesInDir($path_to_check, ['php']) as $php_file_path) { + $files_to_update[] = $php_file_path; + } + } + } + + $project_analyzer->getCodebase()->analyzer->setFilesToUpdate($files_to_update); + } + + $project_analyzer->check($current_dir); +} elseif ($paths_to_check) { + foreach ($paths_to_check as $path_to_check) { + if (is_dir($path_to_check)) { + $project_analyzer->checkDir($path_to_check); + } else { + $project_analyzer->checkFile($path_to_check); + } + } +} + +IssueBuffer::finish($project_analyzer, false, $start_time); diff --git a/lib/composer/vimeo/psalm/src/spl_object_id.php b/lib/composer/vimeo/psalm/src/spl_object_id.php new file mode 100644 index 0000000000000000000000000000000000000000..cb1dfd0d137d26c00d3cec8123408eaa31daf390 --- /dev/null +++ b/lib/composer/vimeo/psalm/src/spl_object_id.php @@ -0,0 +1,52 @@ +isUserDefined()) { + /** + * See https://github.com/runkit7/runkit_object_id for a faster native version for php <= 7.1 + * + * @param object $object + * @return int The object id + */ + function spl_object_id($object) : int + { + return runkit_object_id($object); + } + } elseif (PHP_INT_SIZE === 8) { + /** + * See https://github.com/runkit7/runkit_object_id for a faster native version for php <= 7.1 + * + * @param object $object + * @return int (The object id, XORed with a random number) + */ + function spl_object_id($object) : int + { + $hash = spl_object_hash($object); + // Fit this into a php long (32-bit or 64-bit signed int). + // The first 16 hex digits (64 bytes) vary, the last 16 don't. + // Values are usually padded with 0s at the front. + return intval(substr($hash, 1, 15), 16); + } + } else { + /** + * See https://github.com/runkit7/runkit_object_id for a faster native version for php <= 7.1 + * + * @param object $object + * @return int (The object id, XORed with a random number) + */ + function spl_object_id($object) : int + { + $hash = spl_object_hash($object); + // Fit this into a php long (32-bit or 64-bit signed int). + // The first 16 hex digits (64 bytes) vary, the last 16 don't. + // Values are usually padded with 0s at the front. + return intval(substr($hash, 9, 7), 16); + } + } +} diff --git a/lib/composer/vimeo/psalm/stubs/CoreGenericClasses.phpstub b/lib/composer/vimeo/psalm/stubs/CoreGenericClasses.phpstub new file mode 100644 index 0000000000000000000000000000000000000000..80d09950480c8a93aa6ddea69ddbebfd157c8bce --- /dev/null +++ b/lib/composer/vimeo/psalm/stubs/CoreGenericClasses.phpstub @@ -0,0 +1,2164 @@ + + */ +interface IteratorAggregate extends Traversable { + + /** + * Retrieve an external iterator + * @link http://php.net/manual/en/iteratoraggregate.getiterator.php + * + * @return Traversable An instance of an object implementing Iterator or Traversable + * + * @since 5.0.0 + */ + public function getIterator(); +} + +/** + * Interface for external iterators or objects that can be iterated + * themselves internally. + * @link http://php.net/manual/en/class.iterator.php + * + * @template-covariant TKey + * @template-covariant TValue + * + * @template-extends Traversable + */ +interface Iterator extends Traversable { + + /** + * Return the current element + * @link http://php.net/manual/en/iterator.current.php + * + * @return TValue Can return any type. + * + * @since 5.0.0 + */ + public function current(); + + /** + * Move forward to next element + * @link http://php.net/manual/en/iterator.next.php + * + * @return void Any returned value is ignored. + * + * @since 5.0.0 + */ + public function next(); + + /** + * Return the key of the current element + * @link http://php.net/manual/en/iterator.key.php + * + * @return TKey scalar on success, or null on failure. + * + * @since 5.0.0 + */ + public function key(); + + /** + * Checks if current position is valid + * @link http://php.net/manual/en/iterator.valid.php + * + * @return bool The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + * + * @since 5.0.0 + */ + public function valid(); + + /** + * Rewind the Iterator to the first element + * @link http://php.net/manual/en/iterator.rewind.php + * + * @return void Any returned value is ignored. + * + * @since 5.0.0 + */ + public function rewind(); +} + +/** + * @template-covariant TKey + * @template-covariant TValue + * + * @template-extends Iterator + */ +interface OuterIterator extends Iterator { + /** + * @return Iterator + */ + public function getInnerIterator(); +} + +/** + * @template-covariant TKey + * @template-covariant TValue + * @template TIterator as Traversable + * + * @template-implements OuterIterator + * + * @mixin TIterator + */ +class IteratorIterator implements OuterIterator { + /** + * @param TIterator $iterator + */ + public function __construct(Traversable $iterator) {} + + /** + * @return TIterator + */ + public function getInnerIterator() {} + + /** + * Return the current element + * @link http://php.net/manual/en/iterator.current.php + * + * @return TValue Can return any type. + * + * @since 5.0.0 + */ + public function current() {} + + /** + * Move forward to next element + * @link http://php.net/manual/en/iterator.next.php + * + * @return void Any returned value is ignored. + * + * @since 5.0.0 + */ + public function next() {} + + /** + * Return the key of the current element + * @link http://php.net/manual/en/iterator.key.php + * + * @return TKey scalar on success, or null on failure. + * + * @since 5.0.0 + */ + public function key() {} + + /** + * @return bool The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + * + * @since 5.0.0 + */ + public function valid() {} + + /** + * Rewind the Iterator to the first element + * @link http://php.net/manual/en/iterator.rewind.php + * + * @return void Any returned value is ignored. + * + * @since 5.0.0 + */ + public function rewind() {} +} + +/** + * @template TIterator as RecursiveIterator|IteratorAggregate + * @mixin TIterator + */ +class RecursiveIteratorIterator implements OuterIterator { + /** + * @param TIterator $iterator + * @param int $mode + * @param int $flags + * + * @return void + */ + public function __construct($iterator, $mode = 0, $flags = 0) {} +} + +/** + * @template-covariant TKey + * @template-covariant TValue + * + * @template-extends IteratorIterator + */ +class FilterIterator extends IteratorIterator { + /** @return bool */ + public abstract function accept () {} + } + + +/** + * @template-covariant TKey + * @template-covariant TValue + * + * @template-implements OuterIterator + * @template-implements ArrayAccess + * + * @template-extends IteratorIterator + */ +class CachingIterator extends IteratorIterator implements OuterIterator , ArrayAccess , Countable { + /** + * @param Iterator $iterator + */ + public function __construct(Iterator $iterator, int $flags = self::CALL_TOSTRING) {} + + /** @return bool */ + public function hasNext () {} + + /** + * @return TValue Can return any type. + */ + public function current() {} + + /** + * @return TKey scalar on success, or null on failure. + */ + public function key() {} +} + +/** + * @template-covariant TKey + * @template-covariant TValue + * + * @template-implements OuterIterator + * + * @template-extends IteratorIterator + */ +class InfiniteIterator extends IteratorIterator implements OuterIterator { + /** + * @param Iterator $iterator + */ + public function __construct(Iterator $iterator) {} + + /** + * @return TValue Can return any type. + */ + public function current() {} + + /** + * @return TKey scalar on success, or null on failure. + */ + public function key() {} +} + +/** + * @template-covariant TKey + * @template-covariant TValue + * + * @template-implements OuterIterator + * + * @template-extends IteratorIterator + */ +class LimitIterator extends IteratorIterator implements OuterIterator { + /** + * @param Iterator $iterator + */ + public function __construct(Iterator $iterator, int $offset = 0, int $count = -1) {} + + /** + * @return TValue Can return any type. + */ + public function current() {} + + /** + * @return TKey scalar on success, or null on failure. + */ + public function key() {} +} + +/** + * @template-covariant TKey + * @template-covariant TValue + * + * @template-implements OuterIterator + * + * @template-extends FilterIterator + */ +class CallbackFilterIterator extends FilterIterator implements OuterIterator { + /** + * @param Iterator $iterator + * @param callable(TValue, TKey, Iterator): bool $callback + */ + public function __construct(Iterator $iterator, callable $callback) {} + + /** + * @return TValue Can return any type. + */ + public function current() {} + + /** + * @return TKey scalar on success, or null on failure. + */ + public function key() {} +} + +/** + * @template-covariant TKey + * @template-covariant TValue + * + * @template-extends IteratorIterator + */ +class NoRewindIterator extends IteratorIterator { + /** + * @param Iterator $iterator + */ + public function __construct(Iterator $iterator) {} + + /** + * @return TValue Can return any type. + */ + public function current() {} + + /** + * @return TKey scalar on success, or null on failure. + */ + public function key() {} +} + +/** + * @template-covariant TKey + * @template-covariant TValue + * @template TSend + * @template-covariant TReturn + * + * @template-implements Traversable + */ +class Generator implements Traversable { + /** + * @return TValue Can return any type. + */ + public function current() {} + + /** + * @return void Any returned value is ignored. + */ + public function next() {} + + /** + * @return TKey scalar on success, or null on failure. + */ + public function key() {} + + /** + * @return bool The return value will be casted to boolean and then evaluated. + */ + public function valid() {} + + /** + * @return void Any returned value is ignored. + */ + public function rewind() {} + + /** + * @return TReturn Can return any type. + */ + public function getReturn() {} + + /** + * @param TSend $value + * @return TValue Can return any type. + */ + public function send($value) {} + + /** + * @return TValue Can return any type. + */ + public function throw(Throwable $exception) {} +} + +/** + * Interface to provide accessing objects as arrays. + * @link http://php.net/manual/en/class.arrayaccess.php + * + * @template TKey + * @template TValue + */ +interface ArrayAccess { + + /** + * Whether a offset exists + * @link http://php.net/manual/en/arrayaccess.offsetexists.php + * + * @param TKey $offset An offset to check for. + * @return bool true on success or false on failure. + * The return value will be casted to boolean if non-boolean was returned. + * + * @since 5.0.0 + */ + public function offsetExists($offset); + + /** + * Offset to retrieve + * @link http://php.net/manual/en/arrayaccess.offsetget.php + * + * @param TKey $offset The offset to retrieve. + * @return TValue|null Can return all value types. + * @psalm-ignore-nullable-return + * + * @since 5.0.0 + */ + public function offsetGet($offset); + + /** + * Offset to set + * @link http://php.net/manual/en/arrayaccess.offsetset.php + * + * @param TKey $offset The offset to assign the value to. + * @param TValue $value The value to set. + * @return void + * + * @since 5.0.0 + */ + public function offsetSet($offset, $value); + + /** + * Offset to unset + * @link http://php.net/manual/en/arrayaccess.offsetunset.php + * + * @param TKey $offset The offset to unset. + * @return void + * + * @since 5.0.0 + */ + public function offsetUnset($offset); +} + +/** + * This class allows objects to work as arrays. + * @link http://php.net/manual/en/class.arrayobject.php + * + * @template TKey + * @template TValue + * @template-implements IteratorAggregate + * @template-implements ArrayAccess + */ +class ArrayObject implements IteratorAggregate, ArrayAccess, Serializable, Countable { + /** + * Properties of the object have their normal functionality when accessed as list (var_dump, foreach, etc.). + */ + const STD_PROP_LIST = 1; + + /** + * Entries can be accessed as properties (read and write). + */ + const ARRAY_AS_PROPS = 2; + + /** + * Construct a new array object + * @link http://php.net/manual/en/arrayobject.construct.php + * + * @param array|object $input The input parameter accepts an array or an Object. + * @param int $flags Flags to control the behaviour of the ArrayObject object. + * @param string $iterator_class Specify the class that will be used for iteration of the ArrayObject object. ArrayIterator is the default class used. + * @psalm-param class-string>|class-string> $iterator_class + * + * @since 5.0.0 + */ + public function __construct($input = null, $flags = 0, $iterator_class = "ArrayIterator") { } + + /** + * Returns whether the requested index exists + * @link http://php.net/manual/en/arrayobject.offsetexists.php + * + * @param TKey $index The index being checked. + * @return bool true if the requested index exists, otherwise false + * + * @since 5.0.0 + */ + public function offsetExists($index) { } + + /** + * Returns the value at the specified index + * @link http://php.net/manual/en/arrayobject.offsetget.php + * + * @param TKey $index The index with the value. + * @return TValue The value at the specified index or false. + * + * @since 5.0.0 + */ + public function offsetGet($index) { } + + /** + * Sets the value at the specified index to newval + * @link http://php.net/manual/en/arrayobject.offsetset.php + * + * @param TKey $index The index being set. + * @param TValue $newval The new value for the index. + * @return void + * + * @since 5.0.0 + */ + public function offsetSet($index, $newval) { } + + /** + * Unsets the value at the specified index + * @link http://php.net/manual/en/arrayobject.offsetunset.php + * + * @param TKey $index The index being unset. + * @return void + * + * @since 5.0.0 + */ + public function offsetUnset($index) { } + + /** + * Appends the value + * @link http://php.net/manual/en/arrayobject.append.php + * + * @param TValue $value The value being appended. + * @return void + * + * @since 5.0.0 + */ + public function append($value) { } + + /** + * Creates a copy of the ArrayObject. + * @link http://php.net/manual/en/arrayobject.getarraycopy.php + * + * @return array a copy of the array. When the ArrayObject refers to an object + * an array of the public properties of that object will be returned. + * + * @since 5.0.0 + */ + public function getArrayCopy() { } + + /** + * Get the number of public properties in the ArrayObject + * When the ArrayObject is constructed from an array all properties are public. + * @link http://php.net/manual/en/arrayobject.count.php + * + * @return int The number of public properties in the ArrayObject. + * + * @since 5.0.0 + */ + public function count() { } + + /** + * Gets the behavior flags. + * @link http://php.net/manual/en/arrayobject.getflags.php + * + * @return int the behavior flags of the ArrayObject. + * + * @since 5.1.0 + */ + public function getFlags() { } + + /** + * Sets the behavior flags. + * + * It takes on either a bitmask, or named constants. Using named + * constants is strongly encouraged to ensure compatibility for future + * versions. + * + * The available behavior flags are listed below. The actual + * meanings of these flags are described in the + * predefined constants. + * + * + * ArrayObject behavior flags + * + * + * + * + * + * + * + * + * + * + * + * + *
valueconstant
1 + * ArrayObject::STD_PROP_LIST + *
2 + * ArrayObject::ARRAY_AS_PROPS + *
+ * + * @link http://php.net/manual/en/arrayobject.setflags.php + * + * @param int $flags The new ArrayObject behavior. + * @return void + * + * @since 5.1.0 + */ + public function setFlags($flags) { } + + /** + * Sort the entries by value + * @link http://php.net/manual/en/arrayobject.asort.php + * + * @return void + * + * @since 5.2.0 + */ + public function asort() { } + + /** + * Sort the entries by key + * @link http://php.net/manual/en/arrayobject.ksort.php + * + * @return void + * + * @since 5.2.0 + */ + public function ksort() { } + + /** + * Sort the entries with a user-defined comparison function and maintain key association + * @link http://php.net/manual/en/arrayobject.uasort.php + * + * Function cmp_function should accept two + * parameters which will be filled by pairs of entries. + * The comparison function must return an integer less than, equal + * to, or greater than zero if the first argument is considered to + * be respectively less than, equal to, or greater than the + * second. + * + * @param callable(TValue, TValue):int $cmp_function + * @return void + * + * @since 5.2.0 + */ + public function uasort($cmp_function) { } + + /** + * Sort the entries by keys using a user-defined comparison function + * @link http://php.net/manual/en/arrayobject.uksort.php + * + * Function cmp_function should accept two + * parameters which will be filled by pairs of entry keys. + * The comparison function must return an integer less than, equal + * to, or greater than zero if the first argument is considered to + * be respectively less than, equal to, or greater than the + * second. + * + * @param callable(TKey, TKey):int $cmp_function The callable comparison function. + * @return void + * + * @since 5.2.0 + */ + public function uksort($cmp_function) { } + + /** + * Sort entries using a "natural order" algorithm + * @link http://php.net/manual/en/arrayobject.natsort.php + * + * @return void + * + * @since 5.2.0 + */ + public function natsort() { } + + /** + * Sort an array using a case insensitive "natural order" algorithm + * @link http://php.net/manual/en/arrayobject.natcasesort.php + * + * @return void + * + * @since 5.2.0 + */ + public function natcasesort() { } + + /** + * Unserialize an ArrayObject + * @link http://php.net/manual/en/arrayobject.unserialize.php + * + * @param string $serialized The serialized ArrayObject + * @return void The unserialized ArrayObject + * + * @since 5.3.0 + */ + public function unserialize($serialized) { } + + /** + * Serialize an ArrayObject + * @link http://php.net/manual/en/arrayobject.serialize.php + * + * @return string The serialized representation of the ArrayObject. + * + * @since 5.3.0 + */ + public function serialize() { } + + /** + * Create a new iterator from an ArrayObject instance + * @link http://php.net/manual/en/arrayobject.getiterator.php + * + * @return ArrayIterator An iterator from an ArrayObject. + * + * @since 5.0.0 + */ + public function getIterator() { } + + /** + * Exchange the array for another one. + * @link http://php.net/manual/en/arrayobject.exchangearray.php + * + * @param mixed $input The new array or object to exchange with the current array. + * @return array the old array. + * + * @since 5.1.0 + */ + public function exchangeArray($input) { } + + /** + * Sets the iterator classname for the ArrayObject. + * @link http://php.net/manual/en/arrayobject.setiteratorclass.php + * + * @param string $iterator_class The classname of the array iterator to use when iterating over this object. + * @psalm-param class-string>|class-string> $iterator_class + * @return void + * + * @since 5.1.0 + */ + public function setIteratorClass($iterator_class) { } + + /** + * Gets the iterator classname for the ArrayObject. + * @link http://php.net/manual/en/arrayobject.getiteratorclass.php + * + * @return string the iterator class name that is used to iterate over this object. + * @psalm-return class-string>|class-string> + * + * @since 5.1.0 + */ + public function getIteratorClass() { } +} + +/** + * The Seekable iterator. + * @link https://php.net/manual/en/class.seekableiterator.php + * + * @template-covariant TKey + * @template-covariant TValue + * @template-extends Iterator + */ +interface SeekableIterator extends Iterator { + /** + * Seeks to a position + * @link https://php.net/manual/en/seekableiterator.seek.php + * + * @param int $position The position to seek to. + * @return void + * + * @since 5.1.0 + */ + public function seek($position); +} + +/** + * This iterator allows to unset and modify values and keys while iterating + * over Arrays and Objects. + * @link http://php.net/manual/en/class.arrayiterator.php + * + * @template TKey as array-key + * @template TValue + * @template-implements SeekableIterator + * @template-implements ArrayAccess + */ +class ArrayIterator implements SeekableIterator, ArrayAccess, Serializable, Countable { + const STD_PROP_LIST = 1; + const ARRAY_AS_PROPS = 2; + + /** + * Construct an ArrayIterator + * @link http://php.net/manual/en/arrayiterator.construct.php + * + * @param array $array The array or object to be iterated on. + * @param int $flags Flags to control the behaviour of the ArrayObject object. + * + * @see ArrayObject::setFlags() + * @since 5.0.0 + */ + public function __construct($array = array(), $flags = 0) { } + + /** + * Check if offset exists + * @link http://php.net/manual/en/arrayiterator.offsetexists.php + * + * @param TKey $index The offset being checked. + * @return bool true if the offset exists, otherwise false + * + * @since 5.0.0 + */ + public function offsetExists($index) { } + + /** + * Get value for an offset + * @link http://php.net/manual/en/arrayiterator.offsetget.php + * + * @param TKey $index The offset to get the value from. + * @return TValue The value at offset index. + * + * @since 5.0.0 + */ + public function offsetGet($index) { } + + /** + * Set value for an offset + * @link http://php.net/manual/en/arrayiterator.offsetset.php + * + * @param TKey $index The index to set for. + * @param TValue $newval The new value to store at the index. + * @return void + * + * @since 5.0.0 + */ + public function offsetSet($index, $newval) { } + + /** + * Unset value for an offset + * @link http://php.net/manual/en/arrayiterator.offsetunset.php + * + * @param TKey $index The offset to unset. + * @return void + * + * @since 5.0.0 + */ + public function offsetUnset($index) { } + + /** + * Append an element + * @link http://php.net/manual/en/arrayiterator.append.php + * + * @param TValue $value The value to append. + * @return void + * + * @since 5.0.0 + */ + public function append($value) { } + + /** + * Get array copy + * @link http://php.net/manual/en/arrayiterator.getarraycopy.php + * + * @return array A copy of the array, or array of public properties + * if ArrayIterator refers to an object. + * + * @since 5.0.0 + */ + public function getArrayCopy() { } + + /** + * Count elements + * @link http://php.net/manual/en/arrayiterator.count.php + * + * @return int The number of elements or public properties in the associated + * array or object, respectively. + * + * @since 5.0.0 + */ + public function count() { } + + /** + * Get flags + * @link http://php.net/manual/en/arrayiterator.getflags.php + * + * @return string The current flags. + * + * @since 5.1.0 + */ + public function getFlags() { } + + /** + * Set behaviour flags + * + * A bitmask as follows: + * 0 = Properties of the object have their normal functionality + * when accessed as list (var_dump, foreach, etc.). + * 1 = Array indices can be accessed as properties in read/write. + * + * @link http://php.net/manual/en/arrayiterator.setflags.php + * + * @param string $flags bitmask + * @return void + * + * @since 5.1.0 + */ + public function setFlags($flags) { } + + /** + * Sort array by values + * @link http://php.net/manual/en/arrayiterator.asort.php + * + * @return void + * + * @since 5.2.0 + */ + public function asort() { } + + /** + * Sort array by keys + * @link http://php.net/manual/en/arrayiterator.ksort.php + * + * @return void + * + * @since 5.2.0 + */ + public function ksort() { } + + /** + * User defined sort + * @link http://php.net/manual/en/arrayiterator.uasort.php + * + * @param callable(TValue,TValue):int $cmp_function The compare function used for the sort. + * @return void + * + * @since 5.2.0 + */ + public function uasort($cmp_function) { } + + /** + * User defined sort + * @link http://php.net/manual/en/arrayiterator.uksort.php + * + * @param callable(TKey,TKey):int $cmp_function The compare function used for the sort. + * @return void + * + * @since 5.2.0 + */ + public function uksort($cmp_function) { } + + /** + * Sort an array naturally + * @link http://php.net/manual/en/arrayiterator.natsort.php + * + * @return void + * + * @since 5.2.0 + */ + public function natsort() { } + + /** + * Sort an array naturally, case insensitive + * @link http://php.net/manual/en/arrayiterator.natcasesort.php + * + * @return void + * + * @since 5.2.0 + */ + public function natcasesort() { } + + /** + * Unserialize + * @link http://php.net/manual/en/arrayiterator.unserialize.php + * + * @param string $serialized The serialized ArrayIterator object to be unserialized. + * @return void + * + * @since 5.3.0 + */ + public function unserialize($serialized) { } + + /** + * Serialize + * @link http://php.net/manual/en/arrayiterator.serialize.php + * + * @return string The serialized ArrayIterator + * + * @since 5.3.0 + */ + public function serialize() { } + + /** + * Rewind array back to the start + * @link http://php.net/manual/en/arrayiterator.rewind.php + * + * @return void + * + * @since 5.0.0 + */ + public function rewind() { } + + /** + * Return current array entry + * @link http://php.net/manual/en/arrayiterator.current.php + * + * @return TValue The current array entry. + * + * @since 5.0.0 + */ + public function current() { } + + /** + * Return current array key + * @link http://php.net/manual/en/arrayiterator.key.php + * + * @return TKey The current array key. + * + * @since 5.0.0 + */ + public function key() { } + + /** + * Move to next entry + * @link http://php.net/manual/en/arrayiterator.next.php + * + * @return void + * + * @since 5.0.0 + */ + public function next() { } + + /** + * Check whether array contains more entries + * @link http://php.net/manual/en/arrayiterator.valid.php + * + * @return bool + * + * @since 5.0.0 + */ + public function valid() { } + + /** + * Seek to position + * @link http://php.net/manual/en/arrayiterator.seek.php + * + * @param int $position The position to seek to. + * @return void + * + * @since 5.0.0 + */ + public function seek($position) { } +} + +/** + * The DOMElement class + * @link http://php.net/manual/en/class.domelement.php + */ +class DOMDocument extends DOMNode { + /** + * @return DOMNodeList + */ + public function getElementsByTagName($name) {} + + /** + * @return DOMNodeList + */ + public function getElementsByTagNameNS($namespaceURI, $localName) {} +} + +/** + * The DOMElement class + * @link http://php.net/manual/en/class.domelement.php + */ +class DOMElement extends DOMNode { + public function __construct(string $name, string $value = '', string $namespaceURI = '') {} + + /** + * @return DOMNodeList + */ + public function getElementsByTagName($name) {} + /** + * @return DOMNodeList + */ + public function getElementsByTagNameNS($namespaceURI, $localName) {} +} + +/** + * @template-covariant TNode as DOMNode + * @template-implements Traversable + */ +class DOMNodeList implements Traversable, Countable { + /** + * The number of nodes in the list. The range of valid child node indices is 0 to length - 1 inclusive. + * + * @var int + * + * @since 5.0 + * @link http://php.net/manual/en/class.domnodelist.php#domnodelist.props.length + */ + public $length; + + /** + * @param int $index + * @return TNode|null + * @psalm-ignore-nullable-return + */ + public function item($index) {} +} + +/** + * @template-covariant TNode as DOMNode + * @template-implements Traversable + */ +class DOMNamedNodeMap implements Traversable, Countable { + /** + * @var int + */ + public $length; + + /** + * @return TNode|null + */ + public function getNamedItem(string $name): ?DOMNode {} + + /** + * @return TNode|null + */ + public function getNamedItemNS(string $namespaceURI, string $localName): ?DOMNode {} + + /** + * @return TNode|null + * @psalm-ignore-nullable-return + */ + public function item(int $index): ?DOMNode {} +} + +/** + * The SplDoublyLinkedList class provides the main functionalities of a doubly linked list. + * @link https://php.net/manual/en/class.spldoublylinkedlist.php + * + * @template TKey + * @template TValue + * @template-implements Iterator + * @template-implements ArrayAccess + */ +class SplDoublyLinkedList implements Iterator, Countable, ArrayAccess, Serializable +{ + public function __construct() {} + + /** + * Add/insert a new value at the specified index + * + * @param TKey $index The index where the new value is to be inserted. + * @param TValue $newval The new value for the index. + * @return void + * + * @link https://php.net/spldoublylinkedlist.add + * @since 5.5.0 + */ + public function add($index, $newval) {} + + /** + * Pops a node from the end of the doubly linked list + * @link https://php.net/manual/en/spldoublylinkedlist.pop.php + * + * @return TValue The value of the popped node. + * + * @since 5.3.0 + */ + public function pop() {} + + /** + * Shifts a node from the beginning of the doubly linked list + * @link https://php.net/manual/en/spldoublylinkedlist.shift.php + * + * @return TValue The value of the shifted node. + * + * @since 5.3.0 + */ + public function shift() {} + + /** + * Pushes an element at the end of the doubly linked list + * @link https://php.net/manual/en/spldoublylinkedlist.push.php + * + * @param TValue $value The value to push. + * @return void + * + * @since 5.3.0 + */ + public function push($value) {} + + /** + * Prepends the doubly linked list with an element + * @link https://php.net/manual/en/spldoublylinkedlist.unshift.php + * + * @param TValue $value The value to unshift. + * @return void + * + * @since 5.3.0 + */ + public function unshift($value) {} + + /** + * Peeks at the node from the end of the doubly linked list + * @link https://php.net/manual/en/spldoublylinkedlist.top.php + * + * @return TValue The value of the last node. + * + * @since 5.3.0 + */ + public function top() {} + + /** + * Peeks at the node from the beginning of the doubly linked list + * @link https://php.net/manual/en/spldoublylinkedlist.bottom.php + * + * @return TValue The value of the first node. + * + * @since 5.3.0 + */ + public function bottom() {} + + /** + * Counts the number of elements in the doubly linked list. + * @link https://php.net/manual/en/spldoublylinkedlist.count.php + * + * @return int the number of elements in the doubly linked list. + * + * @since 5.3.0 + */ + public function count() {} + + /** + * Checks whether the doubly linked list is empty. + * @link https://php.net/manual/en/spldoublylinkedlist.isempty.php + * + * @return bool whether the doubly linked list is empty. + * + * @since 5.3.0 + */ + public function isEmpty() {} + + /** + * Returns whether the requested $index exists + * @link https://php.net/manual/en/spldoublylinkedlist.offsetexists.php + * + * @param TKey $index The index being checked. + * @return bool true if the requested index exists, otherwise false + * + * @since 5.3.0 + */ + public function offsetExists($index) {} + + /** + * Returns the value at the specified $index + * @link https://php.net/manual/en/spldoublylinkedlist.offsetget.php + * + * @param TKey $index The index with the value. + * @return TValue The value at the specified index. + * + * @since 5.3.0 + */ + public function offsetGet($index) {} + + /** + * Sets the value at the specified $index to $newval + * @link https://php.net/manual/en/spldoublylinkedlist.offsetset.php + * + * @param TKey $index The index being set. + * @param TValue $newval The new value for the index. + * @return void + * + * @since 5.3.0 + */ + public function offsetSet($index, $newval) {} + + /** + * Unsets the value at the specified $index + * @link https://php.net/manual/en/spldoublylinkedlist.offsetunset.php + * + * @param TKey $index The index being unset. + * @return void + * + * @since 5.3.0 + */ + public function offsetUnset($index) {} + + /** + * Return current array entry + * @link https://php.net/manual/en/spldoublylinkedlist.current.php + * + * @return TValue The current node value. + * + * @since 5.3.0 + */ + public function current() {} + + /** + * Return current node index + * @link https://php.net/manual/en/spldoublylinkedlist.key.php + * + * @return TKey The current node index. + * + * @since 5.3.0 + */ + public function key() {} +} + +/** + * The SplFixedArray class provides the main functionalities of array. + * The main differences between a SplFixedArray and a normal PHP array is that + * the SplFixedArray is of fixed length and allows only integers within the range as indexes. + * The advantage is that it uses less memory than a standard array. + * + * @link https://php.net/manual/en/class.splfixedarray.php + * + * @template TValue + * @template-implements ArrayAccess + * @template-implements Iterator + */ +class SplFixedArray implements Iterator, ArrayAccess, Countable { + /** + * Constructs a new fixed array + * + * Initializes a fixed array with a number of NULL values equal to size. + * @link https://php.net/manual/en/splfixedarray.construct.php + * + * @param int $size The size of the fixed array. This expects a number between 0 and PHP_INT_MAX. + + * @since 5.3.0 + */ + public function __construct(int $size = 0) {} + + /** + * Import a PHP array in a new SplFixedArray instance + * @link https://php.net/manual/en/splfixedarray.fromarray.php + * + * @template TInValue + * @param array $array The array to import + * @param bool $save_indexes [optional] Try to save the numeric indexes used in the original array. + * + * @return SplFixedArray Instance of SplFixedArray containing the array content + + * @since 5.3.0 + */ + public static function fromArray(array $array, bool $save_indexes = true): SplFixedArray {} + + /** + * Returns a PHP array from the fixed array + * @link https://php.net/manual/en/splfixedarray.toarray.php + * + * @return array + + * @since 5.3.0 + */ + public function toArray(): array {} + + /** + * Returns the size of the array. + * @link https://php.net/manual/en/splfixedarray.getsize.php + * + * @return int The size of the array + + * @see SplFixedArray::count() + * + * @since 5.3.0 + */ + public function getSize(): int {} + + /** + * Returns the size of the array. + * @link https://php.net/manual/en/splfixedarray.count.php + * + * @return int The size of the array + * + * @since 5.3.0 + */ + public function count(): int {} + + /** + * Rewind the iterator back to the start + * @link https://php.net/manual/en/splfixedarray.rewind.php + * + * @return void + * + * @since 5.3.0 + */ + public function rewind(): void {} + + /** + * Check whether the array contains more elements + * @link https://php.net/manual/en/splfixedarray.valid.php + * + * @return bool true if the array contains any more elements, false otherwise. + * + * @since 5.3.0 + */ + public function valid(): bool {} + + /** + * Returns current array index + * @link https://php.net/manual/en/splfixedarray.key.php + * + * @return int The current array index + * + * @since 5.3.0 + */ + public function key(): int {} + + /** + * Returns the current array entry + * @link https://php.net/manual/en/splfixedarray.current.php + * + * @return TValue The current element value + * + * @since 5.3.0 + */ + public function current() {} + + /** + * Move to the next entry + * @link https://php.net/manual/en/splfixedarray.next.php + * + * @return void + * + * @since 5.3.0 + */ + public function next(): void {} + + /** + * Returns whether the specified index exists + * @link https://php.net/manual/en/splfixedarray.offsetexists.php + * + * @param int $index The index being checked. + * @return bool true if the requested index exists, and false otherwise. + * + * @since 5.3.0 + */ + public function offsetExists(int $index): bool {} + + /** + * Sets a new value at a specified index + * @link https://php.net/manual/en/splfixedarray.offsetset.php + * + * @param int $index The index being sent. + * @param TValue $newval The new value for the index + * @return void + * + * @since 5.3.0 + */ + public function offsetSet(int $index, $newval): void {} + + /** + * Unsets the value at the specified $index + * @link https://php.net/manual/en/splfixedarray.offsetunset.php + * + * @param int $index The index being unset + * @return void + * + * @since 5.3.0 + */ + public function offsetUnset(int $index): void {} + + /** + * Returns the value at the specified index + * @link https://php.net/manual/en/splfixedarray.offsetget.php + * + * @param int $index The index with the value + * @return TValue The value at the specified index + * + * @since 5.3.0 + */ + public function offsetGet(int $index) {} +} + + +/** + * The SplStack class provides the main functionalities of a stack implemented using a doubly linked list. + * @link https://php.net/manual/en/class.splstack.php + * + * @template TValue + * @template-extends SplDoublyLinkedList + */ +class SplStack extends SplDoublyLinkedList { +} + +/** + * The SplQueue class provides the main functionalities of a queue implemented using a doubly linked list. + * @link https://php.net/manual/en/class.splqueue.php + * + * @template TValue + * @template-extends SplDoublyLinkedList + */ +class SplQueue extends SplDoublyLinkedList { + /** + * Adds an element to the queue. + * @link https://php.net/manual/en/splqueue.enqueue.php + * + * @param TValue $value The value to enqueue. + * @return void + * + * @since 5.3.0 + */ + public function enqueue($value) {} + + /** + * Dequeues a node from the queue + * @link https://php.net/manual/en/splqueue.dequeue.php + * + * @return TValue The value of the dequeued node. + * + * @since 5.3.0 + */ + public function dequeue() {} +} + +/** + * The SplHeap class provides the main functionalities of a Heap. + * @link https://php.net/manual/en/class.splheap.php + * + * @template TValue + * @template-implements Iterator + */ +class SplHeap implements Iterator, Countable { + public function __construct() {} + + /** + * Compare elements in order to place them correctly in the heap while sifting up + * @link https://php.net/manual/en/splheap.compare.php + * + * @param TValue $value1 The value of the first node being compared. + * @param TValue $value2 The value of the second node being compared. + * @return int Positive integer if value1 is greater than value2, 0 if they are equal, negative integer otherwise. + * + * @since 5.3.0 + */ + protected abstract function compare($value1, $value2): int {} + + /** + * Counts the number of elements in the heap + * @link https://php.net/manual/en/splheap.count.php + * + * @return int The number of elements in the heap. + * + * @since 5.3.0 + */ + public function count(): int {} + + /** + * Get the current datastructure node. + * @link https://php.net/manual/en/splheap.current.php + * + * @return TValue The current node value + * + * @since 5.3.0 + */ + public function current() {} + + /** + * Extracts a node from top of the heap and sift up + * @link https://php.net/manual/en/splheap.extract.php + * + * @return TValue The current node value + * + * @since 5.3.0 + */ + public function extract() {} + + /** + * Inserts an element in the heap by sifting it up + * @link https://php.net/manual/en/splheap.insert.php + * + * @param TValue $value The value to insert. + * @return void + * + * @since 5.3.0 + */ + public function insert($value): void {} + + /** + * Tells if the heap is in a corrupted state + * @link https://php.net/manual/en/splheap.isCorrupted.php + * + * @return bool true if the heap is corrupted, false otherwise. + * + * @since 7.0.0 + */ + public function isCorrupted(): bool {} + + /** + * Checks whether the heap is empty + * @link https://php.net/manual/en/splheap.isEmpty.php + * + * @return bool Whether the heap is empty + * + * @since 5.3.0 + */ + public function isEmpty(): bool {} + + /** + * Return current node index + * @link https://php.net/manual/en/splheap.key.php + * + * @return int The current node index + * + * @since 5.3.0 + */ + public function key() {} + + /** + * Move to the next node. This will delete the top node of the heap. + * @link https://php.net/manual/en/splheap.next.php + * + * @return void + * + * @since 5.3.0 + */ + public function next(): void {} + + /** + * Recover from the corrupted state and allow further actions on the heap + * @link https://php.net/manual/en/splheap.recoverFromCorruption.php + * + * @return void + * + * @since 5.3.0 + */ + public function recoverFromCorruption(): void {} + + /** + * Rewind iterator back to the start (no-op) + * @link https://php.net/manual/en/splheap.rewind.php + * + * @return void + * + * @since 5.3.0 + */ + public function rewind(): void {} + + /** + * Peeks at the node from the top of the heap + * @link https://php.net/manual/en/splheap.top.php + * + * @return TValue The value of the node on the top. + * + * @since 5.3.0 + */ + public function top() {} + + /** + * Check whether the heap contains any more nodes + * @link https://php.net/manual/en/splheap.valid.php + * + * @return bool Returns true if the heap contains any more nodes, false otherwise. + * + * @since 5.3.0 + */ + public function valid(): bool {} +} + + +/** + * The SplMaxHeap class provides the main functionalities of a heap, keeping the maximum on the top. + * @link https://php.net/manual/en/class.splmaxheap.php + * + * @template TValue + * @template-extends SplHeap + */ +class SplMaxHeap extends SplHeap { +} + +/** + * The SplMinHeap class provides the main functionalities of a heap, keeping the maximum on the top. + * @link https://php.net/manual/en/class.splminheap.php + * + * @template TValue + * @template-extends SplHeap + */ +class SplMinHeap extends SplHeap { +} + +/** + * The SplPriorityQueue class provides the main functionalities of a prioritized queue, implemented using a max heap. + * @link https://php.net/manual/en/class.splpriorityqueue.php + * + * @template TPriority + * @template TValue + * @template-implements Iterator + */ +class SplPriorityQueue implements Iterator, Countable { + /** + * Extract the data + */ + const EXTR_DATA = 0x00000001; + /** + * Extract the priority + */ + const EXTR_PRIORITY = 0x00000002; + /** + * Extract an array containing both + */ + const EXTR_BOTH = 0x00000003; + + public function __construct() {} + + /** + * Compare priorities in order to place them correctly in the queue while sifting up + * @link https://php.net/manual/en/splpriorityqueue.compare.php + * + * @param TValue $priority1 The priority of the first node being compared. + * @param TValue $priority2 The priority of the second node being compared. + * @return int Positive integer if priority1 is greater than priority2, 0 if they are equal, negative integer otherwise. + * + * @since 5.3.0 + */ + public function compare($priority1, $priority2): int {} + + /** + * Counts the number of elements in the queue + * @link https://php.net/manual/en/splpriorityqueue.count.php + * + * @return int The number of elements in the queue. + * + * @since 5.3.0 + */ + public function count(): int {} + + /** + * Get the current datastructure node. + * @link https://php.net/manual/en/splpriorityqueue.current.php + * + * @return TValue The current node value + * + * @since 5.3.0 + */ + public function current() {} + + /** + * Extracts a node from top of the queue and sift up + * @link https://php.net/manual/en/splpriorityqueue.extract.php + * + * @return TValue The current node value + * + * @since 5.3.0 + */ + public function extract() {} + + /** + * Get the flags of extraction + * @link https://php.net/manual/en/splpriorityqueue.getextractflags.php + * + * @return SplPriorityQueue::EXTR_* Returns the current extraction mode + * + * @see SplPriorityQueue::setExtractFlags + * + * @since 5.3.0 + */ + public function getExtractFlags(): int {} + + /** + * Inserts an element in the queue by sifting it up + * @link https://php.net/manual/en/splpriorityqueue.insert.php + * + * @param TValue $value The value to insert. + * @param TPriority $priority The associated priority. + * @return true + * + * @since 5.3.0 + */ + public function insert($value, $priority): bool {} + + /** + * Tells if the queue is in a corrupted state + * @link https://php.net/manual/en/splpriorityqueue.isCorrupted.php + * + * @return bool true if the queue is corrupted, false otherwise. + * + * @since 7.0.0 + */ + public function isCorrupted(): bool {} + + /** + * Checks whether the queue is empty + * @link https://php.net/manual/en/splpriorityqueue.isEmpty.php + * + * @return bool Whether the queue is empty + * + * @since 5.3.0 + */ + public function isEmpty(): bool {} + + /** + * Return current node index + * @link https://php.net/manual/en/splpriorityqueue.key.php + * + * @return int The current node index + * + * @since 5.3.0 + */ + public function key() {} + + /** + * Move to the next node. + * @link https://php.net/manual/en/splpriorityqueue.next.php + * + * @return void + * + * @since 5.3.0 + */ + public function next(): void {} + + /** + * Recover from the corrupted state and allow further actions on the queue + * @link https://php.net/manual/en/splpriorityqueue.recoverFromCorruption.php + * + * @return void + * + * @since 5.3.0 + */ + public function recoverFromCorruption(): void {} + + /** + * Rewind iterator back to the start (no-op) + * @link https://php.net/manual/en/splpriorityqueue.rewind.php + * + * @return void + * + * @since 5.3.0 + */ + public function rewind(): void {} + + /** + * Sets the mode of extraction + * @link https://php.net/manual/en/splpriorityqueue.setextractflags.php + * + * @param SplPriorityQueue::EXTR_* $flags Defines what is extracted by SplPriorityQueue::current(), SplPriorityQueue::top() and SplPriorityQueue::extract(). + * + * @return void + * + * @since 5.3.0 + */ + public function setExtractFlags(int $flags): void {} + + /** + * Peeks at the node from the top of the queue + * @link https://php.net/manual/en/splpriorityqueue.top.php + * + * @return TValue The value of the node on the top. + * + * @since 5.3.0 + */ + public function top() {} + + /** + * Check whether the queue contains any more nodes + * @link https://php.net/manual/en/splpriorityqueue.valid.php + * + * @return bool Returns true if the queue contains any more nodes, false otherwise. + * + * @since 5.3.0 + */ + public function valid(): bool {} +} + + +/** + * The SplObjectStorage class provides a map from objects to data or, by + * ignoring data, an object set. This dual purpose can be useful in many + * cases involving the need to uniquely identify objects. + * @link https://php.net/manual/en/class.splobjectstorage.php + * + * @template TObject as object + * @template TArrayValue + * @template-implements ArrayAccess + * @template-implements Iterator + */ +class SplObjectStorage implements Countable, Iterator, Serializable, ArrayAccess { + public function __construct() {} + + /** + * Adds an object in the storage + * @link https://php.net/manual/en/splobjectstorage.attach.php + * + * @param TObject $object The object to add. + * @param TArrayValue|null $data [optional] The data to associate with the object. + * @return void + * + * @since 5.1.0 + */ + public function attach($object, $data = null) {} + + /** + * Removes an object from the storage + * @link https://php.net/manual/en/splobjectstorage.detach.php + * + * @param TObject $object The object to remove. + * @return void + * + * @since 5.1.0 + */ + public function detach($object) {} + + /** + * Checks if the storage contains a specific object + * @link https://php.net/manual/en/splobjectstorage.contains.php + * + * @param TObject $object The object to look for. + * @return bool true if the object is in the storage, false otherwise. + * + * @since 5.1.0 + */ + public function contains($object) {} + + /** + * Adds all objects from another storage + * @link https://php.net/manual/en/splobjectstorage.addall.php + * + * @param SplObjectStorage $storage The storage you want to import. + * @return void + * + * @since 5.3.0 + */ + public function addAll($storage) {} + + /** + * Removes objects contained in another storage from the current storage + * @link https://php.net/manual/en/splobjectstorage.removeall.php + * + * @param SplObjectStorage $storage The storage containing the elements to remove. + * @return void + * + * @since 5.3.0 + */ + public function removeAll($storage) {} + + /** + * Removes all objects except for those contained in another storage from the current storage + * @link https://php.net/manual/en/splobjectstorage.removeallexcept.php + * + * @param SplObjectStorage $storage The storage containing the elements to retain in the current storage. + * @return void + * + * @since 5.3.6 + */ + public function removeAllExcept($storage) {} + + /** + * Returns the data associated with the current iterator entry + * @link https://php.net/manual/en/splobjectstorage.getinfo.php + * + * @return TArrayValue The data associated with the current iterator position. + * + * @since 5.3.0 + */ + public function getInfo() {} + + /** + * Sets the data associated with the current iterator entry + * @link https://php.net/manual/en/splobjectstorage.setinfo.php + * + * @param TArrayValue $data The data to associate with the current iterator entry. + * @return void + * + * @since 5.3.0 + */ + public function setInfo($data) {} + + /** + * Returns the number of objects in the storage + * @link https://php.net/manual/en/splobjectstorage.count.php + * + * @return int The number of objects in the storage. + * + * @since 5.1.0 + */ + public function count() {} + + /** + * Rewind the iterator to the first storage element + * @link https://php.net/manual/en/splobjectstorage.rewind.php + * + * @return void + * + * @since 5.1.0 + */ + public function rewind() {} + + /** + * Returns if the current iterator entry is valid + * @link https://php.net/manual/en/splobjectstorage.valid.php + * + * @return bool true if the iterator entry is valid, false otherwise. + * + * @since 5.1.0 + */ + public function valid() {} + + /** + * Returns the index at which the iterator currently is + * @link https://php.net/manual/en/splobjectstorage.key.php + * + * @return int The index corresponding to the position of the iterator. + * + * @since 5.1.0 + */ + public function key() {} + + /** + * Returns the current storage entry + * @link https://php.net/manual/en/splobjectstorage.current.php + * + * @return TObject The object at the current iterator position. + * + * @since 5.1.0 + */ + public function current() {} + + /** + * Move to the next entry + * @link https://php.net/manual/en/splobjectstorage.next.php + * + * @return void + * + * @since 5.1.0 + */ + public function next() {} + + /** + * Unserializes a storage from its string representation + * @link https://php.net/manual/en/splobjectstorage.unserialize.php + * + * @param string $serialized The serialized representation of a storage. + * @return void + * + * @since 5.2.2 + */ + public function unserialize($serialized) {} + + /** + * Serializes the storage + * @link https://php.net/manual/en/splobjectstorage.serialize.php + * + * @return string A string representing the storage. + * + * @since 5.2.2 + */ + public function serialize() {} + + /** + * Checks whether an object exists in the storage + * @link https://php.net/manual/en/splobjectstorage.offsetexists.php + * + * @param TObject $object The object to look for. + * @return bool true if the object exists in the storage, and false otherwise. + * + * @since 5.3.0 + */ + public function offsetExists($object) {} + + /** + * Associates data to an object in the storage + * @link https://php.net/manual/en/splobjectstorage.offsetset.php + * + * @param TObject $object The object to associate data with. + * @param TArrayValue|null $data [optional] The data to associate with the object. + * @return void + * + * @since 5.3.0 + */ + public function offsetSet($object, $data = null) {} + + /** + * Removes an object from the storage + * @link https://php.net/manual/en/splobjectstorage.offsetunset.php + * + * @param TObject $object The object to remove. + * @return void + * + * @since 5.3.0 + */ + public function offsetUnset($object) {} + + /** + * Returns the data associated with an object + * @link https://php.net/manual/en/splobjectstorage.offsetget.php + * + * @param TObject $object The object to look for. + * @return TArrayValue The data previously associated with the object in the storage. + * + * @since 5.3.0 + */ + public function offsetGet($object) {} + + /** + * Calculate a unique identifier for the contained objects + * @link https://php.net/manual/en/splobjectstorage.gethash.php + * + * @param object $object object whose identifier is to be calculated. + * @return string A string with the calculated identifier. + * + * @since 5.4.0 + */ + public function getHash($object) {} + +} + +/** + * @template-covariant T as object + * + * @property-read class-string $name + */ +class ReflectionClass implements Reflector { + + /** + * @var class-string + */ + public $name; + + /** + * @param T|class-string|trait-string $argument + */ + public function __construct($argument) {} + + /** + * @return class-string + */ + public function getName(): string; + + /** + * @param mixed ...$args + * + * @return T + */ + public function newInstance(...$args): object {} + + /** + * @param array $args + * + * @return T + */ + public function newInstanceArgs(array $args): object {} + + /** + * @return T + */ + public function newInstanceWithoutConstructor(): object; + + /** + * @return ?array + * @psalm-ignore-nullable-return + */ + public function getTraitNames(): array {} +} + +/** + * @template-covariant T as object + * @psalm-immutable + */ +final class WeakReference +{ + // always fail + public function __construct(); + + /** + * @template TIn as object + * @param TIn $referent + * @return WeakReference + */ + public static function create(object $referent): WeakReference; + + /** @return ?T */ + public function get(): ?object; +} diff --git a/lib/composer/vimeo/psalm/stubs/CoreGenericFunctions.phpstub b/lib/composer/vimeo/psalm/stubs/CoreGenericFunctions.phpstub new file mode 100644 index 0000000000000000000000000000000000000000..f37af3c13c16bbee4c1249f122da82aff439debc --- /dev/null +++ b/lib/composer/vimeo/psalm/stubs/CoreGenericFunctions.phpstub @@ -0,0 +1,845 @@ + + * + * @param TArray $arr + * @param mixed $search_value + * @param bool $strict + * + * @return (TArray is non-empty-array ? non-empty-list : list) + * @psalm-pure + */ +function array_keys(array $arr, $search_value = null, bool $strict = false) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array $arr2 + * @param array ...$arr3 + * + * @return array + * @psalm-pure + */ +function array_intersect(array $arr, array $arr2, array ...$arr3) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array $arr2 + * @param array ...$arr3 + * + * @return array + * @psalm-pure + */ +function array_intersect_key(array $arr, array $arr2, array ...$arr3) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array $arr2 + * @param array ...$arr3 + * @param callable(TKey): int $keyCompareFunc + * + * @return array + * + * @psalm-pure + */ +function array_intersect_ukey(array $arr, array $arr2, array ...$arr3, callable $keyCompareFunc): array +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array $arr2 + * @param array ...$arr3 + * + * @return array + * @psalm-pure + */ +function array_intersect_assoc(array $arr, array $arr2, array ...$arr3) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array $arr2 + * @param array ...$arr3 + * @param callable(TKey): int $keyCompareFunc + * + * @return array + * + * @psalm-pure + */ +function array_intersect_uassoc(array $arr, array $arr2, array ...$arr3, callable $keyCompareFunc): array {} +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array $arr2 + * + * @return array|false + * @psalm-ignore-falsable-return + * @psalm-pure + */ +function array_combine(array $arr, array $arr2) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array $arr2 + * @param array ...$arr3 + * + * @return array + * @psalm-pure + */ +function array_diff(array $arr, array $arr2, array ...$arr3) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array $arr2 + * @param array ...$arr3 + * + * @return array + * @psalm-pure + */ +function array_diff_key(array $arr, array $arr2, array ...$arr3) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array $arr2 + * @param array ...$arr3 + * @param callable(TKey, TKey): int $keyCompareFunc + * + * @return array + * @psalm-pure + */ +function array_diff_ukey(array $arr, array $arr2, array ...$arr3, callable $keyCompareFunc): array {} +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array $arr2 + * @param array ...$arr3 + * + * @return array + * @psalm-pure + */ +function array_diff_assoc(array $arr, array $arr2, array ...$arr3) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array $arr2 + * @param array ...$arr3 + * @param callable(TKey, TKey): int $keyCompareFunc + * + * @return array + * @psalm-pure + */ +function array_diff_uassoc(array $arr, array $arr2, array ... $arr3, callable $keyCompareFunc): array {} + +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * + * @return array + * @psalm-pure + */ +function array_flip(array $arr) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TArray as array + * + * @param TArray $arr + * + * @return (TArray is array ? null : TKey|null) + * @psalm-pure + * @psalm-ignore-nullable-return + */ +function key($arr) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TArray as array + * + * @param TArray $arr + * + * @return (TArray is array ? null : (TArray is non-empty-array ? TKey : TKey|null)) + * @psalm-pure + */ +function array_key_first($arr) +{ +} + +/** + * @psalm-template TKey as array-key + * @psalm-template TArray as array + * + * @param TArray $arr + * + * @return (TArray is array ? null : (TArray is non-empty-array ? TKey : TKey|null)) + * @psalm-pure + */ +function array_key_last($arr) +{ +} + +/** + * @psalm-template T + * + * @param mixed $needle + * @param array $haystack + * @param bool $strict + * + * @return T|false + * @psalm-pure + */ +function array_search($needle, array $haystack, bool $strict = false) +{ +} + +/** + * @psalm-template T + * + * @param T[] $arr + * @param-out list $arr + */ +function shuffle(array &$arr): bool +{ +} + +/** + * @psalm-template T + * + * @param T[] $arr + * @param-out list $arr + */ +function sort(array &$arr, int $sort_flags = SORT_REGULAR): bool +{ +} + +/** + * @psalm-template T + * + * @param T[] $arr + * @param-out list $arr + */ +function rsort(array &$arr, int $sort_flags = SORT_REGULAR): bool +{ +} + +/** + * @psalm-template T + * + * @param T[] $arr + * @param callable(T,T):int $callback + * @param-out list $arr + */ +function usort(array &$arr, callable $callback): bool +{ +} + +/** + * @psalm-template TKey + * @psalm-template T + * + * @param array $arr + * @param callable(T,T):int $callback + * @param-out array $arr + */ +function uasort(array &$arr, callable $callback): bool +{ +} + +/** + * @psalm-template TKey + * @psalm-template T + * + * @param array $arr + * @param callable(TKey,TKey):int $callback + * @param-out array $arr + */ +function uksort(array &$arr, callable $callback): bool +{ +} + +/** + * @psalm-pure + * + * @psalm-template K of array-key + * @psalm-template T + * + * @param array $arr + * + * @return array + */ +function array_change_key_case(array $arr, int $case = CASE_LOWER) +{ +} + +/** + * @psalm-pure + * + * @psalm-template TKey as array-key + * + * @param TKey $key + * @param array $search + * + * @return bool + */ +function array_key_exists($key, array $search) : bool +{ +} + +/** + * @psalm-pure + * + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $arr + * @param array ...$arr2 + * + * @return array + */ +function array_merge_recursive(array $arr, array ...$arr2) +{ +} + +/** + * @psalm-pure + * + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array $keys + * @param TValue $value + * + * @return array + */ +function array_fill_keys(array $keys, $value): array +{ +} + +/** + * @psalm-pure + * + * @psalm-template TKey + * + * @param string $pattern + * @param array $input + * @param 0|1 $flags 1=PREG_GREP_INVERT + * @return array + */ +function preg_grep($pattern, array $input, $flags = 0) +{ +} + +/** + * @param resource $handle + * @param-out closed-resource $handle + */ +function fclose(&$handle) : bool +{ +} + +/** + * @param string $reference + * @param-out null $reference + */ +function sodium_memzero(string &$reference): void +{ +} + +/** + * @param mixed $var + * @param bool $return + * @return ($return is true ? string : void) + * + * @psalm-taint-specialize + * @psalm-flow ($var) -> return + */ +function var_export($var, bool $return = false) {} + +/** + * @param mixed $var + * @param bool $return + * @return ($return is true ? string : true) + * + * @psalm-taint-specialize + * @psalm-flow ($var) -> return + */ +function print_r($var, bool $return = false) {} + +/** + * @psalm-pure + * + * @param mixed $var + * @return ($return is true ? string : bool) + */ +function highlight_file($var, bool $return = false) {} + +/** + * @psalm-pure + * + * @param mixed $var + * @return ($return is true ? string : bool) + * + * @psalm-flow ($var) -> return + */ +function highlight_string($var, bool $return = false) {} + +/** + * @psalm-pure + * + * @return ($get_as_float is true ? float : string) + */ +function microtime(bool $get_as_float = false) {} + +/** + * @psalm-pure + * + * @return ($return_float is true ? float : array) + */ +function gettimeofday(bool $return_float = false) {} + +/** + * @psalm-pure + * + * @param numeric $number + * @return ($number is int ? int : ($number is float ? float : int|float)) + */ +function abs($number) {} + +/** + * @psalm-pure + * + * @template T as string|int|float + * @template TStep as int|float + * @param T $start + * @param T $end + * @param TStep $step + * @return ( + * T is int + * ? (TStep is int ? list : list) + * : ( + * T is float + * ? list + * : ( + * T is string + * ? list + * : ( + * T is int|float + * ? list + * : list + * ) + * ) + * ) + * ) + */ +function range($start, $end, $step = 1) {} + +/** + * @psalm-pure + * + * @return ( + * $format is 'd'|'j'|'N'|'w'|'z'|'W'|'m'|'n'|'t'|'L'|'o'|'Y'|'y'|'B'|'g'|'G'|'h'|'H'|'i'|'s'|'u'|'v'|'Z'|'U'|'I' + * ? numeric-string + * : ($timestamp is numeric ? string : string|false) + * ) + */ +function date(string $format, int $timestamp = 0) {} + +/** + * @psalm-pure + * + * @param mixed $vars + * @param-out string|int|float $vars + * @return (func_num_args() is 2 ? list : int) + */ +function sscanf(string $str, string $format, &...$vars) {} + +/** + * @psalm-pure + * + * @return ( + * func_num_args() is 1 + * ? array{dirname: string, basename: string, extension?: string, filename: string} + * : string + * ) + */ +function pathinfo(string $path, int $options = \PATHINFO_DIRNAME) {} + +/** + * @psalm-pure + * + * @return (func_num_args() is 0 ? array : string|false) + */ +function getenv(string $varname = '', bool $local_only = false) {} + +/** + * @psalm-pure + * + * @return ( + * $str is non-empty-string ? non-empty-lowercase-string : lowercase-string + * ) + * + * @psalm-flow ($str) -> return + */ +function strtolower(string $str) : string {} + +/** + * @psalm-pure + * + * @psalm-flow ($str) -> return + */ +function strtoupper(string $str) : string {} + +/** + * @psalm-pure + * + * @param string $haystack + * + * @psalm-return positive-int|0|false + */ +function strpos($haystack, $needle, int $offset = 0) : int {} + +/** + * @psalm-pure + * + * @psalm-flow ($str) -> return + */ +function trim(string $str, string $character_mask = " \t\n\r\0\x0B") : string {} + +/** + * @psalm-pure + * + * @psalm-flow ($str) -> return + */ +function ltrim(string $str, string $character_mask = " \t\n\r\0\x0B") : string {} + +/** + * @psalm-pure + * + * @psalm-flow ($str) -> return + */ +function rtrim(string $str, string $character_mask = " \t\n\r\0\x0B") : string {} + +/** + * @psalm-pure + * + * @param string|array $glue + * + * @return ( + * $glue is non-empty-string + * ? ($pieces is non-empty-array + * ? non-empty-string + * : string) + * : string + * ) + * + * @psalm-flow ($glue) -> return + * @psalm-flow ($pieces) -(array-fetch)-> return + */ +function implode($glue, array $pieces = []) : string {} + +/** + * @psalm-pure + * + * @psalm-flow ($string) -(array-assignment)-> return + */ +function explode(string $delimiter, string $string, int $limit = -1) : array {} + +/** + * @psalm-pure + * + * @psalm-flow ($subject) -(array-assignment)-> return + * + * @template TFlags as 0|1|2|3|4|5|6|7 + * + * @param TFlags $flags + * + * @return (TFlags is 0|2 ? non-empty-list|false : (TFlags is 1|3 ? list|false : list|false)) + * + * @psalm-ignore-falsable-return + */ +function preg_split(string $pattern, string $subject, int $limit = -1, int $flags = 0) {} + +/** + * @param array $input + * + * @return ( + * $input is array + * ? int + * : ($input is array + * ? float + * : float|int + * ) + * ) + */ +function array_sum(array $input) {} + +/** + * @param array $input + * + * @return ( + * $input is array + * ? int + * : ($input is array + * ? float + * : float|int + * ) + * ) + */ +function array_product(array $input) {} + +/** + * @psalm-pure + * + * @psalm-taint-escape html + * @psalm-flow ($str) -> return + */ +function strip_tags(string $str, ?string $allowable_tags = null) : string {} + +/** + * @psalm-pure + * + * @psalm-taint-escape html + * + * @psalm-flow ($string) -> return + */ +function htmlentities(string $string, ?int $quote_style = null, ?string $charset = null, bool $double_encode = true) : string {} + +/** + * @psalm-pure + * + * @psalm-taint-unescape html + * + * @psalm-flow ($string) -> return + */ +function html_entity_decode(string $string, ?int $quote_style = null, ?string $charset = null) : string {} + +/** + * @psalm-pure + * + * @psalm-taint-escape html + * @psalm-taint-escape sql + * + * @psalm-flow ($string) -> return + */ +function htmlspecialchars(string $string, int $flags = ENT_COMPAT | ENT_HTML401, string $encoding = 'UTF-8', bool $double_encode = true) : string {} + +/** + * @psalm-pure + * + * @psalm-taint-unescape html + * @psalm-taint-unescape sql + * + * @psalm-flow ($string) -> return + */ +function htmlspecialchars_decode(string $string, ?int $quote_style = null) : string {} + +/** + * @psalm-pure + * + * @param string|array $search + * @param string|array $replace + * @param string|array $subject + * @param int $count + * @return ($subject is array ? array : string) + * + * @psalm-flow ($replace, $subject) -> return + */ +function str_replace($search, $replace, $subject, &$count = null) {} + +/** + * @psalm-pure + * + * @param string|string[] $search + * @param string|array $replace + * @param string|array $subject + * @param int $count + * @return ($subject is array ? array : string) + * + * @psalm-flow ($replace, $subject) -> return + */ +function preg_replace($search, $replace, $subject, int $limit = -1, &$count = null) {} + +/** + * @param string|string[] $search + * @param callable(array):string $replace + * @param string|array $subject + * @param int $count + * @return ($subject is array ? array : string) + * + * @psalm-taint-specialize + * @psalm-flow ($subject) -> return + */ +function preg_replace_callback($search, $replace, $subject, int $limit = -1, &$count = null) {} + +/** + * @psalm-pure + * @template TFlags as int + * + * @param string $pattern + * @param string $subject + * @param mixed $matches + * @param TFlags $flags + * @param-out ( + * TFlags is 1 + * ? array> + * : (TFlags is 2 + * ? list> + * : (TFlags is 256|257 + * ? array> + * : (TFlags is 258 + * ? list> + * : (TFlags is 512|513 + * ? array> + * : (TFlags is 514 + * ? list> + * : (TFlags is 770 + * ? list> + * : array + * ) + * ) + * ) + * ) + * ) + * ) + * ) $matches + * @return int|false + * @psalm-ignore-falsable-return + */ +function preg_match_all($pattern, $replace, &$matches = [], int $flags = 1, int $offset = 0) {} + +/** + * @psalm-pure + * + * @return string|false + * @psalm-ignore-falsable-return + * + * @psalm-flow ($string) -> return + */ +function substr(string $string, int $start, ?int $length = null) {} + +/** + * @psalm-pure + * + * @psalm-flow ($str) -> return + */ +function preg_quote(string $str, ?string $delimiter = null) : string {} + +/** + * @psalm-pure + * + * @param string|int|float $args + * + * @psalm-flow ($format, $args) -> return + */ +function sprintf(string $format, ...$args) : string {} + +/** + * @psalm-pure + * + * @return string|false + * @psalm-ignore-falsable-return + * + * @psalm-flow ($path) -> return + */ +function realpath(string $path) {} + +/** + * @psalm-pure + * + * @param numeric-string $left_operand + * @param numeric-string $right_operand + * @return ($right_operand is "0" ? null : numeric-string) + */ +function bcdiv(string $left_operand, string $right_operand, int $scale = 0): ?string {} + +/** + * @psalm-pure + * + * @param scalar|null|object $var + * @return string The string value of var. + * + * @psalm-flow ($var) -> return + */ +function strval ($var): string {} + +/** + * @return ($input is non-empty-string ? non-empty-list : non-empty-list|array{null}) + * @psalm-pure + */ +function str_getcsv(string $input, string $delimiter = ',', string $enclosure = '"', string $escape = '\\\\') +{ +} + +/** + * @return ($min is positive-int ? positive-int : int) + */ +function random_int(int $min, int $max): int {} + +/** + * @template TKey as array-key + * + * @param array $array + * + * @return array + * + * @psalm-pure + */ +function array_count_values(array $array): array {} diff --git a/lib/composer/vimeo/psalm/stubs/CoreImmutableClasses.phpstub b/lib/composer/vimeo/psalm/stubs/CoreImmutableClasses.phpstub new file mode 100644 index 0000000000000000000000000000000000000000..98968b7227f0a3db3a73af16c6a1bd602b71f56d --- /dev/null +++ b/lib/composer/vimeo/psalm/stubs/CoreImmutableClasses.phpstub @@ -0,0 +1,221 @@ +> +*/ +function xdebug_get_code_coverage() : array +{ +} + +/** +* @param array $configuration +*/ +function xdebug_set_filter(int $group, int $list_type, array $configuration) : array +{ +} + +function xdebug_start_code_coverage(int $options) : void +{ +} + +function xdebug_stop_code_coverage(int $cleanup = 1) : void +{ +} diff --git a/lib/composer/vimeo/psalm/stubs/ext-ds.php b/lib/composer/vimeo/psalm/stubs/ext-ds.php new file mode 100644 index 0000000000000000000000000000000000000000..807fa1b70be2e7a2f630867c69464aa1a805579e --- /dev/null +++ b/lib/composer/vimeo/psalm/stubs/ext-ds.php @@ -0,0 +1,1026 @@ + + */ +interface Collection extends Traversable, Countable, JsonSerializable +{ + /** + * @return Collection + */ + public function copy(): Collection; + + /** + * @return array + */ + public function toArray(): array; +} + +/** + * @template TValue + * @implements Sequence + */ +final class Deque implements Sequence +{ + /** + * @param iterable $values + */ + public function __construct(iterable $values = []) + { + } + + /** + * @return Deque + */ + public function copy(): Deque + { + } + + /** + * @return list + */ + public function toArray(): array + { + } + + /** + * @return TValue + * @throws \UnderflowException + */ + public function first() + { + } + + /** + * @return TValue + * @throws \OutOfRangeException + */ + public function get(int $index) + { + } + + /** + * @return TValue + * @throws \UnderflowException + */ + public function last() + { + } + + /** + * @return TValue + * @throws \UnderflowException + */ + public function pop() + { + } + + /** + * @template TCarry + * @param callable(TCarry, TValue): TCarry $callback + * @param TCarry $initial + * @return TCarry + */ + public function reduce(callable $callback, $initial = null) + { + } + + /** + * @return TValue + * @throws \OutOfRangeException + */ + public function remove(int $index) + { + } + + /** + * @return TValue + * @throws \UnderflowException + */ + public function shift() + { + } + + /** + * @template TValue2 + * @param iterable $values + * @return Deque + */ + public function merge(iterable $values): Deque + { + } + + /** + * @param (callable(TValue): bool)|null $callback + * @return Deque + */ + public function filter(callable $callback = null): Deque + { + } + + /** + * @template TNewValue + * @param callable(TValue): TNewValue $callback + * @return Deque + */ + public function map(callable $callback): Deque + { + } + + /** + * @return Deque + */ + public function reversed(): Deque + { + } + + /** + * @return Deque + */ + public function slice(int $offset, ?int $length = null): Deque + { + } + + /** + * @param (callable(TValue, TValue): int)|null $comparator + * @return Sequence + */ + public function sorted(callable $comparator = null): Deque + { + } +} + +/** + * @template TKey + * @template TValue + * @implements Collection + */ +final class Map implements Collection +{ + /** + * @param iterable $values + */ + public function __construct(iterable $values = []) + { + } + + /** + * @return Map + */ + public function copy(): Map + { + } + + /** + * @param callable(TKey, TValue): TValue $callback + */ + public function apply(callable $callback): void + { + } + + /** + * @return Pair + * @throws UnderflowException + */ + public function first(): Pair + { + } + + /** + * @return Pair + * @throws UnderflowException + */ + public function last(): Pair + { + } + + /** + * @return Pair + * @throws OutOfRangeException + */ + public function skip(int $position): Pair + { + } + + /** + * @template TKey2 + * @template TValue2 + * @param iterable $values + * @return Map + */ + public function merge(iterable $values): Map + { + } + + /** + * @template TKey2 + * @template TValue2 + * @param Map $map + * @return Map + */ + public function intersect(Map $map): Map + { + } + + /** + * @template TValue2 + * @param Map $map + * @return Map + */ + public function diff(Map $map): Map + { + } + + /** + * @param TKey $key + */ + public function hasKey($key): bool + { + } + + /** + * @param TValue $value + */ + public function hasValue($value): bool + { + } + + /** + * @param (callable(TKey, TValue): bool)|null $callback + * @return Map + */ + public function filter(callable $callback = null): Map + { + } + + /** + * @template TDefault + * @param TKey $key + * @param TDefault $default + * @return ( + * func_num_args() is 1 + * ? TValue + * : TValue|TDefault + * ) + * @throws OutOfBoundsException + */ + public function get($key, $default = null) + { + } + + /** + * @return Set + */ + public function keys(): Set + { + } + + /** + * @template TNewValue + * @param callable(TKey, TValue): TNewValue $callback + * @return Map + */ + public function map(callable $callback): Map + { + } + + /** + * @return Sequence> + */ + public function pairs(): Sequence + { + } + + /** + * @param TKey $key + * @param TValue $value + */ + public function put($key, $value) + { + } + + /** + * @param iterable $values + */ + public function putAll(iterable $values) + { + } + + /** + * @template TCarry + * @param callable(TCarry, TKey, TValue): TCarry $callback + * @param TCarry $initial + * @return TCarry + */ + public function reduce(callable $callback, $initial = null) + { + } + + /** + * @template TDefault + * @param TKey $key + * @param TDefault $default + * @return ( + * func_num_args() is 1 + * ? TValue + * : TValue|TDefault + * ) + * @throws \OutOfBoundsException + */ + public function remove($key, $default = null) + { + } + + /** + * @return Map + */ + public function reversed(): Map + { + } + + /** + * @return Map + */ + public function slice(int $offset, ?int $length = null): Map + { + } + + /** + * @param (callable(TValue, TValue): int)|null $comparator + */ + public function sort(callable $comparator = null) + { + } + + /** + * @param (callable(TValue, TValue): int)|null $comparator + * @return Map + */ + public function sorted(callable $comparator = null): Map + { + } + + /** + * @param (callable(TKey, TKey): int)|null $comparator + */ + public function ksort(callable $comparator = null) + { + } + + /** + * @param (callable(TKey, TKey): int)|null $comparator + * @return Map + */ + public function ksorted(callable $comparator = null): Map + { + } + + /** + * @return array + */ + public function toArray(): array + { + } + + /** + * @return Sequence + */ + public function values(): Sequence + { + } + + /** + * @template TKey2 + * @template TValue2 + * @param Map $map + * @return Map + */ + public function union(Map $map): Map + { + } + + /** + * @template TKey2 + * @template TValue2 + * @param Map $map + * @return Map + */ + public function xor(Map $map): Map + { + } +} + +/** + * @template-covariant TKey + * @template-covariant TValue + */ +final class Pair implements JsonSerializable +{ + /** + * @var TKey + */ + public $key; + + /** + * @var TValue + */ + public $value; + + /** + * @param TKey $key + * @param TValue $value + */ + public function __construct($key = null, $value = null) + { + } + + /** + * @return Pair + */ + public function copy(): Pair + { + } +} + +/** + * @template TValue + * @extends Collection + */ +interface Sequence extends Collection +{ + /** + * @param callable(TValue): TValue $callback + */ + public function apply(callable $callback): void; + + /** + * @param TValue ...$values + */ + public function contains(...$values): bool; + + /** + * @param (callable(TValue): bool)|null $callback + * @return Sequence + */ + public function filter(callable $callback = null): Sequence; + + /** + * @param TValue $value + * @return int|false + */ + public function find($value); + + /** + * @return TValue + * @throws \UnderflowException + */ + public function first(); + + /** + * @return TValue + * @throws \OutOfRangeException + */ + public function get(int $index); + + /** + * @param TValue ...$values + * @throws \OutOfRangeException + */ + public function insert(int $index, ...$values); + + /** + * @param string $glue + * @return string + */ + public function join(string $glue = null): string; + + /** + * @return TValue + * @throws \UnderflowException + */ + public function last(); + + /** + * @template TNewValue + * @param callable(TValue): TNewValue $callback + * @return Sequence + */ + public function map(callable $callback): Sequence; + + /** + * @template TValue2 + * @param iterable $values + * @return Sequence + */ + public function merge(iterable $values): Sequence; + + /** + * @return TValue + * @throws \UnderflowException + */ + public function pop(); + + /** + * @param TValue ...$values + */ + public function push(...$values); + + /** + * @template TCarry + * @param callable(TCarry, TValue): TCarry $callback + * @param TCarry $initial + * @return TCarry + */ + public function reduce(callable $callback, $initial = null); + + /** + * @return TValue + * @throws \OutOfRangeException + */ + public function remove(int $index); + + /** + * @return Sequence + */ + public function reversed(): Sequence; + + /** + * @param TValue $value + * @throws \OutOfRangeException + */ + public function set(int $index, $value); + + /** + * @return TValue + * @throws \UnderflowException + */ + public function shift(); + + /** + * @return Sequence + */ + public function slice(int $index, ?int $length = null): Sequence; + + /** + * @param (callable(TValue, TValue): int)|null $comparator + */ + public function sort(callable $comparator = null); + + /** + * @param (callable(TValue, TValue): int)|null $comparator + * @return Sequence + */ + public function sorted(callable $comparator = null): Sequence; + + /** + * @param TValue ...$values + */ + public function unshift(...$values); +} + + +/** + * @template TValue + * @implements Sequence + */ +final class Vector implements Sequence +{ + /** + * @param iterable $values + */ + public function __construct(iterable $values = []) + { + } + + /** + * @return Vector + */ + public function copy(): Vector + { + } + + /** + * @return list + */ + public function toArray(): array + { + } + + /** + * @return TValue + * @throws \UnderflowException + */ + public function first() + { + } + + /** + * @return TValue + * @throws \OutOfRangeException + */ + public function get(int $index) + { + } + + /** + * @return TValue + * @throws \UnderflowException + */ + public function last() + { + } + + /** + * @return TValue + * @throws \UnderflowException + */ + public function pop() + { + } + + /** + * @template TCarry + * @param callable(TCarry, TValue): TCarry $callback + * @param TCarry $initial + * @return TCarry + */ + public function reduce(callable $callback, $initial = null) + { + } + + /** + * @return TValue + * @throws \OutOfRangeException + */ + public function remove(int $index) + { + } + + /** + * @return TValue + * @throws \UnderflowException + */ + public function shift() + { + } + + /** + * @return Vector + */ + public function reversed(): Vector + { + } + + /** + * @return Vector + */ + public function slice(int $offset, ?int $length = null): Vector + { + } + + /** + * @param (callable(TValue, TValue): int)|null $comparator + * @return Vector + */ + public function sorted(callable $comparator = null): Vector + { + } + + /** + * @param (callable(TValue): bool)|null $callback + * @return Vector + */ + public function filter(callable $callback = null): Vector + { + } + + /** + * @template TNewValue + * @param callable(TValue): TNewValue $callback + * @return Vector + */ + public function map(callable $callback): Vector + { + } + + /** + * @template TValue2 + * @param iterable $values + * @return Vector + */ + public function merge(iterable $values): Sequence + { + } +} + +/** + * @template TValue + * @implements Collection + */ +final class Set implements Collection +{ + /** + * @param iterable $values + */ + public function __construct(iterable $values = []) + { + } + + /** + * @param TValue ...$values + */ + public function add(...$values): void + { + } + + /** + * @param TValue ...$values + */ + public function contains(...$values): bool + { + } + + /** + * @return Set + */ + public function copy(): Set + { + } + + /** + * @template TValue2 + * @param Set $set + * @return Set + */ + public function diff(Set $set): Set + { + } + + /** + * @param (callable(TValue): bool)|null $callback + * @return Set + */ + public function filter(callable $callback = null): Set + { + } + + /** + * @return TValue + * @throws \UnderflowException + */ + public function first() + { + } + + /** + * @return TValue + * @throws \OutOfRangeException + */ + public function get(int $index) + { + } + + /** + * @template TValue2 + * @param Set $set + * @return Set + */ + public function intersect(Set $set): Set + { + } + + /** + * @return TValue + * @throws \UnderflowException + */ + public function last() + { + } + + /** + * @template TValue2 + * @param iterable $values + * @return Set + */ + public function merge(iterable $values): Set + { + } + + /** + * @param TValue ...$values + */ + public function remove(...$values): void + { + } + + /** + * @return Set + */ + public function reversed(): Set + { + } + + /** + * @return Set + */ + public function slice(int $index, ?int $length = null): Set + { + } + + /** + * @param (callable(TValue, TValue): int)|null $comparator + */ + public function sort(callable $comparator = null): void + { + } + + /** + * @param (callable(TValue, TValue): int)|null $comparator + * @return Set + */ + public function sorted(callable $comparator = null): Set + { + } + + /** + * @return list + */ + public function toArray(): array + { + } + + /** + * @template TValue2 + * @param Set $set + * @return Set + */ + public function union(Set $set): Set + { + } + + /** + * @template TValue2 + * @param Set $set + * @return Set + */ + public function xor(Set $set): Set + { + } +} + +/** + * @template TValue + * @implements Collection + */ +final class Stack implements Collection +{ + /** + * @param iterable $values + */ + public function __construct(iterable $values = []) + { + } + + /** + * @return Stack + */ + public function copy(): Stack + { + } + + /** + * @return TValue + * @throws UnderflowException + */ + public function peek() + { + } + + /** + * @return TValue + * @throws UnderflowException + */ + public function pop() + { + } + + /** + * @param TValue ...$values + */ + public function push(...$values): void + { + } + + /** + * @return list + */ + public function toArray(): array + { + } +} + +/** + * @template TValue + * @implements Collection + */ +final class Queue implements Collection +{ + /** + * @param iterable $values + */ + public function __construct(iterable $values = []) + { + } + + /** + * @return Queue + */ + public function copy(): Queue + { + } + + /** + * @return TValue + * @throws UnderflowException + */ + public function peek() + { + } + + /** + * @return TValue + * @throws UnderflowException + */ + public function pop() + { + } + + /** + * @param TValue ...$values + */ + public function push(...$values): void + { + } + + /** + * @return list + */ + public function toArray(): array + { + } +} + +/** + * @template TValue + * @implements Collection + */ +final class PriorityQueue implements Collection +{ + /** + * @return PriorityQueue + */ + public function copy(): PriorityQueue + { + } + + /** + * @return TValue + * @throws UnderflowException + */ + public function peek() + { + } + + /** + * @return TValue + * @throws UnderflowException + */ + public function pop() + { + } + + /** + * @param TValue $value + */ + public function push($value, int $priority): void + { + } + + /** + * @return list + */ + public function toArray(): array + { + } +} diff --git a/lib/composer/vimeo/psalm/vendor-bin/box/composer.json b/lib/composer/vimeo/psalm/vendor-bin/box/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..8ba44c0e6fdb1f69dae73faedcbee5e4c3af2dfc --- /dev/null +++ b/lib/composer/vimeo/psalm/vendor-bin/box/composer.json @@ -0,0 +1,7 @@ +{ + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "humbug/box": "^3.8" + } +} diff --git a/lib/composer/webmozart/assert/.editorconfig b/lib/composer/webmozart/assert/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..384453bfb0859785abde2713b0c99ca6b679f1f1 --- /dev/null +++ b/lib/composer/webmozart/assert/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset=utf-8 +end_of_line=lf +trim_trailing_whitespace=true +insert_final_newline=true +indent_style=space +indent_size=4 + +[*.yml] +indent_size=2 diff --git a/lib/composer/webmozart/assert/CHANGELOG.md b/lib/composer/webmozart/assert/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..1d379277ff13fb3579234ed3925dae69c39b61a5 --- /dev/null +++ b/lib/composer/webmozart/assert/CHANGELOG.md @@ -0,0 +1,175 @@ +Changelog +========= + +## UNRELEASED + +## 1.9.1 + +## Fixed + +* provisional support for PHP 8.0 + +## 1.9.0 + +* added better Psalm support for `all*` & `nullOr*` methods + * These methods are now understood by Psalm through a mixin. You may need a newer version of Psalm in order to use this +* added `@psalm-pure` annotation to `Assert::notFalse()` +* added more `@psalm-assert` annotations where appropriate + +## Changed + +* the `all*` & `nullOr*` methods are now declared on an interface, instead of `@method` annotations. +This interface is linked to the `Assert` class with a `@mixin` annotation. Most IDE's have supported this +for a long time, and you should not lose any autocompletion capabilities. PHPStan has supported this since +version `0.12.20`. This package is marked incompatible (with a composer conflict) with phpstan version prior to that. +If you do not use PHPStan than this does not matter. + +## 1.8.0 + +### Added + +* added `Assert::notStartsWith()` +* added `Assert::notEndsWith()` +* added `Assert::inArray()` +* added `@psalm-pure` annotations to pure assertions + +### Fixed + +* Exception messages of comparisons between `DateTime(Immutable)` objects now display their date & time. +* Custom Exception messages for `Assert::count()` now use the values to render the exception message. + +## 1.7.0 (2020-02-14) + +### Added + +* added `Assert::notFalse()` +* added `Assert::isAOf()` +* added `Assert::isAnyOf()` +* added `Assert::isNotA()` + +## 1.6.0 (2019-11-24) + +### Added + +* added `Assert::validArrayKey()` +* added `Assert::isNonEmptyList()` +* added `Assert::isNonEmptyMap()` +* added `@throws InvalidArgumentException` annotations to all methods that throw. +* added `@psalm-assert` for the list type to the `isList` assertion. + +### Fixed + +* `ResourceBundle` & `SimpleXMLElement` now pass the `isCountable` assertions. +They are countable, without implementing the `Countable` interface. +* The doc block of `range` now has the proper variables. +* An empty array will now pass `isList` and `isMap`. As it is a valid form of both. +If a non-empty variant is needed, use `isNonEmptyList` or `isNonEmptyMap`. + +### Changed + +* Removed some `@psalm-assert` annotations, that were 'side effect' assertions See: + * [#144](https://github.com/webmozart/assert/pull/144) + * [#145](https://github.com/webmozart/assert/issues/145) + * [#146](https://github.com/webmozart/assert/pull/146) + * [#150](https://github.com/webmozart/assert/pull/150) +* If you use Psalm, the minimum version needed is `3.6.0`. Which is enforced through a composer conflict. +If you don't use Psalm, then this has no impact. + +## 1.5.0 (2019-08-24) + +### Added + +* added `Assert::uniqueValues()` +* added `Assert::unicodeLetters()` +* added: `Assert::email()` +* added support for [Psalm](https://github.com/vimeo/psalm), by adding `@psalm-assert` annotations where appropriate. + +### Fixed + +* `Assert::endsWith()` would not give the correct result when dealing with a multibyte suffix. +* `Assert::length(), minLength, maxLength, lengthBetween` would not give the correct result when dealing with multibyte characters. + +**NOTE**: These 2 changes may break your assertions if you relied on the fact that multibyte characters didn't behave correctly. + +### Changed + +* The names of some variables have been updated to better reflect what they are. +* All function calls are now in their FQN form, slightly increasing performance. +* Tests are now properly ran against HHVM-3.30 and PHP nightly. + +### Deprecation + +* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()` + * This was already done in 1.3.0, but it was only done through a silenced `trigger_error`. It is now annotated as well. + +## 1.4.0 (2018-12-25) + +### Added + +* added `Assert::ip()` +* added `Assert::ipv4()` +* added `Assert::ipv6()` +* added `Assert::notRegex()` +* added `Assert::interfaceExists()` +* added `Assert::isList()` +* added `Assert::isMap()` +* added polyfill for ctype + +### Fixed + +* Special case when comparing objects implementing `__toString()` + +## 1.3.0 (2018-01-29) + +### Added + +* added `Assert::minCount()` +* added `Assert::maxCount()` +* added `Assert::countBetween()` +* added `Assert::isCountable()` +* added `Assert::notWhitespaceOnly()` +* added `Assert::natural()` +* added `Assert::notContains()` +* added `Assert::isArrayAccessible()` +* added `Assert::isInstanceOfAny()` +* added `Assert::isIterable()` + +### Fixed + +* `stringNotEmpty` will no longer report "0" is an empty string + +### Deprecation + +* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()` + +## 1.2.0 (2016-11-23) + + * added `Assert::throws()` + * added `Assert::count()` + * added extension point `Assert::reportInvalidArgument()` for custom subclasses + +## 1.1.0 (2016-08-09) + + * added `Assert::object()` + * added `Assert::propertyExists()` + * added `Assert::propertyNotExists()` + * added `Assert::methodExists()` + * added `Assert::methodNotExists()` + * added `Assert::uuid()` + +## 1.0.2 (2015-08-24) + + * integrated Style CI + * add tests for minimum package dependencies on Travis CI + +## 1.0.1 (2015-05-12) + + * added support for PHP 5.3.3 + +## 1.0.0 (2015-05-12) + + * first stable release + +## 1.0.0-beta (2015-03-19) + + * first beta release diff --git a/lib/composer/webmozart/assert/LICENSE b/lib/composer/webmozart/assert/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9e2e3075eb844a130a8532ed062169d8d06237a7 --- /dev/null +++ b/lib/composer/webmozart/assert/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Bernhard Schussek + +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/lib/composer/webmozart/assert/README.md b/lib/composer/webmozart/assert/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1407a9a1bdbc3667ea9079c0d09f001afbf76c8c --- /dev/null +++ b/lib/composer/webmozart/assert/README.md @@ -0,0 +1,283 @@ +Webmozart Assert +================ + +[![Build Status](https://travis-ci.org/webmozart/assert.svg?branch=master)](https://travis-ci.org/webmozart/assert) +[![Build status](https://ci.appveyor.com/api/projects/status/lyg83bcsisrr94se/branch/master?svg=true)](https://ci.appveyor.com/project/webmozart/assert/branch/master) +[![Code Coverage](https://scrutinizer-ci.com/g/webmozart/assert/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/webmozart/assert/?branch=master) +[![Latest Stable Version](https://poser.pugx.org/webmozart/assert/v/stable.svg)](https://packagist.org/packages/webmozart/assert) +[![Total Downloads](https://poser.pugx.org/webmozart/assert/downloads.svg)](https://packagist.org/packages/webmozart/assert) + +This library contains efficient assertions to test the input and output of +your methods. With these assertions, you can greatly reduce the amount of coding +needed to write a safe implementation. + +All assertions in the [`Assert`] class throw an `\InvalidArgumentException` if +they fail. + +FAQ +--- + +**What's the difference to [beberlei/assert]?** + +This library is heavily inspired by Benjamin Eberlei's wonderful [assert package], +but fixes a usability issue with error messages that can't be fixed there without +breaking backwards compatibility. + +This package features usable error messages by default. However, you can also +easily write custom error messages: + +``` +Assert::string($path, 'The path is expected to be a string. Got: %s'); +``` + +In [beberlei/assert], the ordering of the `%s` placeholders is different for +every assertion. This package, on the contrary, provides consistent placeholder +ordering for all assertions: + +* `%s`: The tested value as string, e.g. `"/foo/bar"`. +* `%2$s`, `%3$s`, ...: Additional assertion-specific values, e.g. the + minimum/maximum length, allowed values, etc. + +Check the source code of the assertions to find out details about the additional +available placeholders. + +Installation +------------ + +Use [Composer] to install the package: + +``` +$ composer require webmozart/assert +``` + +Example +------- + +```php +use Webmozart\Assert\Assert; + +class Employee +{ + public function __construct($id) + { + Assert::integer($id, 'The employee ID must be an integer. Got: %s'); + Assert::greaterThan($id, 0, 'The employee ID must be a positive integer. Got: %s'); + } +} +``` + +If you create an employee with an invalid ID, an exception is thrown: + +```php +new Employee('foobar'); +// => InvalidArgumentException: +// The employee ID must be an integer. Got: string + +new Employee(-10); +// => InvalidArgumentException: +// The employee ID must be a positive integer. Got: -10 +``` + +Assertions +---------- + +The [`Assert`] class provides the following assertions: + +### Type Assertions + +Method | Description +-------------------------------------------------------- | -------------------------------------------------- +`string($value, $message = '')` | Check that a value is a string +`stringNotEmpty($value, $message = '')` | Check that a value is a non-empty string +`integer($value, $message = '')` | Check that a value is an integer +`integerish($value, $message = '')` | Check that a value casts to an integer +`float($value, $message = '')` | Check that a value is a float +`numeric($value, $message = '')` | Check that a value is numeric +`natural($value, $message= ''')` | Check that a value is a non-negative integer +`boolean($value, $message = '')` | Check that a value is a boolean +`scalar($value, $message = '')` | Check that a value is a scalar +`object($value, $message = '')` | Check that a value is an object +`resource($value, $type = null, $message = '')` | Check that a value is a resource +`isCallable($value, $message = '')` | Check that a value is a callable +`isArray($value, $message = '')` | Check that a value is an array +`isTraversable($value, $message = '')` (deprecated) | Check that a value is an array or a `\Traversable` +`isIterable($value, $message = '')` | Check that a value is an array or a `\Traversable` +`isCountable($value, $message = '')` | Check that a value is an array or a `\Countable` +`isInstanceOf($value, $class, $message = '')` | Check that a value is an `instanceof` a class +`isInstanceOfAny($value, array $classes, $message = '')` | Check that a value is an `instanceof` at least one class on the array of classes +`notInstanceOf($value, $class, $message = '')` | Check that a value is not an `instanceof` a class +`isAOf($value, $class, $message = '')` | Check that a value is of the class or has one of its parents +`isAnyOf($value, array $classes, $message = '')` | Check that a value is of at least one of the classes or has one of its parents +`isNotA($value, $class, $message = '')` | Check that a value is not of the class or has not one of its parents +`isArrayAccessible($value, $message = '')` | Check that a value can be accessed as an array +`uniqueValues($values, $message = '')` | Check that the given array contains unique values + +### Comparison Assertions + +Method | Description +----------------------------------------------- | ------------------------------------------------------------------ +`true($value, $message = '')` | Check that a value is `true` +`false($value, $message = '')` | Check that a value is `false` +`notFalse($value, $message = '')` | Check that a value is not `false` +`null($value, $message = '')` | Check that a value is `null` +`notNull($value, $message = '')` | Check that a value is not `null` +`isEmpty($value, $message = '')` | Check that a value is `empty()` +`notEmpty($value, $message = '')` | Check that a value is not `empty()` +`eq($value, $value2, $message = '')` | Check that a value equals another (`==`) +`notEq($value, $value2, $message = '')` | Check that a value does not equal another (`!=`) +`same($value, $value2, $message = '')` | Check that a value is identical to another (`===`) +`notSame($value, $value2, $message = '')` | Check that a value is not identical to another (`!==`) +`greaterThan($value, $value2, $message = '')` | Check that a value is greater than another +`greaterThanEq($value, $value2, $message = '')` | Check that a value is greater than or equal to another +`lessThan($value, $value2, $message = '')` | Check that a value is less than another +`lessThanEq($value, $value2, $message = '')` | Check that a value is less than or equal to another +`range($value, $min, $max, $message = '')` | Check that a value is within a range +`inArray($value, array $values, $message = '')` | Check that a value is one of a list of values +`oneOf($value, array $values, $message = '')` | Check that a value is one of a list of values (alias of `inArray`) + +### String Assertions + +You should check that a value is a string with `Assert::string()` before making +any of the following assertions. + +Method | Description +--------------------------------------------------- | ----------------------------------------------------------------- +`contains($value, $subString, $message = '')` | Check that a string contains a substring +`notContains($value, $subString, $message = '')` | Check that a string does not contain a substring +`startsWith($value, $prefix, $message = '')` | Check that a string has a prefix +`notStartsWith($value, $prefix, $message = '')` | Check that a string does not have a prefix +`startsWithLetter($value, $message = '')` | Check that a string starts with a letter +`endsWith($value, $suffix, $message = '')` | Check that a string has a suffix +`notEndsWith($value, $suffix, $message = '')` | Check that a string does not have a suffix +`regex($value, $pattern, $message = '')` | Check that a string matches a regular expression +`notRegex($value, $pattern, $message = '')` | Check that a string does not match a regular expression +`unicodeLetters($value, $message = '')` | Check that a string contains Unicode letters only +`alpha($value, $message = '')` | Check that a string contains letters only +`digits($value, $message = '')` | Check that a string contains digits only +`alnum($value, $message = '')` | Check that a string contains letters and digits only +`lower($value, $message = '')` | Check that a string contains lowercase characters only +`upper($value, $message = '')` | Check that a string contains uppercase characters only +`length($value, $length, $message = '')` | Check that a string has a certain number of characters +`minLength($value, $min, $message = '')` | Check that a string has at least a certain number of characters +`maxLength($value, $max, $message = '')` | Check that a string has at most a certain number of characters +`lengthBetween($value, $min, $max, $message = '')` | Check that a string has a length in the given range +`uuid($value, $message = '')` | Check that a string is a valid UUID +`ip($value, $message = '')` | Check that a string is a valid IP (either IPv4 or IPv6) +`ipv4($value, $message = '')` | Check that a string is a valid IPv4 +`ipv6($value, $message = '')` | Check that a string is a valid IPv6 +`email($value, $message = '')` | Check that a string is a valid e-mail address +`notWhitespaceOnly($value, $message = '')` | Check that a string contains at least one non-whitespace character + +### File Assertions + +Method | Description +----------------------------------- | -------------------------------------------------- +`fileExists($value, $message = '')` | Check that a value is an existing path +`file($value, $message = '')` | Check that a value is an existing file +`directory($value, $message = '')` | Check that a value is an existing directory +`readable($value, $message = '')` | Check that a value is a readable path +`writable($value, $message = '')` | Check that a value is a writable path + +### Object Assertions + +Method | Description +----------------------------------------------------- | -------------------------------------------------- +`classExists($value, $message = '')` | Check that a value is an existing class name +`subclassOf($value, $class, $message = '')` | Check that a class is a subclass of another +`interfaceExists($value, $message = '')` | Check that a value is an existing interface name +`implementsInterface($value, $class, $message = '')` | Check that a class implements an interface +`propertyExists($value, $property, $message = '')` | Check that a property exists in a class/object +`propertyNotExists($value, $property, $message = '')` | Check that a property does not exist in a class/object +`methodExists($value, $method, $message = '')` | Check that a method exists in a class/object +`methodNotExists($value, $method, $message = '')` | Check that a method does not exist in a class/object + +### Array Assertions + +Method | Description +-------------------------------------------------- | ------------------------------------------------------------------ +`keyExists($array, $key, $message = '')` | Check that a key exists in an array +`keyNotExists($array, $key, $message = '')` | Check that a key does not exist in an array +`validArrayKey($key, $message = '')` | Check that a value is a valid array key (int or string) +`count($array, $number, $message = '')` | Check that an array contains a specific number of elements +`minCount($array, $min, $message = '')` | Check that an array contains at least a certain number of elements +`maxCount($array, $max, $message = '')` | Check that an array contains at most a certain number of elements +`countBetween($array, $min, $max, $message = '')` | Check that an array has a count in the given range +`isList($array, $message = '')` | Check that an array is a non-associative list +`isNonEmptyList($array, $message = '')` | Check that an array is a non-associative list, and not empty +`isMap($array, $message = '')` | Check that an array is associative and has strings as keys +`isNonEmptyMap($array, $message = '')` | Check that an array is associative and has strings as keys, and is not empty + +### Function Assertions + +Method | Description +------------------------------------------- | ----------------------------------------------------------------------------------------------------- +`throws($closure, $class, $message = '')` | Check that a function throws a certain exception. Subclasses of the exception class will be accepted. + +### Collection Assertions + +All of the above assertions can be prefixed with `all*()` to test the contents +of an array or a `\Traversable`: + +```php +Assert::allIsInstanceOf($employees, 'Acme\Employee'); +``` + +### Nullable Assertions + +All of the above assertions can be prefixed with `nullOr*()` to run the +assertion only if it the value is not `null`: + +```php +Assert::nullOrString($middleName, 'The middle name must be a string or null. Got: %s'); +``` + +### Extending Assert + +The `Assert` class comes with a few methods, which can be overridden to change the class behaviour. You can also extend it to +add your own assertions. + +#### Overriding methods + +Overriding the following methods in your assertion class allows you to change the behaviour of the assertions: + +* `public static function __callStatic($name, $arguments)` + * This method is used to 'create' the `nullOr` and `all` versions of the assertions. +* `protected static function valueToString($value)` + * This method is used for error messages, to convert the value to a string value for displaying. You could use this for representing a value object with a `__toString` method for example. +* `protected static function typeToString($value)` + * This method is used for error messages, to convert the a value to a string representing its type. +* `protected static function strlen($value)` + * This method is used to calculate string length for relevant methods, using the `mb_strlen` if available and useful. +* `protected static function reportInvalidArgument($message)` + * This method is called when an assertion fails, with the specified error message. Here you can throw your own exception, or log something. + + +Authors +------- + +* [Bernhard Schussek] a.k.a. [@webmozart] +* [The Community Contributors] + +Contribute +---------- + +Contributions to the package are always welcome! + +* Report any bugs or issues you find on the [issue tracker]. +* You can grab the source code at the package's [Git repository]. + +License +------- + +All contents of this package are licensed under the [MIT license]. + +[beberlei/assert]: https://github.com/beberlei/assert +[assert package]: https://github.com/beberlei/assert +[Composer]: https://getcomposer.org +[Bernhard Schussek]: https://webmozarts.com +[The Community Contributors]: https://github.com/webmozart/assert/graphs/contributors +[issue tracker]: https://github.com/webmozart/assert/issues +[Git repository]: https://github.com/webmozart/assert +[@webmozart]: https://twitter.com/webmozart +[MIT license]: LICENSE +[`Assert`]: src/Assert.php diff --git a/lib/composer/webmozart/assert/composer.json b/lib/composer/webmozart/assert/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..2e609b6307f65ebb748a1e94bc13eedf4cdd6c6f --- /dev/null +++ b/lib/composer/webmozart/assert/composer.json @@ -0,0 +1,38 @@ +{ + "name": "webmozart/assert", + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "license": "MIT", + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "conflict": { + "vimeo/psalm": "<3.9.1", + "phpstan/phpstan": "<0.12.20" + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Webmozart\\Assert\\Tests\\": "tests/", + "Webmozart\\Assert\\Bin\\": "bin/src" + } + } +} diff --git a/lib/composer/webmozart/assert/psalm.xml b/lib/composer/webmozart/assert/psalm.xml new file mode 100644 index 0000000000000000000000000000000000000000..9a430081953b6fc4aad55138fa3209f651661ad8 --- /dev/null +++ b/lib/composer/webmozart/assert/psalm.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/lib/composer/webmozart/assert/src/Assert.php b/lib/composer/webmozart/assert/src/Assert.php new file mode 100644 index 0000000000000000000000000000000000000000..b28e17841a523ccd22c705f619b2cb86dd1bdef4 --- /dev/null +++ b/lib/composer/webmozart/assert/src/Assert.php @@ -0,0 +1,2048 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\Assert; + +use ArrayAccess; +use BadMethodCallException; +use Closure; +use Countable; +use DateTime; +use DateTimeImmutable; +use Exception; +use InvalidArgumentException; +use ResourceBundle; +use SimpleXMLElement; +use Throwable; +use Traversable; + +/** + * Efficient assertions to validate the input/output of your methods. + * + * @mixin Mixin + * + * @since 1.0 + * + * @author Bernhard Schussek + */ +class Assert +{ + /** + * @psalm-pure + * @psalm-assert string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function string($value, $message = '') + { + if (!\is_string($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a string. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert non-empty-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function stringNotEmpty($value, $message = '') + { + static::string($value, $message); + static::notEq($value, '', $message); + } + + /** + * @psalm-pure + * @psalm-assert int $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function integer($value, $message = '') + { + if (!\is_int($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an integer. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert numeric $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function integerish($value, $message = '') + { + if (!\is_numeric($value) || $value != (int) $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an integerish value. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert float $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function float($value, $message = '') + { + if (!\is_float($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a float. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert numeric $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function numeric($value, $message = '') + { + if (!\is_numeric($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a numeric. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert int $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function natural($value, $message = '') + { + if (!\is_int($value) || $value < 0) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a non-negative integer. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert bool $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function boolean($value, $message = '') + { + if (!\is_bool($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a boolean. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert scalar $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function scalar($value, $message = '') + { + if (!\is_scalar($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a scalar. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert object $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function object($value, $message = '') + { + if (!\is_object($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an object. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert resource $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function resource($value, $type = null, $message = '') + { + if (!\is_resource($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a resource. Got: %s', + static::typeToString($value) + )); + } + + if ($type && $type !== \get_resource_type($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a resource of type %2$s. Got: %s', + static::typeToString($value), + $type + )); + } + } + + /** + * @psalm-pure + * @psalm-assert callable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isCallable($value, $message = '') + { + if (!\is_callable($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a callable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert array $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isArray($value, $message = '') + { + if (!\is_array($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isTraversable($value, $message = '') + { + @\trigger_error( + \sprintf( + 'The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.', + __METHOD__ + ), + \E_USER_DEPRECATED + ); + + if (!\is_array($value) && !($value instanceof Traversable)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a traversable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert array|ArrayAccess $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isArrayAccessible($value, $message = '') + { + if (!\is_array($value) && !($value instanceof ArrayAccess)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array accessible. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert countable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isCountable($value, $message = '') + { + if ( + !\is_array($value) + && !($value instanceof Countable) + && !($value instanceof ResourceBundle) + && !($value instanceof SimpleXMLElement) + ) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a countable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isIterable($value, $message = '') + { + if (!\is_array($value) && !($value instanceof Traversable)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an iterable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isInstanceOf($value, $class, $message = '') + { + if (!($value instanceof $class)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an instance of %2$s. Got: %s', + static::typeToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert !ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notInstanceOf($value, $class, $message = '') + { + if ($value instanceof $class) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an instance other than %2$s. Got: %s', + static::typeToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param mixed $value + * @param array $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isInstanceOfAny($value, array $classes, $message = '') + { + foreach ($classes as $class) { + if ($value instanceof $class) { + return; + } + } + + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an instance of any of %2$s. Got: %s', + static::typeToString($value), + \implode(', ', \array_map(array('static', 'valueToString'), $classes)) + )); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert ExpectedType|class-string $value + * + * @param object|string $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isAOf($value, $class, $message = '') + { + static::string($class, 'Expected class as a string. Got: %s'); + + if (!\is_a($value, $class, \is_string($value))) { + static::reportInvalidArgument(sprintf( + $message ?: 'Expected an instance of this class or to this class among his parents %2$s. Got: %s', + static::typeToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string $class + * @psalm-assert !UnexpectedType $value + * @psalm-assert !class-string $value + * + * @param object|string $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isNotA($value, $class, $message = '') + { + static::string($class, 'Expected class as a string. Got: %s'); + + if (\is_a($value, $class, \is_string($value))) { + static::reportInvalidArgument(sprintf( + $message ?: 'Expected an instance of this class or to this class among his parents other than %2$s. Got: %s', + static::typeToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param object|string $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isAnyOf($value, array $classes, $message = '') + { + foreach ($classes as $class) { + static::string($class, 'Expected class as a string. Got: %s'); + + if (\is_a($value, $class, \is_string($value))) { + return; + } + } + + static::reportInvalidArgument(sprintf( + $message ?: 'Expected an any of instance of this class or to this class among his parents other than %2$s. Got: %s', + static::typeToString($value), + \implode(', ', \array_map(array('static', 'valueToString'), $classes)) + )); + } + + /** + * @psalm-pure + * @psalm-assert empty $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isEmpty($value, $message = '') + { + if (!empty($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an empty value. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !empty $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notEmpty($value, $message = '') + { + if (empty($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a non-empty value. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function null($value, $message = '') + { + if (null !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected null. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notNull($value, $message = '') + { + if (null === $value) { + static::reportInvalidArgument( + $message ?: 'Expected a value other than null.' + ); + } + } + + /** + * @psalm-pure + * @psalm-assert true $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function true($value, $message = '') + { + if (true !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be true. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert false $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function false($value, $message = '') + { + if (false !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be false. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !false $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notFalse($value, $message = '') + { + if (false === $value) { + static::reportInvalidArgument( + $message ?: 'Expected a value other than false.' + ); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function ip($value, $message = '') + { + if (false === \filter_var($value, \FILTER_VALIDATE_IP)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be an IP. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function ipv4($value, $message = '') + { + if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be an IPv4. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function ipv6($value, $message = '') + { + if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be an IPv6. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function email($value, $message = '') + { + if (false === \filter_var($value, FILTER_VALIDATE_EMAIL)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be a valid e-mail address. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * Does non strict comparisons on the items, so ['3', 3] will not pass the assertion. + * + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function uniqueValues(array $values, $message = '') + { + $allValues = \count($values); + $uniqueValues = \count(\array_unique($values)); + + if ($allValues !== $uniqueValues) { + $difference = $allValues - $uniqueValues; + + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array of unique values, but %s of them %s duplicated', + $difference, + (1 === $difference ? 'is' : 'are') + )); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function eq($value, $expect, $message = '') + { + if ($expect != $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value equal to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($expect) + )); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notEq($value, $expect, $message = '') + { + if ($expect == $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a different value than %s.', + static::valueToString($expect) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function same($value, $expect, $message = '') + { + if ($expect !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value identical to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($expect) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notSame($value, $expect, $message = '') + { + if ($expect === $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value not identical to %s.', + static::valueToString($expect) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function greaterThan($value, $limit, $message = '') + { + if ($value <= $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value greater than %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function greaterThanEq($value, $limit, $message = '') + { + if ($value < $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value greater than or equal to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lessThan($value, $limit, $message = '') + { + if ($value >= $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value less than %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lessThanEq($value, $limit, $message = '') + { + if ($value > $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value less than or equal to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * Inclusive range, so Assert::(3, 3, 5) passes. + * + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function range($value, $min, $max, $message = '') + { + if ($value < $min || $value > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value between %2$s and %3$s. Got: %s', + static::valueToString($value), + static::valueToString($min), + static::valueToString($max) + )); + } + } + + /** + * A more human-readable alias of Assert::inArray(). + * + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function oneOf($value, array $values, $message = '') + { + static::inArray($value, $values, $message); + } + + /** + * Does strict comparison, so Assert::inArray(3, ['3']) does not pass the assertion. + * + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function inArray($value, array $values, $message = '') + { + if (!\in_array($value, $values, true)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected one of: %2$s. Got: %s', + static::valueToString($value), + \implode(', ', \array_map(array('static', 'valueToString'), $values)) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function contains($value, $subString, $message = '') + { + if (false === \strpos($value, $subString)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain %2$s. Got: %s', + static::valueToString($value), + static::valueToString($subString) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notContains($value, $subString, $message = '') + { + if (false !== \strpos($value, $subString)) { + static::reportInvalidArgument(\sprintf( + $message ?: '%2$s was not expected to be contained in a value. Got: %s', + static::valueToString($value), + static::valueToString($subString) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notWhitespaceOnly($value, $message = '') + { + if (\preg_match('/^\s*$/', $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a non-whitespace string. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function startsWith($value, $prefix, $message = '') + { + if (0 !== \strpos($value, $prefix)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to start with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($prefix) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notStartsWith($value, $prefix, $message = '') + { + if (0 === \strpos($value, $prefix)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value not to start with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($prefix) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function startsWithLetter($value, $message = '') + { + static::string($value); + + $valid = isset($value[0]); + + if ($valid) { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = \ctype_alpha($value[0]); + \setlocale(LC_CTYPE, $locale); + } + + if (!$valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to start with a letter. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function endsWith($value, $suffix, $message = '') + { + if ($suffix !== \substr($value, -\strlen($suffix))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to end with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($suffix) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notEndsWith($value, $suffix, $message = '') + { + if ($suffix === \substr($value, -\strlen($suffix))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value not to end with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($suffix) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function regex($value, $pattern, $message = '') + { + if (!\preg_match($pattern, $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The value %s does not match the expected pattern.', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notRegex($value, $pattern, $message = '') + { + if (\preg_match($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The value %s matches the pattern %s (at offset %d).', + static::valueToString($value), + static::valueToString($pattern), + $matches[0][1] + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function unicodeLetters($value, $message = '') + { + static::string($value); + + if (!\preg_match('/^\p{L}+$/u', $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain only Unicode letters. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function alpha($value, $message = '') + { + static::string($value); + + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_alpha($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain only letters. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function digits($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_digit($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain digits only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function alnum($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_alnum($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain letters and digits only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert lowercase-string $value + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lower($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_lower($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain lowercase characters only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !lowercase-string $value + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function upper($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_upper($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain uppercase characters only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function length($value, $length, $message = '') + { + if ($length !== static::strlen($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain %2$s characters. Got: %s', + static::valueToString($value), + $length + )); + } + } + + /** + * Inclusive min. + * + * @psalm-pure + * + * @param string $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function minLength($value, $min, $message = '') + { + if (static::strlen($value) < $min) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain at least %2$s characters. Got: %s', + static::valueToString($value), + $min + )); + } + } + + /** + * Inclusive max. + * + * @psalm-pure + * + * @param string $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function maxLength($value, $max, $message = '') + { + if (static::strlen($value) > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain at most %2$s characters. Got: %s', + static::valueToString($value), + $max + )); + } + } + + /** + * Inclusive , so Assert::lengthBetween('asd', 3, 5); passes the assertion. + * + * @psalm-pure + * + * @param string $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lengthBetween($value, $min, $max, $message = '') + { + $length = static::strlen($value); + + if ($length < $min || $length > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s', + static::valueToString($value), + $min, + $max + )); + } + } + + /** + * Will also pass if $value is a directory, use Assert::file() instead if you need to be sure it is a file. + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function fileExists($value, $message = '') + { + static::string($value); + + if (!\file_exists($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The file %s does not exist.', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function file($value, $message = '') + { + static::fileExists($value, $message); + + if (!\is_file($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is not a file.', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function directory($value, $message = '') + { + static::fileExists($value, $message); + + if (!\is_dir($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is no directory.', + static::valueToString($value) + )); + } + } + + /** + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function readable($value, $message = '') + { + if (!\is_readable($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is not readable.', + static::valueToString($value) + )); + } + } + + /** + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function writable($value, $message = '') + { + if (!\is_writable($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is not writable.', + static::valueToString($value) + )); + } + } + + /** + * @psalm-assert class-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function classExists($value, $message = '') + { + if (!\class_exists($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an existing class name. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert class-string|ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function subclassOf($value, $class, $message = '') + { + if (!\is_subclass_of($value, $class)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a sub-class of %2$s. Got: %s', + static::valueToString($value), + static::valueToString($class) + )); + } + } + + /** + * @psalm-assert class-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function interfaceExists($value, $message = '') + { + if (!\interface_exists($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an existing interface name. got %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $interface + * @psalm-assert class-string $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function implementsInterface($value, $interface, $message = '') + { + if (!\in_array($interface, \class_implements($value))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an implementation of %2$s. Got: %s', + static::valueToString($value), + static::valueToString($interface) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function propertyExists($classOrObject, $property, $message = '') + { + if (!\property_exists($classOrObject, $property)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the property %s to exist.', + static::valueToString($property) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function propertyNotExists($classOrObject, $property, $message = '') + { + if (\property_exists($classOrObject, $property)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the property %s to not exist.', + static::valueToString($property) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function methodExists($classOrObject, $method, $message = '') + { + if (!(\is_string($classOrObject) || \is_object($classOrObject)) || !\method_exists($classOrObject, $method)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the method %s to exist.', + static::valueToString($method) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function methodNotExists($classOrObject, $method, $message = '') + { + if ((\is_string($classOrObject) || \is_object($classOrObject)) && \method_exists($classOrObject, $method)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the method %s to not exist.', + static::valueToString($method) + )); + } + } + + /** + * @psalm-pure + * + * @param array $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function keyExists($array, $key, $message = '') + { + if (!(isset($array[$key]) || \array_key_exists($key, $array))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the key %s to exist.', + static::valueToString($key) + )); + } + } + + /** + * @psalm-pure + * + * @param array $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function keyNotExists($array, $key, $message = '') + { + if (isset($array[$key]) || \array_key_exists($key, $array)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the key %s to not exist.', + static::valueToString($key) + )); + } + } + + /** + * Checks if a value is a valid array key (int or string). + * + * @psalm-pure + * @psalm-assert array-key $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function validArrayKey($value, $message = '') + { + if (!(\is_int($value) || \is_string($value))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected string or integer. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function count($array, $number, $message = '') + { + static::eq( + \count($array), + $number, + \sprintf( + $message ?: 'Expected an array to contain %d elements. Got: %d.', + $number, + \count($array) + ) + ); + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function minCount($array, $min, $message = '') + { + if (\count($array) < $min) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array to contain at least %2$d elements. Got: %d', + \count($array), + $min + )); + } + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function maxCount($array, $max, $message = '') + { + if (\count($array) > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array to contain at most %2$d elements. Got: %d', + \count($array), + $max + )); + } + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function countBetween($array, $min, $max, $message = '') + { + $count = \count($array); + + if ($count < $min || $count > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d', + $count, + $min, + $max + )); + } + } + + /** + * @psalm-pure + * @psalm-assert list $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isList($array, $message = '') + { + if (!\is_array($array) || $array !== \array_values($array)) { + static::reportInvalidArgument( + $message ?: 'Expected list - non-associative array.' + ); + } + } + + /** + * @psalm-pure + * @psalm-assert non-empty-list $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isNonEmptyList($array, $message = '') + { + static::isList($array, $message); + static::notEmpty($array, $message); + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array $array + * @psalm-assert array $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isMap($array, $message = '') + { + if ( + !\is_array($array) || + \array_keys($array) !== \array_filter(\array_keys($array), '\is_string') + ) { + static::reportInvalidArgument( + $message ?: 'Expected map - associative array with string keys.' + ); + } + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array $array + * @psalm-assert array $array + * @psalm-assert !empty $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isNonEmptyMap($array, $message = '') + { + static::isMap($array, $message); + static::notEmpty($array, $message); + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function uuid($value, $message = '') + { + $value = \str_replace(array('urn:', 'uuid:', '{', '}'), '', $value); + + // The nil UUID is special form of UUID that is specified to have all + // 128 bits set to zero. + if ('00000000-0000-0000-0000-000000000000' === $value) { + return; + } + + if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Value %s is not a valid UUID.', + static::valueToString($value) + )); + } + } + + /** + * @psalm-param class-string $class + * + * @param Closure $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function throws(Closure $expression, $class = 'Exception', $message = '') + { + static::string($class); + + $actual = 'none'; + + try { + $expression(); + } catch (Exception $e) { + $actual = \get_class($e); + if ($e instanceof $class) { + return; + } + } catch (Throwable $e) { + $actual = \get_class($e); + if ($e instanceof $class) { + return; + } + } + + static::reportInvalidArgument($message ?: \sprintf( + 'Expected to throw "%s", got "%s"', + $class, + $actual + )); + } + + /** + * @throws BadMethodCallException + */ + public static function __callStatic($name, $arguments) + { + if ('nullOr' === \substr($name, 0, 6)) { + if (null !== $arguments[0]) { + $method = \lcfirst(\substr($name, 6)); + \call_user_func_array(array('static', $method), $arguments); + } + + return; + } + + if ('all' === \substr($name, 0, 3)) { + static::isIterable($arguments[0]); + + $method = \lcfirst(\substr($name, 3)); + $args = $arguments; + + foreach ($arguments[0] as $entry) { + $args[0] = $entry; + + \call_user_func_array(array('static', $method), $args); + } + + return; + } + + throw new BadMethodCallException('No such method: '.$name); + } + + /** + * @param mixed $value + * + * @return string + */ + protected static function valueToString($value) + { + if (null === $value) { + return 'null'; + } + + if (true === $value) { + return 'true'; + } + + if (false === $value) { + return 'false'; + } + + if (\is_array($value)) { + return 'array'; + } + + if (\is_object($value)) { + if (\method_exists($value, '__toString')) { + return \get_class($value).': '.self::valueToString($value->__toString()); + } + + if ($value instanceof DateTime || $value instanceof DateTimeImmutable) { + return \get_class($value).': '.self::valueToString($value->format('c')); + } + + return \get_class($value); + } + + if (\is_resource($value)) { + return 'resource'; + } + + if (\is_string($value)) { + return '"'.$value.'"'; + } + + return (string) $value; + } + + /** + * @param mixed $value + * + * @return string + */ + protected static function typeToString($value) + { + return \is_object($value) ? \get_class($value) : \gettype($value); + } + + protected static function strlen($value) + { + if (!\function_exists('mb_detect_encoding')) { + return \strlen($value); + } + + if (false === $encoding = \mb_detect_encoding($value)) { + return \strlen($value); + } + + return \mb_strlen($value, $encoding); + } + + /** + * @param string $message + * + * @throws InvalidArgumentException + * + * @psalm-pure this method is not supposed to perform side-effects + */ + protected static function reportInvalidArgument($message) + { + throw new InvalidArgumentException($message); + } + + private function __construct() + { + } +} diff --git a/lib/composer/webmozart/assert/src/Mixin.php b/lib/composer/webmozart/assert/src/Mixin.php new file mode 100644 index 0000000000000000000000000000000000000000..3ad9b2d042f6aac2f860effabb00e41033ae2535 --- /dev/null +++ b/lib/composer/webmozart/assert/src/Mixin.php @@ -0,0 +1,1971 @@ + $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allString($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|non-empty-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrStringNotEmpty($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allStringNotEmpty($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|int $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrInteger($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allInteger($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|numeric $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIntegerish($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIntegerish($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|float $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrFloat($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allFloat($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|numeric $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNumeric($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNumeric($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|int $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNatural($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNatural($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|bool $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrBoolean($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allBoolean($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|scalar $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrScalar($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allScalar($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|object $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrObject($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allObject($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|resource $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrResource($value, $type = null, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allResource($value, $type = null, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|callable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsCallable($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsCallable($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|array $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsArray($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsArray($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|iterable $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsTraversable($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsTraversable($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|array|ArrayAccess $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsArrayAccessible($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsArrayAccessible($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|countable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsCountable($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsCountable($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsIterable($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsIterable($value, $message = ''); + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert null|ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsInstanceOf($value, $class, $message = ''); + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsInstanceOf($value, $class, $message = ''); + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNotInstanceOf($value, $class, $message = ''); + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNotInstanceOf($value, $class, $message = ''); + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param mixed $value + * @param array $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsInstanceOfAny($value, $classes, $message = ''); + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param mixed $value + * @param array $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsInstanceOfAny($value, $classes, $message = ''); + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert null|ExpectedType|class-string $value + * + * @param null|object|string $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsAOf($value, $class, $message = ''); + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable> $value + * + * @param iterable $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsAOf($value, $class, $message = ''); + + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string $class + * + * @param null|object|string $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsNotA($value, $class, $message = ''); + + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string $class + * + * @param iterable $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsNotA($value, $class, $message = ''); + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param null|object|string $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsAnyOf($value, $classes, $message = ''); + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param iterable $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsAnyOf($value, $classes, $message = ''); + + /** + * @psalm-pure + * @psalm-assert empty $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsEmpty($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsEmpty($value, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNotEmpty($value, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNotEmpty($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNull($value, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNotNull($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|true $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrTrue($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allTrue($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|false $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrFalse($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allFalse($value, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNotFalse($value, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNotFalse($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIp($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIp($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIpv4($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIpv4($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIpv6($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIpv6($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrEmail($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allEmail($value, $message = ''); + + /** + * @param null|array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrUniqueValues($values, $message = ''); + + /** + * @param iterable $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allUniqueValues($values, $message = ''); + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrEq($value, $expect, $message = ''); + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allEq($value, $expect, $message = ''); + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNotEq($value, $expect, $message = ''); + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNotEq($value, $expect, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrSame($value, $expect, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allSame($value, $expect, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNotSame($value, $expect, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNotSame($value, $expect, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrGreaterThan($value, $limit, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allGreaterThan($value, $limit, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrGreaterThanEq($value, $limit, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allGreaterThanEq($value, $limit, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrLessThan($value, $limit, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allLessThan($value, $limit, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrLessThanEq($value, $limit, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allLessThanEq($value, $limit, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrRange($value, $min, $max, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allRange($value, $min, $max, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrOneOf($value, $values, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allOneOf($value, $values, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrInArray($value, $values, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allInArray($value, $values, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrContains($value, $subString, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allContains($value, $subString, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNotContains($value, $subString, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNotContains($value, $subString, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNotWhitespaceOnly($value, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNotWhitespaceOnly($value, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrStartsWith($value, $prefix, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allStartsWith($value, $prefix, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNotStartsWith($value, $prefix, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNotStartsWith($value, $prefix, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrStartsWithLetter($value, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allStartsWithLetter($value, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrEndsWith($value, $suffix, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allEndsWith($value, $suffix, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNotEndsWith($value, $suffix, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNotEndsWith($value, $suffix, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrRegex($value, $pattern, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allRegex($value, $pattern, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrNotRegex($value, $pattern, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allNotRegex($value, $pattern, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrUnicodeLetters($value, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allUnicodeLetters($value, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrAlpha($value, $message = ''); + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allAlpha($value, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrDigits($value, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allDigits($value, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrAlnum($value, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allAlnum($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|lowercase-string $value + * + * @param null|string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrLower($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allLower($value, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrUpper($value, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allUpper($value, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrLength($value, $length, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allLength($value, $length, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrMinLength($value, $min, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allMinLength($value, $min, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrMaxLength($value, $max, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allMaxLength($value, $max, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrLengthBetween($value, $min, $max, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allLengthBetween($value, $min, $max, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrFileExists($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allFileExists($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrFile($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allFile($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrDirectory($value, $message = ''); + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allDirectory($value, $message = ''); + + /** + * @param null|string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrReadable($value, $message = ''); + + /** + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allReadable($value, $message = ''); + + /** + * @param null|string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrWritable($value, $message = ''); + + /** + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allWritable($value, $message = ''); + + /** + * @psalm-assert null|class-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrClassExists($value, $message = ''); + + /** + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allClassExists($value, $message = ''); + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert null|class-string|ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrSubclassOf($value, $class, $message = ''); + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable|ExpectedType> $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allSubclassOf($value, $class, $message = ''); + + /** + * @psalm-assert null|class-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrInterfaceExists($value, $message = ''); + + /** + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allInterfaceExists($value, $message = ''); + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $interface + * @psalm-assert null|class-string $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrImplementsInterface($value, $interface, $message = ''); + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $interface + * @psalm-assert iterable> $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allImplementsInterface($value, $interface, $message = ''); + + /** + * @psalm-pure + * @psalm-param null|class-string|object $classOrObject + * + * @param null|string|object $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrPropertyExists($classOrObject, $property, $message = ''); + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allPropertyExists($classOrObject, $property, $message = ''); + + /** + * @psalm-pure + * @psalm-param null|class-string|object $classOrObject + * + * @param null|string|object $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrPropertyNotExists($classOrObject, $property, $message = ''); + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allPropertyNotExists($classOrObject, $property, $message = ''); + + /** + * @psalm-pure + * @psalm-param null|class-string|object $classOrObject + * + * @param null|string|object $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrMethodExists($classOrObject, $method, $message = ''); + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allMethodExists($classOrObject, $method, $message = ''); + + /** + * @psalm-pure + * @psalm-param null|class-string|object $classOrObject + * + * @param null|string|object $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrMethodNotExists($classOrObject, $method, $message = ''); + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allMethodNotExists($classOrObject, $method, $message = ''); + + /** + * @psalm-pure + * + * @param null|array $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrKeyExists($array, $key, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allKeyExists($array, $key, $message = ''); + + /** + * @psalm-pure + * + * @param null|array $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrKeyNotExists($array, $key, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allKeyNotExists($array, $key, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|array-key $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrValidArrayKey($value, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allValidArrayKey($value, $message = ''); + + /** + * @param null|Countable|array $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrCount($array, $number, $message = ''); + + /** + * @param iterable $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allCount($array, $number, $message = ''); + + /** + * @param null|Countable|array $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrMinCount($array, $min, $message = ''); + + /** + * @param iterable $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allMinCount($array, $min, $message = ''); + + /** + * @param null|Countable|array $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrMaxCount($array, $max, $message = ''); + + /** + * @param iterable $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allMaxCount($array, $max, $message = ''); + + /** + * @param null|Countable|array $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrCountBetween($array, $min, $max, $message = ''); + + /** + * @param iterable $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allCountBetween($array, $min, $max, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|list $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsList($array, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsList($array, $message = ''); + + /** + * @psalm-pure + * @psalm-assert null|non-empty-list $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsNonEmptyList($array, $message = ''); + + /** + * @psalm-pure + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsNonEmptyList($array, $message = ''); + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param null|mixed|array $array + * @psalm-assert null|array $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsMap($array, $message = ''); + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param iterable> $array + * @psalm-assert iterable> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsMap($array, $message = ''); + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param null|mixed|array $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrIsNonEmptyMap($array, $message = ''); + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param iterable> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allIsNonEmptyMap($array, $message = ''); + + /** + * @psalm-pure + * + * @param null|string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrUuid($value, $message = ''); + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allUuid($value, $message = ''); + + /** + * @psalm-param class-string $class + * + * @param null|Closure $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function nullOrThrows($expression, $class = 'Exception', $message = ''); + + /** + * @psalm-param class-string $class + * + * @param iterable $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function allThrows($expression, $class = 'Exception', $message = ''); +} diff --git a/lib/composer/webmozart/glob/.gitignore b/lib/composer/webmozart/glob/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..3a9875b460f3bab5c185900528ba5efe1a18de33 --- /dev/null +++ b/lib/composer/webmozart/glob/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/lib/composer/webmozart/glob/.styleci.yml b/lib/composer/webmozart/glob/.styleci.yml new file mode 100644 index 0000000000000000000000000000000000000000..295fafec0a157999c032cb3ea3d34a5783bba65b --- /dev/null +++ b/lib/composer/webmozart/glob/.styleci.yml @@ -0,0 +1,8 @@ +preset: symfony + +enabled: + - ordered_use + - strict + +disabled: + - empty_return diff --git a/lib/composer/webmozart/glob/.travis.yml b/lib/composer/webmozart/glob/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..04a8b572b30169bfc2952b1f725ee9378f006780 --- /dev/null +++ b/lib/composer/webmozart/glob/.travis.yml @@ -0,0 +1,30 @@ +language: php + +sudo: false + +cache: + directories: + - $HOME/.composer/cache/files + +matrix: + include: + - php: 5.3 + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 5.6 + env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable' + - php: hhvm + - php: nightly + - php: 7.0 + allow_failures: + - php: hhvm + - php: nightly + fast_finish: true + +install: composer update $COMPOSER_FLAGS -n + +script: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover + +after_script: + - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi;' diff --git a/lib/composer/webmozart/glob/CHANGELOG.md b/lib/composer/webmozart/glob/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..7b0ee0eb935c0b88eb15002d38a1e67b549b6261 --- /dev/null +++ b/lib/composer/webmozart/glob/CHANGELOG.md @@ -0,0 +1,80 @@ +Changelog +========= + +* 4.1.0 (2015-12-29) + + * added flag `Glob::FILTER_VALUE` for `Glob::filter()` + * added flag `Glob::FILTER_KEY` for `Glob::filter()` + +* 4.0.0 (2015-12-28) + + * switched to a better-performing algorithm for `Glob::toRegEx()` + * switched to a better-performing algorithm for `Glob::getStaticPrefix()` + * removed `Glob::ESCAPE` flag - escaping is now always enabled + * added argument `$delimiter` to `Glob::toRegEx()` + * removed `Symbol` class + +* 3.3.1 (2015-12-23) + + * checked return value of `glob()` + +* 3.3.0 (2015-12-23) + + * improved globbing performance by falling back to PHP's `glob()` function + whenever possible + * added support for character ranges `[a-c]` + +* 3.2.0 (2015-12-23) + + * added support for `?` which matches any character + * added support for character classes `[abc]` which match any of the specified + characters + * added support for inverted character classes `[^abc]` which match any but + the specified characters + +* 3.1.1 (2015-08-24) + + * fixed minimum versions in composer.json + +* 3.1.0 (2015-08-21) + + * added `TestUtil` class + * fixed normalizing of slashes on Windows + +* 3.0.0 (2015-08-11) + + * `RecursiveDirectoryIterator` now inherits from `\RecursiveDirectoryIterator` + for performance reasons. Support for `seek()` was removed on PHP versions + < 5.5.23 or < 5.6.7 + * made `Glob` final + +* 2.0.1 (2015-05-21) + + * upgraded to webmozart/path-util 2.0 + +* 2.0.0 (2015-04-06) + + * restricted `**` to be used within two separators only: `/**/`. This improves + performance while maintaining equal expressiveness + * added support for stream wrappers + +* 1.0.0 (2015-03-19) + + * added support for sets: `{ab,cd}` + +* 1.0.0-beta3 (2015-01-30) + + * fixed installation on Windows + +* 1.0.0-beta2 (2015-01-22) + + * implemented Ant-like globbing: `*` does not match directory separators + anymore, but `**` does + * escaping must now be explicitly enabled by passing the flag `Glob::ESCAPE` + to any of the `Glob` methods + * fixed: replaced fatal error by `InvalidArgumentException` when globs are + not absolute + +* 1.0.0-beta (2015-01-12) + + * first release diff --git a/lib/composer/webmozart/glob/LICENSE b/lib/composer/webmozart/glob/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9e2e3075eb844a130a8532ed062169d8d06237a7 --- /dev/null +++ b/lib/composer/webmozart/glob/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Bernhard Schussek + +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/lib/composer/webmozart/glob/README.md b/lib/composer/webmozart/glob/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8c17377ab55e4e2ee683bfde25f76470e1d711df --- /dev/null +++ b/lib/composer/webmozart/glob/README.md @@ -0,0 +1,215 @@ +Webmozart Glob +============== + +[![Build Status](https://travis-ci.org/webmozart/glob.svg?branch=4.1.0)](https://travis-ci.org/webmozart/glob) +[![Build status](https://ci.appveyor.com/api/projects/status/das6j3x0org6219m/branch/master?svg=true)](https://ci.appveyor.com/project/webmozart/glob/branch/master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/webmozart/glob/badges/quality-score.png?b=4.1.0)](https://scrutinizer-ci.com/g/webmozart/glob/?branch=4.1.0) +[![Latest Stable Version](https://poser.pugx.org/webmozart/glob/v/stable.svg)](https://packagist.org/packages/webmozart/glob) +[![Total Downloads](https://poser.pugx.org/webmozart/glob/downloads.svg)](https://packagist.org/packages/webmozart/glob) +[![Dependency Status](https://www.versioneye.com/php/webmozart:glob/4.1.0/badge.svg)](https://www.versioneye.com/php/webmozart:glob/4.1.0) + +Latest release: [4.1.0](https://packagist.org/packages/webmozart/glob#4.1.0) + +A utility implementing Ant-like globbing. + +Syntax: + +* `?` matches any character +* `*` matches zero or more characters, except `/` +* `/**/` matches zero or more directory names +* `[abc]` matches a single character `a`, `b` or `c` +* `[a-c]` matches a single character `a`, `b` or `c` +* `[^abc]` matches any character but `a`, `b` or `c` +* `[^a-c]` matches any character but `a`, `b` or `c` +* `{ab,cd}` matches `ab` or `cd` + +[API Documentation] + +Comparison with glob() +---------------------- + +Compared to PHP's native `glob()` function, this utility supports: + +* `/**/` for matching zero or more directories +* globbing custom stream wrappers, like `myscheme://path/**/*.css` +* matching globs against path strings +* filtering arrays of path strings by a glob +* exceptions if the glob contains invalid syntax + +Since PHP's native `glob()` function is much more efficient, this utility uses +`glob()` internally whenever possible (i.e. when no special feature is used). + +Installation +------------ + +Use [Composer] to install the package: + +``` +$ composer require webmozart/glob +``` + +Usage +----- + +The main class of the package is [`Glob`]. Use `Glob::glob()` to glob the +filesystem: + +```php +use Webmozart\Glob\Glob; + +$paths = Glob::glob('/path/to/dir/*.css'); +``` + +You can also use [`GlobIterator`] to search the filesystem iteratively. However, +the iterator is not guaranteed to return sorted results: + +```php +use Webmozart\Glob\Iterator\GlobIterator; + +$iterator = new GlobIterator('/path/to/dir/*.css'); + +foreach ($iterator as $path) { + // ... +} +``` + +### Path Matching + +The package also provides utility methods for comparing paths against globs. +Use `Glob::match()` to match a path against a glob: + +```php +if (Glob::match($path, '/path/to/dir/*.css')) { + // ... +} +``` + +`Glob::filter()` filters a list of paths by a glob: + +```php +$paths = Glob::filter($paths, '/path/to/dir/*.css'); +``` + +The same can be achieved iteratively with [`GlobFilterIterator`]: + +```php +use Webmozart\Glob\Iterator\GlobFilterIterator; + +$iterator = new GlobFilterIterator('/path/to/dir/*.css', new ArrayIterator($paths)); + +foreach ($iterator as $path) { + // ... +} +``` + +You can also filter the keys of the path list by passing the `FILTER_KEY` +constant of the respective class: + + +```php +$paths = Glob::filter($paths, '/path/to/dir/*.css', Glob::FILTER_KEY); + +$iterator = new GlobFilterIterator( + '/path/to/dir/*.css', + new ArrayIterator($paths), + GlobFilterIterator::FILTER_KEY +); +``` + +### Relative Globs + +Relative globs such as `*.css` are not supported. Usually, such globs refer to +paths relative to the current working directory. This utility, however, does not +want to make such assumptions. Hence you should always pass absolute globs. + +If you want to allow users to pass relative globs, I recommend to turn the globs +into absolute globs using the [Webmozart Path Utility]: + +```php +use Webmozart\Glob\Glob; +use Webmozart\PathUtil\Path; + +// If $glob is absolute, that glob is used without modification. +// If $glob is relative, it is turned into an absolute path based on the current +// working directory. +$paths = Glob::glob(Path::makeAbsolute($glob, getcwd()); +``` + +### Windows Compatibility + +Globs need to be passed in [canonical form] with forward slashes only. +Returned paths contain forward slashes only. + +### Escaping + +The `Glob` class supports escaping by typing a backslash character `\` before +any special character: + +```php +$paths = Glob::glob('/backup\\*/*.css'); +``` + +In this example, the glob matches all CSS files in the `/backup*` directory +rather than in all directories starting with `/backup`. Due to PHP's own +escaping in strings, the backslash character `\` needs to be typed twice to +produce a single `\` in the string. + +The following escape sequences are available: + +* `\\?`: match a `?` in the path +* `\\*`: match a `*` in the path +* `\\{`: match a `{` in the path +* `\\}`: match a `}` in the path +* `\\[`: match a `[` in the path +* `\\]`: match a `]` in the path +* `\\^`: match a `^` in the path +* `\\-`: match a `-` in the path +* `\\\\`: match a `\` in the path + +### Stream Wrappers + +The `Glob` class supports [stream wrappers]: + +```php +$paths = Glob::glob('myscheme:///**/*.css'); +``` + +Authors +------- + +* [Bernhard Schussek] a.k.a. [@webmozart] +* [The Community Contributors] + +Contribute +---------- + +Contributions to the package are always welcome! + +* Report any bugs or issues you find on the [issue tracker]. +* You can grab the source code at the package's [Git repository]. + +Support +------- + +If you are having problems, send a mail to bschussek@gmail.com or shout out to +[@webmozart] on Twitter. + +License +------- + +All contents of this package are licensed under the [MIT license]. + +[API Documentation]: https://webmozart.github.io/glob/api/latest +[Composer]: https://getcomposer.org +[Bernhard Schussek]: http://webmozarts.com +[The Community Contributors]: https://github.com/webmozart/glob/graphs/contributors +[issue tracker]: https://github.com/webmozart/glob/issues +[Git repository]: https://github.com/webmozart/glob +[@webmozart]: https://twitter.com/webmozart +[MIT license]: LICENSE +[Webmozart Path Utility]: https://github.com/webmozart/path-util +[canonical form]: https://webmozart.github.io/path-util/api/latest/class-Webmozart.PathUtil.Path.html#_canonicalize +[stream wrappers]: http://php.net/manual/en/wrappers.php +[`Glob`]: https://webmozart.github.io/glob/api/latest/class-Webmozart.Glob.Glob.html +[`GlobIterator`]: https://webmozart.github.io/glob/api/latest/class-Webmozart.Glob.Iterator.GlobIterator.html +[`GlobFilterIterator`]: https://webmozart.github.io/glob/api/latest/class-Webmozart.Glob.Iterator.GlobFilterIterator.html diff --git a/lib/composer/webmozart/glob/appveyor.yml b/lib/composer/webmozart/glob/appveyor.yml new file mode 100644 index 0000000000000000000000000000000000000000..ff63677405780e7c44f68e6c9e58d5bd83269b17 --- /dev/null +++ b/lib/composer/webmozart/glob/appveyor.yml @@ -0,0 +1,34 @@ +build: false +shallow_clone: true +platform: x86 +clone_folder: c:\projects\webmozart\glob + +cache: + - '%LOCALAPPDATA%\Composer\files' + +init: + - SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH% + +environment: + matrix: + - COMPOSER_FLAGS: "" + - COMPOSER_FLAGS: --prefer-lowest --prefer-stable + +install: + - cinst -y OpenSSL.Light + - cinst -y php + - cd c:\tools\php + - copy php.ini-production php.ini /Y + - echo date.timezone="UTC" >> php.ini + - echo extension_dir=ext >> php.ini + - echo extension=php_openssl.dll >> php.ini + - echo extension=php_mbstring.dll >> php.ini + - echo extension=php_fileinfo.dll >> php.ini + - echo memory_limit=1G >> php.ini + - cd c:\projects\webmozart\glob + - php -r "readfile('http://getcomposer.org/installer');" | php + - php composer.phar update %COMPOSER_FLAGS% --no-interaction --no-progress + +test_script: + - cd c:\projects\webmozart\glob + - vendor\bin\phpunit.bat --verbose diff --git a/lib/composer/webmozart/glob/composer.json b/lib/composer/webmozart/glob/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..e10c12aaa5545738bf280f4a9af50b8806218018 --- /dev/null +++ b/lib/composer/webmozart/glob/composer.json @@ -0,0 +1,35 @@ +{ + "name": "webmozart/glob", + "description": "A PHP implementation of Ant's glob.", + "license": "MIT", + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "require": { + "php": "^5.3.3|^7.0", + "webmozart/path-util": "^2.2" + }, + "require-dev": { + "symfony/filesystem": "^2.5", + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "autoload": { + "psr-4": { + "Webmozart\\Glob\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Webmozart\\Glob\\Tests\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + } +} diff --git a/lib/composer/webmozart/glob/phpunit.xml.dist b/lib/composer/webmozart/glob/phpunit.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..93a6b34cb3d59743e1e0e87c1e67adea60ef69cb --- /dev/null +++ b/lib/composer/webmozart/glob/phpunit.xml.dist @@ -0,0 +1,16 @@ + + + + + + ./tests/ + + + + + + + ./src/ + + + diff --git a/lib/composer/webmozart/glob/src/Glob.php b/lib/composer/webmozart/glob/src/Glob.php new file mode 100644 index 0000000000000000000000000000000000000000..e6b3deadea6fb598fe13543c6475bd57e89691ca --- /dev/null +++ b/lib/composer/webmozart/glob/src/Glob.php @@ -0,0 +1,536 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\Glob; + +use InvalidArgumentException; +use Webmozart\Glob\Iterator\GlobIterator; +use Webmozart\PathUtil\Path; + +/** + * Searches and matches file paths using Ant-like globs. + * + * This class implements an Ant-like version of PHP's `glob()` function. The + * wildcard "*" matches any number of characters except directory separators. + * The double wildcard "**" matches any number of characters, including + * directory separators. + * + * Use {@link glob()} to glob the filesystem for paths: + * + * ```php + * foreach (Glob::glob('/project/**.twig') as $path) { + * // do something... + * } + * ``` + * + * Use {@link match()} to match a file path against a glob: + * + * ```php + * if (Glob::match('/project/views/index.html.twig', '/project/**.twig')) { + * // path matches + * } + * ``` + * + * You can also filter an array of paths for all paths that match your glob with + * {@link filter()}: + * + * ```php + * $filteredPaths = Glob::filter($paths, '/project/**.twig'); + * ``` + * + * Internally, the methods described above convert the glob into a regular + * expression that is then matched against the matched paths. If you need to + * match many paths against the same glob, you should convert the glob manually + * and use {@link preg_match()} to test the paths: + * + * ```php + * $staticPrefix = Glob::getStaticPrefix('/project/**.twig'); + * $regEx = Glob::toRegEx('/project/**.twig'); + * + * if (0 !== strpos($path, $staticPrefix)) { + * // no match + * } + * + * if (!preg_match($regEx, $path)) { + * // no match + * } + * ``` + * + * The method {@link getStaticPrefix()} returns the part of the glob up to the + * first wildcard "*". You should always test whether a path has this prefix + * before calling the much more expensive {@link preg_match()}. + * + * @since 1.0 + * + * @author Bernhard Schussek + */ +final class Glob +{ + /** + * Flag: Filter the values in {@link Glob::filter()}. + */ + const FILTER_VALUE = 1; + + /** + * Flag: Filter the keys in {@link Glob::filter()}. + */ + const FILTER_KEY = 2; + + /** + * Globs the file system paths matching the glob. + * + * The glob may contain the wildcard "*". This wildcard matches any number + * of characters, *including* directory separators. + * + * ```php + * foreach (Glob::glob('/project/**.twig') as $path) { + * // do something... + * } + * ``` + * + * @param string $glob The canonical glob. The glob should contain forward + * slashes as directory separators only. It must not + * contain any "." or ".." segments. Use the + * "webmozart/path-util" utility to canonicalize globs + * prior to calling this method. + * @param int $flags A bitwise combination of the flag constants in this + * class. + * + * @return string[] The matching paths. The keys of the array are + * incrementing integers. + */ + public static function glob($glob, $flags = 0) + { + $results = iterator_to_array(new GlobIterator($glob, $flags)); + + sort($results); + + return $results; + } + + /** + * Matches a path against a glob. + * + * ```php + * if (Glob::match('/project/views/index.html.twig', '/project/**.twig')) { + * // path matches + * } + * ``` + * + * @param string $path The path to match. + * @param string $glob The canonical glob. The glob should contain forward + * slashes as directory separators only. It must not + * contain any "." or ".." segments. Use the + * "webmozart/path-util" utility to canonicalize globs + * prior to calling this method. + * @param int $flags A bitwise combination of the flag constants in + * this class. + * + * @return bool Returns `true` if the path is matched by the glob. + */ + public static function match($path, $glob, $flags = 0) + { + if (!self::isDynamic($glob)) { + return $glob === $path; + } + + if (0 !== strpos($path, self::getStaticPrefix($glob, $flags))) { + return false; + } + + if (!preg_match(self::toRegEx($glob, $flags), $path)) { + return false; + } + + return true; + } + + /** + * Filters an array for paths matching a glob. + * + * The filtered array is returned. This array preserves the keys of the + * passed array. + * + * ```php + * $filteredPaths = Glob::filter($paths, '/project/**.twig'); + * ``` + * + * @param string[] $paths A list of paths. + * @param string $glob The canonical glob. The glob should contain + * forward slashes as directory separators only. It + * must not contain any "." or ".." segments. Use the + * "webmozart/path-util" utility to canonicalize + * globs prior to calling this method. + * @param int $flags A bitwise combination of the flag constants in + * this class. + * + * @return string[] The paths matching the glob indexed by their original + * keys. + */ + public static function filter(array $paths, $glob, $flags = self::FILTER_VALUE) + { + if (($flags & self::FILTER_VALUE) && ($flags & self::FILTER_KEY)) { + throw new InvalidArgumentException('The flags Glob::FILTER_VALUE and Glob::FILTER_KEY cannot be passed at the same time.'); + } + + if (!self::isDynamic($glob)) { + if ($flags & self::FILTER_KEY) { + return isset($paths[$glob]) ? array($glob => $paths[$glob]) : array(); + } + + $key = array_search($glob, $paths); + + return false !== $key ? array($key => $glob) : array(); + } + + $staticPrefix = self::getStaticPrefix($glob, $flags); + $regExp = self::toRegEx($glob, $flags); + $filter = function ($path) use ($staticPrefix, $regExp) { + return 0 === strpos($path, $staticPrefix) && preg_match($regExp, $path); + }; + + if (PHP_VERSION_ID >= 50600) { + $filterFlags = ($flags & self::FILTER_KEY) ? ARRAY_FILTER_USE_KEY : 0; + + return array_filter($paths, $filter, $filterFlags); + } + + // No support yet for the third argument of array_filter() + if ($flags & self::FILTER_KEY) { + $result = array(); + + foreach ($paths as $path => $value) { + if ($filter($path)) { + $result[$path] = $value; + } + } + + return $result; + } + + return array_filter($paths, $filter); + } + + /** + * Returns the base path of a glob. + * + * This method returns the most specific directory that contains all files + * matched by the glob. If this directory does not exist on the file system, + * it's not necessary to execute the glob algorithm. + * + * More specifically, the "base path" is the longest path trailed by a "/" + * on the left of the first wildcard "*". If the glob does not contain + * wildcards, the directory name of the glob is returned. + * + * ```php + * Glob::getBasePath('/css/*.css'); + * // => /css + * + * Glob::getBasePath('/css/style.css'); + * // => /css + * + * Glob::getBasePath('/css/st*.css'); + * // => /css + * + * Glob::getBasePath('/*.css'); + * // => / + * ``` + * + * @param string $glob The canonical glob. The glob should contain forward + * slashes as directory separators only. It must not + * contain any "." or ".." segments. Use the + * "webmozart/path-util" utility to canonicalize globs + * prior to calling this method. + * @param int $flags A bitwise combination of the flag constants in this + * class. + * + * @return string The base path of the glob. + */ + public static function getBasePath($glob, $flags = 0) + { + // Search the static prefix for the last "/" + $staticPrefix = self::getStaticPrefix($glob, $flags); + + if (false !== ($pos = strrpos($staticPrefix, '/'))) { + // Special case: Return "/" if the only slash is at the beginning + // of the glob + if (0 === $pos) { + return '/'; + } + + // Special case: Include trailing slash of "scheme:///foo" + if ($pos - 3 === strpos($glob, '://')) { + return substr($staticPrefix, 0, $pos + 1); + } + + return substr($staticPrefix, 0, $pos); + } + + // Glob contains no slashes on the left of the wildcard + // Return an empty string + return ''; + } + + /** + * Converts a glob to a regular expression. + * + * Use this method if you need to match many paths against a glob: + * + * ```php + * $staticPrefix = Glob::getStaticPrefix('/project/**.twig'); + * $regEx = Glob::toRegEx('/project/**.twig'); + * + * if (0 !== strpos($path, $staticPrefix)) { + * // no match + * } + * + * if (!preg_match($regEx, $path)) { + * // no match + * } + * ``` + * + * You should always test whether a path contains the static prefix of the + * glob returned by {@link getStaticPrefix()} to reduce the number of calls + * to the expensive {@link preg_match()}. + * + * @param string $glob The canonical glob. The glob should contain forward + * slashes as directory separators only. It must not + * contain any "." or ".." segments. Use the + * "webmozart/path-util" utility to canonicalize globs + * prior to calling this method. + * @param int $flags A bitwise combination of the flag constants in this + * class. + * + * @return string The regular expression for matching the glob. + */ + public static function toRegEx($glob, $flags = 0, $delimiter = '~') + { + if (!Path::isAbsolute($glob) && false === strpos($glob, '://')) { + throw new InvalidArgumentException(sprintf( + 'The glob "%s" is not absolute and not a URI.', + $glob + )); + } + + $inSquare = false; + $curlyLevels = 0; + $regex = ''; + $length = strlen($glob); + + for ($i = 0; $i < $length; ++$i) { + $c = $glob[$i]; + + switch ($c) { + case '.': + case '(': + case ')': + case '|': + case '+': + case '^': + case '$': + case $delimiter: + $regex .= "\\$c"; + break; + + case '/': + if (isset($glob[$i + 3]) && '**/' === $glob[$i + 1].$glob[$i + 2].$glob[$i + 3]) { + $regex .= '/([^/]+/)*'; + $i += 3; + } else { + $regex .= '/'; + } + break; + + case '*': + $regex .= '[^/]*'; + break; + + case '?': + $regex .= '.'; + break; + + case '{': + $regex .= '('; + ++$curlyLevels; + break; + + case '}': + if ($curlyLevels > 0) { + $regex .= ')'; + --$curlyLevels; + } else { + $regex .= '}'; + } + break; + + case ',': + $regex .= $curlyLevels > 0 ? '|' : ','; + break; + + case '[': + $regex .= '['; + $inSquare = true; + if (isset($glob[$i + 1]) && '^' === $glob[$i + 1]) { + $regex .= '^'; + ++$i; + } + break; + + case ']': + $regex .= $inSquare ? ']' : '\\]'; + $inSquare = false; + break; + + case '-': + $regex .= $inSquare ? '-' : '\\-'; + break; + + case '\\': + if (isset($glob[$i + 1])) { + switch ($glob[$i + 1]) { + case '*': + case '?': + case '{': + case '}': + case '[': + case ']': + case '-': + case '^': + case '\\': + $regex .= '\\'.$glob[$i + 1]; + ++$i; + break; + + default: + $regex .= '\\\\'; + } + } else { + $regex .= '\\\\'; + } + break; + + default: + $regex .= $c; + break; + } + } + + if ($inSquare) { + throw new InvalidArgumentException(sprintf( + 'Invalid glob: missing ] in %s', + $glob + )); + } + + if ($curlyLevels > 0) { + throw new InvalidArgumentException(sprintf( + 'Invalid glob: missing } in %s', + $glob + )); + } + + return $delimiter.'^'.$regex.'$'.$delimiter; + } + + /** + * Returns the static prefix of a glob. + * + * The "static prefix" is the part of the glob up to the first wildcard "*". + * If the glob does not contain wildcards, the full glob is returned. + * + * @param string $glob The canonical glob. The glob should contain forward + * slashes as directory separators only. It must not + * contain any "." or ".." segments. Use the + * "webmozart/path-util" utility to canonicalize globs + * prior to calling this method. + * @param int $flags A bitwise combination of the flag constants in this + * class. + * + * @return string The static prefix of the glob. + */ + public static function getStaticPrefix($glob, $flags = 0) + { + if (!Path::isAbsolute($glob) && false === strpos($glob, '://')) { + throw new InvalidArgumentException(sprintf( + 'The glob "%s" is not absolute and not a URI.', + $glob + )); + } + + $prefix = ''; + $length = strlen($glob); + + for ($i = 0; $i < $length; ++$i) { + $c = $glob[$i]; + + switch ($c) { + case '/': + $prefix .= '/'; + if (isset($glob[$i + 3]) && '**/' === $glob[$i + 1].$glob[$i + 2].$glob[$i + 3]) { + break 2; + } + break; + + case '*': + case '?': + case '{': + case '[': + break 2; + + case '\\': + if (isset($glob[$i + 1])) { + switch ($glob[$i + 1]) { + case '*': + case '?': + case '{': + case '[': + case '\\': + $prefix .= $glob[$i + 1]; + ++$i; + break; + + default: + $prefix .= '\\'; + } + } else { + $prefix .= '\\'; + } + break; + + default: + $prefix .= $c; + break; + } + } + + return $prefix; + } + + /** + * Returns whether the glob contains a dynamic part. + * + * The glob contains a dynamic part if it contains an unescaped "*" or + * "{" character. + * + * @param string $glob The glob to test. + * + * @return bool Returns `true` if the glob contains a dynamic part and + * `false` otherwise. + */ + public static function isDynamic($glob) + { + return false !== strpos($glob, '*') || false !== strpos($glob, '{') || false !== strpos($glob, '?') || false !== strpos($glob, '['); + } + + private function __construct() + { + } +} diff --git a/lib/composer/webmozart/glob/src/Iterator/GlobFilterIterator.php b/lib/composer/webmozart/glob/src/Iterator/GlobFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..5e9f6339357c0fa9509d44eca24dc8162fc2a7f8 --- /dev/null +++ b/lib/composer/webmozart/glob/src/Iterator/GlobFilterIterator.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 Webmozart\Glob\Iterator; + +use Iterator; +use Webmozart\Glob\Glob; + +/** + * Filters an iterator by a glob. + * + * @since 1.0 + * + * @author Bernhard Schussek + * + * @see Glob + */ +class GlobFilterIterator extends RegexFilterIterator +{ + /** + * Creates a new iterator. + * + * @param string $glob The canonical glob. + * @param Iterator $innerIterator The filtered iterator. + * @param int $mode A bitwise combination of the mode constants. + * @param int $flags A bitwise combination of the flag constants + * in {@link Glob}. + */ + public function __construct($glob, Iterator $innerIterator, $mode = self::FILTER_VALUE, $flags = 0) + { + parent::__construct( + Glob::toRegEx($glob, $flags), + Glob::getStaticPrefix($glob, $flags), + $innerIterator, + $mode + ); + } +} diff --git a/lib/composer/webmozart/glob/src/Iterator/GlobIterator.php b/lib/composer/webmozart/glob/src/Iterator/GlobIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..e88bbeb7faf6fcec5e0a7c9b3a0832957645eb17 --- /dev/null +++ b/lib/composer/webmozart/glob/src/Iterator/GlobIterator.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 Webmozart\Glob\Iterator; + +use ArrayIterator; +use EmptyIterator; +use IteratorIterator; +use RecursiveIteratorIterator; +use Webmozart\Glob\Glob; + +/** + * Returns filesystem paths matching a glob. + * + * @since 1.0 + * + * @author Bernhard Schussek + * + * @see Glob + */ +class GlobIterator extends IteratorIterator +{ + /** + * Creates a new iterator. + * + * @param string $glob The glob pattern. + * @param int $flags A bitwise combination of the flag constants in + * {@link Glob}. + */ + public function __construct($glob, $flags = 0) + { + $basePath = Glob::getBasePath($glob, $flags); + + if (!Glob::isDynamic($glob) && file_exists($glob)) { + // If the glob is a file path, return that path + $innerIterator = new ArrayIterator(array($glob)); + } elseif (is_dir($basePath)) { + // Use the system's much more efficient glob() function where we can + if ( + // glob() does not support /**/ + false === strpos($glob, '/**/') && + // glob() does not support stream wrappers + false === strpos($glob, '://') && + // glob() does not support [^...] on Windows + ('\\' !== DIRECTORY_SEPARATOR || false === strpos($glob, '[^')) + ) { + $results = glob($glob, GLOB_BRACE); + + // $results may be empty or false if $glob is invalid + if (empty($results)) { + // Parse glob and provoke errors if invalid + Glob::toRegEx($glob); + + // Otherwise return empty result set + $innerIterator = new EmptyIterator(); + } else { + $innerIterator = new ArrayIterator($results); + } + } else { + // Otherwise scan the glob's base directory for matches + $innerIterator = new GlobFilterIterator( + $glob, + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $basePath, + RecursiveDirectoryIterator::CURRENT_AS_PATHNAME + | RecursiveDirectoryIterator::SKIP_DOTS + ), + RecursiveIteratorIterator::SELF_FIRST + ), + GlobFilterIterator::FILTER_VALUE, + $flags + ); + } + } else { + // If the glob's base directory does not exist, return nothing + $innerIterator = new EmptyIterator(); + } + + parent::__construct($innerIterator); + } +} diff --git a/lib/composer/webmozart/glob/src/Iterator/RecursiveDirectoryIterator.php b/lib/composer/webmozart/glob/src/Iterator/RecursiveDirectoryIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..0e8c4829a6732409b852359001a1f35bd6d06e5d --- /dev/null +++ b/lib/composer/webmozart/glob/src/Iterator/RecursiveDirectoryIterator.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 Webmozart\Glob\Iterator; + +/** + * Recursive directory iterator that is working during recursive iteration. + * + * Recursive iteration is broken on PHP < 5.5.23 and on PHP 5.6 < 5.6.7. + * + * @since 1.0 + * @since 3.0 Removed support for seek(), added \RecursiveDirectoryIterator + * base class, adapted API to match \RecursiveDirectoryIterator + * @since 3.1 Slashes are normalized to forward slashes on Windows + * + * @author Bernhard Schussek + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + /** + * @var bool + */ + private $normalizeKey; + + /** + * @var bool + */ + private $normalizeCurrent; + + /** + * {@inheritdoc} + */ + public function __construct($path, $flags = 0) + { + parent::__construct($path, $flags); + + // Normalize slashes on Windows + $this->normalizeKey = '\\' === DIRECTORY_SEPARATOR && !($flags & self::KEY_AS_FILENAME); + $this->normalizeCurrent = '\\' === DIRECTORY_SEPARATOR && ($flags & self::CURRENT_AS_PATHNAME); + } + + /** + * {@inheritdoc} + */ + public function getChildren() + { + return new static($this->getPathname(), $this->getFlags()); + } + + /** + * {@inheritdoc} + */ + public function key() + { + $key = parent::key(); + + if ($this->normalizeKey) { + $key = str_replace('\\', '/', $key); + } + + return $key; + } + + /** + * {@inheritdoc} + */ + public function current() + { + $current = parent::current(); + + if ($this->normalizeCurrent) { + $current = str_replace('\\', '/', $current); + } + + return $current; + } +} diff --git a/lib/composer/webmozart/glob/src/Iterator/RegexFilterIterator.php b/lib/composer/webmozart/glob/src/Iterator/RegexFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..bce2e685853f73063fab2744557216480ab4e7a3 --- /dev/null +++ b/lib/composer/webmozart/glob/src/Iterator/RegexFilterIterator.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 Webmozart\Glob\Iterator; + +use FilterIterator; +use Iterator; + +/** + * Filters an iterator by a regular expression. + * + * @since 1.0 + * + * @author Bernhard Schussek + * + * @see Glob + */ +class RegexFilterIterator extends FilterIterator +{ + /** + * Mode: Filters the values of the inner iterator. + */ + const FILTER_VALUE = 1; + + /** + * Mode: Filters the keys of the inner iterator. + */ + const FILTER_KEY = 2; + + /** + * Mode: Return incrementing numbers as keys. + */ + const CURSOR_AS_KEY = 16; + + /** + * Mode: Return the original keys as keys. + */ + const KEY_AS_KEY = 32; + + /** + * @var string + */ + private $regExp; + + /** + * @var string + */ + private $staticPrefix; + + /** + * @var int + */ + private $cursor = 0; + + /** + * @var int + */ + private $mode; + + /** + * Creates a new iterator. + * + * @param string $regExp The regular expression to filter by. + * @param string $staticPrefix The static prefix of the regular + * expression. + * @param Iterator $innerIterator The filtered iterator. + * @param int $mode A bitwise combination of the mode constants. + */ + public function __construct($regExp, $staticPrefix, Iterator $innerIterator, $mode = null) + { + parent::__construct($innerIterator); + + if (!($mode & (self::FILTER_KEY | self::FILTER_VALUE))) { + $mode |= self::FILTER_VALUE; + } + + if (!($mode & (self::CURSOR_AS_KEY | self::KEY_AS_KEY))) { + $mode |= self::CURSOR_AS_KEY; + } + + $this->regExp = $regExp; + $this->staticPrefix = $staticPrefix; + $this->mode = $mode; + } + + /** + * Rewind the iterator to the first position. + */ + public function rewind() + { + parent::rewind(); + + $this->cursor = 0; + } + + /** + * Returns the current position. + * + * @return int The current position. + */ + public function key() + { + if (!$this->valid()) { + return null; + } + + if ($this->mode & self::KEY_AS_KEY) { + return parent::key(); + } + + return $this->cursor; + } + + /** + * Advances to the next match. + * + * @see Iterator::next() + */ + public function next() + { + if ($this->valid()) { + parent::next(); + ++$this->cursor; + } + } + + /** + * Accepts paths matching the glob. + * + * @return bool Whether the path is accepted. + */ + public function accept() + { + $path = ($this->mode & self::FILTER_VALUE) ? $this->current() : parent::key(); + + if (0 !== strpos($path, $this->staticPrefix)) { + return false; + } + + $result = (bool) preg_match($this->regExp, $path); + + return $result; + } +} diff --git a/lib/composer/webmozart/glob/src/Test/TestUtil.php b/lib/composer/webmozart/glob/src/Test/TestUtil.php new file mode 100644 index 0000000000000000000000000000000000000000..5bfa3488f5d79f9f5a9ad4318cadc881ec507972 --- /dev/null +++ b/lib/composer/webmozart/glob/src/Test/TestUtil.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 Webmozart\Glob\Test; + +use Webmozart\PathUtil\Path; + +/** + * Contains utility methods for testing. + * + * @since 3.1 + * + * @author Bernhard Schussek + */ +final class TestUtil +{ + /** + * Creates a temporary directory. + * + * @param string $namespace The directory path in the system's temporary + * directory. + * @param string $className The name of the test class. + * + * @return string The path to the created directory. + */ + public static function makeTempDir($namespace, $className) + { + if (false !== ($pos = strrpos($className, '\\'))) { + $shortClass = substr($className, $pos + 1); + } else { + $shortClass = $className; + } + + // Usage of realpath() is important if the temporary directory is a + // symlink to another directory (e.g. /var => /private/var on some Macs) + // We want to know the real path to avoid comparison failures with + // code that uses real paths only + $systemTempDir = Path::normalize(realpath(sys_get_temp_dir())); + $basePath = $systemTempDir.'/'.$namespace.'/'.$shortClass; + + while (false === @mkdir($tempDir = $basePath.rand(10000, 99999), 0777, true)) { + // Run until we are able to create a directory + } + + return $tempDir; + } + + private function __construct() + { + } +} diff --git a/lib/composer/webmozart/glob/tests/Fixtures/base.css b/lib/composer/webmozart/glob/tests/Fixtures/base.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/composer/webmozart/glob/tests/Fixtures/css/reset.css b/lib/composer/webmozart/glob/tests/Fixtures/css/reset.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/composer/webmozart/glob/tests/Fixtures/css/style.css b/lib/composer/webmozart/glob/tests/Fixtures/css/style.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/composer/webmozart/glob/tests/Fixtures/css/style.cts b/lib/composer/webmozart/glob/tests/Fixtures/css/style.cts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/composer/webmozart/glob/tests/Fixtures/css/style.cxs b/lib/composer/webmozart/glob/tests/Fixtures/css/style.cxs new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/composer/webmozart/glob/tests/Fixtures/js/script.js b/lib/composer/webmozart/glob/tests/Fixtures/js/script.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/composer/webmozart/glob/tests/GlobTest.php b/lib/composer/webmozart/glob/tests/GlobTest.php new file mode 100644 index 0000000000000000000000000000000000000000..dc77f3b244bcc36685f09572ca2d5a71c8f98ce7 --- /dev/null +++ b/lib/composer/webmozart/glob/tests/GlobTest.php @@ -0,0 +1,981 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\Glob\Tests; + +use PHPUnit_Framework_TestCase; +use Symfony\Component\Filesystem\Filesystem; +use Webmozart\Glob\Glob; +use Webmozart\Glob\Test\TestUtil; + +/** + * @since 1.0 + * + * @author Bernhard Schussek + */ +class GlobTest extends PHPUnit_Framework_TestCase +{ + private $tempDir; + + protected function setUp() + { + $this->tempDir = TestUtil::makeTempDir('webmozart-glob', __CLASS__); + + $filesystem = new Filesystem(); + $filesystem->mirror(__DIR__.'/Fixtures', $this->tempDir); + + TestStreamWrapper::register('globtest', __DIR__.'/Fixtures'); + } + + protected function tearDown() + { + $filesystem = new Filesystem(); + $filesystem->remove($this->tempDir); + + TestStreamWrapper::unregister('globtest'); + } + + public function testGlob() + { + $this->assertSame(array( + $this->tempDir.'/base.css', + ), Glob::glob($this->tempDir.'/*.css')); + + $this->assertSame(array( + $this->tempDir.'/base.css', + $this->tempDir.'/css', + ), Glob::glob($this->tempDir.'/*css*')); + + $this->assertSame(array( + $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css', + ), Glob::glob($this->tempDir.'/*/*.css')); + + $this->assertSame(array( + $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css', + $this->tempDir.'/css/style.cts', + $this->tempDir.'/css/style.cxs', + ), Glob::glob($this->tempDir.'/*/*.c?s')); + + $this->assertSame(array( + $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css', + $this->tempDir.'/css/style.cts', + ), Glob::glob($this->tempDir.'/*/*.c[st]s')); + + $this->assertSame(array( + $this->tempDir.'/css/style.cts', + ), Glob::glob($this->tempDir.'/*/*.c[t]s')); + + $this->assertSame(array( + $this->tempDir.'/css/style.cts', + $this->tempDir.'/css/style.cxs', + ), Glob::glob($this->tempDir.'/*/*.c[t-x]s')); + + $this->assertSame(array( + $this->tempDir.'/css/style.cts', + $this->tempDir.'/css/style.cxs', + ), Glob::glob($this->tempDir.'/*/*.c[^s]s')); + + $this->assertSame(array( + $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css', + ), Glob::glob($this->tempDir.'/*/*.c[^t-x]s')); + + $this->assertSame(array( + $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css', + ), Glob::glob($this->tempDir.'/*/**/*.css')); + + $this->assertSame(array( + $this->tempDir.'/base.css', + $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css', + ), Glob::glob($this->tempDir.'/**/*.css')); + + $this->assertSame(array( + $this->tempDir.'/base.css', + $this->tempDir.'/css', + $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css', + ), Glob::glob($this->tempDir.'/**/*css')); + + $this->assertSame(array( + $this->tempDir.'/base.css', + $this->tempDir.'/css/reset.css', + ), Glob::glob($this->tempDir.'/**/{base,reset}.css')); + + $this->assertSame(array( + $this->tempDir.'/css', + $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css', + $this->tempDir.'/css/style.cts', + $this->tempDir.'/css/style.cxs', + ), Glob::glob($this->tempDir.'/css{,/**/*}')); + + $this->assertSame(array(), Glob::glob($this->tempDir.'/*foo*')); + } + + public function testGlobStreamWrapper() + { + $this->assertSame(array( + 'globtest:///base.css', + ), Glob::glob('globtest:///*.css')); + + $this->assertSame(array( + 'globtest:///base.css', + 'globtest:///css', + ), Glob::glob('globtest:///*css*')); + + $this->assertSame(array( + 'globtest:///css/reset.css', + 'globtest:///css/style.css', + ), Glob::glob('globtest:///*/*.css')); + + $this->assertSame(array( + 'globtest:///css/reset.css', + 'globtest:///css/style.css', + 'globtest:///css/style.cts', + 'globtest:///css/style.cxs', + ), Glob::glob('globtest:///*/*.c?s')); + + $this->assertSame(array( + 'globtest:///css/reset.css', + 'globtest:///css/style.css', + 'globtest:///css/style.cts', + ), Glob::glob('globtest:///*/*.c[st]s')); + + $this->assertSame(array( + 'globtest:///css/style.cts', + ), Glob::glob('globtest:///*/*.c[t]s')); + + $this->assertSame(array( + 'globtest:///css/style.cts', + 'globtest:///css/style.cxs', + ), Glob::glob('globtest:///*/*.c[t-x]s')); + + $this->assertSame(array( + 'globtest:///css/style.cts', + 'globtest:///css/style.cxs', + ), Glob::glob('globtest:///*/*.c[^s]s')); + + $this->assertSame(array( + 'globtest:///css/reset.css', + 'globtest:///css/style.css', + ), Glob::glob('globtest:///*/*.c[^t-x]s')); + + $this->assertSame(array( + 'globtest:///css/reset.css', + 'globtest:///css/style.css', + ), Glob::glob('globtest:///*/**/*.css')); + + $this->assertSame(array( + 'globtest:///base.css', + 'globtest:///css/reset.css', + 'globtest:///css/style.css', + ), Glob::glob('globtest:///**/*.css')); + + $this->assertSame(array( + 'globtest:///base.css', + 'globtest:///css', + 'globtest:///css/reset.css', + 'globtest:///css/style.css', + ), Glob::glob('globtest:///**/*css')); + + $this->assertSame(array( + 'globtest:///base.css', + 'globtest:///css/reset.css', + ), Glob::glob('globtest:///**/{base,reset}.css')); + + $this->assertSame(array( + 'globtest:///css', + 'globtest:///css/reset.css', + 'globtest:///css/style.css', + 'globtest:///css/style.cts', + 'globtest:///css/style.cxs', + ), Glob::glob('globtest:///css{,/**/*}')); + + $this->assertSame(array(), Glob::glob('globtest:///*foo*')); + } + + public function testGlobEscape() + { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $this->markTestSkipped('A "*" in filenames is not supported on Windows.'); + + return; + } + + touch($this->tempDir.'/css/style*.css'); + touch($this->tempDir.'/css/style{.css'); + touch($this->tempDir.'/css/style}.css'); + touch($this->tempDir.'/css/style?.css'); + touch($this->tempDir.'/css/style[.css'); + touch($this->tempDir.'/css/style].css'); + touch($this->tempDir.'/css/style^.css'); + + $this->assertSame(array( + $this->tempDir.'/css/style*.css', + $this->tempDir.'/css/style.css', + $this->tempDir.'/css/style?.css', + $this->tempDir.'/css/style[.css', + $this->tempDir.'/css/style].css', + $this->tempDir.'/css/style^.css', + $this->tempDir.'/css/style{.css', + $this->tempDir.'/css/style}.css', + ), Glob::glob($this->tempDir.'/css/style*.css')); + + $this->assertSame(array( + $this->tempDir.'/css/style*.css', + ), Glob::glob($this->tempDir.'/css/style\\*.css')); + + $this->assertSame(array( + $this->tempDir.'/css/style{.css', + ), Glob::glob($this->tempDir.'/css/style\\{.css')); + + $this->assertSame(array( + $this->tempDir.'/css/style}.css', + ), Glob::glob($this->tempDir.'/css/style\\}.css')); + + $this->assertSame(array( + $this->tempDir.'/css/style?.css', + ), Glob::glob($this->tempDir.'/css/style\\?.css')); + + $this->assertSame(array( + $this->tempDir.'/css/style[.css', + ), Glob::glob($this->tempDir.'/css/style\\[.css')); + + $this->assertSame(array( + $this->tempDir.'/css/style].css', + ), Glob::glob($this->tempDir.'/css/style\\].css')); + + $this->assertSame(array( + $this->tempDir.'/css/style^.css', + ), Glob::glob($this->tempDir.'/css/style\\^.css')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testNativeGlobThrowsExceptionIfUnclosedBrace() + { + // native impl + $this->assertSame(array(), Glob::glob($this->tempDir.'/*.cs{t,s')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testCustomGlobThrowsExceptionIfUnclosedBrace() + { + // custom impl + $this->assertSame(array(), Glob::glob($this->tempDir.'/**/*.cs{t,s')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testNativeGlobThrowsExceptionIfUnclosedBracket() + { + // native impl + $this->assertSame(array(), Glob::glob($this->tempDir.'/*.cs[ts')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testCustomGlobThrowsExceptionIfUnclosedBracket() + { + // custom impl + $this->assertSame(array(), Glob::glob($this->tempDir.'/**/*.cs[ts')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage *.css + */ + public function testGlobFailsIfNotAbsolute() + { + Glob::glob('*.css'); + } + + /** + * @dataProvider provideWildcardMatches + */ + public function testToRegEx($path, $isMatch) + { + $regExp = Glob::toRegEx('/foo/*.js~'); + + $this->assertSame($isMatch, preg_match($regExp, $path)); + } + + /** + * @dataProvider provideDoubleWildcardMatches + */ + public function testToRegExDoubleWildcard($path, $isMatch) + { + $regExp = Glob::toRegEx('/foo/**/*.js~'); + + $this->assertSame($isMatch, preg_match($regExp, $path)); + } + + public function provideWildcardMatches() + { + return array( + // The method assumes that the path is already consolidated + array('/bar/baz.js~', 0), + array('/foo/baz.js~', 1), + array('/foo/../bar/baz.js~', 0), + array('/foo/../foo/baz.js~', 0), + array('/bar/baz.js', 0), + array('/foo/bar/baz.js~', 0), + array('foo/baz.js~', 0), + array('/bar/foo/baz.js~', 0), + array('/bar/.js~', 0), + ); + } + + public function provideDoubleWildcardMatches() + { + return array( + array('/bar/baz.js~', 0), + array('/foo/baz.js~', 1), + array('/foo/../bar/baz.js~', 1), + array('/foo/../foo/baz.js~', 1), + array('/bar/baz.js', 0), + array('/foo/bar/baz.js~', 1), + array('foo/baz.js~', 0), + array('/bar/foo/baz.js~', 0), + array('/bar/.js~', 0), + ); + } + + // From the PHP manual: To specify a literal single quote, escape it with a + // backslash (\). To specify a literal backslash, double it (\\). + // All other instances of backslash will be treated as a literal backslash + + public function testEscapedWildcard() + { + // evaluates to "\*" + $regExp = Glob::toRegEx('/foo/\\*.js~'); + + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/*.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\*.js~')); + } + + public function testEscapedWildcard2() + { + // evaluates to "\*" + $regExp = Glob::toRegEx('/foo/\*.js~'); + + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/*.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\*.js~')); + } + + public function testMatchEscapedWildcard() + { + // evaluates to "\*" + $regExp = Glob::toRegEx('/foo/\\*.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/*.js~')); + } + + public function testMatchEscapedDoubleWildcard() + { + // evaluates to "\*\*" + $regExp = Glob::toRegEx('/foo/\\*\\*.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/**.js~')); + } + + public function testMatchWildcardWithLeadingBackslash() + { + // evaluates to "\\*" + $regExp = Glob::toRegEx('/foo/\\\\*.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + } + + public function testMatchWildcardWithLeadingBackslash2() + { + // evaluates to "\\*" + $regExp = Glob::toRegEx('/foo/\\\*.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + } + + public function testMatchEscapedWildcardWithLeadingBackslash() + { + // evaluates to "\\\*" + $regExp = Glob::toRegEx('/foo/\\\\\\*.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\*.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\*.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/*.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\baz.js~')); + } + + public function testMatchWildcardWithTwoLeadingBackslashes() + { + // evaluates to "\\\\*" + $regExp = Glob::toRegEx('/foo/\\\\\\\\*.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\\\baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\\\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + } + + public function testMatchEscapedWildcardWithTwoLeadingBackslashes() + { + // evaluates to "\\\\*" + $regExp = Glob::toRegEx('/foo/\\\\\\\\\\*.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\\\*.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\\\*.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\*.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\*.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/*.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\\\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\\baz.js~')); + } + + public function testMatchEscapedLeftBrace() + { + $regExp = Glob::toRegEx('/foo/\\{.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/{.js~')); + } + + public function testMatchLeftBraceWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/\\\\{b,c}az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + } + + public function testMatchEscapedLeftBraceWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/\\\\\\{b,c}az.js~'); + + $this->assertSame(0, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\\{b,c}az.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\{b,c}az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/{b,c}az.js~')); + } + + public function testMatchUnescapedRightBraceWithoutLeadingLeftBrace() + { + $regExp = Glob::toRegEx('/foo/}.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/}.js~')); + } + + public function testMatchEscapedRightBrace() + { + $regExp = Glob::toRegEx('/foo/\\}.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/}.js~')); + } + + public function testMatchRightBraceWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/{b,c\\\\}az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/c\\az.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/c\az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/caz.js~')); + } + + public function testMatchEscapedRightBraceWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/{b,c\\\\\\}}az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/c\\}az.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/c\}az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/c\\az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/c\az.js~')); + } + + public function testCloseBracesAsSoonAsPossible() + { + $regExp = Glob::toRegEx('/foo/{b,c}}az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/b}az.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/c}az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/caz.js~')); + } + + public function testMatchEscapedQuestionMark() + { + $regExp = Glob::toRegEx('/foo/\\?.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/?.js~')); + } + + public function testMatchQuestionMarkWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/\\\\?az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\\caz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\caz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/caz.js~')); + } + + public function testMatchEscapedQuestionMarkWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/\\\\\\??az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\?baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\?baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\\?caz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\?caz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\caz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\caz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/caz.js~')); + } + + public function testMatchEscapedLeftBracket() + { + $regExp = Glob::toRegEx('/foo/\\[.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/[.js~')); + } + + public function testMatchLeftBracketWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/\\\\[bc]az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + } + + public function testMatchEscapedLeftBracketWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/\\\\\\[bc]az.js~'); + + $this->assertSame(0, preg_match($regExp, '/foo/\\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\\[bc]az.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\[bc]az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/[bc]az.js~')); + } + + public function testMatchUnescapedRightBracketWithoutLeadingLeftBracket() + { + $regExp = Glob::toRegEx('/foo/].js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/].js~')); + } + + public function testMatchEscapedRightBracket() + { + $regExp = Glob::toRegEx('/foo/\\].js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/].js~')); + } + + public function testMatchRightBracketWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/[bc\\\\]az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/caz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\\az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/bc\\az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/c\\az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/az.js~')); + } + + public function testMatchEscapedRightBracketWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/[bc\\\\\\]]az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/caz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/\\az.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/]az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/bc\\]az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/c\\]az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/\\]az.js~')); + } + + public function testMatchUnescapedCaretWithoutLeadingLeftBracket() + { + $regExp = Glob::toRegEx('/foo/^.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/^.js~')); + } + + public function testMatchEscapedCaret() + { + $regExp = Glob::toRegEx('/foo/\\^.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/^.js~')); + } + + public function testMatchCaretWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/[\\\\^]az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\az.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/^az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/az.js~')); + } + + public function testMatchEscapedCaretWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/[\\\\\\^]az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\az.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/^az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/az.js~')); + } + + public function testMatchUnescapedHyphenWithoutLeadingLeftBracket() + { + $regExp = Glob::toRegEx('/foo/-.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/-.js~')); + } + + public function testMatchEscapedHyphen() + { + $regExp = Glob::toRegEx('/foo/\\-.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/-.js~')); + } + + public function testMatchHyphenWithLeadingBackslash() + { + // range from "\" to "a" + $regExp = Glob::toRegEx('/foo/[\\\\-a]az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\az.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/aaz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/caz.js~')); + } + + public function testMatchEscapedHyphenWithLeadingBackslash() + { + $regExp = Glob::toRegEx('/foo/[\\\\\\-]az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/\\az.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/-az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + } + + public function testCloseBracketsAsSoonAsPossible() + { + $regExp = Glob::toRegEx('/foo/[bc]]az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/b]az.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/c]az.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/caz.js~')); + } + + public function testMatchCharacterRanges() + { + $regExp = Glob::toRegEx('/foo/[a-c]az.js~'); + + $this->assertSame(1, preg_match($regExp, '/foo/aaz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/baz.js~')); + $this->assertSame(1, preg_match($regExp, '/foo/caz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/daz.js~')); + $this->assertSame(0, preg_match($regExp, '/foo/eaz.js~')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage *.css + */ + public function testToRegexFailsIfNotAbsolute() + { + Glob::toRegEx('*.css'); + } + + /** + * @dataProvider provideStaticPrefixes + */ + public function testGetStaticPrefix($glob, $prefix) + { + $this->assertSame($prefix, Glob::getStaticPrefix($glob)); + } + + public function provideStaticPrefixes() + { + return array( + // The method assumes that the path is already consolidated + array('/foo/baz/../*/bar/*', '/foo/baz/../'), + array('/foo/baz/bar\\*', '/foo/baz/bar*'), + array('/foo/baz/bar\\\\*', '/foo/baz/bar\\'), + array('/foo/baz/bar\\\\\\*', '/foo/baz/bar\\*'), + array('/foo/baz/bar\\\\\\\\*', '/foo/baz/bar\\\\'), + array('/foo/baz/bar\\*\\\\', '/foo/baz/bar*\\'), + array('/foo/baz/bar\\{a,b}', '/foo/baz/bar{a,b}'), + array('/foo/baz/bar\\\\{a,b}', '/foo/baz/bar\\'), + array('/foo/baz/bar\\\\\\{a,b}', '/foo/baz/bar\\{a,b}'), + array('/foo/baz/bar\\\\\\\\{a,b}', '/foo/baz/bar\\\\'), + array('/foo/baz/bar\\{a,b}\\\\', '/foo/baz/bar{a,b}\\'), + array('/foo/baz/bar\\?', '/foo/baz/bar?'), + array('/foo/baz/bar\\\\?', '/foo/baz/bar\\'), + array('/foo/baz/bar\\\\\\?', '/foo/baz/bar\\?'), + array('/foo/baz/bar\\\\\\\\?', '/foo/baz/bar\\\\'), + array('/foo/baz/bar\\?\\\\', '/foo/baz/bar?\\'), + array('/foo/baz/bar\\[ab]', '/foo/baz/bar[ab]'), + array('/foo/baz/bar\\\\[ab]', '/foo/baz/bar\\'), + array('/foo/baz/bar\\\\\\[ab]', '/foo/baz/bar\\[ab]'), + array('/foo/baz/bar\\\\\\\\[ab]', '/foo/baz/bar\\\\'), + array('/foo/baz/bar\\[ab]\\\\', '/foo/baz/bar[ab]\\'), + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage *.css + */ + public function testGetStaticPrefixFailsIfNotAbsolute() + { + Glob::getStaticPrefix('*.css'); + } + + /** + * @dataProvider provideBasePaths + */ + public function testGetBasePath($glob, $basePath, $flags = 0) + { + $this->assertSame($basePath, Glob::getBasePath($glob, $flags)); + } + + /** + * @dataProvider provideBasePaths + */ + public function testGetBasePathStream($glob, $basePath) + { + $this->assertSame('globtest://'.$basePath, Glob::getBasePath('globtest://'.$glob)); + } + + public function provideBasePaths() + { + return array( + // The method assumes that the path is already consolidated + array('/foo/baz/../*/bar/*', '/foo/baz/..'), + array('/foo/baz/bar*', '/foo/baz'), + array('/foo/baz/bar', '/foo/baz'), + array('/foo/baz*', '/foo'), + array('/foo*', '/'), + array('/*', '/'), + array('/foo/baz*/bar', '/foo'), + array('/foo/baz\\*/bar', '/foo/baz*'), + array('/foo/baz\\\\*/bar', '/foo'), + array('/foo/baz\\\\\\*/bar', '/foo/baz\\*'), + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage *.css + */ + public function testGetBasePathFailsIfNotAbsolute() + { + Glob::getBasePath('*.css'); + } + + /** + * @dataProvider provideDoubleWildcardMatches + */ + public function testMatch($path, $isMatch) + { + $this->assertSame((bool) $isMatch, Glob::match($path, '/foo/**/*.js~')); + } + + public function testMatchPathWithoutWildcard() + { + $this->assertTrue(Glob::match('/foo/bar.js~', '/foo/bar.js~')); + $this->assertFalse(Glob::match('/foo/bar.js', '/foo/bar.js~')); + } + + public function testMatchEscaped() + { + $this->assertTrue(Glob::match('/foo/bar*.js~', '/foo/bar*.js~')); + $this->assertTrue(Glob::match('/foo/bar\\*.js~', '/foo/bar*.js~')); + $this->assertTrue(Glob::match('/foo/bar\\baz.js~', '/foo/bar*.js~')); + $this->assertTrue(Glob::match('/foo/bar*.js~', '/foo/bar\\*.js~')); + $this->assertFalse(Glob::match('/foo/bar\\*.js~', '/foo/bar\\*.js~')); + $this->assertFalse(Glob::match('/foo/bar\\baz.js~', '/foo/bar\\*.js~')); + $this->assertFalse(Glob::match('/foo/bar*.js~', '/foo/bar\\\\*.js~')); + $this->assertTrue(Glob::match('/foo/bar\\*.js~', '/foo/bar\\\\*.js~')); + $this->assertTrue(Glob::match('/foo/bar\\baz.js~', '/foo/bar\\\\*.js~')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage *.css + */ + public function testMatchFailsIfNotAbsolute() + { + Glob::match('/foo/bar.css', '*.css'); + } + + public function testFilter() + { + $paths = array(); + $filtered = array(); + + // The keys remain the same in the filtered array + $i = 42; + + foreach ($this->provideDoubleWildcardMatches() as $input) { + $paths[$i] = $input[0]; + + if ($input[1]) { + $filtered[$i] = $input[0]; + } + + ++$i; + } + + $this->assertSame($filtered, Glob::filter($paths, '/foo/**/*.js~')); + } + + public function testFilterWithoutWildcard() + { + $paths = array( + '/foo', + '/foo/bar.js', + ); + + $this->assertSame(array(1 => '/foo/bar.js'), Glob::filter($paths, '/foo/bar.js')); + $this->assertSame(array(), Glob::filter($paths, '/foo/bar.js~')); + } + + public function testFilterEscaped() + { + $paths = array( + '/foo', + '/foo*.js', + '/foo/bar.js', + '/foo/bar*.js', + '/foo/bar\\*.js', + '/foo/bar\\baz.js', + ); + + $this->assertSame(array( + 1 => '/foo*.js', + 3 => '/foo/bar*.js', + 4 => '/foo/bar\\*.js', + ), Glob::filter($paths, '/**/*\\*.js')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage *.css + */ + public function testFilterFailsIfNotAbsolute() + { + Glob::filter(array('/foo/bar.css'), '*.css'); + } + + public function testFilterKeys() + { + $paths = array(); + $filtered = array(); + + // The values remain the same in the filtered array + $i = 42; + + foreach ($this->provideDoubleWildcardMatches() as $input) { + $paths[$input[0]] = $i; + + if ($input[1]) { + $filtered[$input[0]] = $i; + } + + ++$i; + } + + $this->assertSame($filtered, Glob::filter($paths, '/foo/**/*.js~', Glob::FILTER_KEY)); + } + + public function testFilterKeysWithoutWildcard() + { + $paths = array( + '/foo' => 2, + '/foo/bar.js' => 3, + ); + + $this->assertSame(array('/foo/bar.js' => 3), Glob::filter($paths, '/foo/bar.js', Glob::FILTER_KEY)); + $this->assertSame(array(), Glob::filter($paths, '/foo/bar.js~', Glob::FILTER_KEY)); + } + + public function testFilterKeysEscaped() + { + $paths = array( + '/foo' => 3, + '/foo*.js' => 4, + '/foo/bar.js' => 5, + '/foo/bar*.js' => 6, + '/foo/bar\\*.js' => 7, + '/foo/bar\\baz.js' => 8, + ); + + $this->assertSame(array( + '/foo*.js' => 4, + '/foo/bar*.js' => 6, + '/foo/bar\\*.js' => 7, + ), Glob::filter($paths, '/**/*\\*.js', Glob::FILTER_KEY)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage *.css + */ + public function testFilterKeysFailsIfNotAbsolute() + { + Glob::filter(array('/foo/bar.css' => 42), '*.css', Glob::FILTER_KEY); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFilterFailsIfInvalidFlags() + { + Glob::filter(array(42 => '/foo/bar.css'), '/foo/*.css', Glob::FILTER_KEY | Glob::FILTER_VALUE); + } +} diff --git a/lib/composer/webmozart/glob/tests/Iterator/GlobIteratorTest.php b/lib/composer/webmozart/glob/tests/Iterator/GlobIteratorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e148a6088195378d4c1fab7a533ebe7a8cc57ec5 --- /dev/null +++ b/lib/composer/webmozart/glob/tests/Iterator/GlobIteratorTest.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\Glob\Tests\Iterator; + +use PHPUnit_Framework_TestCase; +use Symfony\Component\Filesystem\Filesystem; +use Webmozart\Glob\Glob; +use Webmozart\Glob\Iterator\GlobIterator; +use Webmozart\Glob\Test\TestUtil; + +/** + * @since 1.0 + * + * @author Bernhard Schussek + */ +class GlobIteratorTest extends PHPUnit_Framework_TestCase +{ + private $tempDir; + + private $tempFile; + + protected function setUp() + { + $this->tempDir = TestUtil::makeTempDir('webmozart-glob', __CLASS__); + + $filesystem = new Filesystem(); + $filesystem->mirror(__DIR__.'/../Fixtures', $this->tempDir); + + $this->tempFile = tempnam(sys_get_temp_dir(), 'webmozart_GlobIteratorTest'); + } + + protected function tearDown() + { + $filesystem = new Filesystem(); + $filesystem->remove($this->tempDir); + $filesystem->remove($this->tempFile); + } + + public function testIterate() + { + $iterator = new GlobIterator($this->tempDir.'/*.css'); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/base.css', + ), iterator_to_array($iterator)); + } + + public function testIterateEscaped() + { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $this->markTestSkipped('A "*" in filenames is not supported on Windows.'); + + return; + } + + touch($this->tempDir.'/css/style*.css'); + + $iterator = new GlobIterator($this->tempDir.'/css/style\\*.css'); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/css/style*.css', + ), iterator_to_array($iterator)); + } + + public function testIterateSpecialChars() + { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $this->markTestSkipped('A "*" in filenames is not supported on Windows.'); + + return; + } + + touch($this->tempDir.'/css/style*.css'); + + $iterator = new GlobIterator($this->tempDir.'/css/style*.css'); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/css/style*.css', + $this->tempDir.'/css/style.css', + ), iterator_to_array($iterator)); + } + + public function testIterateDoubleWildcard() + { + $iterator = new GlobIterator($this->tempDir.'/**/*.css'); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/base.css', + $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css', + ), iterator_to_array($iterator)); + } + + public function testIterateSingleDirectory() + { + $iterator = new GlobIterator($this->tempDir.'/css'); + + $this->assertSame(array( + $this->tempDir.'/css', + ), iterator_to_array($iterator)); + } + + public function testIterateSingleFile() + { + $iterator = new GlobIterator($this->tempDir.'/css/style.css'); + + $this->assertSame(array( + $this->tempDir.'/css/style.css', + ), iterator_to_array($iterator)); + } + + public function testIterateSingleFileInDirectoryWithUnreadableFiles() + { + $iterator = new GlobIterator($this->tempFile); + + $this->assertSame(array( + $this->tempFile, + ), iterator_to_array($iterator)); + } + + public function testWildcardMayMatchZeroCharacters() + { + $iterator = new GlobIterator($this->tempDir.'/*css'); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/base.css', + $this->tempDir.'/css', + ), iterator_to_array($iterator)); + } + + public function testDoubleWildcardMayMatchZeroCharacters() + { + $iterator = new GlobIterator($this->tempDir.'/**/*css'); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/base.css', + $this->tempDir.'/css', + $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css', + ), iterator_to_array($iterator)); + } + + public function testWildcardInRoot() + { + $iterator = new GlobIterator($this->tempDir.'/*'); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/base.css', + $this->tempDir.'/css', + $this->tempDir.'/js', + ), iterator_to_array($iterator)); + } + + public function testDoubleWildcardInRoot() + { + $iterator = new GlobIterator($this->tempDir.'/**/*'); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/base.css', + $this->tempDir.'/css', + $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css', + $this->tempDir.'/css/style.cts', + $this->tempDir.'/css/style.cxs', + $this->tempDir.'/js', + $this->tempDir.'/js/script.js', + ), iterator_to_array($iterator)); + } + + public function testNoMatches() + { + $iterator = new GlobIterator($this->tempDir.'/foo*'); + + $this->assertSame(array(), iterator_to_array($iterator)); + } + + public function testNonExistingBaseDirectory() + { + $iterator = new GlobIterator($this->tempDir.'/foo/*'); + + $this->assertSame(array(), iterator_to_array($iterator)); + } + + /** + * Compares that an array is the same as another after sorting. + * + * This is necessary since RecursiveDirectoryIterator is not guaranteed to + * return sorted results on all filesystems. + * + * @param mixed $expected + * @param mixed $actual + * @param string $message + */ + private function assertSameAfterSorting($expected, $actual, $message = '') + { + if (is_array($actual)) { + sort($actual); + } + + $this->assertSame($expected, $actual, $message); + } +} diff --git a/lib/composer/webmozart/glob/tests/Iterator/RecursiveDirectoryIteratorTest.php b/lib/composer/webmozart/glob/tests/Iterator/RecursiveDirectoryIteratorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7bbd15f6b4bbd8a47cd0c5e407ab1bfc0a852d41 --- /dev/null +++ b/lib/composer/webmozart/glob/tests/Iterator/RecursiveDirectoryIteratorTest.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 Webmozart\Glob\Tests\Iterator; + +use PHPUnit_Framework_TestCase; +use RecursiveIteratorIterator; +use Symfony\Component\Filesystem\Filesystem; +use Webmozart\Glob\Iterator\RecursiveDirectoryIterator; +use Webmozart\Glob\Test\TestUtil; + +/** + * @since 1.0 + * + * @author Bernhard Schussek + */ +class RecursiveDirectoryIteratorTest extends PHPUnit_Framework_TestCase +{ + private $tempDir; + + protected function setUp() + { + $this->tempDir = TestUtil::makeTempDir('webmozart-glob', __CLASS__); + + $filesystem = new Filesystem(); + $filesystem->mirror(__DIR__.'/../Fixtures', $this->tempDir); + } + + protected function tearDown() + { + $filesystem = new Filesystem(); + $filesystem->remove($this->tempDir); + } + + public function testIterate() + { + $iterator = new RecursiveDirectoryIterator( + $this->tempDir, + RecursiveDirectoryIterator::CURRENT_AS_PATHNAME + ); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/.' => $this->tempDir.'/.', + $this->tempDir.'/..' => $this->tempDir.'/..', + $this->tempDir.'/base.css' => $this->tempDir.'/base.css', + $this->tempDir.'/css' => $this->tempDir.'/css', + $this->tempDir.'/js' => $this->tempDir.'/js', + ), iterator_to_array($iterator)); + } + + public function testIterateSkipDots() + { + $iterator = new RecursiveDirectoryIterator( + $this->tempDir, + RecursiveDirectoryIterator::CURRENT_AS_PATHNAME | RecursiveDirectoryIterator::SKIP_DOTS + ); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/base.css' => $this->tempDir.'/base.css', + $this->tempDir.'/css' => $this->tempDir.'/css', + $this->tempDir.'/js' => $this->tempDir.'/js', + ), iterator_to_array($iterator)); + } + + public function testIterateTrailingSlash() + { + $iterator = new RecursiveDirectoryIterator( + $this->tempDir.'/', + RecursiveDirectoryIterator::CURRENT_AS_PATHNAME + ); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/.' => $this->tempDir.'/.', + $this->tempDir.'/..' => $this->tempDir.'/..', + $this->tempDir.'/base.css' => $this->tempDir.'/base.css', + $this->tempDir.'/css' => $this->tempDir.'/css', + $this->tempDir.'/js' => $this->tempDir.'/js', + ), iterator_to_array($iterator)); + } + + public function testIterateRecursively() + { + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $this->tempDir, + RecursiveDirectoryIterator::CURRENT_AS_PATHNAME + ), + RecursiveIteratorIterator::SELF_FIRST + ); + + $this->assertSameAfterSorting(array( + $this->tempDir.'/.' => $this->tempDir.'/.', + $this->tempDir.'/..' => $this->tempDir.'/..', + $this->tempDir.'/base.css' => $this->tempDir.'/base.css', + $this->tempDir.'/css' => $this->tempDir.'/css', + $this->tempDir.'/css/.' => $this->tempDir.'/css/.', + $this->tempDir.'/css/..' => $this->tempDir.'/css/..', + $this->tempDir.'/css/reset.css' => $this->tempDir.'/css/reset.css', + $this->tempDir.'/css/style.css' => $this->tempDir.'/css/style.css', + $this->tempDir.'/css/style.cts' => $this->tempDir.'/css/style.cts', + $this->tempDir.'/css/style.cxs' => $this->tempDir.'/css/style.cxs', + $this->tempDir.'/js' => $this->tempDir.'/js', + $this->tempDir.'/js/.' => $this->tempDir.'/js/.', + $this->tempDir.'/js/..' => $this->tempDir.'/js/..', + $this->tempDir.'/js/script.js' => $this->tempDir.'/js/script.js', + ), iterator_to_array($iterator)); + } + + /** + * @expectedException \UnexpectedValueException + */ + public function testFailIfNonExistingBaseDirectory() + { + new RecursiveDirectoryIterator($this->tempDir.'/foobar'); + } + + /** + * Compares that an array is the same as another after sorting. + * + * This is necessary since RecursiveDirectoryIterator is not guaranteed to + * return sorted results on all filesystems. + * + * @param mixed $expected + * @param mixed $actual + * @param string $message + */ + private function assertSameAfterSorting($expected, $actual, $message = '') + { + if (is_array($expected)) { + ksort($expected); + } + + if (is_array($actual)) { + ksort($actual); + } + + $this->assertSame($expected, $actual, $message); + } +} diff --git a/lib/composer/webmozart/glob/tests/Iterator/RegexFilterIteratorTest.php b/lib/composer/webmozart/glob/tests/Iterator/RegexFilterIteratorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b593f4f4d7e4796605236e44b4f5f5c5c14f87ca --- /dev/null +++ b/lib/composer/webmozart/glob/tests/Iterator/RegexFilterIteratorTest.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\Glob\Tests\Iterator; + +use ArrayIterator; +use PHPUnit_Framework_TestCase; +use Webmozart\Glob\Iterator\RegexFilterIterator; + +/** + * @since 1.0 + * + * @author Bernhard Schussek + */ +class RegexFilterIteratorTest extends PHPUnit_Framework_TestCase +{ + public function testIterate() + { + $values = array( + '/foo', + '/foo/bar', + '/foo/bar/baz', + '/foo/baz', + '/bar', + ); + + $expected = array( + '/foo', + '/foo/bar', + '/foo/baz', + ); + + $iterator = new RegexFilterIterator( + '~^/foo(/[^/]+)?$~', + '/foo', + new ArrayIterator($values) + ); + + $this->assertSame($expected, iterator_to_array($iterator)); + + $this->assertFalse($iterator->valid()); + $this->assertNull($iterator->key()); + $this->assertNull($iterator->current()); + } + + public function testIterateTwice() + { + $values = array( + '/foo', + '/foo/bar', + '/foo/bar/baz', + '/foo/baz', + '/bar', + ); + + $expected = array( + '/foo', + '/foo/bar', + '/foo/baz', + ); + + $iterator = new RegexFilterIterator( + '~^/foo(/[^/]+)?$~', + '/foo', + new ArrayIterator($values) + ); + + // Make sure everything is rewound correctly + $this->assertSame($expected, iterator_to_array($iterator)); + $this->assertSame($expected, iterator_to_array($iterator)); + + $this->assertFalse($iterator->valid()); + $this->assertNull($iterator->key()); + $this->assertNull($iterator->current()); + } + + public function testIterateKeyAsKey() + { + $values = array( + 'a' => '/foo', + 'b' => '/foo/bar', + 'c' => '/foo/bar/baz', + 'd' => '/foo/baz', + 'e' => '/bar', + ); + + $expected = array( + 'a' => '/foo', + 'b' => '/foo/bar', + 'd' => '/foo/baz', + ); + + $iterator = new RegexFilterIterator( + '~^/foo(/[^/]+)?$~', + '/foo', + new ArrayIterator($values), + RegexFilterIterator::KEY_AS_KEY + ); + + $this->assertSame($expected, iterator_to_array($iterator)); + + $this->assertFalse($iterator->valid()); + $this->assertNull($iterator->key()); + $this->assertNull($iterator->current()); + } +} diff --git a/lib/composer/webmozart/glob/tests/TestStreamWrapper.php b/lib/composer/webmozart/glob/tests/TestStreamWrapper.php new file mode 100644 index 0000000000000000000000000000000000000000..c40c1a4d6885a37a4ffaa9f942ae00d717e94094 --- /dev/null +++ b/lib/composer/webmozart/glob/tests/TestStreamWrapper.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\Glob\Tests; + +/** + * @since 1.0 + * + * @author Bernhard Schussek + */ +class TestStreamWrapper +{ + /** + * @var string[] + */ + private static $basePaths = array(); + + /** + * @var resource + */ + private $handle; + + public static function register($scheme, $basePath) + { + self::$basePaths[$scheme] = $basePath; + + stream_wrapper_register($scheme, __CLASS__); + } + + public static function unregister($scheme) + { + if (!isset(self::$basePaths[$scheme])) { + return; + } + + unset(self::$basePaths[$scheme]); + + stream_wrapper_unregister($scheme); + } + + public function dir_opendir($uri, $options) + { + $this->handle = opendir($this->uriToPath($uri)); + + return true; + } + + public function dir_closedir() + { + assert(null !== $this->handle); + + closedir($this->handle); + + return false; + } + + public function dir_readdir() + { + assert(null !== $this->handle); + + return readdir($this->handle); + } + + public function dir_rewinddir() + { + assert(null !== $this->handle); + + rewinddir($this->handle); + + return true; + } + + public function mkdir($uri, $mode, $options) + { + } + + public function rename($uriFrom, $uriTo) + { + } + + public function rmdir($uri, $options) + { + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + } + + public function stream_eof() + { + } + + public function stream_flush() + { + } + + public function stream_lock($operation) + { + } + + public function stream_metadata($uri, $option, $value) + { + } + + public function stream_open($uri, $mode, $options, &$openedPath) + { + } + + public function stream_read($length) + { + } + + public function stream_seek($offset, $whence = SEEK_SET) + { + } + + public function stream_set_option($option, $arg1, $arg2) + { + } + + public function stream_stat() + { + } + + public function stream_tell() + { + } + + public function stream_truncate($newSize) + { + } + + public function stream_write($data) + { + } + + public function unlink($uri) + { + } + + public function url_stat($uri, $flags) + { + $path = $this->uriToPath($uri); + + if ($flags & STREAM_URL_STAT_LINK) { + return lstat($path); + } + + return stat($path); + } + + private function uriToPath($uri) + { + $parts = explode('://', $uri); + + return self::$basePaths[$parts[0]].$parts[1]; + } +} diff --git a/lib/composer/webmozart/path-util/.gitignore b/lib/composer/webmozart/path-util/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..3a9875b460f3bab5c185900528ba5efe1a18de33 --- /dev/null +++ b/lib/composer/webmozart/path-util/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/lib/composer/webmozart/path-util/.styleci.yml b/lib/composer/webmozart/path-util/.styleci.yml new file mode 100644 index 0000000000000000000000000000000000000000..295fafec0a157999c032cb3ea3d34a5783bba65b --- /dev/null +++ b/lib/composer/webmozart/path-util/.styleci.yml @@ -0,0 +1,8 @@ +preset: symfony + +enabled: + - ordered_use + - strict + +disabled: + - empty_return diff --git a/lib/composer/webmozart/path-util/.travis.yml b/lib/composer/webmozart/path-util/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..d103428ec2db6b55aef977ca1fc65e13b9e36f3d --- /dev/null +++ b/lib/composer/webmozart/path-util/.travis.yml @@ -0,0 +1,29 @@ +language: php + +sudo: false + +cache: + directories: + - $HOME/.composer/cache/files + +matrix: + include: + - php: 5.3 + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 5.6 + env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable' + - php: hhvm + - php: nightly + allow_failures: + - php: hhvm + - php: nightly + fast_finish: true + +install: composer update $COMPOSER_FLAGS -n + +script: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover + +after_script: + - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi;' diff --git a/lib/composer/webmozart/path-util/CHANGELOG.md b/lib/composer/webmozart/path-util/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..b669a191cf76737c3a4d08528c899a0c90d19993 --- /dev/null +++ b/lib/composer/webmozart/path-util/CHANGELOG.md @@ -0,0 +1,68 @@ +Changelog +========= + +* 2.3.0 (2015-12-17) + + * added `Url::makeRelative()` for calculating relative paths between URLs + * fixed `Path::makeRelative()` to trim leading dots when moving outside of + the base path + +* 2.2.3 (2015-10-05) + + * fixed `Path::makeRelative()` to produce `..` when called with the parent + directory of a path + +* 2.2.2 (2015-08-24) + + * `Path::makeAbsolute()` does not fail anymore if an absolute path is passed + with a different root (partition) than the base path + +* 2.2.1 (2015-08-24) + + * fixed minimum versions in composer.json + +* 2.2.0 (2015-08-14) + + * added `Path::normalize()` + +* 2.1.0 (2015-07-14) + + * `Path::canonicalize()` now turns `~` into the user's home directory on + Unix and Windows 8 or later. + +* 2.0.0 (2015-05-21) + + * added support for streams, e.g. "phar://C:/path/to/file" + * added `Path::join()` + * all `Path` methods now throw exceptions if parameters with invalid types are + passed + * added an internal buffer to `Path::canonicalize()` in order to increase the + performance of the `Path` class + +* 1.1.0 (2015-03-19) + + * added `Path::getFilename()` + * added `Path::getFilenameWithoutExtension()` + * added `Path::getExtension()` + * added `Path::hasExtension()` + * added `Path::changeExtension()` + * `Path::makeRelative()` now works when the absolute path and the base path + have equal directory names beneath different base directories + (e.g. "/webmozart/css/style.css" relative to "/puli/css") + +* 1.0.2 (2015-01-12) + + * `Path::makeAbsolute()` fails now if the base path is not absolute + * `Path::makeRelative()` now works when a relative path is passed and the base + path is empty + +* 1.0.1 (2014-12-03) + + * Added PHP 5.6 to Travis. + * Fixed bug in `Path::makeRelative()` when first argument is shorter than second + * Made HHVM compatibility mandatory in .travis.yml + * Added PHP 5.3.3 to travis.yml + +* 1.0.0 (2014-11-26) + + * first release diff --git a/lib/composer/webmozart/path-util/LICENSE b/lib/composer/webmozart/path-util/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9e2e3075eb844a130a8532ed062169d8d06237a7 --- /dev/null +++ b/lib/composer/webmozart/path-util/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Bernhard Schussek + +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/lib/composer/webmozart/path-util/README.md b/lib/composer/webmozart/path-util/README.md new file mode 100644 index 0000000000000000000000000000000000000000..de86ca112eb8912cc68e960ece8bc2ca98d83000 --- /dev/null +++ b/lib/composer/webmozart/path-util/README.md @@ -0,0 +1,143 @@ +File Path Utility +================= + +[![Build Status](https://travis-ci.org/webmozart/path-util.svg?branch=2.3.0)](https://travis-ci.org/webmozart/path-util) +[![Build status](https://ci.appveyor.com/api/projects/status/d5uuypr6p162gpxf/branch/master?svg=true)](https://ci.appveyor.com/project/webmozart/path-util/branch/master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/webmozart/path-util/badges/quality-score.png?b=2.3.0)](https://scrutinizer-ci.com/g/webmozart/path-util/?branch=2.3.0) +[![Latest Stable Version](https://poser.pugx.org/webmozart/path-util/v/stable.svg)](https://packagist.org/packages/webmozart/path-util) +[![Total Downloads](https://poser.pugx.org/webmozart/path-util/downloads.svg)](https://packagist.org/packages/webmozart/path-util) +[![Dependency Status](https://www.versioneye.com/php/webmozart:path-util/2.3.0/badge.svg)](https://www.versioneye.com/php/webmozart:path-util/2.3.0) + +Latest release: [2.3.0](https://packagist.org/packages/webmozart/path-util#2.3.0) + +PHP >= 5.3.3 + +This package provides robust, cross-platform utility functions for normalizing, +comparing and modifying file paths and URLs. + +Installation +------------ + +The utility can be installed with [Composer]: + +``` +$ composer require webmozart/path-util +``` + +Usage +----- + +Use the `Path` class to handle file paths: + +```php +use Webmozart\PathUtil\Path; + +echo Path::canonicalize('/var/www/vhost/webmozart/../config.ini'); +// => /var/www/vhost/config.ini + +echo Path::canonicalize('C:\Programs\Webmozart\..\config.ini'); +// => C:/Programs/config.ini + +echo Path::canonicalize('~/config.ini'); +// => /home/webmozart/config.ini + +echo Path::makeAbsolute('config/config.yml', '/var/www/project'); +// => /var/www/project/config/config.yml + +echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project/uploads'); +// => ../config/config.yml + +$paths = array( + '/var/www/vhosts/project/httpdocs/config/config.yml', + '/var/www/vhosts/project/httpdocs/images/banana.gif', + '/var/www/vhosts/project/httpdocs/uploads/../images/nicer-banana.gif', +); + +Path::getLongestCommonBasePath($paths); +// => /var/www/vhosts/project/httpdocs + +Path::getFilename('/views/index.html.twig'); +// => index.html.twig + +Path::getFilenameWithoutExtension('/views/index.html.twig'); +// => index.html + +Path::getFilenameWithoutExtension('/views/index.html.twig', 'html.twig'); +Path::getFilenameWithoutExtension('/views/index.html.twig', '.html.twig'); +// => index + +Path::getExtension('/views/index.html.twig'); +// => twig + +Path::hasExtension('/views/index.html.twig'); +// => true + +Path::hasExtension('/views/index.html.twig', 'twig'); +// => true + +Path::hasExtension('/images/profile.jpg', array('jpg', 'png', 'gif')); +// => true + +Path::changeExtension('/images/profile.jpeg', 'jpg'); +// => /images/profile.jpg + +Path::join('phar://C:/Documents', 'projects/my-project.phar', 'composer.json'); +// => phar://C:/Documents/projects/my-project.phar/composer.json + +Path::getHomeDirectory(); +// => /home/webmozart +``` + +Use the `Url` class to handle URLs: + +```php +use Webmozart\PathUtil\Url; + +echo Url::makeRelative('http://example.com/css/style.css', 'http://example.com/puli'); +// => ../css/style.css + +echo Url::makeRelative('http://cdn.example.com/css/style.css', 'http://example.com/puli'); +// => http://cdn.example.com/css/style.css +``` + +Learn more in the [Documentation] and the [API Docs]. + +Authors +------- + +* [Bernhard Schussek] a.k.a. [@webmozart] +* [The Community Contributors] + +Documentation +------------- + +Read the [Documentation] if you want to learn more about the contained functions. + +Contribute +---------- + +Contributions are always welcome! + +* Report any bugs or issues you find on the [issue tracker]. +* You can grab the source code at the [Git repository]. + +Support +------- + +If you are having problems, send a mail to bschussek@gmail.com or shout out to +[@webmozart] on Twitter. + +License +------- + +All contents of this package are licensed under the [MIT license]. + +[Bernhard Schussek]: http://webmozarts.com +[The Community Contributors]: https://github.com/webmozart/path-util/graphs/contributors +[Composer]: https://getcomposer.org +[Documentation]: docs/usage.md +[API Docs]: https://webmozart.github.io/path-util/api/latest/class-Webmozart.PathUtil.Path.html +[issue tracker]: https://github.com/webmozart/path-util/issues +[Git repository]: https://github.com/webmozart/path-util +[@webmozart]: https://twitter.com/webmozart +[MIT license]: LICENSE diff --git a/lib/composer/webmozart/path-util/appveyor.yml b/lib/composer/webmozart/path-util/appveyor.yml new file mode 100644 index 0000000000000000000000000000000000000000..e32482d4051677af90324fde51e7be560805bd14 --- /dev/null +++ b/lib/composer/webmozart/path-util/appveyor.yml @@ -0,0 +1,34 @@ +build: false +shallow_clone: true +platform: x86 +clone_folder: c:\projects\webmozart\path-util + +cache: + - '%LOCALAPPDATA%\Composer\files' + +init: + - SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH% + +environment: + matrix: + - COMPOSER_FLAGS: "" + - COMPOSER_FLAGS: --prefer-lowest --prefer-stable + +install: + - cinst -y OpenSSL.Light + - cinst -y php + - cd c:\tools\php + - copy php.ini-production php.ini /Y + - echo date.timezone="UTC" >> php.ini + - echo extension_dir=ext >> php.ini + - echo extension=php_openssl.dll >> php.ini + - echo extension=php_mbstring.dll >> php.ini + - echo extension=php_fileinfo.dll >> php.ini + - echo memory_limit=1G >> php.ini + - cd c:\projects\webmozart\path-util + - php -r "readfile('http://getcomposer.org/installer');" | php + - php composer.phar update %COMPOSER_FLAGS% --no-interaction --no-progress + +test_script: + - cd c:\projects\webmozart\path-util + - vendor\bin\phpunit.bat --verbose diff --git a/lib/composer/webmozart/path-util/composer.json b/lib/composer/webmozart/path-util/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..884ccac043f8631a80e7facd69eaa94a3d3f5db7 --- /dev/null +++ b/lib/composer/webmozart/path-util/composer.json @@ -0,0 +1,34 @@ +{ + "name": "webmozart/path-util", + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "license": "MIT", + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Webmozart\\PathUtil\\Tests\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/lib/composer/webmozart/path-util/docs/usage.md b/lib/composer/webmozart/path-util/docs/usage.md new file mode 100644 index 0000000000000000000000000000000000000000..5135ca0b35e059c26bee38e42690c99cdb2ac5c9 --- /dev/null +++ b/lib/composer/webmozart/path-util/docs/usage.md @@ -0,0 +1,203 @@ +Painfree Handling of File Paths +=============================== + +Dealing with file paths usually involves some difficulties: + +* **System Heterogeneity**: File paths look different on different platforms. + UNIX file paths start with a slash ("/"), while Windows file paths start with + a system drive ("C:"). UNIX uses forward slashes, while Windows uses + backslashes by default ("\"). + +* **Absolute/Relative Paths**: Web applications frequently need to deal with + absolute and relative paths. Converting one to the other properly is tricky + and repetitive. + +This package provides few, but robust utility methods to simplify your life +when dealing with file paths. + +Canonicalization +---------------- + +*Canonicalization* is the transformation of a path into a normalized (the +"canonical") format. You can canonicalize a path with `Path::canonicalize()`: + +```php +echo Path::canonicalize('/var/www/vhost/webmozart/../config.ini'); +// => /var/www/vhost/config.ini +``` + +The following modifications happen during canonicalization: + +* "." segments are removed; +* ".." segments are resolved; +* backslashes ("\") are converted into forward slashes ("/"); +* root paths ("/" and "C:/") always terminate with a slash; +* non-root paths never terminate with a slash; +* schemes (such as "phar://") are kept; +* replace "~" with the user's home directory. + +You can pass absolute paths and relative paths to `canonicalize()`. When a +relative path is passed, ".." segments at the beginning of the path are kept: + +```php +echo Path::canonicalize('../uploads/../config/config.yml'); +// => ../config/config.yml +``` + +Malformed paths are returned unchanged: + +```php +echo Path::canonicalize('C:Programs/PHP/php.ini'); +// => C:Programs/PHP/php.ini +``` + +Converting Absolute/Relative Paths +---------------------------------- + +Absolute/relative paths can be converted with the methods `Path::makeAbsolute()` +and `Path::makeRelative()`. + +`makeAbsolute()` expects a relative path and a base path to base that relative +path upon: + +```php +echo Path::makeAbsolute('config/config.yml', '/var/www/project'); +// => /var/www/project/config/config.yml +``` + +If an absolute path is passed in the first argument, the absolute path is +returned unchanged: + +```php +echo Path::makeAbsolute('/usr/share/lib/config.ini', '/var/www/project'); +// => /usr/share/lib/config.ini +``` + +The method resolves ".." segments, if there are any: + +```php +echo Path::makeAbsolute('../config/config.yml', '/var/www/project/uploads'); +// => /var/www/project/config/config.yml +``` + +This method is very useful if you want to be able to accept relative paths (for +example, relative to the root directory of your project) and absolute paths at +the same time. + +`makeRelative()` is the inverse operation to `makeAbsolute()`: + +```php +echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project'); +// => config/config.yml +``` + +If the path is not within the base path, the method will prepend ".." segments +as necessary: + +```php +echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project/uploads'); +// => ../config/config.yml +``` + +Use `isAbsolute()` and `isRelative()` to check whether a path is absolute or +relative: + +```php +Path::isAbsolute('C:\Programs\PHP\php.ini') +// => true +``` + +All four methods internally canonicalize the passed path. + +Finding Longest Common Base Paths +--------------------------------- + +When you store absolute file paths on the file system, this leads to a lot of +duplicated information: + +```php +return array( + '/var/www/vhosts/project/httpdocs/config/config.yml', + '/var/www/vhosts/project/httpdocs/config/routing.yml', + '/var/www/vhosts/project/httpdocs/config/services.yml', + '/var/www/vhosts/project/httpdocs/images/banana.gif', + '/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif', +); +``` + +Especially when storing many paths, the amount of duplicated information is +noticeable. You can use `Path::getLongestCommonBasePath()` to check a list of +paths for a common base path: + +```php +$paths = array( + '/var/www/vhosts/project/httpdocs/config/config.yml', + '/var/www/vhosts/project/httpdocs/config/routing.yml', + '/var/www/vhosts/project/httpdocs/config/services.yml', + '/var/www/vhosts/project/httpdocs/images/banana.gif', + '/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif', +); + +Path::getLongestCommonBasePath($paths); +// => /var/www/vhosts/project/httpdocs +``` + +Use this path together with `Path::makeRelative()` to shorten the stored paths: + +```php +$bp = '/var/www/vhosts/project/httpdocs'; + +return array( + $bp.'/config/config.yml', + $bp.'/config/routing.yml', + $bp.'/config/services.yml', + $bp.'/images/banana.gif', + $bp.'/uploads/images/nicer-banana.gif', +); +``` + +`getLongestCommonBasePath()` always returns canonical paths. + +Use `Path::isBasePath()` to test whether a path is a base path of another path: + +```php +Path::isBasePath("/var/www", "/var/www/project"); +// => true + +Path::isBasePath("/var/www", "/var/www/project/.."); +// => true + +Path::isBasePath("/var/www", "/var/www/project/../.."); +// => false +``` + +Finding Directories/Root Directories +------------------------------------ + +PHP offers the function `dirname()` to obtain the directory path of a file path. +This method has a few quirks: + +* `dirname()` does not accept backslashes on UNIX +* `dirname("C:/Programs")` returns "C:", not "C:/" +* `dirname("C:/")` returns ".", not "C:/" +* `dirname("C:")` returns ".", not "C:/" +* `dirname("Programs")` returns ".", not "" +* `dirname()` does not canonicalize the result + +`Path::getDirectory()` fixes these shortcomings: + +```php +echo Path::getDirectory("C:\Programs"); +// => C:/ +``` + +Additionally, you can use `Path::getRoot()` to obtain the root of a path: + +```php +echo Path::getRoot("/etc/apache2/sites-available"); +// => / + +echo Path::getRoot("C:\Programs\Apache\Config"); +// => C:/ +``` + diff --git a/lib/composer/webmozart/path-util/phpunit.xml.dist b/lib/composer/webmozart/path-util/phpunit.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..68cf2d336055f7a0379de94e1bc0659ec20b9d10 --- /dev/null +++ b/lib/composer/webmozart/path-util/phpunit.xml.dist @@ -0,0 +1,16 @@ + + + + + + ./tests/ + + + + + + + ./src/ + + + diff --git a/lib/composer/webmozart/path-util/src/Path.php b/lib/composer/webmozart/path-util/src/Path.php new file mode 100644 index 0000000000000000000000000000000000000000..2f4a177950cbb3991d989adf135ce9777f05d74c --- /dev/null +++ b/lib/composer/webmozart/path-util/src/Path.php @@ -0,0 +1,1008 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\PathUtil; + +use InvalidArgumentException; +use RuntimeException; +use Webmozart\Assert\Assert; + +/** + * 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. + * + * @since 1.0 + * + * @author Bernhard Schussek + * @author Thomas Schulz + */ +final class Path +{ + /** + * The number of buffer entries that triggers a cleanup operation. + */ + const CLEANUP_THRESHOLD = 1250; + + /** + * The buffer size after the cleanup operation. + */ + const CLEANUP_SIZE = 1000; + + /** + * Buffers input/output of {@link canonicalize()}. + * + * @var array + */ + private static $buffer = array(); + + /** + * The size of the buffer. + * + * @var int + */ + private static $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("\webmozart\puli\..\css\style.css"); + * // => /webmozart/css/style.css + * + * echo Path::canonicalize("../css/./style.css"); + * // => ../css/style.css + * ``` + * + * This method is able to deal with both UNIX and Windows paths. + * + * @param string $path A path string. + * + * @return string The canonical path. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + * @since 2.1 Added support for `~`. + */ + public static function canonicalize($path) + { + if ('' === $path) { + return ''; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + + // 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 = static::getHomeDirectory().substr($path, 1); + } + + $path = str_replace('\\', '/', $path); + + list($root, $pathWithoutRoot) = self::split($path); + + $parts = explode('/', $pathWithoutRoot); + $canonicalParts = array(); + + // 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; + } + } + + // 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. + * + * @param string $path A path string. + * + * @return string The normalized path. + * + * @since 2.2 Added method. + */ + public static function normalize($path) + { + Assert::string($path, 'The path must be a string. Got: %s'); + + 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:/webmozart") returns "C:", not "C:/" + * - dirname("C:/") returns ".", not "C:/" + * - dirname("C:") returns ".", not "C:/" + * - dirname("webmozart") returns ".", not "" + * - dirname() does not canonicalize the result + * + * This method fixes these shortcomings and behaves like dirname() + * otherwise. + * + * The result is a canonical path. + * + * @param string $path A path string. + * + * @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. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function getDirectory($path) + { + if ('' === $path) { + return ''; + } + + $path = static::canonicalize($path); + + // Maintain scheme + if (false !== ($pos = strpos($path, '://'))) { + $scheme = substr($path, 0, $pos + 3); + $path = substr($path, $pos + 3); + } else { + $scheme = ''; + } + + if (false !== ($pos = strrpos($path, '/'))) { + // Directory equals root directory "/" + if (0 === $pos) { + return $scheme.'/'; + } + + // Directory equals Windows root "C:/" + if (2 === $pos && ctype_alpha($path[0]) && ':' === $path[1]) { + return $scheme.substr($path, 0, 3); + } + + return $scheme.substr($path, 0, $pos); + } + + return ''; + } + + /** + * Returns canonical path of the user's home directory. + * + * Supported operating systems: + * + * - UNIX + * - Windows8 and upper + * + * If your operation system or environment isn't supported, an exception is thrown. + * + * The result is a canonical path. + * + * @return string The canonical home directory + * + * @throws RuntimeException If your operation system or environment isn't supported + * + * @since 2.1 Added method. + */ + public static function getHomeDirectory() + { + // For UNIX support + if (getenv('HOME')) { + return static::canonicalize(getenv('HOME')); + } + + // For >= Windows8 support + if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) { + return static::canonicalize(getenv('HOMEDRIVE').getenv('HOMEPATH')); + } + + throw new RuntimeException("Your environment or operation system isn't supported"); + } + + /** + * Returns the root directory of a path. + * + * The result is a canonical path. + * + * @param string $path A path string. + * + * @return string The canonical root directory. Returns an empty string if + * the given path is relative or empty. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function getRoot($path) + { + if ('' === $path) { + return ''; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + + // Maintain scheme + if (false !== ($pos = strpos($path, '://'))) { + $scheme = substr($path, 0, $pos + 3); + $path = substr($path, $pos + 3); + } else { + $scheme = ''; + } + + // UNIX root "/" or "\" (Windows style) + if ('/' === $path[0] || '\\' === $path[0]) { + return $scheme.'/'; + } + + $length = strlen($path); + + // Windows root + if ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) { + // Special case: "C:" + if (2 === $length) { + return $scheme.$path.'/'; + } + + // Normal case: "C:/ or "C:\" + if ('/' === $path[2] || '\\' === $path[2]) { + return $scheme.$path[0].$path[1].'/'; + } + } + + return ''; + } + + /** + * Returns the file name from a file path. + * + * @param string $path The path string. + * + * @return string The file name. + * + * @since 1.1 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function getFilename($path) + { + if ('' === $path) { + return ''; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + + return basename($path); + } + + /** + * Returns the file name without the extension from a file path. + * + * @param string $path The path string. + * @param string|null $extension If specified, only that extension is cut + * off (may contain leading dot). + * + * @return string The file name without extension. + * + * @since 1.1 Added method. + * @since 2.0 Method now fails if $path or $extension have invalid types. + */ + public static function getFilenameWithoutExtension($path, $extension = null) + { + if ('' === $path) { + return ''; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + Assert::nullOrString($extension, 'The extension must be a string or null. Got: %s'); + + 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. + * + * @param string $path The path string. + * @param bool $forceLowerCase Forces the extension to be lower-case + * (requires mbstring extension for correct + * multi-byte character handling in extension). + * + * @return string The extension of the file path (without leading dot). + * + * @since 1.1 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function getExtension($path, $forceLowerCase = false) + { + if ('' === $path) { + return ''; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + + $extension = pathinfo($path, PATHINFO_EXTENSION); + + if ($forceLowerCase) { + $extension = self::toLower($extension); + } + + return $extension; + } + + /** + * Returns whether the path has an extension. + * + * @param string $path The path string. + * @param string|array|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 + * (requires mbstring extension for + * correct multi-byte character + * handling in the extension). + * + * @return bool Returns `true` if the path has an (or the specified) + * extension and `false` otherwise. + * + * @since 1.1 Added method. + * @since 2.0 Method now fails if $path or $extensions have invalid types. + */ + public static function hasExtension($path, $extensions = null, $ignoreCase = false) + { + if ('' === $path) { + return false; + } + + $extensions = is_object($extensions) ? array($extensions) : (array) $extensions; + + Assert::allString($extensions, 'The extensions must be strings. Got: %s'); + + $actualExtension = self::getExtension($path, $ignoreCase); + + // Only check if path has any extension + if (empty($extensions)) { + return '' !== $actualExtension; + } + + 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); + } + + /** + * 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. + * + * @since 1.1 Added method. + * @since 2.0 Method now fails if $path or $extension is not a string. + */ + public static function changeExtension($path, $extension) + { + if ('' === $path) { + return ''; + } + + Assert::string($extension, 'The extension must be a string. Got: %s'); + + $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; + } + + /** + * Returns whether a path is absolute. + * + * @param string $path A path string. + * + * @return bool Returns true if the path is absolute, false if it is + * relative or empty. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function isAbsolute($path) + { + if ('' === $path) { + return false; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + + // Strip scheme + if (false !== ($pos = strpos($path, '://'))) { + $path = substr($path, $pos + 3); + } + + // UNIX root "/" or "\" (Windows style) + if ('/' === $path[0] || '\\' === $path[0]) { + return true; + } + + // Windows root + if (strlen($path) > 1 && ctype_alpha($path[0]) && ':' === $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; + } + + /** + * Returns whether a path is relative. + * + * @param string $path A path string. + * + * @return bool Returns true if the path is relative or empty, false if + * it is absolute. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function isRelative($path) + { + return !static::isAbsolute($path); + } + + /** + * Turns a relative path into an absolute path. + * + * 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", "/webmozart/puli/css"); + * // => /webmozart/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", "/webmozart/puli/css"); + * // => /style.css + * + * Path::makeAbsolute("C:/style.css", "C:/webmozart/puli/css"); + * // => C:/style.css + * + * Path::makeAbsolute("C:/style.css", "/webmozart/puli/css"); + * // InvalidArgumentException + * ``` + * + * If the base path is not an absolute path, an exception is thrown. + * + * The result is a canonical path. + * + * @param string $path A path to make absolute. + * @param string $basePath An absolute base path. + * + * @return string An absolute path in canonical form. + * + * @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. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path or $basePath is not a string. + * @since 2.2.2 Method does not fail anymore of $path and $basePath are + * absolute, but on different partitions. + */ + public static function makeAbsolute($path, $basePath) + { + Assert::stringNotEmpty($basePath, 'The base path must be a non-empty string. Got: %s'); + + if (!static::isAbsolute($basePath)) { + throw new InvalidArgumentException(sprintf( + 'The base path "%s" is not an absolute path.', + $basePath + )); + } + + if (static::isAbsolute($path)) { + return static::canonicalize($path); + } + + if (false !== ($pos = strpos($basePath, '://'))) { + $scheme = substr($basePath, 0, $pos + 3); + $basePath = substr($basePath, $pos + 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("/webmozart/style.css", "/webmozart/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", "/webmozart/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", "webmozart/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:/webmozart/style.css", "/webmozart/puli"); + * // InvalidArgumentException + * ``` + * + * If the passed path is absolute, but the base path is not, an exception + * is thrown as well: + * + * ```php + * Path::makeRelative("/webmozart/style.css", "webmozart/puli"); + * // InvalidArgumentException + * ``` + * + * If the base path is not an absolute path, an exception is thrown. + * + * The result is a canonical path. + * + * @param string $path A path to make relative. + * @param string $basePath A base path. + * + * @return string A relative path in canonical form. + * + * @throws InvalidArgumentException If the base path is not absolute or if + * the given path has a different root + * than the base path. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path or $basePath is not a string. + */ + public static function makeRelative($path, $basePath) + { + Assert::string($basePath, 'The base path must be a string. Got: %s'); + + $path = static::canonicalize($path); + $basePath = static::canonicalize($basePath); + + list($root, $relativePath) = self::split($path); + list($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 $i => $basePart) { + if ($match && isset($parts[$i]) && $basePart === $parts[$i]) { + unset($parts[$i]); + + continue; + } + + $match = false; + $dotDotPrefix .= '../'; + } + + return rtrim($dotDotPrefix.implode('/', $parts), '/'); + } + + /** + * Returns whether the given path is on the local filesystem. + * + * @param string $path A path string. + * + * @return bool Returns true if the path is local, false for a URL. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function isLocal($path) + { + Assert::string($path, 'The path must be a string. Got: %s'); + + return '' !== $path && false === strpos($path, '://'); + } + + /** + * Returns the longest common base path of a set of paths. + * + * Dot segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * $basePath = Path::getLongestCommonBasePath(array( + * '/webmozart/css/style.css', + * '/webmozart/css/..' + * )); + * // => /webmozart + * ``` + * + * The root is returned if no common base path can be found: + * + * ```php + * $basePath = Path::getLongestCommonBasePath(array( + * '/webmozart/css/style.css', + * '/puli/css/..' + * )); + * // => / + * ``` + * + * If the paths are located on different Windows partitions, `null` is + * returned. + * + * ```php + * $basePath = Path::getLongestCommonBasePath(array( + * 'C:/webmozart/css/style.css', + * 'D:/webmozart/css/..' + * )); + * // => null + * ``` + * + * @param array $paths A list of paths. + * + * @return string|null The longest common base path in canonical form or + * `null` if the paths are on different Windows + * partitions. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $paths are not strings. + */ + public static function getLongestCommonBasePath(array $paths) + { + Assert::allString($paths, 'The paths must be strings. Got: %s'); + + list($bpRoot, $basePath) = self::split(self::canonicalize(reset($paths))); + + for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) { + list($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 (0 === strpos($path.'/', $basePath.'/')) { + // Next path + continue 2; + } + + $basePath = dirname($basePath); + } + } + + return $bpRoot.$basePath; + } + + /** + * Joins two or more path strings. + * + * The result is a canonical path. + * + * @param string[]|string $paths Path parts as parameters or array. + * + * @return string The joint path. + * + * @since 2.0 Added method. + */ + public static function join($paths) + { + if (!is_array($paths)) { + $paths = func_get_args(); + } + + Assert::allString($paths, 'The paths must be strings. Got: %s'); + + $finalPath = null; + $wasScheme = false; + + foreach ($paths as $path) { + $path = (string) $path; + + if ('' === $path) { + continue; + } + + if (null === $finalPath) { + // For first part we keep slashes, like '/top', 'C:\' or 'phar://' + $finalPath = $path; + $wasScheme = (strpos($path, '://') !== false); + continue; + } + + // Only add slash if previous part didn't end with '/' or '\' + if (!in_array(substr($finalPath, -1), array('/', '\\'))) { + $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('/webmozart', '/webmozart/css'); + * // => true + * + * Path::isBasePath('/webmozart', '/webmozart'); + * // => true + * + * Path::isBasePath('/webmozart', '/webmozart/..'); + * // => false + * + * Path::isBasePath('/webmozart', '/puli'); + * // => false + * ``` + * + * @param string $basePath The base path to test. + * @param string $ofPath The other path. + * + * @return bool Whether the base path is a base path of the other path. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $basePath or $ofPath is not a string. + */ + public static function isBasePath($basePath, $ofPath) + { + Assert::string($basePath, 'The base path must be a string. Got: %s'); + + $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 0 === strpos($ofPath.'/', rtrim($basePath, '/').'/'); + } + + /** + * Splits a part 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:/webmozart") + * // => array("C:/", "webmozart") + * + * list ($root, $path) = Path::split("C:") + * // => array("C:/", "") + * + * @param string $path The canonical path to split. + * + * @return string[] An array with the root directory and the remaining + * relative path. + */ + private static function split($path) + { + if ('' === $path) { + return array('', ''); + } + + // Remember scheme as part of the root, if any + if (false !== ($pos = strpos($path, '://'))) { + $root = substr($path, 0, $pos + 3); + $path = substr($path, $pos + 3); + } else { + $root = ''; + } + + $length = strlen($path); + + // Remove and remember root directory + if ('/' === $path[0]) { + $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 array($root, $path); + } + + /** + * Converts string to lower-case (multi-byte safe if mbstring is installed). + * + * @param string $str The string + * + * @return string Lower case string + */ + private static function toLower($str) + { + if (function_exists('mb_strtolower')) { + return mb_strtolower($str, mb_detect_encoding($str)); + } + + return strtolower($str); + } + + private function __construct() + { + } +} diff --git a/lib/composer/webmozart/path-util/src/Url.php b/lib/composer/webmozart/path-util/src/Url.php new file mode 100644 index 0000000000000000000000000000000000000000..834e7213d023945afc4cf78a22db80a6645afc00 --- /dev/null +++ b/lib/composer/webmozart/path-util/src/Url.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 Webmozart\PathUtil; + +use InvalidArgumentException; +use Webmozart\Assert\Assert; + +/** + * Contains utility methods for handling URL strings. + * + * The methods in this class are able to deal with URLs. + * + * @since 2.3 + * + * @author Bernhard Schussek + * @author Claudio Zizza + */ +final class Url +{ + /** + * Turns a URL into a relative path. + * + * The result is a canonical path. This class is using functionality of Path class. + * + * @see Path + * + * @param string $url A URL to make relative. + * @param string $baseUrl A base URL. + * + * @return string + * + * @throws InvalidArgumentException If the URL and base URL does + * not match. + */ + public static function makeRelative($url, $baseUrl) + { + Assert::string($url, 'The URL must be a string. Got: %s'); + Assert::string($baseUrl, 'The base URL must be a string. Got: %s'); + Assert::contains($baseUrl, '://', '%s is not an absolute Url.'); + + list($baseHost, $basePath) = self::split($baseUrl); + + if (false === strpos($url, '://')) { + if (0 === strpos($url, '/')) { + $host = $baseHost; + } else { + $host = ''; + } + $path = $url; + } else { + list($host, $path) = self::split($url); + } + + if ('' !== $host && $host !== $baseHost) { + throw new InvalidArgumentException(sprintf( + 'The URL "%s" cannot be made relative to "%s" since their host names are different.', + $host, + $baseHost + )); + } + + return Path::makeRelative($path, $basePath); + } + + /** + * Splits a URL into its host and the path. + * + * ```php + * list ($root, $path) = Path::split("http://example.com/webmozart") + * // => array("http://example.com", "/webmozart") + * + * list ($root, $path) = Path::split("http://example.com") + * // => array("http://example.com", "") + * ``` + * + * @param string $url The URL to split. + * + * @return string[] An array with the host and the path of the URL. + * + * @throws InvalidArgumentException If $url is not a URL. + */ + private static function split($url) + { + $pos = strpos($url, '://'); + $scheme = substr($url, 0, $pos + 3); + $url = substr($url, $pos + 3); + + if (false !== ($pos = strpos($url, '/'))) { + $host = substr($url, 0, $pos); + $url = substr($url, $pos); + } else { + // No path, only host + $host = $url; + $url = '/'; + } + + // At this point, we have $scheme, $host and $path + $root = $scheme.$host; + + return array($root, $url); + } +} diff --git a/lib/composer/webmozart/path-util/tests/PathTest.php b/lib/composer/webmozart/path-util/tests/PathTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cc88b97a39a19f17153b1d2230cb7ec5e4905e16 --- /dev/null +++ b/lib/composer/webmozart/path-util/tests/PathTest.php @@ -0,0 +1,1340 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\PathUtil\Tests; + +use Webmozart\PathUtil\Path; + +/** + * @since 1.0 + * + * @author Bernhard Schussek + * @author Thomas Schulz + */ +class PathTest extends \PHPUnit_Framework_TestCase +{ + protected $storedEnv = array(); + + public function setUp() + { + $this->storedEnv['HOME'] = getenv('HOME'); + $this->storedEnv['HOMEDRIVE'] = getenv('HOMEDRIVE'); + $this->storedEnv['HOMEPATH'] = getenv('HOMEPATH'); + + putenv('HOME=/home/webmozart'); + putenv('HOMEDRIVE='); + putenv('HOMEPATH='); + } + + public function tearDown() + { + putenv('HOME='.$this->storedEnv['HOME']); + putenv('HOMEDRIVE='.$this->storedEnv['HOMEDRIVE']); + putenv('HOMEPATH='.$this->storedEnv['HOMEPATH']); + } + + public function provideCanonicalizationTests() + { + return array( + // relative paths (forward slash) + array('css/./style.css', 'css/style.css'), + array('css/../style.css', 'style.css'), + array('css/./../style.css', 'style.css'), + array('css/.././style.css', 'style.css'), + array('css/../../style.css', '../style.css'), + array('./css/style.css', 'css/style.css'), + array('../css/style.css', '../css/style.css'), + array('./../css/style.css', '../css/style.css'), + array('.././css/style.css', '../css/style.css'), + array('../../css/style.css', '../../css/style.css'), + array('', ''), + array('.', ''), + array('..', '..'), + array('./..', '..'), + array('../.', '..'), + array('../..', '../..'), + + // relative paths (backslash) + array('css\\.\\style.css', 'css/style.css'), + array('css\\..\\style.css', 'style.css'), + array('css\\.\\..\\style.css', 'style.css'), + array('css\\..\\.\\style.css', 'style.css'), + array('css\\..\\..\\style.css', '../style.css'), + array('.\\css\\style.css', 'css/style.css'), + array('..\\css\\style.css', '../css/style.css'), + array('.\\..\\css\\style.css', '../css/style.css'), + array('..\\.\\css\\style.css', '../css/style.css'), + array('..\\..\\css\\style.css', '../../css/style.css'), + + // absolute paths (forward slash, UNIX) + array('/css/style.css', '/css/style.css'), + array('/css/./style.css', '/css/style.css'), + array('/css/../style.css', '/style.css'), + array('/css/./../style.css', '/style.css'), + array('/css/.././style.css', '/style.css'), + array('/./css/style.css', '/css/style.css'), + array('/../css/style.css', '/css/style.css'), + array('/./../css/style.css', '/css/style.css'), + array('/.././css/style.css', '/css/style.css'), + array('/../../css/style.css', '/css/style.css'), + + // absolute paths (backslash, UNIX) + array('\\css\\style.css', '/css/style.css'), + array('\\css\\.\\style.css', '/css/style.css'), + array('\\css\\..\\style.css', '/style.css'), + array('\\css\\.\\..\\style.css', '/style.css'), + array('\\css\\..\\.\\style.css', '/style.css'), + array('\\.\\css\\style.css', '/css/style.css'), + array('\\..\\css\\style.css', '/css/style.css'), + array('\\.\\..\\css\\style.css', '/css/style.css'), + array('\\..\\.\\css\\style.css', '/css/style.css'), + array('\\..\\..\\css\\style.css', '/css/style.css'), + + // absolute paths (forward slash, Windows) + array('C:/css/style.css', 'C:/css/style.css'), + array('C:/css/./style.css', 'C:/css/style.css'), + array('C:/css/../style.css', 'C:/style.css'), + array('C:/css/./../style.css', 'C:/style.css'), + array('C:/css/.././style.css', 'C:/style.css'), + array('C:/./css/style.css', 'C:/css/style.css'), + array('C:/../css/style.css', 'C:/css/style.css'), + array('C:/./../css/style.css', 'C:/css/style.css'), + array('C:/.././css/style.css', 'C:/css/style.css'), + array('C:/../../css/style.css', 'C:/css/style.css'), + + // absolute paths (backslash, Windows) + array('C:\\css\\style.css', 'C:/css/style.css'), + array('C:\\css\\.\\style.css', 'C:/css/style.css'), + array('C:\\css\\..\\style.css', 'C:/style.css'), + array('C:\\css\\.\\..\\style.css', 'C:/style.css'), + array('C:\\css\\..\\.\\style.css', 'C:/style.css'), + array('C:\\.\\css\\style.css', 'C:/css/style.css'), + array('C:\\..\\css\\style.css', 'C:/css/style.css'), + array('C:\\.\\..\\css\\style.css', 'C:/css/style.css'), + array('C:\\..\\.\\css\\style.css', 'C:/css/style.css'), + array('C:\\..\\..\\css\\style.css', 'C:/css/style.css'), + + // Windows special case + array('C:', 'C:/'), + + // Don't change malformed path + array('C:css/style.css', 'C:css/style.css'), + + // absolute paths (stream, UNIX) + array('phar:///css/style.css', 'phar:///css/style.css'), + array('phar:///css/./style.css', 'phar:///css/style.css'), + array('phar:///css/../style.css', 'phar:///style.css'), + array('phar:///css/./../style.css', 'phar:///style.css'), + array('phar:///css/.././style.css', 'phar:///style.css'), + array('phar:///./css/style.css', 'phar:///css/style.css'), + array('phar:///../css/style.css', 'phar:///css/style.css'), + array('phar:///./../css/style.css', 'phar:///css/style.css'), + array('phar:///.././css/style.css', 'phar:///css/style.css'), + array('phar:///../../css/style.css', 'phar:///css/style.css'), + + // absolute paths (stream, Windows) + array('phar://C:/css/style.css', 'phar://C:/css/style.css'), + array('phar://C:/css/./style.css', 'phar://C:/css/style.css'), + array('phar://C:/css/../style.css', 'phar://C:/style.css'), + array('phar://C:/css/./../style.css', 'phar://C:/style.css'), + array('phar://C:/css/.././style.css', 'phar://C:/style.css'), + array('phar://C:/./css/style.css', 'phar://C:/css/style.css'), + array('phar://C:/../css/style.css', 'phar://C:/css/style.css'), + array('phar://C:/./../css/style.css', 'phar://C:/css/style.css'), + array('phar://C:/.././css/style.css', 'phar://C:/css/style.css'), + array('phar://C:/../../css/style.css', 'phar://C:/css/style.css'), + + // paths with "~" UNIX + array('~/css/style.css', '/home/webmozart/css/style.css'), + array('~/css/./style.css', '/home/webmozart/css/style.css'), + array('~/css/../style.css', '/home/webmozart/style.css'), + array('~/css/./../style.css', '/home/webmozart/style.css'), + array('~/css/.././style.css', '/home/webmozart/style.css'), + array('~/./css/style.css', '/home/webmozart/css/style.css'), + array('~/../css/style.css', '/home/css/style.css'), + array('~/./../css/style.css', '/home/css/style.css'), + array('~/.././css/style.css', '/home/css/style.css'), + array('~/../../css/style.css', '/css/style.css'), + ); + } + + /** + * @dataProvider provideCanonicalizationTests + */ + public function testCanonicalize($path, $canonicalized) + { + $this->assertSame($canonicalized, Path::canonicalize($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testCanonicalizeFailsIfInvalidPath() + { + Path::canonicalize(array()); + } + + public function provideGetDirectoryTests() + { + return array( + array('/webmozart/puli/style.css', '/webmozart/puli'), + array('/webmozart/puli', '/webmozart'), + array('/webmozart', '/'), + array('/', '/'), + array('', ''), + + array('\\webmozart\\puli\\style.css', '/webmozart/puli'), + array('\\webmozart\\puli', '/webmozart'), + array('\\webmozart', '/'), + array('\\', '/'), + + array('C:/webmozart/puli/style.css', 'C:/webmozart/puli'), + array('C:/webmozart/puli', 'C:/webmozart'), + array('C:/webmozart', 'C:/'), + array('C:/', 'C:/'), + array('C:', 'C:/'), + + array('C:\\webmozart\\puli\\style.css', 'C:/webmozart/puli'), + array('C:\\webmozart\\puli', 'C:/webmozart'), + array('C:\\webmozart', 'C:/'), + array('C:\\', 'C:/'), + + array('phar:///webmozart/puli/style.css', 'phar:///webmozart/puli'), + array('phar:///webmozart/puli', 'phar:///webmozart'), + array('phar:///webmozart', 'phar:///'), + array('phar:///', 'phar:///'), + + array('phar://C:/webmozart/puli/style.css', 'phar://C:/webmozart/puli'), + array('phar://C:/webmozart/puli', 'phar://C:/webmozart'), + array('phar://C:/webmozart', 'phar://C:/'), + array('phar://C:/', 'phar://C:/'), + + array('webmozart/puli/style.css', 'webmozart/puli'), + array('webmozart/puli', 'webmozart'), + array('webmozart', ''), + + array('webmozart\\puli\\style.css', 'webmozart/puli'), + array('webmozart\\puli', 'webmozart'), + array('webmozart', ''), + + array('/webmozart/./puli/style.css', '/webmozart/puli'), + array('/webmozart/../puli/style.css', '/puli'), + array('/webmozart/./../puli/style.css', '/puli'), + array('/webmozart/.././puli/style.css', '/puli'), + array('/webmozart/../../puli/style.css', '/puli'), + array('/.', '/'), + array('/..', '/'), + + array('C:webmozart', ''), + ); + } + + /** + * @dataProvider provideGetDirectoryTests + */ + public function testGetDirectory($path, $directory) + { + $this->assertSame($directory, Path::getDirectory($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testGetDirectoryFailsIfInvalidPath() + { + Path::getDirectory(array()); + } + + public function provideGetFilenameTests() + { + return array( + array('/webmozart/puli/style.css', 'style.css'), + array('/webmozart/puli/STYLE.CSS', 'STYLE.CSS'), + array('/webmozart/puli/style.css/', 'style.css'), + array('/webmozart/puli/', 'puli'), + array('/webmozart/puli', 'puli'), + array('/', ''), + array('', ''), + ); + } + + /** + * @dataProvider provideGetFilenameTests + */ + public function testGetFilename($path, $filename) + { + $this->assertSame($filename, Path::getFilename($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testGetFilenameFailsIfInvalidPath() + { + Path::getFilename(array()); + } + + public function provideGetFilenameWithoutExtensionTests() + { + return array( + array('/webmozart/puli/style.css.twig', null, 'style.css'), + array('/webmozart/puli/style.css.', null, 'style.css'), + array('/webmozart/puli/style.css', null, 'style'), + array('/webmozart/puli/.style.css', null, '.style'), + array('/webmozart/puli/', null, 'puli'), + array('/webmozart/puli', null, 'puli'), + array('/', null, ''), + array('', null, ''), + + array('/webmozart/puli/style.css', 'css', 'style'), + array('/webmozart/puli/style.css', '.css', 'style'), + array('/webmozart/puli/style.css', 'twig', 'style.css'), + array('/webmozart/puli/style.css', '.twig', 'style.css'), + array('/webmozart/puli/style.css', '', 'style.css'), + array('/webmozart/puli/style.css.', '', 'style.css'), + array('/webmozart/puli/style.css.', '.', 'style.css'), + array('/webmozart/puli/style.css.', '.css', 'style.css'), + array('/webmozart/puli/.style.css', 'css', '.style'), + array('/webmozart/puli/.style.css', '.css', '.style'), + ); + } + + /** + * @dataProvider provideGetFilenameWithoutExtensionTests + */ + public function testGetFilenameWithoutExtension($path, $extension, $filename) + { + $this->assertSame($filename, Path::getFilenameWithoutExtension($path, $extension)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testGetFilenameWithoutExtensionFailsIfInvalidPath() + { + Path::getFilenameWithoutExtension(array(), '.css'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The extension must be a string or null. Got: array + */ + public function testGetFilenameWithoutExtensionFailsIfInvalidExtension() + { + Path::getFilenameWithoutExtension('/style.css', array()); + } + + public function provideGetExtensionTests() + { + $tests = array( + array('/webmozart/puli/style.css.twig', false, 'twig'), + array('/webmozart/puli/style.css', false, 'css'), + array('/webmozart/puli/style.css.', false, ''), + array('/webmozart/puli/', false, ''), + array('/webmozart/puli', false, ''), + array('/', false, ''), + array('', false, ''), + + array('/webmozart/puli/style.CSS', false, 'CSS'), + array('/webmozart/puli/style.CSS', true, 'css'), + array('/webmozart/puli/style.ÄÖÜ', false, 'ÄÖÜ'), + ); + + if (extension_loaded('mbstring')) { + // This can only be tested, when mbstring is installed + $tests[] = array('/webmozart/puli/style.ÄÖÜ', true, 'äöü'); + } + + return $tests; + } + + /** + * @dataProvider provideGetExtensionTests + */ + public function testGetExtension($path, $forceLowerCase, $extension) + { + $this->assertSame($extension, Path::getExtension($path, $forceLowerCase)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testGetExtensionFailsIfInvalidPath() + { + Path::getExtension(array()); + } + + public function provideHasExtensionTests() + { + $tests = array( + array(true, '/webmozart/puli/style.css.twig', null, false), + array(true, '/webmozart/puli/style.css', null, false), + array(false, '/webmozart/puli/style.css.', null, false), + array(false, '/webmozart/puli/', null, false), + array(false, '/webmozart/puli', null, false), + array(false, '/', null, false), + array(false, '', null, false), + + array(true, '/webmozart/puli/style.css.twig', 'twig', false), + array(false, '/webmozart/puli/style.css.twig', 'css', false), + array(true, '/webmozart/puli/style.css', 'css', false), + array(true, '/webmozart/puli/style.css', '.css', false), + array(true, '/webmozart/puli/style.css.', '', false), + array(false, '/webmozart/puli/', 'ext', false), + array(false, '/webmozart/puli', 'ext', false), + array(false, '/', 'ext', false), + array(false, '', 'ext', false), + + array(false, '/webmozart/puli/style.css', 'CSS', false), + array(true, '/webmozart/puli/style.css', 'CSS', true), + array(false, '/webmozart/puli/style.CSS', 'css', false), + array(true, '/webmozart/puli/style.CSS', 'css', true), + array(true, '/webmozart/puli/style.ÄÖÜ', 'ÄÖÜ', false), + + array(true, '/webmozart/puli/style.css', array('ext', 'css'), false), + array(true, '/webmozart/puli/style.css', array('.ext', '.css'), false), + array(true, '/webmozart/puli/style.css.', array('ext', ''), false), + array(false, '/webmozart/puli/style.css', array('foo', 'bar', ''), false), + array(false, '/webmozart/puli/style.css', array('.foo', '.bar', ''), false), + ); + + if (extension_loaded('mbstring')) { + // This can only be tested, when mbstring is installed + $tests[] = array(true, '/webmozart/puli/style.ÄÖÜ', 'äöü', true); + $tests[] = array(true, '/webmozart/puli/style.ÄÖÜ', array('äöü'), true); + } + + return $tests; + } + + /** + * @dataProvider provideHasExtensionTests + */ + public function testHasExtension($hasExtension, $path, $extension, $ignoreCase) + { + $this->assertSame($hasExtension, Path::hasExtension($path, $extension, $ignoreCase)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testHasExtensionFailsIfInvalidPath() + { + Path::hasExtension(array()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The extensions must be strings. Got: stdClass + */ + public function testHasExtensionFailsIfInvalidExtension() + { + Path::hasExtension('/style.css', (object) array()); + } + + public function provideChangeExtensionTests() + { + return array( + array('/webmozart/puli/style.css.twig', 'html', '/webmozart/puli/style.css.html'), + array('/webmozart/puli/style.css', 'sass', '/webmozart/puli/style.sass'), + array('/webmozart/puli/style.css', '.sass', '/webmozart/puli/style.sass'), + array('/webmozart/puli/style.css', '', '/webmozart/puli/style.'), + array('/webmozart/puli/style.css.', 'twig', '/webmozart/puli/style.css.twig'), + array('/webmozart/puli/style.css.', '', '/webmozart/puli/style.css.'), + array('/webmozart/puli/style.css', 'äöü', '/webmozart/puli/style.äöü'), + array('/webmozart/puli/style.äöü', 'css', '/webmozart/puli/style.css'), + array('/webmozart/puli/', 'css', '/webmozart/puli/'), + array('/webmozart/puli', 'css', '/webmozart/puli.css'), + array('/', 'css', '/'), + array('', 'css', ''), + ); + } + + /** + * @dataProvider provideChangeExtensionTests + */ + public function testChangeExtension($path, $extension, $pathExpected) + { + static $call = 0; + $this->assertSame($pathExpected, Path::changeExtension($path, $extension)); + ++$call; + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testChangeExtensionFailsIfInvalidPath() + { + Path::changeExtension(array(), '.sass'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The extension must be a string. Got: array + */ + public function testChangeExtensionFailsIfInvalidExtension() + { + Path::changeExtension('/style.css', array()); + } + + public function provideIsAbsolutePathTests() + { + return array( + array('/css/style.css', true), + array('/', true), + array('css/style.css', false), + array('', false), + + array('\\css\\style.css', true), + array('\\', true), + array('css\\style.css', false), + + array('C:/css/style.css', true), + array('D:/', true), + + array('E:\\css\\style.css', true), + array('F:\\', true), + + array('phar:///css/style.css', true), + array('phar:///', true), + + // Windows special case + array('C:', true), + + // Not considered absolute + array('C:css/style.css', false), + ); + } + + /** + * @dataProvider provideIsAbsolutePathTests + */ + public function testIsAbsolute($path, $isAbsolute) + { + $this->assertSame($isAbsolute, Path::isAbsolute($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testIsAbsoluteFailsIfInvalidPath() + { + Path::isAbsolute(array()); + } + + /** + * @dataProvider provideIsAbsolutePathTests + */ + public function testIsRelative($path, $isAbsolute) + { + $this->assertSame(!$isAbsolute, Path::isRelative($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testIsRelativeFailsIfInvalidPath() + { + Path::isRelative(array()); + } + + public function provideGetRootTests() + { + return array( + array('/css/style.css', '/'), + array('/', '/'), + array('css/style.css', ''), + array('', ''), + + array('\\css\\style.css', '/'), + array('\\', '/'), + array('css\\style.css', ''), + + array('C:/css/style.css', 'C:/'), + array('C:/', 'C:/'), + array('C:', 'C:/'), + + array('D:\\css\\style.css', 'D:/'), + array('D:\\', 'D:/'), + + array('phar:///css/style.css', 'phar:///'), + array('phar:///', 'phar:///'), + + array('phar://C:/css/style.css', 'phar://C:/'), + array('phar://C:/', 'phar://C:/'), + array('phar://C:', 'phar://C:/'), + ); + } + + /** + * @dataProvider provideGetRootTests + */ + public function testGetRoot($path, $root) + { + $this->assertSame($root, Path::getRoot($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testGetRootFailsIfInvalidPath() + { + Path::getRoot(array()); + } + + public function providePathTests() + { + return array( + // relative to absolute path + array('css/style.css', '/webmozart/puli', '/webmozart/puli/css/style.css'), + array('../css/style.css', '/webmozart/puli', '/webmozart/css/style.css'), + array('../../css/style.css', '/webmozart/puli', '/css/style.css'), + + // relative to root + array('css/style.css', '/', '/css/style.css'), + array('css/style.css', 'C:', 'C:/css/style.css'), + array('css/style.css', 'C:/', 'C:/css/style.css'), + + // same sub directories in different base directories + array('../../puli/css/style.css', '/webmozart/css', '/puli/css/style.css'), + + array('', '/webmozart/puli', '/webmozart/puli'), + array('..', '/webmozart/puli', '/webmozart'), + ); + } + + public function provideMakeAbsoluteTests() + { + return array_merge($this->providePathTests(), array( + // collapse dots + array('css/./style.css', '/webmozart/puli', '/webmozart/puli/css/style.css'), + array('css/../style.css', '/webmozart/puli', '/webmozart/puli/style.css'), + array('css/./../style.css', '/webmozart/puli', '/webmozart/puli/style.css'), + array('css/.././style.css', '/webmozart/puli', '/webmozart/puli/style.css'), + array('./css/style.css', '/webmozart/puli', '/webmozart/puli/css/style.css'), + + array('css\\.\\style.css', '\\webmozart\\puli', '/webmozart/puli/css/style.css'), + array('css\\..\\style.css', '\\webmozart\\puli', '/webmozart/puli/style.css'), + array('css\\.\\..\\style.css', '\\webmozart\\puli', '/webmozart/puli/style.css'), + array('css\\..\\.\\style.css', '\\webmozart\\puli', '/webmozart/puli/style.css'), + array('.\\css\\style.css', '\\webmozart\\puli', '/webmozart/puli/css/style.css'), + + // collapse dots on root + array('./css/style.css', '/', '/css/style.css'), + array('../css/style.css', '/', '/css/style.css'), + array('../css/./style.css', '/', '/css/style.css'), + array('../css/../style.css', '/', '/style.css'), + array('../css/./../style.css', '/', '/style.css'), + array('../css/.././style.css', '/', '/style.css'), + + array('.\\css\\style.css', '\\', '/css/style.css'), + array('..\\css\\style.css', '\\', '/css/style.css'), + array('..\\css\\.\\style.css', '\\', '/css/style.css'), + array('..\\css\\..\\style.css', '\\', '/style.css'), + array('..\\css\\.\\..\\style.css', '\\', '/style.css'), + array('..\\css\\..\\.\\style.css', '\\', '/style.css'), + + array('./css/style.css', 'C:/', 'C:/css/style.css'), + array('../css/style.css', 'C:/', 'C:/css/style.css'), + array('../css/./style.css', 'C:/', 'C:/css/style.css'), + array('../css/../style.css', 'C:/', 'C:/style.css'), + array('../css/./../style.css', 'C:/', 'C:/style.css'), + array('../css/.././style.css', 'C:/', 'C:/style.css'), + + array('.\\css\\style.css', 'C:\\', 'C:/css/style.css'), + array('..\\css\\style.css', 'C:\\', 'C:/css/style.css'), + array('..\\css\\.\\style.css', 'C:\\', 'C:/css/style.css'), + array('..\\css\\..\\style.css', 'C:\\', 'C:/style.css'), + array('..\\css\\.\\..\\style.css', 'C:\\', 'C:/style.css'), + array('..\\css\\..\\.\\style.css', 'C:\\', 'C:/style.css'), + + array('./css/style.css', 'phar:///', 'phar:///css/style.css'), + array('../css/style.css', 'phar:///', 'phar:///css/style.css'), + array('../css/./style.css', 'phar:///', 'phar:///css/style.css'), + array('../css/../style.css', 'phar:///', 'phar:///style.css'), + array('../css/./../style.css', 'phar:///', 'phar:///style.css'), + array('../css/.././style.css', 'phar:///', 'phar:///style.css'), + + array('./css/style.css', 'phar://C:/', 'phar://C:/css/style.css'), + array('../css/style.css', 'phar://C:/', 'phar://C:/css/style.css'), + array('../css/./style.css', 'phar://C:/', 'phar://C:/css/style.css'), + array('../css/../style.css', 'phar://C:/', 'phar://C:/style.css'), + array('../css/./../style.css', 'phar://C:/', 'phar://C:/style.css'), + array('../css/.././style.css', 'phar://C:/', 'phar://C:/style.css'), + + // absolute paths + array('/css/style.css', '/webmozart/puli', '/css/style.css'), + array('\\css\\style.css', '/webmozart/puli', '/css/style.css'), + array('C:/css/style.css', 'C:/webmozart/puli', 'C:/css/style.css'), + array('D:\\css\\style.css', 'D:/webmozart/puli', 'D:/css/style.css'), + )); + } + + /** + * @dataProvider provideMakeAbsoluteTests + */ + public function testMakeAbsolute($relativePath, $basePath, $absolutePath) + { + $this->assertSame($absolutePath, Path::makeAbsolute($relativePath, $basePath)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testMakeAbsoluteFailsIfInvalidPath() + { + Path::makeAbsolute(array(), '/webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a non-empty string. Got: array + */ + public function testMakeAbsoluteFailsIfInvalidBasePath() + { + Path::makeAbsolute('css/style.css', array()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path "webmozart/puli" is not an absolute path. + */ + public function testMakeAbsoluteFailsIfBasePathNotAbsolute() + { + Path::makeAbsolute('css/style.css', 'webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a non-empty string. Got: "" + */ + public function testMakeAbsoluteFailsIfBasePathEmpty() + { + Path::makeAbsolute('css/style.css', ''); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a non-empty string. Got: NULL + */ + public function testMakeAbsoluteFailsIfBasePathNull() + { + Path::makeAbsolute('css/style.css', null); + } + + public function provideAbsolutePathsWithDifferentRoots() + { + return array( + array('C:/css/style.css', '/webmozart/puli'), + array('C:/css/style.css', '\\webmozart\\puli'), + array('C:\\css\\style.css', '/webmozart/puli'), + array('C:\\css\\style.css', '\\webmozart\\puli'), + + array('/css/style.css', 'C:/webmozart/puli'), + array('/css/style.css', 'C:\\webmozart\\puli'), + array('\\css\\style.css', 'C:/webmozart/puli'), + array('\\css\\style.css', 'C:\\webmozart\\puli'), + + array('D:/css/style.css', 'C:/webmozart/puli'), + array('D:/css/style.css', 'C:\\webmozart\\puli'), + array('D:\\css\\style.css', 'C:/webmozart/puli'), + array('D:\\css\\style.css', 'C:\\webmozart\\puli'), + + array('phar:///css/style.css', '/webmozart/puli'), + array('/css/style.css', 'phar:///webmozart/puli'), + + array('phar://C:/css/style.css', 'C:/webmozart/puli'), + array('phar://C:/css/style.css', 'C:\\webmozart\\puli'), + array('phar://C:\\css\\style.css', 'C:/webmozart/puli'), + array('phar://C:\\css\\style.css', 'C:\\webmozart\\puli'), + ); + } + + /** + * @dataProvider provideAbsolutePathsWithDifferentRoots + */ + public function testMakeAbsoluteDoesNotFailIfDifferentRoot($basePath, $absolutePath) + { + // If a path in partition D: is passed, but $basePath is in partition + // C:, the path should be returned unchanged + $this->assertSame(Path::canonicalize($absolutePath), Path::makeAbsolute($absolutePath, $basePath)); + } + + public function provideMakeRelativeTests() + { + $paths = array_map(function (array $arguments) { + return array($arguments[2], $arguments[1], $arguments[0]); + }, $this->providePathTests()); + + return array_merge($paths, array( + array('/webmozart/puli/./css/style.css', '/webmozart/puli', 'css/style.css'), + array('/webmozart/puli/../css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/.././css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/./../css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/../../css/style.css', '/webmozart/puli', '../../css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/./puli', 'css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/../puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/./../puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/.././puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/../../puli', '../webmozart/puli/css/style.css'), + + // first argument shorter than second + array('/css', '/webmozart/puli', '../../css'), + + // second argument shorter than first + array('/webmozart/puli', '/css', '../webmozart/puli'), + + array('\\webmozart\\puli\\css\\style.css', '\\webmozart\\puli', 'css/style.css'), + array('\\webmozart\\css\\style.css', '\\webmozart\\puli', '../css/style.css'), + array('\\css\\style.css', '\\webmozart\\puli', '../../css/style.css'), + + array('C:/webmozart/puli/css/style.css', 'C:/webmozart/puli', 'css/style.css'), + array('C:/webmozart/css/style.css', 'C:/webmozart/puli', '../css/style.css'), + array('C:/css/style.css', 'C:/webmozart/puli', '../../css/style.css'), + + array('C:\\webmozart\\puli\\css\\style.css', 'C:\\webmozart\\puli', 'css/style.css'), + array('C:\\webmozart\\css\\style.css', 'C:\\webmozart\\puli', '../css/style.css'), + array('C:\\css\\style.css', 'C:\\webmozart\\puli', '../../css/style.css'), + + array('phar:///webmozart/puli/css/style.css', 'phar:///webmozart/puli', 'css/style.css'), + array('phar:///webmozart/css/style.css', 'phar:///webmozart/puli', '../css/style.css'), + array('phar:///css/style.css', 'phar:///webmozart/puli', '../../css/style.css'), + + array('phar://C:/webmozart/puli/css/style.css', 'phar://C:/webmozart/puli', 'css/style.css'), + array('phar://C:/webmozart/css/style.css', 'phar://C:/webmozart/puli', '../css/style.css'), + array('phar://C:/css/style.css', 'phar://C:/webmozart/puli', '../../css/style.css'), + + // already relative + already in root basepath + array('../style.css', '/', 'style.css'), + array('./style.css', '/', 'style.css'), + array('../../style.css', '/', 'style.css'), + array('..\\style.css', 'C:\\', 'style.css'), + array('.\\style.css', 'C:\\', 'style.css'), + array('..\\..\\style.css', 'C:\\', 'style.css'), + array('../style.css', 'C:/', 'style.css'), + array('./style.css', 'C:/', 'style.css'), + array('../../style.css', 'C:/', 'style.css'), + array('..\\style.css', '\\', 'style.css'), + array('.\\style.css', '\\', 'style.css'), + array('..\\..\\style.css', '\\', 'style.css'), + array('../style.css', 'phar:///', 'style.css'), + array('./style.css', 'phar:///', 'style.css'), + array('../../style.css', 'phar:///', 'style.css'), + array('..\\style.css', 'phar://C:\\', 'style.css'), + array('.\\style.css', 'phar://C:\\', 'style.css'), + array('..\\..\\style.css', 'phar://C:\\', 'style.css'), + + array('css/../style.css', '/', 'style.css'), + array('css/./style.css', '/', 'css/style.css'), + array('css\\..\\style.css', 'C:\\', 'style.css'), + array('css\\.\\style.css', 'C:\\', 'css/style.css'), + array('css/../style.css', 'C:/', 'style.css'), + array('css/./style.css', 'C:/', 'css/style.css'), + array('css\\..\\style.css', '\\', 'style.css'), + array('css\\.\\style.css', '\\', 'css/style.css'), + array('css/../style.css', 'phar:///', 'style.css'), + array('css/./style.css', 'phar:///', 'css/style.css'), + array('css\\..\\style.css', 'phar://C:\\', 'style.css'), + array('css\\.\\style.css', 'phar://C:\\', 'css/style.css'), + + // already relative + array('css/style.css', '/webmozart/puli', 'css/style.css'), + array('css\\style.css', '\\webmozart\\puli', 'css/style.css'), + + // both relative + array('css/style.css', 'webmozart/puli', '../../css/style.css'), + array('css\\style.css', 'webmozart\\puli', '../../css/style.css'), + + // relative to empty + array('css/style.css', '', 'css/style.css'), + array('css\\style.css', '', 'css/style.css'), + + // different slashes in path and base path + array('/webmozart/puli/css/style.css', '\\webmozart\\puli', 'css/style.css'), + array('\\webmozart\\puli\\css\\style.css', '/webmozart/puli', 'css/style.css'), + )); + } + + /** + * @dataProvider provideMakeRelativeTests + */ + public function testMakeRelative($absolutePath, $basePath, $relativePath) + { + $this->assertSame($relativePath, Path::makeRelative($absolutePath, $basePath)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testMakeRelativeFailsIfInvalidPath() + { + Path::makeRelative(array(), '/webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a string. Got: array + */ + public function testMakeRelativeFailsIfInvalidBasePath() + { + Path::makeRelative('/webmozart/puli/css/style.css', array()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The absolute path "/webmozart/puli/css/style.css" cannot be made relative to the relative path "webmozart/puli". You should provide an absolute base path instead. + */ + public function testMakeRelativeFailsIfAbsolutePathAndBasePathNotAbsolute() + { + Path::makeRelative('/webmozart/puli/css/style.css', 'webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The absolute path "/webmozart/puli/css/style.css" cannot be made relative to the relative path "". You should provide an absolute base path instead. + */ + public function testMakeRelativeFailsIfAbsolutePathAndBasePathEmpty() + { + Path::makeRelative('/webmozart/puli/css/style.css', ''); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a string. Got: NULL + */ + public function testMakeRelativeFailsIfBasePathNull() + { + Path::makeRelative('/webmozart/puli/css/style.css', null); + } + + /** + * @dataProvider provideAbsolutePathsWithDifferentRoots + * @expectedException \InvalidArgumentException + */ + public function testMakeRelativeFailsIfDifferentRoot($absolutePath, $basePath) + { + Path::makeRelative($absolutePath, $basePath); + } + + public function provideIsLocalTests() + { + return array( + array('/bg.png', true), + array('bg.png', true), + array('http://example.com/bg.png', false), + array('http://example.com', false), + array('', false), + ); + } + + /** + * @dataProvider provideIsLocalTests + */ + public function testIsLocal($path, $isLocal) + { + $this->assertSame($isLocal, Path::isLocal($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testIsLocalFailsIfInvalidPath() + { + Path::isLocal(array()); + } + + public function provideGetLongestCommonBasePathTests() + { + return array( + // same paths + array(array('/base/path', '/base/path'), '/base/path'), + array(array('C:/base/path', 'C:/base/path'), 'C:/base/path'), + array(array('C:\\base\\path', 'C:\\base\\path'), 'C:/base/path'), + array(array('C:/base/path', 'C:\\base\\path'), 'C:/base/path'), + array(array('phar:///base/path', 'phar:///base/path'), 'phar:///base/path'), + array(array('phar://C:/base/path', 'phar://C:/base/path'), 'phar://C:/base/path'), + + // trailing slash + array(array('/base/path/', '/base/path'), '/base/path'), + array(array('C:/base/path/', 'C:/base/path'), 'C:/base/path'), + array(array('C:\\base\\path\\', 'C:\\base\\path'), 'C:/base/path'), + array(array('C:/base/path/', 'C:\\base\\path'), 'C:/base/path'), + array(array('phar:///base/path/', 'phar:///base/path'), 'phar:///base/path'), + array(array('phar://C:/base/path/', 'phar://C:/base/path'), 'phar://C:/base/path'), + + array(array('/base/path', '/base/path/'), '/base/path'), + array(array('C:/base/path', 'C:/base/path/'), 'C:/base/path'), + array(array('C:\\base\\path', 'C:\\base\\path\\'), 'C:/base/path'), + array(array('C:/base/path', 'C:\\base\\path\\'), 'C:/base/path'), + array(array('phar:///base/path', 'phar:///base/path/'), 'phar:///base/path'), + array(array('phar://C:/base/path', 'phar://C:/base/path/'), 'phar://C:/base/path'), + + // first in second + array(array('/base/path/sub', '/base/path'), '/base/path'), + array(array('C:/base/path/sub', 'C:/base/path'), 'C:/base/path'), + array(array('C:\\base\\path\\sub', 'C:\\base\\path'), 'C:/base/path'), + array(array('C:/base/path/sub', 'C:\\base\\path'), 'C:/base/path'), + array(array('phar:///base/path/sub', 'phar:///base/path'), 'phar:///base/path'), + array(array('phar://C:/base/path/sub', 'phar://C:/base/path'), 'phar://C:/base/path'), + + // second in first + array(array('/base/path', '/base/path/sub'), '/base/path'), + array(array('C:/base/path', 'C:/base/path/sub'), 'C:/base/path'), + array(array('C:\\base\\path', 'C:\\base\\path\\sub'), 'C:/base/path'), + array(array('C:/base/path', 'C:\\base\\path\\sub'), 'C:/base/path'), + array(array('phar:///base/path', 'phar:///base/path/sub'), 'phar:///base/path'), + array(array('phar://C:/base/path', 'phar://C:/base/path/sub'), 'phar://C:/base/path'), + + // first is prefix + array(array('/base/path/di', '/base/path/dir'), '/base/path'), + array(array('C:/base/path/di', 'C:/base/path/dir'), 'C:/base/path'), + array(array('C:\\base\\path\\di', 'C:\\base\\path\\dir'), 'C:/base/path'), + array(array('C:/base/path/di', 'C:\\base\\path\\dir'), 'C:/base/path'), + array(array('phar:///base/path/di', 'phar:///base/path/dir'), 'phar:///base/path'), + array(array('phar://C:/base/path/di', 'phar://C:/base/path/dir'), 'phar://C:/base/path'), + + // second is prefix + array(array('/base/path/dir', '/base/path/di'), '/base/path'), + array(array('C:/base/path/dir', 'C:/base/path/di'), 'C:/base/path'), + array(array('C:\\base\\path\\dir', 'C:\\base\\path\\di'), 'C:/base/path'), + array(array('C:/base/path/dir', 'C:\\base\\path\\di'), 'C:/base/path'), + array(array('phar:///base/path/dir', 'phar:///base/path/di'), 'phar:///base/path'), + array(array('phar://C:/base/path/dir', 'phar://C:/base/path/di'), 'phar://C:/base/path'), + + // root is common base path + array(array('/first', '/second'), '/'), + array(array('C:/first', 'C:/second'), 'C:/'), + array(array('C:\\first', 'C:\\second'), 'C:/'), + array(array('C:/first', 'C:\\second'), 'C:/'), + array(array('phar:///first', 'phar:///second'), 'phar:///'), + array(array('phar://C:/first', 'phar://C:/second'), 'phar://C:/'), + + // windows vs unix + array(array('/base/path', 'C:/base/path'), null), + array(array('C:/base/path', '/base/path'), null), + array(array('/base/path', 'C:\\base\\path'), null), + array(array('phar:///base/path', 'phar://C:/base/path'), null), + + // different partitions + array(array('C:/base/path', 'D:/base/path'), null), + array(array('C:/base/path', 'D:\\base\\path'), null), + array(array('C:\\base\\path', 'D:\\base\\path'), null), + array(array('phar://C:/base/path', 'phar://D:/base/path'), null), + + // three paths + array(array('/base/path/foo', '/base/path', '/base/path/bar'), '/base/path'), + array(array('C:/base/path/foo', 'C:/base/path', 'C:/base/path/bar'), 'C:/base/path'), + array(array('C:\\base\\path\\foo', 'C:\\base\\path', 'C:\\base\\path\\bar'), 'C:/base/path'), + array(array('C:/base/path//foo', 'C:/base/path', 'C:\\base\\path\\bar'), 'C:/base/path'), + array(array('phar:///base/path/foo', 'phar:///base/path', 'phar:///base/path/bar'), 'phar:///base/path'), + array(array('phar://C:/base/path/foo', 'phar://C:/base/path', 'phar://C:/base/path/bar'), 'phar://C:/base/path'), + + // three paths with root + array(array('/base/path/foo', '/', '/base/path/bar'), '/'), + array(array('C:/base/path/foo', 'C:/', 'C:/base/path/bar'), 'C:/'), + array(array('C:\\base\\path\\foo', 'C:\\', 'C:\\base\\path\\bar'), 'C:/'), + array(array('C:/base/path//foo', 'C:/', 'C:\\base\\path\\bar'), 'C:/'), + array(array('phar:///base/path/foo', 'phar:///', 'phar:///base/path/bar'), 'phar:///'), + array(array('phar://C:/base/path/foo', 'phar://C:/', 'phar://C:/base/path/bar'), 'phar://C:/'), + + // three paths, different roots + array(array('/base/path/foo', 'C:/base/path', '/base/path/bar'), null), + array(array('/base/path/foo', 'C:\\base\\path', '/base/path/bar'), null), + array(array('C:/base/path/foo', 'D:/base/path', 'C:/base/path/bar'), null), + array(array('C:\\base\\path\\foo', 'D:\\base\\path', 'C:\\base\\path\\bar'), null), + array(array('C:/base/path//foo', 'D:/base/path', 'C:\\base\\path\\bar'), null), + array(array('phar:///base/path/foo', 'phar://C:/base/path', 'phar:///base/path/bar'), null), + array(array('phar://C:/base/path/foo', 'phar://D:/base/path', 'phar://C:/base/path/bar'), null), + + // only one path + array(array('/base/path'), '/base/path'), + array(array('C:/base/path'), 'C:/base/path'), + array(array('C:\\base\\path'), 'C:/base/path'), + array(array('phar:///base/path'), 'phar:///base/path'), + array(array('phar://C:/base/path'), 'phar://C:/base/path'), + ); + } + + /** + * @dataProvider provideGetLongestCommonBasePathTests + */ + public function testGetLongestCommonBasePath(array $paths, $basePath) + { + $this->assertSame($basePath, Path::getLongestCommonBasePath($paths)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The paths must be strings. Got: array + */ + public function testGetLongestCommonBasePathFailsIfInvalidPath() + { + Path::getLongestCommonBasePath(array(array())); + } + + public function provideIsBasePathTests() + { + return array( + // same paths + array('/base/path', '/base/path', true), + array('C:/base/path', 'C:/base/path', true), + array('C:\\base\\path', 'C:\\base\\path', true), + array('C:/base/path', 'C:\\base\\path', true), + array('phar:///base/path', 'phar:///base/path', true), + array('phar://C:/base/path', 'phar://C:/base/path', true), + + // trailing slash + array('/base/path/', '/base/path', true), + array('C:/base/path/', 'C:/base/path', true), + array('C:\\base\\path\\', 'C:\\base\\path', true), + array('C:/base/path/', 'C:\\base\\path', true), + array('phar:///base/path/', 'phar:///base/path', true), + array('phar://C:/base/path/', 'phar://C:/base/path', true), + + array('/base/path', '/base/path/', true), + array('C:/base/path', 'C:/base/path/', true), + array('C:\\base\\path', 'C:\\base\\path\\', true), + array('C:/base/path', 'C:\\base\\path\\', true), + array('phar:///base/path', 'phar:///base/path/', true), + array('phar://C:/base/path', 'phar://C:/base/path/', true), + + // first in second + array('/base/path/sub', '/base/path', false), + array('C:/base/path/sub', 'C:/base/path', false), + array('C:\\base\\path\\sub', 'C:\\base\\path', false), + array('C:/base/path/sub', 'C:\\base\\path', false), + array('phar:///base/path/sub', 'phar:///base/path', false), + array('phar://C:/base/path/sub', 'phar://C:/base/path', false), + + // second in first + array('/base/path', '/base/path/sub', true), + array('C:/base/path', 'C:/base/path/sub', true), + array('C:\\base\\path', 'C:\\base\\path\\sub', true), + array('C:/base/path', 'C:\\base\\path\\sub', true), + array('phar:///base/path', 'phar:///base/path/sub', true), + array('phar://C:/base/path', 'phar://C:/base/path/sub', true), + + // first is prefix + array('/base/path/di', '/base/path/dir', false), + array('C:/base/path/di', 'C:/base/path/dir', false), + array('C:\\base\\path\\di', 'C:\\base\\path\\dir', false), + array('C:/base/path/di', 'C:\\base\\path\\dir', false), + array('phar:///base/path/di', 'phar:///base/path/dir', false), + array('phar://C:/base/path/di', 'phar://C:/base/path/dir', false), + + // second is prefix + array('/base/path/dir', '/base/path/di', false), + array('C:/base/path/dir', 'C:/base/path/di', false), + array('C:\\base\\path\\dir', 'C:\\base\\path\\di', false), + array('C:/base/path/dir', 'C:\\base\\path\\di', false), + array('phar:///base/path/dir', 'phar:///base/path/di', false), + array('phar://C:/base/path/dir', 'phar://C:/base/path/di', false), + + // root + array('/', '/second', true), + array('C:/', 'C:/second', true), + array('C:', 'C:/second', true), + array('C:\\', 'C:\\second', true), + array('C:/', 'C:\\second', true), + array('phar:///', 'phar:///second', true), + array('phar://C:/', 'phar://C:/second', true), + + // windows vs unix + array('/base/path', 'C:/base/path', false), + array('C:/base/path', '/base/path', false), + array('/base/path', 'C:\\base\\path', false), + array('/base/path', 'phar:///base/path', false), + array('phar:///base/path', 'phar://C:/base/path', false), + + // different partitions + array('C:/base/path', 'D:/base/path', false), + array('C:/base/path', 'D:\\base\\path', false), + array('C:\\base\\path', 'D:\\base\\path', false), + array('C:/base/path', 'phar://C:/base/path', false), + array('phar://C:/base/path', 'phar://D:/base/path', false), + ); + } + + /** + * @dataProvider provideIsBasePathTests + */ + public function testIsBasePath($path, $ofPath, $result) + { + $this->assertSame($result, Path::isBasePath($path, $ofPath)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a string. Got: array + */ + public function testIsBasePathFailsIfInvalidBasePath() + { + Path::isBasePath(array(), '/base/path'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testIsBasePathFailsIfInvalidPath() + { + Path::isBasePath('/base/path', array()); + } + + public function provideJoinTests() + { + return array( + array('', '', ''), + array('/path/to/test', '', '/path/to/test'), + array('/path/to//test', '', '/path/to/test'), + array('', '/path/to/test', '/path/to/test'), + array('', '/path/to//test', '/path/to/test'), + + array('/path/to/test', 'subdir', '/path/to/test/subdir'), + array('/path/to/test/', 'subdir', '/path/to/test/subdir'), + array('/path/to/test', '/subdir', '/path/to/test/subdir'), + array('/path/to/test/', '/subdir', '/path/to/test/subdir'), + array('/path/to/test', './subdir', '/path/to/test/subdir'), + array('/path/to/test/', './subdir', '/path/to/test/subdir'), + array('/path/to/test/', '../parentdir', '/path/to/parentdir'), + array('/path/to/test', '../parentdir', '/path/to/parentdir'), + array('path/to/test/', '/subdir', 'path/to/test/subdir'), + array('path/to/test', '/subdir', 'path/to/test/subdir'), + array('../path/to/test', '/subdir', '../path/to/test/subdir'), + array('path', '../../subdir', '../subdir'), + array('/path', '../../subdir', '/subdir'), + array('../path', '../../subdir', '../../subdir'), + + array(array('/path/to/test', 'subdir'), '', '/path/to/test/subdir'), + array(array('/path/to/test', '/subdir'), '', '/path/to/test/subdir'), + array(array('/path/to/test/', 'subdir'), '', '/path/to/test/subdir'), + array(array('/path/to/test/', '/subdir'), '', '/path/to/test/subdir'), + + array(array('/path'), '', '/path'), + array(array('/path', 'to', '/test'), '', '/path/to/test'), + array(array('/path', '', '/test'), '', '/path/test'), + array(array('path', 'to', 'test'), '', 'path/to/test'), + array(array(), '', ''), + + array('base/path', 'to/test', 'base/path/to/test'), + + array('C:\\path\\to\\test', 'subdir', 'C:/path/to/test/subdir'), + array('C:\\path\\to\\test\\', 'subdir', 'C:/path/to/test/subdir'), + array('C:\\path\\to\\test', '/subdir', 'C:/path/to/test/subdir'), + array('C:\\path\\to\\test\\', '/subdir', 'C:/path/to/test/subdir'), + + array('/', 'subdir', '/subdir'), + array('/', '/subdir', '/subdir'), + array('C:/', 'subdir', 'C:/subdir'), + array('C:/', '/subdir', 'C:/subdir'), + array('C:\\', 'subdir', 'C:/subdir'), + array('C:\\', '/subdir', 'C:/subdir'), + array('C:', 'subdir', 'C:/subdir'), + array('C:', '/subdir', 'C:/subdir'), + + array('phar://', '/path/to/test', 'phar:///path/to/test'), + array('phar:///', '/path/to/test', 'phar:///path/to/test'), + array('phar:///path/to/test', 'subdir', 'phar:///path/to/test/subdir'), + array('phar:///path/to/test', 'subdir/', 'phar:///path/to/test/subdir'), + array('phar:///path/to/test', '/subdir', 'phar:///path/to/test/subdir'), + array('phar:///path/to/test/', 'subdir', 'phar:///path/to/test/subdir'), + array('phar:///path/to/test/', '/subdir', 'phar:///path/to/test/subdir'), + + array('phar://', 'C:/path/to/test', 'phar://C:/path/to/test'), + array('phar://', 'C:\\path\\to\\test', 'phar://C:/path/to/test'), + array('phar://C:/path/to/test', 'subdir', 'phar://C:/path/to/test/subdir'), + array('phar://C:/path/to/test', 'subdir/', 'phar://C:/path/to/test/subdir'), + array('phar://C:/path/to/test', '/subdir', 'phar://C:/path/to/test/subdir'), + array('phar://C:/path/to/test/', 'subdir', 'phar://C:/path/to/test/subdir'), + array('phar://C:/path/to/test/', '/subdir', 'phar://C:/path/to/test/subdir'), + array('phar://C:', 'path/to/test', 'phar://C:/path/to/test'), + array('phar://C:', '/path/to/test', 'phar://C:/path/to/test'), + array('phar://C:/', 'path/to/test', 'phar://C:/path/to/test'), + array('phar://C:/', '/path/to/test', 'phar://C:/path/to/test'), + ); + } + + /** + * @dataProvider provideJoinTests + */ + public function testJoin($path1, $path2, $result) + { + $this->assertSame($result, Path::join($path1, $path2)); + } + + public function testJoinVarArgs() + { + $this->assertSame('/path', Path::join('/path')); + $this->assertSame('/path/to', Path::join('/path', 'to')); + $this->assertSame('/path/to/test', Path::join('/path', 'to', '/test')); + $this->assertSame('/path/to/test/subdir', Path::join('/path', 'to', '/test', 'subdir/')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The paths must be strings. Got: array + */ + public function testJoinFailsIfInvalidPath() + { + Path::join('/path', array()); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Your environment or operation system isn't supported + */ + public function testGetHomeDirectoryFailsIfNotSupportedOperationSystem() + { + putenv('HOME='); + + Path::getHomeDirectory(); + } + + public function testGetHomeDirectoryForUnix() + { + $this->assertEquals('/home/webmozart', Path::getHomeDirectory()); + } + + public function testGetHomeDirectoryForWindows() + { + putenv('HOME='); + putenv('HOMEDRIVE=C:'); + putenv('HOMEPATH=/users/webmozart'); + + $this->assertEquals('C:/users/webmozart', Path::getHomeDirectory()); + } + + public function testNormalize() + { + $this->assertSame('C:/Foo/Bar/test', Path::normalize('C:\\Foo\\Bar/test')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testNormalizeFailsIfNoString() + { + Path::normalize(true); + } +} diff --git a/lib/composer/webmozart/path-util/tests/UrlTest.php b/lib/composer/webmozart/path-util/tests/UrlTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ae7816abe8a7abe0319c27299a5ffb0535c00b40 --- /dev/null +++ b/lib/composer/webmozart/path-util/tests/UrlTest.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\PathUtil\Tests; + +use Webmozart\PathUtil\Url; + +/** + * @since 2.3 + * + * @author Bernhard Schussek + * @author Claudio Zizza + */ +class UrlTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provideMakeRelativeTests + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelative($absolutePath, $basePath, $relativePath) + { + $host = 'http://example.com'; + + $relative = Url::makeRelative($host.$absolutePath, $host.$basePath); + $this->assertSame($relativePath, $relative); + $relative = Url::makeRelative($absolutePath, $host.$basePath); + $this->assertSame($relativePath, $relative); + } + + /** + * @dataProvider provideMakeRelativeIsAlreadyRelativeTests + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeIsAlreadyRelative($absolutePath, $basePath, $relativePath) + { + $host = 'http://example.com'; + + $relative = Url::makeRelative($absolutePath, $host.$basePath); + $this->assertSame($relativePath, $relative); + } + + /** + * @dataProvider provideMakeRelativeTests + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeWithFullUrl($absolutePath, $basePath, $relativePath) + { + $host = 'ftp://user:password@example.com:8080'; + + $relative = Url::makeRelative($host.$absolutePath, $host.$basePath); + $this->assertSame($relativePath, $relative); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The URL must be a string. Got: array + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfInvalidUrl() + { + Url::makeRelative(array(), 'http://example.com/webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base URL must be a string. Got: array + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfInvalidBaseUrl() + { + Url::makeRelative('http://example.com/webmozart/puli/css/style.css', array()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage "webmozart/puli" is not an absolute Url. + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfBaseUrlNoUrl() + { + Url::makeRelative('http://example.com/webmozart/puli/css/style.css', 'webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage "" is not an absolute Url. + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfBaseUrlEmpty() + { + Url::makeRelative('http://example.com/webmozart/puli/css/style.css', ''); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base URL must be a string. Got: NULL + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfBaseUrlNull() + { + Url::makeRelative('http://example.com/webmozart/puli/css/style.css', null); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The URL "http://example.com" cannot be made relative to "http://example2.com" since + * their host names are different. + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfDifferentDomains() + { + Url::makeRelative('http://example.com/webmozart/puli/css/style.css', 'http://example2.com/webmozart/puli'); + } + + public function provideMakeRelativeTests() + { + return array( + + array('/webmozart/puli/css/style.css', '/webmozart/puli', 'css/style.css'), + array('/webmozart/puli/css/style.css?key=value&key2=value', '/webmozart/puli', 'css/style.css?key=value&key2=value'), + array('/webmozart/puli/css/style.css?key[]=value&key[]=value', '/webmozart/puli', 'css/style.css?key[]=value&key[]=value'), + array('/webmozart/css/style.css', '/webmozart/puli', '../css/style.css'), + array('/css/style.css', '/webmozart/puli', '../../css/style.css'), + array('/', '/', ''), + + // relative to root + array('/css/style.css', '/', 'css/style.css'), + + // same sub directories in different base directories + array('/puli/css/style.css', '/webmozart/css', '../../puli/css/style.css'), + + array('/webmozart/puli/./css/style.css', '/webmozart/puli', 'css/style.css'), + array('/webmozart/puli/../css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/.././css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/./../css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/../../css/style.css', '/webmozart/puli', '../../css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/./puli', 'css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/../puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/./../puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/.././puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/../../puli', '../webmozart/puli/css/style.css'), + + // first argument shorter than second + array('/css', '/webmozart/puli', '../../css'), + + // second argument shorter than first + array('/webmozart/puli', '/css', '../webmozart/puli'), + + array('', '', ''), + ); + } + + public function provideMakeRelativeIsAlreadyRelativeTests() + { + return array( + array('css/style.css', '/webmozart/puli', 'css/style.css'), + array('css/style.css', '', 'css/style.css'), + array('css/../style.css', '', 'style.css'), + array('css/./style.css', '', 'css/style.css'), + array('../style.css', '/', 'style.css'), + array('./style.css', '/', 'style.css'), + array('../../style.css', '/', 'style.css'), + array('../../style.css', '', 'style.css'), + array('./style.css', '', 'style.css'), + array('../style.css', '', 'style.css'), + array('./../style.css', '', 'style.css'), + array('css/./../style.css', '', 'style.css'), + array('css//style.css', '', 'css/style.css'), + ); + } +}