Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 7 additions & 0 deletions .changes/nextrelease/phpstan-annotations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"type": "feature",
"category": "",
"description": "Adds `@phpstan-method` method annotations for better service client static analysis support."
}
]
36 changes: 36 additions & 0 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Static Analysis

on:
push:
branches: [master]
pull_request:
branches: [master]

permissions:
contents: read

jobs:
phpstan:
runs-on: ubuntu-latest
name: PHPStan
steps:
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
coverage: none
php-version: '8.1'
ini-values: memory_limit=4G

- name: Checkout codebase
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Install dependencies
run: composer install --no-interaction --prefer-dist --no-progress

- name: PHPStan
run: |
composer require --dev nette/neon "^3.4.4" phpstan/phpstan "2.2.2" --ignore-platform-req=php --update-with-all-dependencies
vendor/bin/phpstan analyze src
5 changes: 0 additions & 5 deletions .github/workflows/tests-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,3 @@ jobs:

- name: Run test suite
run: make test

- name: Static analysis
run: |
composer require --dev nette/neon "^3.4.4" phpstan/phpstan "2.2.2" --ignore-platform-req=php --update-with-all-dependencies
vendor\bin\phpstan analyse src
6 changes: 0 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,6 @@ jobs:
- name: Run test suite
run: make test

# static analysis
- name: Static analysis
run: |
composer require --dev nette/neon "^3.4.4" phpstan/phpstan "2.2.2" --ignore-platform-req=php --update-with-all-dependencies
vendor/bin/phpstan analyse src

# generate package
- if: ${{ matrix.composer-options == '' }}
name: Package generation
Expand Down
87 changes: 83 additions & 4 deletions build/ClassAnnotationUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,21 @@ private function buildUpdatedDocBlock()
$this->reflection->getDocComment() ?: $this->defaultDocBlock
);

// remove lines matching exclusion patterns
// 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 = array_filter($docBlockLines, function ($line) {
return !preg_match($this->removeMatching, trim($line));
});
$docBlockLines = $this->stripAnnotationBlocks($docBlockLines);
}

// hold on to the closing line
$lastLine = array_pop($docBlockLines);


// add a padding line if needed
if (' *' !== end($docBlockLines)) {
$docLines []= ' *';
Expand All @@ -117,4 +122,78 @@ private function writeClassFile($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;
}
}



Loading