Skip to content

Commit 5e52533

Browse files
authored
bugfix: omit PHPStan method annotations from Ec2Client (#3349)
1 parent c8f42d0 commit 5e52533

6 files changed

Lines changed: 106 additions & 12142 deletions

File tree

‎build/ClientAnnotator.php‎

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
* read this tag; PhpStorm ignores it entirely (so its parser doesn't
1616
* trip on array shapes it doesn't understand).
1717
*
18-
* Pairing invariant: every legacy `@method` must have a matching
19-
* `@phpstan-method`. When an operation has no modeled input (or its input
20-
* shape is empty), we still emit the unsealed empty shape `array{...}` so
21-
* consumers and static-analysis tools see a uniform surface across all
18+
* Unless a service is explicitly excluded, every legacy `@method` has a
19+
* matching `@phpstan-method`. When an operation has no modeled input (or its
20+
* input shape is empty), we still emit the unsealed empty shape `array{...}`
21+
* so consumers and static-analysis tools see a uniform surface across all
2222
* operations.
2323
*/
2424
class ClientAnnotator
@@ -36,6 +36,15 @@ class ClientAnnotator
3636
private const GENERATED_ANNOTATION_PATTERN =
3737
'/^\* @(?:phpstan-)?method (\\\\Aws\\\\Result|\\\\GuzzleHttp\\\\Promise\\\\Promise)[ <(]/';
3838

39+
/**
40+
* EC2's inline shapes make Ec2Client.php large enough that PHPStan may
41+
* not terminate when resolving the class. Keep its legacy annotations
42+
* while a more compact representation is investigated.
43+
*/
44+
private const PHPSTAN_METHOD_EXCLUDED_ENDPOINTS = [
45+
'ec2',
46+
];
47+
3948
/** @var ReflectionClass */
4049
private $reflection;
4150

@@ -114,16 +123,17 @@ public function updateApiMethodAnnotations()
114123
}
115124

116125
/**
117-
* Returns the list of docblock lines to emit. Each operation produces
118-
* a `@method` line (for PhpStorm + runtime baseline) immediately
119-
* followed by a `@phpstan-method` line (or lines, for multi-line
120-
* shapes) carrying the input array shape (for PHPStan/Psalm). When
121-
* the operation has no modeled input, we fall back to `array{...}`
122-
* so the pair invariant always holds.
126+
* Returns the list of docblock lines to emit. Each operation produces a
127+
* `@method` line for PhpStorm and other IDEs. Unless the service is
128+
* excluded, it is immediately followed by a `@phpstan-method` line (or
129+
* lines, for multi-line shapes) carrying the input array shape for
130+
* PHPStan and Psalm. When the operation has no modeled input, we fall
131+
* back to `array{...}`.
123132
*/
124133
private function getMethodAnnotations()
125134
{
126135
$annotations = [];
136+
$emitPhpstanMethods = $this->shouldEmitPhpstanMethodAnnotations();
127137
$latestVersion = $this->getLatestVersion();
128138

129139
foreach ($this->getMethods() as $command => $apiVersions) {
@@ -132,29 +142,28 @@ private function getMethodAnnotations()
132142
"{$command}Async" => '\\GuzzleHttp\\Promise\\Promise',
133143
];
134144

135-
// Look up the input shape once per operation. Both the sync
136-
// and async variants share the same input.
137-
//
138-
// When the operation has no modeled input (`input` key absent
139-
// from api-2.json, e.g. AutoScaling::DescribeAdjustmentTypes,
140-
// Acm::GetAccountConfiguration) or its input shape is empty,
141-
// we still emit a paired `@phpstan-method` carrying the
142-
// unsealed empty shape `array{...}`. Keeping the pair
143-
// invariant means downstream consumers and the sweep tool
144-
// never see a mixed `@method`/`@phpstan-method` surface.
145-
$inputShape = $this->resolveInputShape(
146-
$command,
147-
$latestVersion,
148-
$apiVersions
149-
);
150-
$renderedShape = ($inputShape !== null)
151-
? $this->getFormatter($latestVersion)->formatInput($inputShape)
152-
: 'array{...}';
153-
if ($renderedShape === null) {
154-
// Formatter returned null for an exotic shape (e.g. an
155-
// input keyed to a non-structure type). Fall back to the
156-
// unsealed empty shape so the pair invariant still holds.
157-
$renderedShape = 'array{...}';
145+
if ($emitPhpstanMethods) {
146+
// Look up the input shape once per operation. Both the sync
147+
// and async variants share the same input.
148+
//
149+
// When the operation has no modeled input (`input` key absent
150+
// from api-2.json, e.g. AutoScaling::DescribeAdjustmentTypes,
151+
// Acm::GetAccountConfiguration) or its input shape is empty,
152+
// emit the unsealed empty shape `array{...}`.
153+
$inputShape = $this->resolveInputShape(
154+
$command,
155+
$latestVersion,
156+
$apiVersions
157+
);
158+
$renderedShape = ($inputShape !== null)
159+
? $this->getFormatter($latestVersion)
160+
->formatInput($inputShape)
161+
: 'array{...}';
162+
if ($renderedShape === null) {
163+
// Formatter returned null for an exotic shape (e.g. an
164+
// input keyed to a non-structure type).
165+
$renderedShape = 'array{...}';
166+
}
158167
}
159168

160169
foreach ($commandMethods as $method => $returnType) {
@@ -164,12 +173,14 @@ private function getMethodAnnotations()
164173
$apiVersions
165174
);
166175

167-
foreach ($this->getPhpstanAnnotationLines(
168-
$method,
169-
$returnType,
170-
$renderedShape
171-
) as $line) {
172-
$annotations []= $line;
176+
if ($emitPhpstanMethods) {
177+
foreach ($this->getPhpstanAnnotationLines(
178+
$method,
179+
$returnType,
180+
$renderedShape
181+
) as $line) {
182+
$annotations []= $line;
183+
}
173184
}
174185
}
175186
}
@@ -333,6 +344,15 @@ private function getLatestVersion(): string
333344
return end($versions);
334345
}
335346

347+
private function shouldEmitPhpstanMethodAnnotations(): bool
348+
{
349+
return !in_array(
350+
$this->getEndpoint(),
351+
self::PHPSTAN_METHOD_EXCLUDED_ENDPOINTS,
352+
true
353+
);
354+
}
355+
336356
private function getApiDefinition($version = 'latest')
337357
{
338358
$provider = ApiProvider::defaultProvider();

‎build/PhpstanShapeFormatter.php‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,12 @@ public function __construct(array $apiDefinition)
9494
* or not a structure type).
9595
*
9696
* Empty-input operations (modeled as a structure with `"members": {}`)
97-
* return the unsealed empty shape `array{...}`. This keeps the
98-
* `@method` / `@phpstan-method` pairing invariant in ClientAnnotator —
99-
* every legacy `@method` we emit must have a matching `@phpstan-method`
100-
* so consumers and static-analysis tools never see a mixed surface.
101-
* `array{...}` is an unsealed array shape with no required keys, so it
102-
* accepts any `array` at runtime without flagging extra keys — exactly
103-
* the semantics consumers want for an op that takes no modeled args
104-
* but may receive an empty (or sentinel) array.
97+
* return the unsealed empty shape `array{...}`. For services that emit
98+
* PHPStan annotations, this keeps the `@method` / `@phpstan-method`
99+
* pairing invariant. `array{...}` is an unsealed array shape with no
100+
* required keys, so it accepts any `array` at runtime without flagging
101+
* extra keys — exactly the semantics consumers want for an op that takes
102+
* no modeled args but may receive an empty (or sentinel) array.
105103
*
106104
* @param string $inputShapeName Name of the input shape from operations[op].input.shape
107105
* @return string|null PHPStan/Psalm array-shape string, multi-line for nested shapes

0 commit comments

Comments
 (0)