Skip to content

Commit

Permalink
Merge pull request #852 from kukulich/mutants
Browse files Browse the repository at this point in the history
Improved mutation score
  • Loading branch information
Ocramius authored Nov 14, 2021
2 parents 6eec472 + 2696b26 commit d6b7d25
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 41 deletions.
74 changes: 42 additions & 32 deletions src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ public function generateClassStub(string $className): ?StubData
}

$extension = $this->getExtensionFromFilePath($filePath);
$stub = $this->createStub($this->classNodes[$lowercaseClassName], $extension);
$stub = $this->createStub($this->classNodes[$lowercaseClassName]);

if ($className === Traversable::class) {
// See https://github.com/JetBrains/phpstorm-stubs/commit/0778a26992c47d7dbee4d0b0bfb7fad4344371b1#diff-575bacb45377d474336c71cbf53c1729
Expand Down Expand Up @@ -259,7 +259,7 @@ public function generateFunctionStub(string $functionName): ?StubData

$extension = $this->getExtensionFromFilePath($filePath);

return new StubData($this->createStub($this->functionNodes[$lowercaseFunctionName], $extension), $extension);
return new StubData($this->createStub($this->functionNodes[$lowercaseFunctionName]), $extension);
}

public function generateConstantStub(string $constantName): ?StubData
Expand Down Expand Up @@ -295,7 +295,7 @@ public function generateConstantStub(string $constantName): ?StubData

$extension = $this->getExtensionFromFilePath($filePath);

return new StubData($this->createStub($constantNode, $extension), $extension);
return new StubData($this->createStub($constantNode), $extension);
}

private function parseFile(string $filePath): void
Expand Down Expand Up @@ -363,7 +363,7 @@ private function parseFile(string $filePath): void
/**
* @param Node\Stmt\ClassLike|Node\Stmt\Function_|Node\Stmt\Const_|Node\Expr\FuncCall $node
*/
private function createStub(Node $node, string $extension): string
private function createStub(Node $node): string
{
if (! ($node instanceof Node\Expr\FuncCall)) {
$this->addDeprecatedDocComment($node);
Expand All @@ -376,13 +376,19 @@ private function createStub(Node $node, string $extension): string
$node = $namespaceBuilder->getNode();
}

return "<?php\n\n" . $this->prettyPrinter->prettyPrint([$node]) . ($node instanceof Node\Expr\FuncCall ? ';' : '') . "\n";
return sprintf(
"<?php\n\n%s%s\n",
$this->prettyPrinter->prettyPrint([$node]),
$node instanceof Node\Expr\FuncCall ? ';' : '',
);
}

private function createCachingVisitor(): NodeVisitorAbstract
{
return new class () extends NodeVisitorAbstract
{
private const TRUE_FALSE_NULL = ['true', 'false', 'null'];

/** @var array<string, Node\Stmt\ClassLike> */
private array $classNodes = [];

Expand Down Expand Up @@ -450,7 +456,7 @@ public function enterNode(Node $node): ?int
assert($nameNode instanceof Node\Scalar\String_);
$constantName = $nameNode->value;

if (in_array($constantName, ['true', 'false', 'null'], true)) {
if (in_array($constantName, self::TRUE_FALSE_NULL, true)) {
$constantName = strtoupper($constantName);
$nameNode->value = $constantName;
}
Expand Down Expand Up @@ -549,17 +555,13 @@ private function modifyStmtsByPhpVersion(array $stmts, bool $isCoreExtension): a
{
$newStmts = [];
foreach ($stmts as $stmt) {
assert($stmt instanceof Node\Stmt\ClassConst || $stmt instanceof Node\Stmt\Property || $stmt instanceof Node\Stmt\ClassMethod);

if (! $this->isSupportedInPhpVersion($stmt, $isCoreExtension)) {
continue;
}

if (
$stmt instanceof Node\Stmt\ClassConst
|| $stmt instanceof Node\Stmt\Property
|| $stmt instanceof Node\Stmt\ClassMethod
) {
$this->addDeprecatedDocComment($stmt);
}
$this->addDeprecatedDocComment($stmt);

$newStmts[] = $stmt;
}
Expand Down Expand Up @@ -620,8 +622,10 @@ private function isDeprecatedInPhpVersion(Node\Stmt\ClassLike|Node\Stmt\ClassCon
return false;
}

private function isSupportedInPhpVersion(Node\Stmt\ClassLike|Node\Stmt\Function_|Node\Stmt\Const_|Node\Expr\FuncCall|Node\Stmt $node, bool $isCoreExtension): bool
{
private function isSupportedInPhpVersion(
Node\Stmt\ClassLike|Node\Stmt\Function_|Node\Stmt\Const_|Node\Expr\FuncCall|Node\Stmt\ClassConst|Node\Stmt\Property|Node\Stmt\ClassMethod $node,
bool $isCoreExtension,
): bool {
if ($this->phpVersion === null) {
return true;
}
Expand All @@ -631,22 +635,32 @@ private function isSupportedInPhpVersion(Node\Stmt\ClassLike|Node\Stmt\Function_
return true;
}

[$fromVersion, $toVersion] = $this->getSupportedPhpVersions($node);

if ($fromVersion !== null && $fromVersion > $this->phpVersion) {
return false;
}

return $toVersion === null || $toVersion >= $this->phpVersion;
}

/**
* @return array{0: int|null, 1: int|null}
*/
private function getSupportedPhpVersions(
Node\Stmt\ClassLike|Node\Stmt\Function_|Node\Stmt\Const_|Node\Expr\FuncCall|Node\Stmt\ClassConst|Node\Stmt\Property|Node\Stmt\ClassMethod $node,
): array {
$fromVersion = null;
$toVersion = null;

$docComment = $node->getDocComment();
if ($docComment !== null) {
if (preg_match('~@since\s+(?P<version>\d+\.\d+(?:\.\d+)?)\s+~', $docComment->getText(), $sinceMatches) === 1) {
$sincePhpVersion = $this->parsePhpVersion($sinceMatches['version']);

if ($sincePhpVersion > $this->phpVersion) {
return false;
}
$fromVersion = $this->parsePhpVersion($sinceMatches['version']);
}

if (preg_match('~@removed\s+(?P<version>\d+\.\d+(?:\.\d+)?)\s+~', $docComment->getText(), $removedMatches) === 1) {
$removedPhpVersion = $this->parsePhpVersion($removedMatches['version']);

if ($removedPhpVersion <= $this->phpVersion) {
return false;
}
$toVersion = $this->parsePhpVersion($removedMatches['version']) - 1;
}
}

Expand All @@ -662,9 +676,7 @@ private function isSupportedInPhpVersion(Node\Stmt\ClassLike|Node\Stmt\Function_
if ($attributeArg->name === null || $attributeArg->name->toString() === 'from') {
assert($attributeArg->value instanceof Node\Scalar\String_);

if ($this->parsePhpVersion($attributeArg->value->value) > $this->phpVersion) {
return false;
}
$fromVersion = $this->parsePhpVersion($attributeArg->value->value);
}

if ($attributeArg->name?->toString() !== 'to') {
Expand All @@ -673,15 +685,13 @@ private function isSupportedInPhpVersion(Node\Stmt\ClassLike|Node\Stmt\Function_

assert($attributeArg->value instanceof Node\Scalar\String_);

if ($this->parsePhpVersion($attributeArg->value->value) < $this->phpVersion) {
return false;
}
$toVersion = $this->parsePhpVersion($attributeArg->value->value);
}
}
}
}

return true;
return [$fromVersion, $toVersion];
}

private function parsePhpVersion(string $version): int
Expand Down
2 changes: 1 addition & 1 deletion src/SourceLocator/Type/MemoizingSourceLocator.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
*/
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array
{
$cacheKey = $this->reflectorCacheKey($reflector) . '_' . $this->identifierTypeToCacheKey($identifierType);
$cacheKey = sprintf('%s_%s', $this->reflectorCacheKey($reflector), $this->identifierTypeToCacheKey($identifierType));

if (array_key_exists($cacheKey, $this->cacheByIdentifierTypeKeyAndOid)) {
return $this->cacheByIdentifierTypeKeyAndOid[$cacheKey];
Expand Down
20 changes: 20 additions & 0 deletions test/unit/Reflection/ReflectionClassTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,8 @@ public function testGetPropertiesForPureEnum(): void

self::assertTrue($property->isPublic());
self::assertTrue($property->isReadOnly());
self::assertFalse($property->isPromoted());
self::assertTrue($property->isDefault());
self::assertSame(0, $property->getPositionInAst());
}

Expand All @@ -479,6 +481,8 @@ public function testGetPropertiesForBackedEnum(): void

self::assertTrue($property->isPublic(), $propertyName);
self::assertTrue($property->isReadOnly(), $propertyName);
self::assertFalse($property->isPromoted());
self::assertTrue($property->isDefault());
self::assertSame(0, $property->getPositionInAst(), $propertyName);
}
}
Expand Down Expand Up @@ -782,6 +786,22 @@ public function testGetMethodIsCaseInsensitive(): void
}

public function testGetDefaultProperties(): void
{
$reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/DefaultProperties.php', $this->astLocator));
$classInfo = $reflector->reflectClass(DefaultProperties::class);

self::assertSame([
'fromTrait' => 'anything',
'hasDefault' => 'const',
'hasNullAsDefault' => null,
'noDefault' => null,
'hasDefaultWithType' => 123,
'hasNullAsDefaultWithType' => null,
'noDefaultWithType' => null,
], $classInfo->getDefaultProperties());
}

public function testGetDefaultPropertiesShouldIgnoreRuntimeProperty(): void
{
$object = new DefaultProperties();
$object->notDefaultProperty = null;
Expand Down
Loading

0 comments on commit d6b7d25

Please sign in to comment.