Skip to content

feat: improve namespaces PHPFile #22

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 1 commit into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/PhpFileCleaner.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ class PhpFileCleaner
public static function setTypeConfig(array $types): void
{
foreach ($types as $type) {
self::$typeConfig[$type[0]] = array(
self::$typeConfig[$type[0]] = [
'name' => $type,
'length' => \strlen($type),
'pattern' => '{.\b(?<![\$:>])'.$type.'\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais',
);
];
}

self::$restPattern = '{[^?"\'</'.implode('', array_keys(self::$typeConfig)).']+}A';
Expand Down Expand Up @@ -110,6 +110,7 @@ public function clean(): string
$this->skipToNewline();
continue;
}

if ($this->peek('*')) {
$this->skipComment();
continue;
Expand All @@ -122,9 +123,7 @@ public function clean(): string
\substr($this->contents, $this->index, $type['length']) === $type['name']
&& Preg::isMatch($type['pattern'], $this->contents, $match, 0, $this->index - 1)
) {
$clean .= $match[0];

return $clean;
return $clean . $match[0];
}
}

Expand Down Expand Up @@ -161,10 +160,12 @@ private function skipString(string $delimiter): void
$this->index += 2;
continue;
}

if ($this->contents[$this->index] === $delimiter) {
$this->index += 1;
break;
}

$this->index += 1;
}
}
Expand All @@ -188,6 +189,7 @@ private function skipToNewline(): void
if ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n") {
return;
}

$this->index += 1;
}
}
Expand All @@ -214,6 +216,7 @@ private function skipHeredoc(string $delimiter): void

return;
}

break;
}

Expand Down
37 changes: 21 additions & 16 deletions src/PhpFileParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace Composer\ClassMapGenerator;

use RuntimeException;
use Composer\Pcre\Preg;

/**
Expand All @@ -23,16 +24,17 @@ class PhpFileParser
* Extract the classes in the given file
*
* @param string $path The file to check
* @throws \RuntimeException
* @throws RuntimeException
* @return list<class-string> The found classes
*/
public static function findClasses(string $path): array
{
$extraTypes = self::getExtraTypes();

if (!function_exists('php_strip_whitespace')) {
throw new \RuntimeException('Classmap generation relies on the php_strip_whitespace function, but it has been disabled by the disable_functions directive.');
throw new RuntimeException('Classmap generation relies on the php_strip_whitespace function, but it has been disabled by the disable_functions directive.');
}

// Use @ here instead of Silencer to actively suppress 'unhelpful' output
// @link https://github.com/composer/composer/pull/4886
$contents = @php_strip_whitespace($path);
Expand All @@ -43,21 +45,23 @@ public static function findClasses(string $path): array
$message = 'File at "%s" is not readable, check its permissions';
} elseif ('' === trim((string) file_get_contents($path))) {
// The input file was really empty and thus contains no classes
return array();
return [];
} else {
$message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted';
}

$error = error_get_last();
if (isset($error['message'])) {
$message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message'];
}
throw new \RuntimeException(sprintf($message, $path));

throw new RuntimeException(sprintf($message, $path));
}

// return early if there is no chance of matching anything in this file
Preg::matchAllStrictGroups('{\b(?:class|interface|trait'.$extraTypes.')\s}i', $contents, $matches);
if (0 === \count($matches)) {
return array();
if ([] === $matches) {
return [];
}

$p = new PhpFileCleaner($contents, count($matches[0]));
Expand All @@ -71,22 +75,26 @@ public static function findClasses(string $path): array
)
}ix', $contents, $matches);

$classes = array();
$classes = [];
$namespace = '';

for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
for ($i = 0, $len = count($matches['type']); $i < $len; ++$i) {
if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') {
$namespace = str_replace(array(' ', "\t", "\r", "\n"), '', (string) $matches['nsname'][$i]) . '\\';
$namespace = str_replace([' ', "\t", "\r", "\n"], '', (string) $matches['nsname'][$i]) . '\\';
} else {
$name = $matches['name'][$i];
assert(is_string($name));
// skip anon classes extending/implementing
if ($name === 'extends' || $name === 'implements') {
if ($name === 'extends') {
continue;
}
if ($name === 'implements') {
continue;
}

if ($name[0] === ':') {
// This is an XHP class, https://github.com/facebook/xhp
$name = 'xhp'.substr(str_replace(array('-', ':'), array('_', '__'), $name), 1);
$name = 'xhp'.substr(str_replace(['-', ':'], ['_', '__'], $name), 1);
} elseif (strtolower((string) $matches['type'][$i]) === 'enum') {
// something like:
// enum Foo: int { HERP = '123'; }
Expand All @@ -101,6 +109,7 @@ public static function findClasses(string $path): array
$name = substr($name, 0, $colonPos);
}
}

/** @var class-string */
$className = ltrim($namespace . $name, '\\');
$classes[] = $className;
Expand All @@ -110,9 +119,6 @@ public static function findClasses(string $path): array
return $classes;
}

/**
* @return string
*/
private static function getExtraTypes(): string
{
static $extraTypes = null;
Expand All @@ -123,7 +129,7 @@ private static function getExtraTypes(): string
$extraTypes .= '|enum';
}

$extraTypesArray = array_filter(explode('|', $extraTypes), function (string $type) {
$extraTypesArray = array_filter(explode('|', $extraTypes), function (string $type): bool {
return $type !== '';
});
PhpFileCleaner::setTypeConfig(array_merge(['class', 'interface', 'trait'], $extraTypesArray));
Expand All @@ -140,7 +146,6 @@ private static function getExtraTypes(): string
*
* @see Composer\Util\Filesystem::isReadable
*
* @param string $path
* @return bool
*/
private static function isReadable(string $path)
Expand Down