diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f495f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +zsh_history +.idea +vendor +composer.lock diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..032164b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,65 @@ +FROM ubuntu:18.04 +ENV DEBIAN_FRONTEND noninteractive + +RUN apt-get update && apt-get install -y software-properties-common locales + +RUN locale-gen en_US.UTF-8 +ENV LANG=en_US.UTF-8 + +RUN apt-add-repository ppa:git-core/ppa -y && \ + apt-get update && \ + apt-get install -y --force-yes \ + nano \ + git \ + curl \ + vim \ + wget \ + zsh \ + iputils-ping \ + gnupg-agent \ + rsync + +RUN useradd -ms /bin/zsh gowork + +WORKDIR /home/gowork + +RUN apt-add-repository -y ppa:ondrej/php && apt-get update && apt-get install -y --force-yes \ + php7.3-curl \ + php7.3-gd \ + php7.3-intl \ + php7.3-mysql \ + php7.3-xml \ + php7.3-mbstring \ + php7.3-bcmath \ + php7.3-mongo \ + php7.3-zip \ + php7.3-opcache \ + php7.3-bz2 \ + php7.3-gmp \ + php7.3-redis \ + php7.3-cli + +RUN curl -sS https://getcomposer.org/installer | php && \ + mv composer.phar /usr/local/bin/composer + +USER gowork + +RUN git clone git://github.com/robbyrussell/oh-my-zsh.git /home/gowork/.oh-my-zsh \ + && cp /home/gowork/.oh-my-zsh/templates/zshrc.zsh-template /home/gowork/.zshrc \ + && sed -i.bak 's/robbyrussell/nebirhos/' /home/gowork/.zshrc + +RUN echo "plugins=(git symfony symfony2 composer yarn)" >> /home/gowork/.zshrc + +# from https://hub.docker.com/r/themattrix/develop/~/dockerfile/ +RUN git clone https://github.com/junegunn/fzf.git /home/gowork/.fzf \ + && (cd /home/gowork/.fzf) \ + && (yes | /home/gowork/.fzf/install) + +RUN mkdir /home/gowork/.ssh && \ + echo "StrictHostKeyChecking no\n" >> /home/gowork/.ssh/config + +RUN echo "export FZF_DEFAULT_OPTS='--no-height --no-reverse'" >> /home/gowork/.zshrc + +RUN mkdir /home/gowork/current +RUN touch /home/gowork/.zsh_history +WORKDIR /home/gowork/current diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ebd56d3 --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "name": "gowork/phpstan-gw-values", + "description": "GW Values extensions for PHPStan", + "license": ["MIT"], + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": "~7.1", + "phpstan/phpstan": "^0.10", + "nikic/php-parser": "^4.0" + }, + "require-dev": { + "gowork/values": "^0.2", + "phpunit/phpunit": "^7.0" + }, + "autoload": { + "psr-4": { + "GW\\PHPStan\\GwValues\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "tests\\GW\\PHPStan\\GwValues\\": "tests/" + } + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a63b988 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: "2" + +services: + develop: + build: . + image: values_console + volumes: + - ./:/home/gowork/current + - ~/.ssh/id_rsa:/home/gowork/.ssh/id_rsa + - ~/.ssh/id_rsa.pub:/home/gowork/.ssh/id_rsa.pub + - ./zsh_history:/home/gowork/.zsh_history + - ~/.gitconfig:/home/gowork/.gitconfig + - ~/.ssh/known_hosts:/home/gowork/.ssh/known_hosts diff --git a/extension.neon b/extension.neon new file mode 100644 index 0000000..fd8e77a --- /dev/null +++ b/extension.neon @@ -0,0 +1,57 @@ +services: + - + class: GW\PHPStan\GwValues\ArrayValueDynamicReturn + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: GW\PHPStan\GwValues\ArrayValueMapDynamicReturn + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: GW\PHPStan\GwValues\ArrayValueToArrayDynamicReturn + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: GW\PHPStan\GwValues\ArrayValuePopDynamicReturn + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: GW\PHPStan\GwValues\ArrayValueJoinDynamicReturn + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: GW\PHPStan\GwValues\ArrayValueReduceDynamicReturn + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: GW\PHPStan\GwValues\ArrayValueToAssocValueDynamicReturn + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: GW\PHPStan\GwValues\ArrayValueToStringValueDynamicReturn + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: GW\PHPStan\GwValues\ArrayValueChunkDynamicReturn + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: GW\PHPStan\GwValues\AssocValueToArrayValueDynamicReturn + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: GW\PHPStan\GwValues\WrapDynamicReturn + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - + class: GW\PHPStan\GwValues\ArrayValueCallbackRule + tags: + - phpstan.rules.rule + - + class: GW\PHPStan\GwValues\ArrayValuePushRule + tags: + - phpstan.rules.rule + - + class: GW\PHPStan\GwValues\ArrayReduceCallbackRule + tags: + - phpstan.rules.rule diff --git a/src/ArrayReduceCallbackRule.php b/src/ArrayReduceCallbackRule.php new file mode 100644 index 0000000..027e373 --- /dev/null +++ b/src/ArrayReduceCallbackRule.php @@ -0,0 +1,88 @@ +ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return Node\Expr\MethodCall::class; + } + + /** + * @return string[] errors + */ + public function processNode(Node $methodCall, Scope $scope): array + { + if (!$methodCall instanceof Node\Expr\MethodCall) { + return []; + } + + $identifier = $methodCall->name; + + if (!$identifier instanceof Node\Identifier) { + return []; + } + + if ($methodCall->name->name !== 'reduce') { + return []; + } + + $valueType = TypeHelper::searchArrayValueType($scope->getType($methodCall->var)); + + if (!$valueType instanceof ArrayValueType) { + return []; + } + + $errors = []; + $parameterIndex = 1; + + $attribute = $methodCall->args[0]->value; + $callableType = $scope->getType($attribute); + + if ($callableType instanceof ClosureType) { + /** @var NativeParameterReflection[] $parameters */ + $parameters = $callableType->getParameters(); + $parameter = $parameters[$parameterIndex]; + $parameterType = $parameters[$parameterIndex]->getType(); + $argumentValueType = $valueType->innerType(); + + $accepts = $this->ruleLevelHelper->accepts( + $parameterType, + $argumentValueType, + $scope->isDeclareStrictTypes() + ); + + if (!$accepts) { + $errors[] = \sprintf( + 'Parameter #%d %s of method ' . $methodCall->name->name . ' callback expects %s, %s given.', + $parameterIndex + 1, + sprintf('%s$%s', $parameter->isVariadic() ? '...' : '', $parameter->getName()), + $parameterType->describe(VerbosityLevel::typeOnly()), + $argumentValueType->describe( + $parameterType->isCallable()->yes() ? VerbosityLevel::value() + : VerbosityLevel::typeOnly() + ) + ); + } + } + + return $errors; + } +} diff --git a/src/ArrayValueCallbackRule.php b/src/ArrayValueCallbackRule.php new file mode 100644 index 0000000..e7c0add --- /dev/null +++ b/src/ArrayValueCallbackRule.php @@ -0,0 +1,103 @@ +ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return Node\Expr\MethodCall::class; + } + + /** + * @return string[] errors + */ + public function processNode(Node $methodCall, Scope $scope): array + { + if (!$methodCall instanceof Node\Expr\MethodCall) { + return []; + } + + $identifier = $methodCall->name; + + if (!$identifier instanceof Node\Identifier) { + return []; + } + + if (!\in_array( + $identifier->name, + ['map', 'filter', 'each', 'sort', 'unique', 'any', 'every'], + true + )) { + return []; + } + + $numberOfParameters = 1; + + if (\in_array($methodCall->name->name, ['unique', 'sort'], true)) { + $numberOfParameters = 2; + } + + $valueType = TypeHelper::searchArrayValueType($scope->getType($methodCall->var)); + + if (!$valueType instanceof ArrayValueType) { + return []; + } + + $errors = []; + + for ($parameterIndex = 0; $parameterIndex < $numberOfParameters; $parameterIndex++) { + if (!isset($methodCall->args[$parameterIndex])) { + continue; + } + + $attribute = $methodCall->args[$parameterIndex]->value; + $callableType = $scope->getType($attribute); + + if ($callableType instanceof ClosureType) { + /** @var NativeParameterReflection[] $parameters */ + $parameters = $callableType->getParameters(); + $parameter = $parameters[$parameterIndex]; + $parameterType = $parameters[$parameterIndex]->getType(); + $argumentValueType = $valueType->innerType(); + + $accepts = $this->ruleLevelHelper->accepts( + $parameterType, + $argumentValueType, + $scope->isDeclareStrictTypes() + ); + + if (!$accepts) { + $errors[] = \sprintf( + 'Parameter #%d %s of method ' . $methodCall->name->name . ' callback expects %s, %s given.', + $parameterIndex + 1, + sprintf('%s$%s', $parameter->isVariadic() ? '...' : '', $parameter->getName()), + $parameterType->describe(VerbosityLevel::typeOnly()), + $argumentValueType->describe( + $parameterType->isCallable()->yes() ? VerbosityLevel::value() + : VerbosityLevel::typeOnly() + ) + ); + } + } + } + + return $errors; + } +} diff --git a/src/ArrayValueChunkDynamicReturn.php b/src/ArrayValueChunkDynamicReturn.php new file mode 100644 index 0000000..b545d1b --- /dev/null +++ b/src/ArrayValueChunkDynamicReturn.php @@ -0,0 +1,42 @@ +getName() === 'chunk'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $valueType = TypeHelper::searchArrayValueType($scope->getType($methodCall->var)); + + if (\count($methodCall->args) === 0) { + return new ArrayValueType(new MixedType()); + } + + $innerType = new ArrayType(new IntegerType(), $valueType ? $valueType->innerType() : new MixedType()); + + return new ArrayValueType($innerType); + } +} diff --git a/src/ArrayValueDynamicReturn.php b/src/ArrayValueDynamicReturn.php new file mode 100644 index 0000000..12b5025 --- /dev/null +++ b/src/ArrayValueDynamicReturn.php @@ -0,0 +1,38 @@ +getName(), ['map', 'shuffle'], true)) { + return false; + } + + return $methodReflection->getDeclaringClass()->isSubclassOf(ArrayValue::class); + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + /** @var ArrayValueType $type */ + $valueType = $scope->getType($methodCall->var); + + return $valueType; + } +} diff --git a/src/ArrayValueJoinDynamicReturn.php b/src/ArrayValueJoinDynamicReturn.php new file mode 100644 index 0000000..523fdab --- /dev/null +++ b/src/ArrayValueJoinDynamicReturn.php @@ -0,0 +1,42 @@ +getName() === 'join'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $type = TypeHelper::searchArrayValueType($scope->getType($methodCall->var)); + + $parameter = $methodCall->args[0]->value; + + /** @var ArrayValueType $parameterType */ + $parameterType = $scope->getType($parameter); + + return new ArrayValueType( + new UnionType([$type ? $type->innerType() : new MixedType(), $parameterType->innerType()]) + ); + } +} diff --git a/src/ArrayValueMapDynamicReturn.php b/src/ArrayValueMapDynamicReturn.php new file mode 100644 index 0000000..d931f11 --- /dev/null +++ b/src/ArrayValueMapDynamicReturn.php @@ -0,0 +1,71 @@ +getDeclaringClass()->getName() === ArrayValue::class + || $methodReflection->getDeclaringClass()->isSubclassOf( + ArrayValue::class + )) + && \in_array( + $methodReflection->getName(), + ['map', 'flatMap'], + true + ); + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $valueType = TypeHelper::searchArrayValueType($scope->getType($methodCall->var)); + + if (\count($methodCall->args) === 0) { + return new ArrayValueType(new MixedType()); + } + + $innerType = new MixedType(); + $firstAttribute = $methodCall->args[0]->value; + $callableType = $scope->getType($firstAttribute); + + if ($callableType instanceof ClosureType) { + $innerType = $callableType->getReturnType(); + } + + if ($callableType instanceof CallableType) { + $innerType = $callableType->getReturnType() ?? new MixedType(); + } + + if ($methodReflection->getName() === 'flatMap' && !$innerType instanceof MixedType) { + if (!$innerType instanceof ArrayType) { + throw new ShouldNotHappenException('flatMap callback must return array'); + } + + $innerType = $innerType->getItemType(); + } + + return new ArrayValueType($innerType, $methodReflection->getDeclaringClass()->getName()); + } +} diff --git a/src/ArrayValuePopDynamicReturn.php b/src/ArrayValuePopDynamicReturn.php new file mode 100644 index 0000000..92932d8 --- /dev/null +++ b/src/ArrayValuePopDynamicReturn.php @@ -0,0 +1,46 @@ +getName(), ['pop', 'offsetGet', 'first', 'last', 'find', 'findLast'], true); + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $type = TypeHelper::searchArrayValueType($scope->getType($methodCall->var)); + + if (!$type) { + return new MixedType(); + } + + $innerType = $type->innerType(); + + if ($innerType instanceof UnionType) { + return new UnionType(\array_merge([new NullType()], $innerType->getTypes())); + } + + return new UnionType([new NullType(), $innerType]); + } +} diff --git a/src/ArrayValuePushRule.php b/src/ArrayValuePushRule.php new file mode 100644 index 0000000..4bfaaf5 --- /dev/null +++ b/src/ArrayValuePushRule.php @@ -0,0 +1,76 @@ +ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return Node\Expr\MethodCall::class; + } + + /** + * @return string[] errors + */ + public function processNode(Node $methodCall, Scope $scope): array + { + if (!$methodCall instanceof Node\Expr\MethodCall) { + return []; + } + + $identifier = $methodCall->name; + + if (!$identifier instanceof Node\Identifier) { + return []; + } + + if (!\in_array($methodCall->name->name, ['push', 'unshift'], true)) { + return []; + } + + $valueType = TypeHelper::searchArrayValueType($scope->getType($methodCall->var)); + + if (!$valueType instanceof ArrayValueType) { + return []; + } + + $attribute = $methodCall->args[0]->value; + $parameterType = $scope->getType($attribute); + + $accepts = $this->ruleLevelHelper->accepts( + $parameterType, + $valueType->innerType(), + $scope->isDeclareStrictTypes() + ); + + $errors = []; + + if (!$accepts) { + $errors[] = \sprintf( + 'Parameter #%d of method ' . $methodCall->name->name . ' callback expects %s, %s given.', + 1, + $valueType->innerType()->describe(VerbosityLevel::typeOnly()), + $parameterType->describe( + $parameterType->isCallable()->yes() ? VerbosityLevel::value() + : VerbosityLevel::typeOnly() + ) + ); + } + + return $errors; + } +} diff --git a/src/ArrayValueReduceDynamicReturn.php b/src/ArrayValueReduceDynamicReturn.php new file mode 100644 index 0000000..253dc42 --- /dev/null +++ b/src/ArrayValueReduceDynamicReturn.php @@ -0,0 +1,55 @@ +getName() === 'reduce'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + if (\count($methodCall->args) === 0) { + return new MixedType(); + } + + $firstAttribute = $methodCall->args[0]->value; + $callableType = $scope->getType($firstAttribute); + + if ($callableType instanceof ClosureType) { + return $callableType->getReturnType(); + } + + if ($callableType instanceof CallableType) { + return $callableType->getReturnType() ?? new MixedType(); + } + + $type = TypeHelper::searchArrayValueType($scope->getType($methodCall->var)); + + if ($type) { + return $type->innerType(); + } + + return new MixedType(); + } +} diff --git a/src/ArrayValueToArrayDynamicReturn.php b/src/ArrayValueToArrayDynamicReturn.php new file mode 100644 index 0000000..ba95d17 --- /dev/null +++ b/src/ArrayValueToArrayDynamicReturn.php @@ -0,0 +1,57 @@ +getName(), ['toArray', 'getIterator'], true); + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $type = $scope->getType($methodCall->var); + + return $this->fromType($type) ?? new ArrayType(new IntegerType(), new MixedType()); + } + + private function fromType(Type $type): ?ArrayType + { + if ($type instanceof ArrayValueType) { + return new ArrayType(new IntegerType(), $type->innerType()); + } + + if ($type instanceof IntersectionType || $type instanceof UnionType) { + foreach ($type->getTypes() as $subType) { + $try = $this->fromType($subType); + + if ($try) { + return $try; + } + } + } + + return null; + } +} diff --git a/src/ArrayValueToAssocValueDynamicReturn.php b/src/ArrayValueToAssocValueDynamicReturn.php new file mode 100644 index 0000000..94d98dc --- /dev/null +++ b/src/ArrayValueToAssocValueDynamicReturn.php @@ -0,0 +1,35 @@ +getName() === 'toAssocValue'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $type = TypeHelper::searchArrayValueType($scope->getType($methodCall->var)); + + return new AssocValueType(new IntegerType(), $type ? $type->innerType() : new MixedType()); + } +} diff --git a/src/ArrayValueToStringValueDynamicReturn.php b/src/ArrayValueToStringValueDynamicReturn.php new file mode 100644 index 0000000..63975ce --- /dev/null +++ b/src/ArrayValueToStringValueDynamicReturn.php @@ -0,0 +1,31 @@ +getName() === 'toStringValue'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + return new StringValueType(); + } +} diff --git a/src/ArrayValueType.php b/src/ArrayValueType.php new file mode 100644 index 0000000..bf49142 --- /dev/null +++ b/src/ArrayValueType.php @@ -0,0 +1,30 @@ +innerType = $innerType; + } + + public function innerType(): Type + { + return $this->innerType; + } + + public function describe(VerbosityLevel $level): string + { + return \sprintf('%s<%s>', parent::describe($level), $this->innerType->describe($level)); + } +} diff --git a/src/AssocValueToArrayValueDynamicReturn.php b/src/AssocValueToArrayValueDynamicReturn.php new file mode 100644 index 0000000..3d6c1cd --- /dev/null +++ b/src/AssocValueToArrayValueDynamicReturn.php @@ -0,0 +1,38 @@ +getName() === 'values'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $type = $scope->getType($methodCall->var); + + if ($type instanceof AssocValueType) { + return new ArrayValueType($type->innerType()); + } + + return new ArrayValueType(new MixedType()); + } +} diff --git a/src/AssocValueType.php b/src/AssocValueType.php new file mode 100644 index 0000000..7891cba --- /dev/null +++ b/src/AssocValueType.php @@ -0,0 +1,39 @@ +innerType = $innerType; + $this->keyType = $keyType; + } + + public function innerType(): Type + { + return $this->innerType; + } + + public function keyType(): Type + { + return $this->keyType; + } + + public function describe(VerbosityLevel $level): string + { + return \sprintf('%s<%s>', parent::describe($level), $this->innerType->describe($level)); + } +} diff --git a/src/StringValueType.php b/src/StringValueType.php new file mode 100644 index 0000000..f0f032d --- /dev/null +++ b/src/StringValueType.php @@ -0,0 +1,14 @@ +getTypes(); + + if (\count($subTypes) === 2 + && $subTypes[0] instanceof ObjectType + && $subTypes[1] instanceof IterableType + && $subTypes[0]->isSuperTypeOf(new ObjectType(ArrayValue::class))) { + return new ArrayValueType($subTypes[1]->getIterableValueType()); + } + } + + if ($type instanceof IntersectionType || $type instanceof UnionType) { + foreach ($type->getTypes() as $subType) { + $try = self::searchArrayValueType($subType); + + if ($try) { + return $try; + } + } + } + + return null; + } +} diff --git a/src/WrapDynamicReturn.php b/src/WrapDynamicReturn.php new file mode 100644 index 0000000..e36ebb7 --- /dev/null +++ b/src/WrapDynamicReturn.php @@ -0,0 +1,40 @@ +getName() === 'array'; + } + + public function getTypeFromStaticMethodCall( + MethodReflection $methodReflection, + StaticCall $methodCall, + Scope $scope + ): Type { + $passedType = $scope->getType($methodCall->args[0]->value); + $innerType = new MixedType(); + + if ($passedType instanceof ArrayType) { + $innerType = $passedType->getItemType(); + } + + return new ArrayValueType($innerType); + } +} diff --git a/tests/Integration/ArrayValueIntegrationTest.php b/tests/Integration/ArrayValueIntegrationTest.php new file mode 100644 index 0000000..93c5072 --- /dev/null +++ b/tests/Integration/ArrayValueIntegrationTest.php @@ -0,0 +1,33 @@ +acceptingArrayOfInts(Wrap::assocArray(['test' => 1, 'x' => 3])->values()->toArray()); + } + + /** + * @param int[] $ints + */ + private function acceptingArrayOfInts(array $ints): void + { + } +} diff --git a/tests/Integration/data/arrayValue-0.json b/tests/Integration/data/arrayValue-0.json new file mode 100644 index 0000000..63ad95e --- /dev/null +++ b/tests/Integration/data/arrayValue-0.json @@ -0,0 +1,12 @@ +[ + { + "message": "Parameter #1 $s of method map callback expects string, int given.", + "line": 24, + "ignorable": true + }, + { + "message": "Parameter #1 $s of method map callback expects int, string given.", + "line": 30, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/Integration/data/arrayValue-2.json b/tests/Integration/data/arrayValue-2.json new file mode 100644 index 0000000..01eadd0 --- /dev/null +++ b/tests/Integration/data/arrayValue-2.json @@ -0,0 +1,7 @@ +[ + { + "message": "Binary operation \"+\" between string and 2 results in an error.", + "line": 22, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/Integration/data/arrayValue-5.json b/tests/Integration/data/arrayValue-5.json new file mode 100644 index 0000000..e63d547 --- /dev/null +++ b/tests/Integration/data/arrayValue-5.json @@ -0,0 +1,12 @@ +[ + { + "message": "Parameter #1 $ascii of function chr expects int, string|null given.", + "line": 20, + "ignorable": true + }, + { + "message": "Parameter #1 $ints of method tests\\GW\\PHPStan\\GwValues\\Integration\\arrayValue::acceptingArrayOfInts() expects array, array> given.", + "line": 47, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/Integration/data/arrayValue-7.json b/tests/Integration/data/arrayValue-7.json new file mode 100644 index 0000000..2fa1f12 --- /dev/null +++ b/tests/Integration/data/arrayValue-7.json @@ -0,0 +1,7 @@ +[ + { + "message": "Parameter #1 $ascii of function chr expects int, int|null given.", + "line": 19, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/Integration/data/arrayValue.php b/tests/Integration/data/arrayValue.php new file mode 100644 index 0000000..82a5e6b --- /dev/null +++ b/tests/Integration/data/arrayValue.php @@ -0,0 +1,59 @@ +map( + function (int $n): string { + return "n{$n}"; + } + ); + + chr($ints->first()); + chr($strings->first()); + + $n = $strings->toArray()[0] + 2; + + $ints->map( + function (string $s): int { + return 2; + } + ); + + $ints + ->map( + function (int $s): int { + return 2; + } + ) + ->map( + function (int $n): string { + return "n{$n}"; + } + ) + ->map( + function (int $s): int { + return 2; + } + ); + + $this->acceptingArrayOfInts($ints->chunk(2)->toArray()); + $this->acceptingArrayOfInts($ints->chunk(2)->toArray()[0]); + + $strings->unique(); + } + + /** + * @param int[] $ints + */ + private function acceptingArrayOfInts(array $ints): void + { + } +} diff --git a/tests/Integration/data/docBlocks-0.json b/tests/Integration/data/docBlocks-0.json new file mode 100644 index 0000000..f9dfa6f --- /dev/null +++ b/tests/Integration/data/docBlocks-0.json @@ -0,0 +1,7 @@ +[ + { + "message": "Parameter #1 $x of method map callback expects int, tests\\GW\\PHPStan\\GwValues\\Integration\\Label given.", + "line": 23, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/Integration/data/docBlocks.php b/tests/Integration/data/docBlocks.php new file mode 100644 index 0000000..6ac5322 --- /dev/null +++ b/tests/Integration/data/docBlocks.php @@ -0,0 +1,34 @@ +items = Wrap::array($items); + } + + public function test(): void + { + $this->items->map(function (Label $x): string { return ''; }); + $this->items->map(function (int $x): string { return ''; }); + + $this->acceptingArrayOfLabels($this->items->toArray()); + } + + /** + * @param Label[] $items + */ + private function acceptingArrayOfLabels(array $items): void + { + } +} diff --git a/tests/Integration/data/stringsValue.php b/tests/Integration/data/stringsValue.php new file mode 100644 index 0000000..3133a8a --- /dev/null +++ b/tests/Integration/data/stringsValue.php @@ -0,0 +1,19 @@ +map(function (StringValue $value): StringValue { return $value; }) + ->trim(); + + Wrap::string('test') + ->trim(); + } +} diff --git a/tests/Integration/phpstan.neon b/tests/Integration/phpstan.neon new file mode 100644 index 0000000..a72243c --- /dev/null +++ b/tests/Integration/phpstan.neon @@ -0,0 +1,2 @@ +includes: + - ../../extension.neon diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..43813af --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,2 @@ + + + + ../src + + + + + .//tests + +