Skip to content

Commit

Permalink
Refactor class name extraction logic with helper methods
Browse files Browse the repository at this point in the history
Simplify the class name extraction by introducing helper methods for token parsing. This improves readability and maintainability of the code. Added Psalm type annotations for better static analysis.
  • Loading branch information
koriym committed Nov 4, 2024
1 parent d12eac1 commit acb109c
Showing 1 changed file with 114 additions and 67 deletions.
181 changes: 114 additions & 67 deletions src/ClassName.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
/**
* Extracting the fully qualified class name from a given PHP file
*
* @psalm-type TokenValue = array{0: int, 1: string, 2: int}

Check failure on line 27 in src/ClassName.php

View workflow job for this annotation

GitHub Actions / cs / Coding Standards

Incorrect annotations group.
* @psalm-type Token = TokenValue|string
* @psalm-type Tokens = array<int, Token>
* @psalm-type ParserResult = array{string, int}
*
* @psalm-immutable
*/
final class ClassName
Expand All @@ -41,88 +46,130 @@ public static function from(string $filePath): ?string
return null;
}

$namespace = '';
$className = null;
/** @var array<int, array{0: int, 1: string, 2: int}|string> $tokens */
/** @var Tokens $tokens */

Check failure on line 49 in src/ClassName.php

View workflow job for this annotation

GitHub Actions / cs / Coding Standards

Use assertion instead of inline documentation comment.
$tokens = token_get_all(file_get_contents($filePath)); // @phpstan-ignore-line
$count = count($tokens);
$position = 0;
$namespace = '';

$i = 0;
while ($i < $count) {
/** @var array{0: int, 1: string, 2: int}|string $token */
$token = $tokens[$i];

if (is_array($token) && $token[0] === T_NAMESPACE) {
$i++;
while ($i < $count && is_array($tokens[$i]) && $tokens[$i][0] === T_WHITESPACE) {
$i++;
}

/** @var array{0: int, 1: string, 2: int}|string $token */
$token = $tokens[$i];
if (is_array($token)) {
if ($token[0] === self::T_NAME_QUALIFIED) {
$namespace = $token[1];
} elseif ($token[0] === T_STRING) {
/** @var list<string> $namespaceParts */
$namespaceParts = [];
while ($i < $count) {
/** @var array{0: int, 1: string, 2: int}|string $token */
$token = $tokens[$i];
if (is_array($token) && $token[0] === T_STRING) {
$namespaceParts[] = $token[1];
$i++;
if ($i < $count && $tokens[$i] === '\\') {
$namespaceParts[] = '\\';
$i++;
}

continue;
}

if ($token === ';' || $token === '{') {
break;
}

$i++;
}

$namespace = implode('', $namespaceParts);
}
}

while ($position < $count) {
[$token, $position] = self::nextToken($tokens, $position);
if (! is_array($token)) {
continue;
}

if (is_array($token) && in_array($token[0], [T_ABSTRACT, T_FINAL], true)) {
$i++;
continue;
switch ($token[0]) {
case T_NAMESPACE:
[$namespace, $position] = self::parseNamespace($tokens, $position + 1, $count);
continue 2;
case T_CLASS:
$className = self::parseClassName($tokens, $position + 1, $count);
if ($className !== null) {
/** @var string $namespace */
return $namespace !== '' ? $namespace . '\\' . $className : $className;
}
}
}

if (is_array($token) && $token[0] === T_CLASS) {
$i++;
while (
$i < $count &&
is_array($tokens[$i]) &&
in_array($tokens[$i][0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)
) {
$i++;
}
return null;
}

/**
* @param Tokens $tokens

Check failure on line 78 in src/ClassName.php

View workflow job for this annotation

GitHub Actions / cs / Coding Standards

Incorrect annotations group.
* @return array{Token, int}
*/
private static function nextToken(array $tokens, int $position): array
{
if (is_array($tokens[$position]) && in_array($tokens[$position][0], [T_ABSTRACT, T_FINAL], true)) {

Check failure on line 83 in src/ClassName.php

View workflow job for this annotation

GitHub Actions / cs / Coding Standards

Expected 1 line after "if", found 0.
$position++;
}
return [$tokens[$position], $position + 1];

Check failure on line 86 in src/ClassName.php

View workflow job for this annotation

GitHub Actions / cs / Coding Standards

Expected 1 line before "return", found 0.
}

/**

Check failure on line 89 in src/ClassName.php

View workflow job for this annotation

GitHub Actions / cs / Coding Standards

Found multi-line doc comment with single line content, use one-line doc comment instead.
* @param Tokens $tokens
*/
private static function parseClassName(array $tokens, int $position, int $count): ?string
{
$position = self::skipWhitespace($tokens, $position, $count);
if ($position >= $count || ! is_array($tokens[$position])) {
return null;
}

return $tokens[$position][1];
}

if ($i < $count && is_array($tokens[$i])) {
$className = $tokens[$i][1];
/**

Check failure on line 102 in src/ClassName.php

View workflow job for this annotation

GitHub Actions / cs / Coding Standards

Found multi-line doc comment with single line content, use one-line doc comment instead.
* @param Tokens $tokens
*/
private static function skipWhitespace(array $tokens, int $position, int $count): int
{
while (
$position < $count &&
is_array($tokens[$position]) &&
in_array($tokens[$position][0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)
) {
$position++;
}

return $position;
}

/**
* @param Tokens $tokens

Check failure on line 119 in src/ClassName.php

View workflow job for this annotation

GitHub Actions / cs / Coding Standards

Incorrect annotations group.
* @return ParserResult
*/
private static function parseNamespace(array $tokens, int $position, int $count): array
{
$position = self::skipWhitespace($tokens, $position, $count);
if (! is_array($tokens[$position])) {
return ['', $position];

Check warning on line 126 in src/ClassName.php

View check run for this annotation

Codecov / codecov/patch

src/ClassName.php#L126

Added line #L126 was not covered by tests
}

if ($tokens[$position][0] === self::T_NAME_QUALIFIED) {
return [$tokens[$position][1], $position + 1];

Check warning on line 130 in src/ClassName.php

View check run for this annotation

Codecov / codecov/patch

src/ClassName.php#L130

Added line #L130 was not covered by tests
}

return $tokens[$position][0] === T_STRING
? self::parseNamespaceParts($tokens, $position, $count)
: ['', $position];
}

/**
* @param Tokens $tokens

Check failure on line 139 in src/ClassName.php

View workflow job for this annotation

GitHub Actions / cs / Coding Standards

Incorrect annotations group.
* @return ParserResult
*/
private static function parseNamespaceParts(array $tokens, int $position, int $count): array
{
/** @var list<string> $namespaceParts */
$namespaceParts = [];

while ($position < $count) {
/** @var Token $token */

Check failure on line 148 in src/ClassName.php

View workflow job for this annotation

GitHub Actions / cs / Coding Standards

Use assertion instead of inline documentation comment.
$token = $tokens[$position];

if (! is_array($token)) {
if ($token === ';' || $token === '{') {
break;
}
$position++;
continue;

Check warning on line 156 in src/ClassName.php

View check run for this annotation

Codecov / codecov/patch

src/ClassName.php#L155-L156

Added lines #L155 - L156 were not covered by tests
}

$i++;
}
if ($token[0] !== T_STRING) {
$position++;
continue;
}

if ($className === null) {
return null;
$namespaceParts[] = $token[1];
$position++;

if ($position < $count && $tokens[$position] === '\\') {
$namespaceParts[] = '\\';
$position++;

Check warning on line 169 in src/ClassName.php

View check run for this annotation

Codecov / codecov/patch

src/ClassName.php#L168-L169

Added lines #L168 - L169 were not covered by tests
}
}

/** @var string $namespace */
return $namespace !== '' ? $namespace . '\\' . $className : $className;
return [implode('', $namespaceParts), $position];
}
}

0 comments on commit acb109c

Please sign in to comment.