Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/Internal/StringLiteralHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php declare(strict_types = 1);

namespace PHPStan\Internal;

use function addcslashes;
use function preg_match;
use function str_contains;
use function strtr;
use function sprintf;

final class StringLiteralHelper
{

public static function escape(string $string): string
{
return addcslashes($string, "\0..\37\177");
}

public static function escapeAndQuoteIfNeeded(string $string): string
{
if ($string === '') {
return "''";
}

$escaped = self::escape($string);

if (str_contains($escaped, '\\') || str_contains($escaped, "'")) {
return sprintf('"%s"', strtr($escaped, ['"' => '\\"']));
}

if (preg_match('/[!-\/:-@\[-^`\{-~]/', $escaped) === 1 || preg_match('/\p{Zs}/u', $escaped) === 1) {
return sprintf("'%s'", strtr($escaped, ['\\' => '\\\\']));
}

return $escaped;
}

public static function quote(string $string): string
{
if ($string === '') {
return "''";
}

$escaped = self::escape($string);

if (str_contains($escaped, '\\') || str_contains($escaped, "'")) {
return sprintf('"%s"', strtr($escaped, ['"' => '\\"']));
}

return sprintf("'%s'", strtr($escaped, ['\\' => '\\\\']));
}

}
7 changes: 2 additions & 5 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PHPStan\Analyser\OutOfClassScope;
use PHPStan\DependencyInjection\BleedingEdgeToggle;
use PHPStan\Internal\CombinationsHelper;
use PHPStan\Internal\StringLiteralHelper;
use PHPStan\Php\PhpVersion;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
Expand Down Expand Up @@ -1474,11 +1475,7 @@ public function describe(VerbosityLevel $level): string

$keyDescription = $keyType->getValue();
if (is_string($keyDescription)) {
if (str_contains($keyDescription, '"')) {
$keyDescription = sprintf('\'%s\'', $keyDescription);
} elseif (str_contains($keyDescription, '\'')) {
$keyDescription = sprintf('"%s"', $keyDescription);
}
$keyDescription = StringLiteralHelper::escapeAndQuoteIfNeeded($keyDescription);
}

$valueTypeDescription = $valueType->describe($level);
Expand Down
9 changes: 2 additions & 7 deletions src/Type/Constant/ConstantStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PhpParser\Node\Name;
use PHPStan\Analyser\OutOfClassScope;
use PHPStan\DependencyInjection\BleedingEdgeToggle;
use PHPStan\Internal\StringLiteralHelper;
use PHPStan\PhpDocParser\Ast\ConstExpr\QuoteAwareConstExprStringNode;
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
Expand Down Expand Up @@ -43,7 +44,6 @@
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\VerbosityLevel;
use function addcslashes;
use function in_array;
use function is_float;
use function is_int;
Expand Down Expand Up @@ -139,12 +139,7 @@ function (): string {

private function export(string $value): string
{
$escapedValue = addcslashes($value, "\0..\37");
if ($escapedValue !== $value) {
return '"' . addcslashes($value, "\0..\37\\\"") . '"';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The escape of DEL (\177) was missing.

}

return "'" . addcslashes($value, '\\\'') . "'";
return StringLiteralHelper::quote($value);
}

public function isSuperTypeOf(Type $type): TrinaryLogic
Expand Down
182 changes: 182 additions & 0 deletions tests/PHPStan/Rules/Debug/DumpTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,188 @@ public function testRuleInUse(): void
]);
}

public function testRuleSymbols(): void
{
$this->analyse([__DIR__ . '/data/dump-type-symbols.php'], [
[
'Dumped type: array{"\000": \'NUL\', NUL: "\000"}',
6,
],
[
'Dumped type: array{"\001": \'SOH\', SOH: "\001"}',
7,
],
[
'Dumped type: array{"\002": \'STX\', STX: "\002"}',
8
],
[
'Dumped type: array{"\003": \'ETX\', ETX: "\003"}',
9,
],
[
'Dumped type: array{"\004": \'EOT\', EOT: "\004"}',
10,
],
[
'Dumped type: array{"\005": \'ENQ\', ENQ: "\005"}',
11,
],
[
'Dumped type: array{"\006": \'ACK\', ACK: "\006"}',
12,
],
[
'Dumped type: array{"\a": \'BEL\', BEL: "\a"}',
13,
],
[
'Dumped type: array{"\b": \'BS\', BS: "\b"}',
14,
],
[
'Dumped type: array{"\t": \'HT\', HT: "\t"}',
15,
],
[
'Dumped type: array{"\n": \'LF\', LF: "\n"}',
16,
],
[
'Dumped type: array{"\v": \'VT\', VT: "\v"}',
17,
],
[
'Dumped type: array{"\f": \'FF\', FF: "\f"}',
18,
],
[
'Dumped type: array{"\r": \'CR\', CR: "\r"}',
19,
],
[
'Dumped type: array{"\016": \'SO\', SO: "\016"}',
20,
],
[
'Dumped type: array{"\017": \'SI\', SI: "\017"}',
21,
],
[
'Dumped type: array{"\020": \'DLE\', DLE: "\020"}',
22,
],
[
'Dumped type: array{"\021": \'DC1\', DC1: "\021"}',
23,
],
[
'Dumped type: array{"\022": \'DC2\', DC2: "\022"}',
24,
],
[
'Dumped type: array{"\023": \'DC3\', DC3: "\023"}',
25,
],
[
'Dumped type: array{"\024": \'DC4\', DC4: "\024"}',
26,
],
[
'Dumped type: array{"\025": \'NAK\', NAK: "\025"}',
27,
],
[
'Dumped type: array{"\026": \'SYN\', SYN: "\026"}',
28,
],
[
'Dumped type: array{"\027": \'ETB\', ETB: "\027"}',
29,
],
[
'Dumped type: array{"\030": \'CAN\', CAN: "\030"}',
30,
],
[
'Dumped type: array{"\031": \'EM\', EM: "\031"}',
31,
],
[
'Dumped type: array{"\032": \'SUB\', SUB: "\032"}',
32,
],
[
'Dumped type: array{"\033": \'ESC\', ESC: "\033"}',
33,
],
[
'Dumped type: array{"\034": \'FS\', FS: "\034"}',
34,
],
[
'Dumped type: array{"\035": \'GS\', GS: "\035"}',
35,
],
[
'Dumped type: array{"\036": \'RS\', RS: "\036"}',
36,
],
[
'Dumped type: array{"\037": \'US\', US: "\037"}',
37,
],
[
'Dumped type: array{"\177": \'DEL\', DEL: "\177"}',
38,
],
[
'Dumped type: array{\' \': \'SP\', SP: \' \'}',
41,
],
[
"Dumped type: array{'foo ': 'ends with SP', ' foo': 'starts with SP', ' foo ': 'surrounded by SP', foo: 'no SP'}",
42,
],
[
"Dumped type: array{'foo?': 'foo?'}",
45,
],
[
"Dumped type: array{shallwedance: 'yes'}",
46,
],
[
"Dumped type: array{'shallwedance?': 'yes'}",
47,
],
[
"Dumped type: array{'Shall we dance': 'yes'}",
48,
],
[
"Dumped type: array{'Shall we dance?': 'yes'}",
49,
],
[
"Dumped type: array{shall_we_dance: 'yes'}",
50,
],
[
"Dumped type: array{'shall_we_dance?': 'yes'}",
51,
],
[
"Dumped type: array{'shall-we-dance': 'yes'}",
52,
],
[
"Dumped type: array{'shall-we-dance?': 'yes'}",
53,
],
]);
}

public function testBug7803(): void
{
$this->analyse([__DIR__ . '/data/bug-7803.php'], [
Expand Down
Loading