Skip to content

Commit a503f54

Browse files
authored
Fix usage with grouped constant statements (#219)
1 parent d56d480 commit a503f54

File tree

4 files changed

+89
-26
lines changed

4 files changed

+89
-26
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ potentially very difficult to debug due to dissimilar or unsupported package ver
5151
- [PSR-0 support](#psr-0-support)
5252
- [String values](#string-values)
5353
- [Native functions and constants shadowing](#native-functions-shadowing)
54+
- [Grouped constants whitelisting](#grouped-constants-whitelisting)
5455
- [Composer](#composer)
5556
- [Composer Plugins](#composer-plugins)
5657
- [Contributing](#contributing)
@@ -543,6 +544,23 @@ is_array([]);
543544
The situation is exactly the same for constants.
544545

545546

547+
### Grouped constants whitelisting
548+
549+
When a grouped constant declaration like the following is given:
550+
551+
```php
552+
const X = 'foo', Y = 'bar';
553+
```
554+
555+
PHP-Scoper will not be able to whitelist either `X` or `Y`. The statement
556+
above should be replaced by multiple constant statements:
557+
558+
```php
559+
const X = 'foo';
560+
const Y = 'bar';
561+
```
562+
563+
546564
### Composer
547565

548566
PHP-Scoper does not support prefixing the dumped Composer autoloader and autoloading files. This is why you have to

specs/const/const-declaration.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<?php
2727
2828
const FOO_CONST = foo();
29+
const X = 'x', Y = '';
2930
define('BAR_CONST', foo());
3031
define('Acme\BAR_CONST', foo());
3132
define(FOO_CONST, foo());
@@ -37,6 +38,7 @@
3738
namespace Humbug;
3839
3940
const FOO_CONST = \Humbug\foo();
41+
const X = 'x', Y = '';
4042
\define('BAR_CONST', \Humbug\foo());
4143
\define('Humbug\\Acme\\BAR_CONST', \Humbug\foo());
4244
\define(\Humbug\FOO_CONST, \Humbug\foo());
@@ -52,6 +54,7 @@
5254
<?php
5355
5456
const FOO_CONST = foo();
57+
const X = 'x', Y = '';
5558
define('BAR_CONST', foo());
5659
define('Acme\BAR_CONST', foo());
5760
define(FOO_CONST, foo());
@@ -62,6 +65,7 @@
6265
6366
namespace {
6467
const FOO_CONST = \foo();
68+
const X = 'x', Y = '';
6569
\define('BAR_CONST', \foo());
6670
\define('Acme\\BAR_CONST', \foo());
6771
\define(\FOO_CONST, \foo());
@@ -78,6 +82,7 @@
7882
<?php
7983
8084
const FOO_CONST = foo();
85+
const X = 'x', Y = '';
8186
define('BAR_CONST', foo());
8287
define('Acme\BAR_CONST', foo());
8388
define(FOO_CONST, foo());
@@ -89,12 +94,23 @@
8994
namespace Humbug;
9095
9196
\define('FOO_CONST', \Humbug\foo());
97+
const X = 'x', Y = '';
9298
\define('BAR_CONST', \Humbug\foo());
9399
\define('Acme\\BAR_CONST', \Humbug\foo());
94100
\define(\FOO_CONST, \Humbug\foo());
95101
\define(\FOO_CONST, \Humbug\foo());
96102
\define(\Acme\BAR_CONST, \Humbug\foo());
97103

104+
PHP
105+
],
106+
107+
'Whitelisted grouped constants declaration in the global namespace' => [
108+
'whitelist' => ['X'],
109+
'payload' => <<<'PHP'
110+
<?php
111+
112+
const X = 'x', Y = '';
113+
----
98114
PHP
99115
],
100116

@@ -105,6 +121,7 @@
105121
namespace Acme;
106122
107123
const FOO_CONST = foo();
124+
const X = 'x', Y = '';
108125
define('BAR_CONST', foo());
109126
define('Acme\BAR_CONST', foo());
110127
define(FOO_CONST, foo());
@@ -116,6 +133,7 @@
116133
namespace Humbug\Acme;
117134
118135
const FOO_CONST = foo();
136+
const X = 'x', Y = '';
119137
\define('BAR_CONST', foo());
120138
\define('Humbug\\Acme\\BAR_CONST', foo());
121139
\define(FOO_CONST, foo());
@@ -133,6 +151,7 @@
133151
namespace Acme;
134152
135153
const FOO_CONST = foo();
154+
const X = 'x', Y = '';
136155
define('BAR_CONST', foo());
137156
define('Acme\BAR_CONST', foo());
138157
define(FOO_CONST, foo());
@@ -144,6 +163,7 @@
144163
namespace Acme;
145164
146165
const FOO_CONST = foo();
166+
const X = 'x', Y = '';
147167
\define('BAR_CONST', foo());
148168
\define('Acme\\BAR_CONST', foo());
149169
\define(FOO_CONST, foo());
@@ -161,6 +181,7 @@
161181
namespace Acme;
162182
163183
const FOO_CONST = foo();
184+
const X = 'x', Y = '';
164185
define('BAR_CONST', foo());
165186
define('Acme\BAR_CONST', foo());
166187
define(FOO_CONST, foo());
@@ -172,6 +193,7 @@
172193
namespace Humbug\Acme;
173194
174195
const FOO_CONST = foo();
196+
const X = 'x', Y = '';
175197
\define('BAR_CONST', foo());
176198
\define('Acme\\BAR_CONST', foo());
177199
\define(FOO_CONST, foo());

src/PhpParser/NodeVisitor/ConstStmtReplacer.php

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020
use PhpParser\Node\Expr\FuncCall;
2121
use PhpParser\Node\Name;
2222
use PhpParser\Node\Name\FullyQualified;
23-
use PhpParser\Node\Param;
2423
use PhpParser\Node\Scalar\String_;
2524
use PhpParser\Node\Stmt\Expression;
2625
use PhpParser\NodeVisitorAbstract;
26+
use UnexpectedValueException;
27+
use function count;
2728

2829
/**
2930
* Replaces const declaration by define.
@@ -62,31 +63,38 @@ public function enterNode(Node $node): Node
6263
return $node;
6364
}
6465

65-
// TODO: check this: when can Node\Stmt\Const_ be empty or have more than one constant
66-
/** @var Node\Const_ $constant */
67-
$constant = current($node->consts);
66+
foreach ($node->consts as $constant) {
67+
/** @var Node\Const_ $constant */
68+
$resolvedConstantName = $this->nameResolver->resolveName(
69+
new Name(
70+
(string) $constant->name,
71+
$node->getAttributes()
72+
)
73+
)->getName();
6874

69-
$resolvedConstantName = $this->nameResolver->resolveName(
70-
new Name(
71-
(string) $constant->name,
72-
$node->getAttributes() // Take the parent node attribute since no "parent" attribute is recorded in
73-
// Node\Const_
74-
// TODO: check with nikic if this is expected
75-
)
76-
)->getName();
75+
if (false === $this->whitelist->isClassWhitelisted((string) $resolvedConstantName)) {
76+
continue;
77+
}
7778

78-
if (false === $this->whitelist->isClassWhitelisted((string) $resolvedConstantName)) {
79-
return $node;
79+
if (count($node->consts) > 1) {
80+
throw new UnexpectedValueException(
81+
'Whitelisting a constant declared in a grouped constant statement (e.g. `const FOO = '
82+
.'\'foo\', BAR = \'bar\'; is not supported. Consider breaking it down in multiple constant '
83+
.'declaration statements'
84+
);
85+
}
86+
87+
return new Expression(
88+
new FuncCall(
89+
new FullyQualified('define'),
90+
[
91+
new String_((string) $resolvedConstantName),
92+
$constant->value,
93+
]
94+
)
95+
);
8096
}
8197

82-
return new Expression(
83-
new FuncCall(
84-
new FullyQualified('define'),
85-
[
86-
new String_((string) $resolvedConstantName),
87-
$constant->value,
88-
]
89-
)
90-
);
98+
return $node;
9199
}
92100
}

tests/Scoper/PhpScoperTest.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
use Roave\BetterReflection\SourceLocator\Type\StringSourceLocator;
3737
use Symfony\Component\Finder\Finder;
3838
use Throwable;
39+
use UnexpectedValueException;
3940
use function Humbug\PhpScoper\create_fake_patcher;
4041
use function Humbug\PhpScoper\create_parser;
4142
use function implode;
@@ -422,7 +423,7 @@ function (...$args) use (&$i): bool {
422423
/**
423424
* @dataProvider provideValidFiles
424425
*/
425-
public function test_can_scope_valid_files(string $spec, string $contents, string $prefix, Whitelist $whitelist, string $expected)
426+
public function test_can_scope_valid_files(string $spec, string $contents, string $prefix, Whitelist $whitelist, ?string $expected)
426427
{
427428
$filePath = 'file.php';
428429

@@ -448,7 +449,21 @@ public function test_can_scope_valid_files(string $spec, string $contents, strin
448449
)
449450
);
450451

451-
$actual = $this->scoper->scope($filePath, $contents, $prefix, $patchers, $whitelist);
452+
try {
453+
$actual = $this->scoper->scope($filePath, $contents, $prefix, $patchers, $whitelist);
454+
455+
if (null === $expected) {
456+
$this->fail('Expected exception to be thrown.');
457+
}
458+
} catch (UnexpectedValueException $exception) {
459+
if (null !== $expected) {
460+
throw $exception;
461+
}
462+
463+
$this->assertTrue(true);
464+
465+
return;
466+
}
452467

453468
$formattedWhitelist = 0 === count($whitelist)
454469
? '[]'
@@ -559,7 +574,7 @@ private function parseSpecFile(array $meta, $fixtureTitle, $fixtureSet): Generat
559574
$fixtureSet['whitelist-global-constants'] ?? $meta['whitelist-global-constants'],
560575
...($fixtureSet['whitelist'] ?? $meta['whitelist'])
561576
),
562-
$payloadParts[1], // Expected output
577+
'' === $payloadParts[1] ? null : $payloadParts[1], // Expected output
563578
];
564579
}
565580
}

0 commit comments

Comments
 (0)