Install phan/phpstan local

This commit is contained in:
Clemens Schwaighofer
2023-02-08 12:02:18 +09:00
parent 53eef03387
commit f94b350ba4
1166 changed files with 298568 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2021 Tyson Andre
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,50 @@
var_representation_polyfill
=============================
[![Build Status](https://github.com/TysonAndre/var_representation_polyfill/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/TysonAndre/var_representation_polyfill/actions/workflows/main.yml?query=branch%3Amain)
[![License](https://img.shields.io/github/license/TysonAndre/var_representation_polyfill.svg)](https://github.com/TysonAndre/var_representation_polyfill/blob/main/LICENSE)
[![Latest Stable Version](https://img.shields.io/github/v/release/TysonAndre/var_representation_polyfill.svg)](https://packagist.org/packages/TysonAndre/var_representation_polyfill)
[var_representation_polyfill](https://github.com/TysonAndre/var_representation_polyfill) is a polyfill for https://pecl.php.net/var_representation
This provides a polyfill for the function `var_representation(mixed $value, int $flags = 0): string`, which converts a
variable to a string in a way that fixes the shortcomings of `var_export()`
See [the var_representation PECL documentation](https://github.com/TysonAndre/var_representation) for more details
Installation
------------
```
composer require tysonandre/var_representation_polyfill
```
Usage
-----
```php
// uses short arrays, and omits array keys if array_is_list() would be true
php > echo var_representation(['a','b']);
[
'a',
'b',
]
// can dump everything on one line.
php > echo var_representation(['a', 'b', 'c'], VAR_REPRESENTATION_SINGLE_LINE);
['a', 'b', 'c']
php > echo var_representation("uses double quotes: \$\"'\\\n");
"uses double quotes: \$\"'\\\n"
// Can disable the escaping of control characters as of polyfill version 0.1.0
// (e.g. if the return value needs to be escaped again)
php > echo var_representation("has\nnewlines", VAR_REPRESENTATION_UNESCAPED);
'has
newlines'
php > echo json_encode("uses single quotes:\0\r\n\$\"'\\");
"uses single quotes:\u0000\r\n$\"'\\"
php > echo json_encode(var_representation("uses single quotes:\0\r\n\$\"'\\",
VAR_REPRESENTATION_UNESCAPED));
"'uses single quotes:\u0000\r\n$\"\\'\\\\'"
```

View File

@@ -0,0 +1,44 @@
{
"name": "tysonandre/var_representation_polyfill",
"description": "Polyfill for var_representation: convert a variable to a string in a way that fixes the shortcomings of var_export",
"keywords": ["var_export", "var_representation"],
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Tyson Andre"
}
],
"config": {
"sort-packages": true,
"platform": {
"php": "7.2.24"
}
},
"require": {
"php": "^7.2.0|^8.0.0",
"ext-tokenizer": "*"
},
"require-dev": {
"phan/phan": "^5.4.1",
"phpunit/phpunit": "^8.5.0"
},
"provide": {
"ext-var_representation": "*"
},
"suggest": {
"ext-var_representation": "For best performance"
},
"autoload": {
"psr-4": {"VarRepresentation\\": "src/VarRepresentation"},
"files": ["src/var_representation.php"]
},
"autoload-dev": {
"psr-4": {"VarRepresentation\\Tests\\": "tests/"}
},
"extra": {
"branch-alias": {
"dev-main": "0.1.3-dev"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,325 @@
<?php
declare(strict_types=1);
namespace VarRepresentation;
use RuntimeException;
use VarRepresentation\Node\Array_;
use VarRepresentation\Node\ArrayEntry;
use VarRepresentation\Node\Group;
use VarRepresentation\Node\Object_;
/**
* Encodes var_export output into var_representation() output
*/
class Encoder
{
/** @var list<string|array{0:int,1:string,2:int}> the raw tokens from token_get_all */
protected $tokens;
/** @var int the last valid index */
protected $endIndex;
/** @var string the original raw var_export output */
protected $raw;
/** @var int the current offset */
protected $i = 1;
/** @var bool whether the flags for the most recent call are VAR_REPRESENTATION_UNESCAPED */
protected $unescaped = false;
protected function __construct(string $raw)
{
$this->tokens = self::getTokensWithoutWhitespace($raw);
$this->endIndex = \count($this->tokens);
$this->raw = $raw;
unset($this->tokens[0]);
}
/**
* Get tokens without T_WHITESPACE tokens
* @return list<string|array{0:int,1:string,2:int}>
* @api
*/
public static function getTokensWithoutWhitespace(string $raw): array
{
$tokens = \token_get_all('<?php ' . $raw);
foreach ($tokens as $i => $token) {
if (\is_array($token) && $token[0] === \T_WHITESPACE) {
unset($tokens[$i]);
}
}
return \array_values($tokens);
}
/**
* Generate a readable var_representation from the original var_export output
* @param mixed $value
* @param int $flags bitmask of flags (VAR_REPRESENTATION_SINGLE_LINE)
*/
public static function toVarRepresentation($value, int $flags = 0): string
{
$raw_string = \var_export($value, true);
if (!\function_exists('token_get_all')) {
return $raw_string;
}
return (new self($raw_string))->encode($flags);
}
/**
* Encode the entire sequence of tokens
*/
protected function encode(int $flags): string
{
$this->unescaped = ($flags & \VAR_REPRESENTATION_UNESCAPED) !== 0;
$result = $this->encodeValue();
if ($this->i !== \count($this->tokens) + 1) {
throw new RuntimeException("Failed to read token #$this->i of $this->raw: " . \var_export($this->tokens[$this->i] ?? null, true));
}
if ($flags & \VAR_REPRESENTATION_SINGLE_LINE) {
return $result->__toString();
}
return $result->toIndentedString(0);
}
/**
* Read the current token and advance
* @return string|array{0:int,1:string,2:int}
*/
private function getToken()
{
$token = $this->tokens[$this->i++];
if ($token === null) {
throw new RuntimeException("Unexpected end of tokens in $this->raw");
}
return $token;
}
/**
* Read the current token without advancing
* @return string|array{0:int,1:string,2:int}
*/
private function peekToken()
{
$token = $this->tokens[$this->i];
if ($token === null) {
throw new RuntimeException("Unexpected end of tokens in $this->raw");
}
return $token;
}
/**
* Convert a expression representation to the readable representation
*/
protected function encodeValue(): Node
{
$values = [];
while (true) {
$token = $this->peekToken();
if (\is_string($token)) {
if ($token === ',') {
if (!$values) {
throw new RuntimeException("Unexpected token '$token', expected expression in $this->raw at token #$this->i");
}
break;
}
if ($token === ')') {
throw new RuntimeException("Unexpected token '$token', expected expression in $this->raw at token #$this->i");
}
$this->i++;
if ($token === '(') {
return $this->encodeObject(\implode('', $values) . '(');
}
// TODO: Handle `*` in *RECURSION*, `-`, etc
$values[] = $token;
} else {
$this->i++;
// TODO: Handle PHP_INT_MIN as a multi-part expression, strings, etc
switch ($token[0]) {
case \T_DOUBLE_ARROW:
$this->i--;
break 2;
case \T_CONSTANT_ENCAPSED_STRING:
$values[] = $this->encodeString($token[1]);
break;
case \T_ARRAY:
$next = $this->getToken();
if ($next !== '(') {
throw $this->createUnexpectedTokenException("'('", $next);
}
$values[] = $this->encodeArray();
break;
case \T_OBJECT_CAST:
$values[] = $token[1];
$values[] = ' ';
break;
case \T_STRING:
switch ($token[1]) {
case 'NULL';
$values[] = 'null';
break 2;
/*
case 'stdClass':
// $this->encodeLegacyStdClass();
$next = $this->getToken();
if ($next !== T_DOUBLE_COLON) {
throw $this->createUnexpectedTokenException("'::'", $next);
}
*/
}
default:
$values[] = $token[1];
}
}
if ($this->i >= $this->endIndex) {
break;
}
}
return Group::fromParts($values);
}
/**
* Unescape a string literal generated by var_export
*/
protected static function unescapeStringRepresentation(string $value): string
{
if ($value === '"\0"') {
return "\0";
}
return \preg_replace('/\\\\([\'\\\\])/', '\1', (string)\substr($value, 1, -1));
}
private const CHAR_LOOKUP = [
"\n" => '\n',
"\t" => '\t',
"\r" => '\r',
'"' => '\"',
'\\' => '\\\\',
'$' => '\$',
];
/**
* Outputs an encoded string representation
*/
protected function encodeString(string $prefix): Group
{
$unescaped_str = self::unescapeStringRepresentation($prefix);
while ($this->i < $this->endIndex && $this->peekToken() === '.') {
$this->i++;
$token = $this->getToken();
if (!\is_array($token) || $token[0] !== \T_CONSTANT_ENCAPSED_STRING) {
throw $this->createUnexpectedTokenException('T_CONSTANT_ENCAPSED_STRING', $token);
}
$unescaped_str .= self::unescapeStringRepresentation($token[1]);
}
if (!\preg_match('/[\\x00-\\x1f\\x7f]/', $unescaped_str)) {
// This does not have '"\0"', so it is already a single quoted string
return new Group([$prefix]);
}
if ($this->unescaped) {
$repr = self::encodeRawStringUnescapedSingleQuoted($unescaped_str);
} else {
$repr = self::encodeRawStringDoubleQuoted($unescaped_str);
}
return new Group([$repr]);
}
/**
* Returns the representation of $raw in a single or double quoted string,
* the way var_representation() would
* @api
*/
public static function encodeRawString(string $raw): string
{
if (!\preg_match('/[\\x00-\\x1f\\x7f]/', $raw)) {
// This does not have '"\0"', so var_export will return a single quoted string
return \var_export($raw, true);
}
return self::encodeRawStringDoubleQuoted($raw);
}
/**
* Returns the representation of $raw in a double quoted string
* @api
*/
public static function encodeRawStringDoubleQuoted(string $raw): string
{
return '"' . \preg_replace_callback(
'/[\\x00-\\x1f\\x7f\\\\"$]/',
/** @param array{0:string} $match */
static function (array $match): string {
$char = $match[0];
return self::CHAR_LOOKUP[$char] ?? \sprintf('\x%02x', \ord($char));
},
$raw
) . '"';
}
/**
* Returns the representation of $raw in an unescaped single quoted string
* (only escaping \\ and \', not escaping other control characters)
* @api
*/
public static function encodeRawStringUnescapedSingleQuoted(string $raw): string
{
return "'" . \preg_replace('/[\'\\\\]/', '\\\0', $raw) . "'";
}
/**
* Encode an array
*/
protected function encodeArray(): Array_
{
$entries = [];
while (true) {
$token = $this->peekToken();
if ($token === ')') {
$this->i++;
break;
}
$key = $this->encodeValue();
$token = $this->getToken();
if (!\is_array($token) || $token[0] !== \T_DOUBLE_ARROW) {
throw $this->createUnexpectedTokenException("'=>'", $token);
}
$value = $this->encodeValue();
$entries[] = new ArrayEntry($key, $value);
$token = $this->getToken();
if ($token !== ',') {
throw $this->createUnexpectedTokenException("','", $token);
}
}
return new Array_($entries);
}
/**
* Throw an exception for an unexpected token
* @param string|array{0:int,1:string,2:int} $token
*/
private function createUnexpectedTokenException(string $expected, $token): RuntimeException
{
return new RuntimeException("Expected $expected but got " . \var_export($token, true) . ' in ' . $this->raw);
}
/**
* Encode an object from a set_state call
*/
protected function encodeObject(string $prefix): Object_
{
$token = $this->getToken();
if (!\is_array($token) || $token[0] !== \T_ARRAY) {
throw $this->createUnexpectedTokenException('T_ARRAY', $token);
}
$token = $this->getToken();
if ($token !== '(') {
throw $this->createUnexpectedTokenException("'('", $token);
}
$array = $this->encodeArray();
$token = $this->getToken();
if ($token !== ')') {
throw $this->createUnexpectedTokenException("')'", $token);
}
return new Object_($prefix, $array, ')');
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace VarRepresentation;
/**
* Represents an expression
*/
abstract class Node
{
/** Convert this to a single line string */
abstract public function __toString(): string;
/**
* Convert this to an indented string
*/
abstract public function toIndentedString(int $depth): string;
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace VarRepresentation\Node;
use VarRepresentation\Node;
/**
* Represents a 'key => value' entry
*/
class ArrayEntry extends Node
{
/** @var Node the key */
public $key;
/** @var Node the value */
public $value;
public function __construct(Node $key, Node $value)
{
$this->key = $key;
$this->value = $value;
}
public function __toString(): string
{
return $this->key->__toString() . ' => ' . $this->value->__toString();
}
public function toIndentedString(int $depth): string
{
return $this->key->__toString() . ' => ' . $this->value->toIndentedString($depth);
}
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace VarRepresentation\Node;
use VarRepresentation\Node;
/**
* Represents an array literal
*/
class Array_ extends Node
{
/** @var list<ArrayEntry> the list of nodes (keys and optional values) in the array */
public $entries;
/** @param list<ArrayEntry> $entries the list of nodes (keys and optional values) in the array */
public function __construct(array $entries)
{
$this->entries = $entries;
}
/**
* If this is a list, returns only the nodes for values.
* If this is not a list, returns the entries with keys and values.
*
* @return list<ArrayEntry>|list<Node>
*/
public function getValuesOrEntries(): array
{
$values = [];
foreach ($this->entries as $i => $entry) {
if ($entry->key->__toString() !== (string)$i) {
// not a list
return $this->entries;
}
$values[] = $entry->value;
}
return $values;
}
public function __toString(): string
{
// TODO check if list
$inner = \implode(', ', $this->getValuesOrEntries());
return '[' . $inner . ']';
}
public function toIndentedString(int $depth): string
{
$parts = $this->getValuesOrEntries();
if (\count($parts) === 0) {
return '[]';
}
$representation = "[\n";
foreach ($parts as $part) {
$representation .= \str_repeat(' ', $depth + 1) . $part->toIndentedString($depth + 1) . ",\n";
}
return $representation . \str_repeat(' ', $depth) . "]";
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace VarRepresentation\Node;
use RuntimeException;
use VarRepresentation\Node;
/**
* A group of 1 or more strings/nodes
*/
class Group extends Node
{
/** @var list<string|Node> the parts, e.g. '-' '1' */
protected $parts;
/**
* @param list<string|node> $parts
*/
public function __construct(array $parts)
{
if (\count($parts) === 0) {
throw new RuntimeException(__METHOD__ . ' passed no parts');
}
$this->parts = $parts;
}
/**
* Create a node or a group from a list of Node|string parts
* @param list<string|Node> $parts
*/
public static function fromParts(array $parts): Node
{
if (\count($parts) === 1 && $parts[0] instanceof Node) {
return $parts[0];
}
return new self($parts);
}
public function toIndentedString(int $depth): string
{
$result = '';
foreach ($this->parts as $part) {
$result .= \is_string($part) ? $part : $part->toIndentedString($depth);
}
return $result;
}
public function __toString(): string
{
return \implode('', $this->parts);
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace VarRepresentation\Node;
use VarRepresentation\Node;
/**
* Represents an object construction.
*/
class Object_ extends Node
{
/** @var string prefix (e.g. ArrayObject::__set_state() */
protected $prefix;
/** @var Array_ inner array */
protected $array;
/** @var string suffix (e.g. ')') */
protected $suffix;
public function __construct(string $prefix, Array_ $array, string $suffix)
{
if ($prefix === 'stdClass::__set_state(') {
$this->prefix = '(object) ';
$this->suffix = '';
} else {
$this->prefix = \PHP_VERSION_ID >= 80200 ? $prefix : '\\' . $prefix;
$this->suffix = $suffix;
}
$this->array = $array;
}
public function __toString(): string
{
if ($this->prefix === 'stdClass::__set_state(') {
return '(object) ' . $this->array;
}
return $this->prefix . $this->array->__toString() . $this->suffix;
}
public function toIndentedString(int $depth): string
{
return $this->prefix . $this->array->toIndentedString($depth) . $this->suffix;
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/**
* The MIT License (MIT)
*
* Copyright (c) 2021 Tyson Andre
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use VarRepresentation\Encoder;
if (!function_exists('var_representation')) {
/**
* Convert a variable to a string in a way that fixes the shortcomings of `var_export()`.
*
* @param mixed $value
* @param int $flags bitmask of flags (VAR_REPRESENTATION_SINGLE_LINE, VAR_REPRESENTATION_UNESCAPED)
* @suppress PhanRedefineFunctionInternal this is a polyfill
*/
function var_representation($value, int $flags = 0): string
{
return Encoder::toVarRepresentation($value, $flags);
}
}
if (!defined('VAR_REPRESENTATION_SINGLE_LINE')) {
define('VAR_REPRESENTATION_SINGLE_LINE', 1);
}
if (!defined('VAR_REPRESENTATION_UNESCAPED')) {
define('VAR_REPRESENTATION_UNESCAPED', 2);
}