Skip to content

Commit

Permalink
feat: introduce enum extractor (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
simPod authored Jul 22, 2022
1 parent 7cf9a39 commit b565bae
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 0 deletions.
94 changes: 94 additions & 0 deletions src/EnumExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

namespace Cdn77\EntityFqnExtractor;

use Cdn77\EntityFqnExtractor\Exception\EnumDefinitionInFileIsInvalid;

use function count;
use function ltrim;
use function Safe\file_get_contents;
use function token_get_all;

use const T_ENUM;
use const T_NAME_QUALIFIED;
use const T_NAMESPACE;
use const T_STRING;
use const T_WHITESPACE;

final class EnumExtractor
{
/** @return list<class-string> */
public static function all(string $filePathName): array
{
$code = file_get_contents($filePathName);

/** @var list<class-string> $enums */
$enums = [];
$namespace = '';
$tokens = token_get_all($code);
$count = count($tokens);

foreach ($tokens as $i => $token) {
if ($i < 2) {
continue;
}

if ($token[0] === T_NAMESPACE) {
for ($j = $i + 1; $j < $count; ++$j) {
if ($tokens[$j][0] === T_NAME_QUALIFIED) {
$namespace = $tokens[$j][1];

break;
}

if ($tokens[$j][0] === T_STRING) {
$namespace .= '\\' . $tokens[$j][1];

continue;
}

if ($tokens[$j] === '{' || $tokens[$j] === ';') {
$namespace = ltrim($namespace, '\\');

break;
}
}

continue;
}

if (
$tokens[$i - 2][0] !== T_ENUM
|| $tokens[$i - 1][0] !== T_WHITESPACE
|| $token[0] !== T_STRING
) {
continue;
}

$enumName = $tokens[$i][1];
/** @psalm-var class-string $fqn */
$fqn = $namespace . '\\' . $enumName;
$enums[] = $fqn;
}

if ($enums === []) {
throw EnumDefinitionInFileIsInvalid::noEnum($filePathName);
}

return $enums;
}

/** @return class-string */
public static function get(string $filePathName): string
{
$enums = self::all($filePathName);

if (count($enums) > 1) {
throw EnumDefinitionInFileIsInvalid::multipleEnums($filePathName);
}

return $enums[0];
}
}
35 changes: 35 additions & 0 deletions src/Exception/EnumDefinitionInFileIsInvalid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Cdn77\EntityFqnExtractor\Exception;

use InvalidArgumentException;
use PhpParser\Error;

use function Safe\sprintf;

final class EnumDefinitionInFileIsInvalid extends InvalidArgumentException
{
public static function cannotParse(string $filePathName, Error $error): self
{
return new self(
sprintf('Cannot parse file %s', $filePathName),
previous: $error
);
}

public static function noEnum(string $filePathName): self
{
return new self(
sprintf('There is no enum in a file %s', $filePathName)
);
}

public static function multipleEnums(string $filePathName): self
{
return new self(
sprintf('There are multiple enums in a file %s', $filePathName)
);
}
}
76 changes: 76 additions & 0 deletions tests/EnumExtractorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Cdn77\EntityFqnExtractor\Tests;

use Cdn77\EntityFqnExtractor\EnumExtractor;
use Cdn77\EntityFqnExtractor\Exception\EnumDefinitionInFileIsInvalid;
use Cdn77\EntityFqnExtractor\Fixtures\SomeDirectory\EnumFixture;
use Generator;

final class EnumExtractorTest extends TestCaseBase
{
public function testGet(): void
{
self::assertSame(
EnumFixture::class,
EnumExtractor::get(
__DIR__ . '/Fixtures/SomeDirectory/EnumFixture.php'
)
);
}

/**
* @param list<class-string> $expectedClasses
*
* @dataProvider dataProviderAll
*/
public function testAll(array $expectedClasses, string $fixture): void
{
self::assertSame(
$expectedClasses,
EnumExtractor::all(__DIR__ . $fixture)
);
}

/** @return Generator<string, array{list<string>, string}> */
public function dataProviderAll(): Generator
{
yield 'two enums' => [
[
'Cdn77\EntityFqnExtractor\Fixtures\SomeDirectory\Enum1',
'Cdn77\EntityFqnExtractor\Fixtures\SomeDirectory\Enum2',
],
'/Fixtures/SomeDirectory/EnumsFixture.php',
];

yield 'class and interface' => [
[
'Cdn77\EntityFqnExtractor\Fixtures\SomeDirectory\A',
'Cdn77\EntityFqnExtractor\Fixtures\SomeDirectory\B',
],
'/Fixtures/SomeDirectory/EnumInterfaceFixture.php',
];
}

/** @dataProvider dataProviderGetThrows */
public function testGetThrows(string $expectedMessage, string $path): void
{
$this->expectException(EnumDefinitionInFileIsInvalid::class);
$this->expectExceptionMessage($expectedMessage);
EnumExtractor::get(__DIR__ . $path);
}

/** @return Generator<string, array{string, string}> */
public function dataProviderGetThrows(): Generator
{
yield 'class' => ['There is no enum in a file', '/Fixtures/SomeDirectory/ClassFixture.php'];
yield 'interface' => ['There is no enum in a file', '/Fixtures/SomeDirectory/InterfaceFixture.php'];
yield 'trait' => ['There is no enum in a file', '/Fixtures/SomeDirectory/TraitFixture.php'];
yield 'multiple enums' => [
'There are multiple enums in a file',
'/Fixtures/SomeDirectory/EnumsFixture.php',
];
}
}
17 changes: 17 additions & 0 deletions tests/Fixtures/SomeDirectory/EnumInterfaceFixture.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Cdn77\EntityFqnExtractor\Fixtures\SomeDirectory;

// @codingStandardsIgnoreFile
enum A
{
}

interface InterfaceFixture
{
}

enum B {
}
15 changes: 15 additions & 0 deletions tests/Fixtures/SomeDirectory/EnumsFixture.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Cdn77\EntityFqnExtractor\Fixtures\SomeDirectory;

// @codingStandardsIgnoreFile

enum Enum1
{
}

enum Enum2
{
}

0 comments on commit b565bae

Please sign in to comment.