phpstorm-stubs/tests/StubsTest.php

451 lines
18 KiB
PHP

<?php
namespace StubTests;
use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;
use phpDocumentor\Reflection\DocBlock\Tags\Link;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url;
use phpDocumentor\Reflection\DocBlock\Tags\See;
use phpDocumentor\Reflection\DocBlock\Tags\Since;
use PHPUnit\Framework\TestCase;
use StubTests\Model\BasePHPClass;
use StubTests\Model\BasePHPElement;
use StubTests\Model\PHPClass;
use StubTests\Model\PHPConst;
use StubTests\Model\PHPDocElement;
use StubTests\Model\PHPFunction;
use StubTests\Model\PHPInterface;
use StubTests\Model\PHPMethod;
use StubTests\Model\StubProblemType;
use StubTests\Model\Tags\RemovedTag;
use StubTests\Parsers\Utils;
use StubTests\TestData\Providers\PhpStormStubsSingleton;
class StubsTest extends TestCase
{
/**
* @dataProvider \StubTests\TestData\Providers\ReflectionTestDataProviders::constantProvider
*/
public function testConstants(PHPConst $constant): void
{
$constantName = $constant->name;
$constantValue = $constant->value;
$stubConstants = PhpStormStubsSingleton::getPhpStormStubs()->getConstants();
if ($constant->hasMutedProblem(StubProblemType::STUB_IS_MISSED)) {
static::markTestSkipped('constant is excluded');
}
static::assertArrayHasKey(
$constantName,
$stubConstants,
"Missing constant: const $constantName = $constantValue\n"
);
}
/**
* @dataProvider \StubTests\TestData\Providers\ReflectionTestDataProviders::constantProvider
*/
public function testConstantsValues(PHPConst $constant): void
{
$constantName = $constant->name;
$constantValue = $constant->value;
$stubConstants = PhpStormStubsSingleton::getPhpStormStubs()->getConstants();
if ($constant->hasMutedProblem(StubProblemType::STUB_IS_MISSED)) {
static::markTestSkipped('constant is excluded');
}
if ($constant->hasMutedProblem(StubProblemType::WRONG_CONSTANT_VALUE)) {
static::markTestSkipped('constant is excluded');
}
static::assertEquals(
$constantValue,
$stubConstants[$constantName]->value,
"Constant value mismatch: const $constantName \n
Expected value: $constantValue but was {$stubConstants[$constantName]->value}"
);
}
/**
* @dataProvider \StubTests\TestData\Providers\ReflectionTestDataProviders::functionProvider
*/
public function testFunctions(PHPFunction $function): void
{
$functionName = $function->name;
$stubFunctions = PhpStormStubsSingleton::getPhpStormStubs()->getFunctions();
$params = self::getParameterRepresentation($function);
if ($function->hasMutedProblem(StubProblemType::STUB_IS_MISSED)) {
static::markTestSkipped('function is excluded');
}
static::assertArrayHasKey($functionName, $stubFunctions, "Missing function: function $functionName($params){}");
$phpstormFunction = $stubFunctions[$functionName];
if (!$function->hasMutedProblem(StubProblemType::FUNCTION_IS_DEPRECATED)) {
static::assertFalse(
$function->is_deprecated && $phpstormFunction->is_deprecated !== true,
"Function $functionName is not deprecated in stubs"
);
}
if (!$function->hasMutedProblem(StubProblemType::FUNCTION_PARAMETER_MISMATCH)) {
static::assertSameSize(
$function->parameters,
$phpstormFunction->parameters,
"Parameter number mismatch for function $functionName.
Expected: " . self::getParameterRepresentation($function)
);
}
}
/**
* @dataProvider \StubTests\TestData\Providers\ReflectionTestDataProviders::classProvider
*/
public function testClasses(PHPClass $class): void
{
$className = $class->name;
$stubClasses = PhpStormStubsSingleton::getPhpStormStubs()->getClasses();
if ($class->hasMutedProblem(StubProblemType::STUB_IS_MISSED)) {
static::markTestSkipped('class is skipped');
}
static::assertArrayHasKey($className, $stubClasses, "Missing class $className: class $className {}");
$stubClass = $stubClasses[$className];
if (!$class->hasMutedProblem(StubProblemType::WRONG_PARENT)) {
static::assertEquals(
$class->parentClass,
$stubClass->parentClass,
"Class $className should extend {$class->parentClass}"
);
}
foreach ($class->constants as $constant) {
if (!$constant->hasMutedProblem(StubProblemType::STUB_IS_MISSED)) {
static::assertArrayHasKey(
$constant->name,
$stubClass->constants,
"Missing constant $className::{$constant->name}"
);
}
}
foreach ($class->methods as $method) {
$params = self::getParameterRepresentation($method);
$methodName = $method->name;
if (!$method->hasMutedProblem(StubProblemType::STUB_IS_MISSED)) {
static::assertArrayHasKey(
$methodName,
$stubClass->methods,
"Missing method $className::$methodName($params){}"
);
$stubMethod = $stubClass->methods[$methodName];
if (!$method->hasMutedProblem(StubProblemType::FUNCTION_IS_FINAL)) {
static::assertEquals(
$method->is_final,
$stubMethod->is_final,
"Method $className::$methodName final modifier is incorrect"
);
}
if (!$method->hasMutedProblem(StubProblemType::FUNCTION_IS_STATIC)) {
static::assertEquals(
$method->is_static,
$stubMethod->is_static,
"Method $className::$methodName static modifier is incorrect"
);
}
if (!$method->hasMutedProblem(StubProblemType::FUNCTION_ACCESS)) {
static::assertEquals(
$method->access,
$stubMethod->access,
"Method $className::$methodName access modifier is incorrect"
);
}
if (!$method->hasMutedProblem(StubProblemType::FUNCTION_PARAMETER_MISMATCH)) {
static::assertSameSize(
$method->parameters,
$stubMethod->parameters,
"Parameter number mismatch for method $className::$methodName.
Expected: " . self::getParameterRepresentation($method)
);
}
}
}
foreach ($class->interfaces as $interface) {
if (!$class->hasMutedProblem(StubProblemType::WRONG_INTERFACE)) {
static::assertContains(
$interface,
$stubClass->interfaces,
"Class $className doesn't implement interface $interface"
);
}
}
foreach ($class->properties as $property) {
$propertyName = $property->name;
if ($property->access === "private") {
continue;
}
if (!$property->hasMutedProblem(StubProblemType::STUB_IS_MISSED)) {
static::assertArrayHasKey(
$propertyName,
$stubClass->properties,
"Missing property $className::$property->access $property->type $$propertyName"
);
$stubProperty = $stubClass->properties[$propertyName];
if (!$property->hasMutedProblem(StubProblemType::PROPERTY_IS_STATIC)) {
static::assertEquals(
$property->is_static,
$stubProperty->is_static,
"Property $className::$propertyName static modifier is incorrect"
);
}
if (!$property->hasMutedProblem(StubProblemType::PROPERTY_ACCESS)) {
static::assertEquals(
$property->access,
$stubProperty->access,
"Property $className::$propertyName access modifier is incorrect"
);
}
if (!$property->hasMutedProblem(StubProblemType::PROPERTY_TYPE)
&& !empty($property->type)) {
static::assertEquals(
$property->type,
$stubProperty->type,
"Property type doesn't match for property $className::$propertyName"
);
}
}
}
}
/**
* @dataProvider \StubTests\TestData\Providers\ReflectionTestDataProviders::interfaceProvider
*/
public function testInterfaces(PHPInterface $interface): void
{
$interfaceName = $interface->name;
$stubInterfaces = PhpStormStubsSingleton::getPhpStormStubs()->getInterfaces();
if ($interface->hasMutedProblem(StubProblemType::STUB_IS_MISSED)) {
static::markTestSkipped('interface is skipped');
}
static::assertArrayHasKey(
$interfaceName,
$stubInterfaces,
"Missing interface $interfaceName: interface $interfaceName {}"
);
$stubInterface = $stubInterfaces[$interfaceName];
if (!$interface->hasMutedProblem(StubProblemType::WRONG_PARENT)) {
foreach ($interface->parentInterfaces as $parentInterface) {
static::assertContains(
$parentInterface,
$stubInterface->parentInterfaces,
"Missing parent interface $parentInterface"
);
}
}
foreach ($interface->constants as $constant) {
if (!$constant->hasMutedProblem(StubProblemType::STUB_IS_MISSED)) {
static::assertArrayHasKey(
$constant->name,
$stubInterface->constants,
"Missing constant $interfaceName::{$constant->name}"
);
}
}
foreach ($interface->methods as $method) {
$params = self::getParameterRepresentation($method);
$methodName = $method->name;
if (!$method->hasMutedProblem(StubProblemType::STUB_IS_MISSED)) {
static::assertArrayHasKey(
$methodName,
$stubInterface->methods,
"Missing method $interfaceName::$methodName($params){}"
);
$stubMethod = $stubInterface->methods[$methodName];
if (!$method->hasMutedProblem(StubProblemType::FUNCTION_IS_FINAL)) {
static::assertEquals(
$method->is_final,
$stubMethod->is_final,
"Method $interfaceName::$methodName final modifier is incorrect"
);
}
if (!$method->hasMutedProblem(StubProblemType::FUNCTION_IS_STATIC)) {
static::assertEquals(
$method->is_static,
$stubMethod->is_static,
"Method $interfaceName::$methodName static modifier is incorrect"
);
}
if (!$method->hasMutedProblem(StubProblemType::FUNCTION_ACCESS)) {
static::assertEquals(
$method->access,
$stubMethod->access,
"Method $interfaceName::$methodName access modifier is incorrect"
);
}
if (!$method->hasMutedProblem(StubProblemType::FUNCTION_PARAMETER_MISMATCH)) {
static::assertSameSize(
$method->parameters,
$stubMethod->parameters,
"Parameter number mismatch for method $interfaceName::$methodName.
Expected: " . self::getParameterRepresentation($method)
);
}
}
}
}
/**
* @dataProvider \StubTests\TestData\Providers\StubsTestDataProviders::stubClassConstantProvider
*/
public function testClassConstantsPHPDocs(string $className, PHPConst $constant): void
{
static::assertNull($constant->parseError, $constant->parseError ?: '');
$this->checkPHPDocCorrectness($constant, "constant $className::$constant->name");
}
/**
* @dataProvider \StubTests\TestData\Providers\StubsTestDataProviders::stubConstantProvider
*/
public function testConstantsPHPDocs(PHPConst $constant): void
{
static::assertNull($constant->parseError, $constant->parseError ?: '');
$this->checkPHPDocCorrectness($constant, "constant $constant->name");
}
/**
* @dataProvider \StubTests\TestData\Providers\StubsTestDataProviders::stubFunctionProvider
*/
public function testFunctionPHPDocs(PHPFunction $function): void
{
static::assertNull($function->parseError, $function->parseError ?: '');
$this->checkPHPDocCorrectness($function, "function $function->name");
}
/**
* @dataProvider \StubTests\TestData\Providers\StubsTestDataProviders::stubClassProvider
*/
public function testClassesPHPDocs(BasePHPClass $class): void
{
static::assertNull($class->parseError, $class->parseError ?: '');
$this->checkPHPDocCorrectness($class, "class $class->name");
}
/**
* @dataProvider \StubTests\TestData\Providers\StubsTestDataProviders::stubMethodProvider
*/
public function testMethodsPHPDocs(string $methodName, PHPMethod $method): void
{
if ($methodName === '__construct') {
static::assertNull($method->returnTag, '@return tag for __construct should be omitted');
}
static::assertNull($method->parseError, $method->parseError ?: '');
$this->checkPHPDocCorrectness($method, "method $methodName");
}
private function checkPHPDocCorrectness(BasePHPElement $element, string $elementName): void
{
$this->checkLinks($element, $elementName);
if ($element->stubBelongsToCore) {
$this->checkDeprecatedRemovedSinceVersionsMajor($element, $elementName);
}
$this->checkContainsOnlyValidTags($element, $elementName);
}
private function checkContainsOnlyValidTags(BasePHPElement $element, string $elementName): void
{
$VALID_TAGS = [
'author',
'copyright',
'deprecated',
'example', //temporary addition due to the number of existing cases
'inheritdoc',
'link',
'meta',
'method',
'mixin',
'package',
'param',
'property',
'property-read',
'removed',
'return',
'see',
'since',
'throws',
'uses',
'var',
'version',
];
/** @var PHPDocElement $element */
foreach ($element->tagNames as $tagName) {
static::assertContains($tagName, $VALID_TAGS, "Element $elementName has invalid tag: @$tagName");
}
}
private static function getParameterRepresentation(PHPFunction $function): string
{
$result = '';
foreach ($function->parameters as $parameter) {
if (!empty($parameter->type)) {
$result .= $parameter->type . ' ';
}
if ($parameter->is_passed_by_ref) {
$result .= '&';
}
if ($parameter->is_vararg) {
$result .= '...';
}
$result .= '$' . $parameter->name . ', ';
}
$result = rtrim($result, ', ');
return $result;
}
private function checkLinks(BasePHPElement $element, string $elementName): void
{
/** @var PHPDocElement $element */
foreach ($element->links as $link) {
if ($link instanceof Link) {
static::assertStringStartsWith(
'https',
$link->getLink(),
"In $elementName @link doesn't start with https"
);
}
}
foreach ($element->see as $see) {
if ($see instanceof See && $see->getReference() instanceof Url && strncmp($see, 'http', 4) === 0) {
static::assertStringStartsWith('https', $see, "In $elementName @see doesn't start with https");
}
}
}
private function checkDeprecatedRemovedSinceVersionsMajor(BasePHPElement $element, $elementName): void
{
/** @var PHPDocElement $element */
foreach ($element->sinceTags as $sinceTag) {
if ($sinceTag instanceof Since) {
$version = $sinceTag->getVersion();
if ($version !== null) {
self::assertTrue(Utils::tagDoesNotHaveZeroPatchVersion($sinceTag), "$elementName has
'since' version $version.'Since' version for PHP Core functionallity for style consistensy
should have X.X format for the case when patch version is '0'.");
}
}
}
foreach ($element->deprecatedTags as $deprecatedTag) {
if ($deprecatedTag instanceof Deprecated) {
$version = $deprecatedTag->getVersion();
if ($version !== null) {
self::assertTrue(Utils::tagDoesNotHaveZeroPatchVersion($deprecatedTag), "$elementName has
'deprecated' version $version.'Deprecated' version for PHP Core functionallity for style consistensy
should have X.X format for the case when patch version is '0'.");
}
}
}
foreach ($element->removedTags as $removedTag) {
if ($removedTag instanceof RemovedTag) {
$version = $removedTag->getVersion();
if ($version !== null) {
self::assertTrue(Utils::tagDoesNotHaveZeroPatchVersion($removedTag), "$elementName has
'removed' version $version.'Removed' version for PHP Core functionallity for style consistensy
should have X.X format for the case when patch version is '0'.");
}
}
}
}
}