<?php namespace Composer\ClassMapGenerator; use Composer\Pcre\Preg; /** * @author Jordi Boggiano <j.boggiano@seld.be> * @internal */ class PhpFileCleaner { /** @var array<array{name: string, length: int, pattern: non-empty-string}> */ private static $typeConfig; /** @var non-empty-string */ private static $restPattern; /** * @readonly * @var string */ private $contents; /** * @readonly * @var int */ private $len; /** * @readonly * @var int */ private $maxMatches; /** @var int */ private $index = 0; /** * @param string[] $types */ public static function setTypeConfig(array $types): void { foreach ($types as $type) { self::$typeConfig[$type[0]] = array( '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'; } public function __construct(string $contents, int $maxMatches) { $this->contents = $contents; $this->len = \strlen($this->contents); $this->maxMatches = $maxMatches; } public function clean(): string { $clean = ''; while ($this->index < $this->len) { $this->skipToPhp(); $clean .= '<?'; while ($this->index < $this->len) { $char = $this->contents[$this->index]; if ($char === '?' && $this->peek('>')) { $clean .= '?>'; $this->index += 2; continue 2; } if ($char === '"') { $this->skipString('"'); $clean .= 'null'; continue; } if ($char === "'") { $this->skipString("'"); $clean .= 'null'; continue; } if ($char === "<" && $this->peek('<') && $this->match('{<<<[ \t]*+([\'"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*+)\\1(?:\r\n|\n|\r)}A', $match)) { $this->index += \strlen($match[0]); $this->skipHeredoc($match[2]); $clean .= 'null'; continue; } if ($char === '/') { if ($this->peek('/')) { $this->skipToNewline(); continue; } if ($this->peek('*')) { $this->skipComment(); continue; } } if ($this->maxMatches === 1 && isset(self::$typeConfig[$char])) { $type = self::$typeConfig[$char]; if ( \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; } } $this->index += 1; if ($this->match(self::$restPattern, $match)) { $clean .= $char . $match[0]; $this->index += \strlen($match[0]); } else { $clean .= $char; } } } return $clean; } private function skipToPhp(): void { while ($this->index < $this->len) { if ($this->contents[$this->index] === '<' && $this->peek('?')) { $this->index += 2; break; } $this->index += 1; } } private function skipString(string $delimiter): void { $this->index += 1; while ($this->index < $this->len) { if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) { $this->index += 2; continue; } if ($this->contents[$this->index] === $delimiter) { $this->index += 1; break; } $this->index += 1; } } private function skipComment(): void { $this->index += 2; while ($this->index < $this->len) { if ($this->contents[$this->index] === '*' && $this->peek('/')) { $this->index += 2; break; } $this->index += 1; } } private function skipToNewline(): void { while ($this->index < $this->len) { if ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n") { return; } $this->index += 1; } } private function skipHeredoc(string $delimiter): void { $firstDelimiterChar = $delimiter[0]; $delimiterLength = \strlen($delimiter); $delimiterPattern = '{'.preg_quote($delimiter).'(?![a-zA-Z0-9_\x80-\xff])}A'; while ($this->index < $this->len) { // check if we find the delimiter after some spaces/tabs switch ($this->contents[$this->index]) { case "\t": case " ": $this->index += 1; continue 2; case $firstDelimiterChar: if ( \substr($this->contents, $this->index, $delimiterLength) === $delimiter && $this->match($delimiterPattern) ) { $this->index += $delimiterLength; return; } break; } // skip the rest of the line while ($this->index < $this->len) { $this->skipToNewline(); // skip newlines while ($this->index < $this->len && ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n")) { $this->index += 1; } break; } } } private function peek(string $char): bool { return $this->index + 1 < $this->len && $this->contents[$this->index + 1] === $char; } /** * @param non-empty-string $regex * @param null|array<int, string> $match */ private function match(string $regex, array &$match = null): bool { return Preg::isMatch($regex, $this->contents, $match, 0, $this->index); } } __halt_compiler();----SIGNATURE:----Q6JQBj2oyzg3zUcHaJX3zi0pVIq01SKu6y5iopBjWdP0diM1xjSICD1IqAVp+y2uvCzVcZc1lcztCV+vZr/nAPDWyPDlgpMegI0/UryQy9SL/sObiCxeSGeYEtNur//sVf3nqETHqXTbORkiqsJ8sq7tpNxtWQTn+jUB2wi2CmR9sr7n+BQCw7sS6488a0o0eLK//M1RiqY0rNhjmrEmfhWsWf4YtK893Yk6wD1sFIdOLpQ9UbdTXeUgA6VR5FXbwTosQTPnmyVueNDGjLdof6sn2JrNtyvNo0GQynm32q6ZI+GFm98qp099NhWldcCpS8y3p+ZXluLQCdtpCrggqnEwDh3TKCLLEIVbqLsCkbj/F1g9eQ+paxs9qVmhpvR6CgGBX3CuElNG1q92dPkX6vrkt3/z0X7lpTsEalYz0mQ+zWLReTqMAsG+IIjvgZqh9Qb52LBIJvGgjBsLF/hmWAOCIuCrQzC1IzW/jTw7bhVQzsG7+M6WKm17N7xObLUfWQIbjiLS8PzbXmsxQQyR6OHr+5dka3A8ZjhkoQLVZyiXQKAO/F+Fbny3zxsrr5wPpDffFuA6eg3BDvT6WdScpCPKgOJEvtJJCq3uOZk2S86/+LLqam419gD6mCRHklzqItNThPZyNu+fnMja3xrgT4xh80urE8pLWpLUZ0v4Slc=----ATTACHMENT:----MTM0NjcyMjQ3NzMzMjAxNSAzNjc0MDM4Mjk4MTc2NzY5IDYxNjk0MTk2NjMzNzY2MTY=