idrivee2-media-upload/vendor/aws/aws-sdk-php/build/ClassAnnotationUpdater.php
2026-09-18 14:34:43 +00:00

196 lines
6 KiB
PHP

<?php
/**
* Adds and removes annotations to a class.
*
* @internal
*/
class ClassAnnotationUpdater
{
use PhpFileLinterTrait;
/** @var ReflectionClass */
private $reflection;
/** @var string[] */
private $linesToAppend;
/** @var string */
private $defaultDocBlock;
/** @var string */
private $removeMatching;
public function __construct(
ReflectionClass $reflection,
array $linesToAppend,
$defaultDocBlock,
$removeMatching = ''
) {
$this->reflection = $reflection;
$this->linesToAppend = $linesToAppend;
$this->defaultDocBlock = $defaultDocBlock;
$this->removeMatching = $removeMatching;
}
/**
* Performs update on class file and lints the output. If the output fails
* linting, the change is reverted.
*
* @return bool TRUE on success, FALSE on failure
*/
public function update()
{
// copy the code into memory
$backup = file($this->reflection->getFileName());
list($preamble, $class) = $this->splitClassFile($backup);
$preamble = $this->stripOutExistingDocBlock($preamble);
$preamble .= $this->buildUpdatedDocBlock();
if ($this->writeClassFile(implode(PHP_EOL, [$preamble, $class]))
&& $this->commandLineLint($this->reflection->getFileName())
) {
return true;
}
$this->writeClassFile(implode('', $backup));
return false;
}
private function splitClassFile(array $lines)
{
$classLineOffset = $this->reflection->getStartLine() - 1;
return [
implode('', array_slice($lines, 0, $classLineOffset)),
implode('', array_slice($lines, $classLineOffset)),
];
}
private function stripOutExistingDocBlock($preamble)
{
if ($this->reflection->getDocComment()) {
return str_replace(
$this->reflection->getDocComment() . PHP_EOL,
'',
$preamble
);
}
return $preamble;
}
private function buildUpdatedDocBlock()
{
$docBlockLines = explode(
PHP_EOL,
$this->reflection->getDocComment() ?: $this->defaultDocBlock
);
// Remove annotation lines matching the exclusion pattern. The
// pattern matches the opening line of a generated annotation; when
// that line opens a multi-line `@phpstan-method` block, we also
// strip the continuation lines up to and including the block
// terminator (the line ending with `$args = [])`). This keeps
// regeneration idempotent even when previous runs produced
// multi-line array shapes.
if ($this->removeMatching) {
$docBlockLines = $this->stripAnnotationBlocks($docBlockLines);
}
// hold on to the closing line
$lastLine = array_pop($docBlockLines);
// add a padding line if needed
if (' *' !== end($docBlockLines)) {
$docLines []= ' *';
}
// append API @method annotations
$docBlockLines = array_merge($docBlockLines, $this->linesToAppend);
// add back the closing line
$docBlockLines []= $lastLine;
// send everything back as a string
return implode(PHP_EOL, $docBlockLines);
}
private function writeClassFile($contents)
{
return (bool) file_put_contents(
$this->reflection->getFileName(),
$contents,
LOCK_EX
);
}
/**
* Block-aware filter: walks the docblock lines and drops any run that
* begins with a line matching the exclusion pattern. When the opener
* is a `@phpstan-method` line that starts a multi-line array-shape
* block (open `(` without a matching `)` on the same line), we
* continue dropping lines until we consume the terminator (the line
* that closes the outer `(...)`).
*
* A line is considered a *complete* single-line annotation when its
* open and close paren counts balance. This handles the legacy
* form:
*
* * @method \Aws\Result foo(array $args = [])
*
* as well as its version-tagged variant:
*
* * @method \Aws\Result foo(array $args = []) (supported in versions X)
*
* both of which have balanced parens on a single line. Multi-line
* `@phpstan-method` blocks look like:
*
* * @phpstan-method \Aws\Result foo(array{
* * Key?: string,
* * ...,
* * } $args = [])
*
* where the opening line has an unbalanced `(` and the closing line
* balances it. Using paren balance (rather than looking for a
* specific `$args = [])` suffix) means the trailing "(supported in
* versions ...)" tag doesn't get mistaken for a continuation opener.
*
* @param string[] $lines Docblock lines in original order
* @return string[]
*/
private function stripAnnotationBlocks(array $lines): array
{
$out = [];
$depth = 0;
foreach ($lines as $line) {
$trimmed = trim($line);
if ($depth > 0) {
// We're inside a multi-line block: keep dropping lines
// and tracking paren balance until we close the outer
// `(`.
$depth += substr_count($trimmed, '(') - substr_count($trimmed, ')');
if ($depth <= 0) {
$depth = 0;
}
continue;
}
if (preg_match($this->removeMatching, $trimmed)) {
$opens = substr_count($trimmed, '(');
$closes = substr_count($trimmed, ')');
if ($opens > $closes) {
// Opener of a multi-line block. Initialise depth to
// the net-open count from this line.
$depth = $opens - $closes;
}
// Either way, drop the opener line.
continue;
}
$out[] = $line;
}
return $out;
}
}