Dep: update

주로 PHAN
This commit is contained in:
2021-08-06 22:39:09 +09:00
parent 5310c2e7f6
commit b306601c72
1098 changed files with 92137 additions and 33228 deletions
@@ -0,0 +1,2 @@
/vendor/
.phpunit*
@@ -0,0 +1,604 @@
<?php
declare(strict_types=1);
use Phan\Issue;
/**
* This configuration will be read and overlaid on top of the
* default configuration. Command line arguments will be applied
* after this file is read.
*
* @see https://github.com/phan/phan/wiki/Phan-Config-Settings for all configurable options
* @see src/Phan/Config.php for the configurable options in this version of Phan
*
* A Note About Paths
* ==================
*
* Files referenced from this file should be defined as
*
* ```
* Config::projectPath('relative_path/to/file')
* ```
*
* where the relative path is relative to the root of the
* project which is defined as either the working directory
* of the phan executable or a path passed in via the CLI
* '-d' flag.
*/
return [
// The PHP version that the codebase will be checked for compatibility against.
// For best results, the PHP binary used to run Phan should have the same PHP version.
// (Phan relies on Reflection for some types, param counts,
// and checks for undefined classes/methods/functions)
//
// Supported values: `'5.6'`, `'7.0'`, `'7.1'`, `'7.2'`, `'7.3'`, `'7.4'`,
// `'8.0'`, `'8.1'`, `null`.
// If this is set to `null`,
// then Phan assumes the PHP version which is closest to the minor version
// of the php executable used to execute Phan.
//
// Note that the **only** effect of choosing `'5.6'` is to infer that functions removed in php 7.0 exist.
// (See `backward_compatibility_checks` for additional options)
'target_php_version' => null,
// The PHP version that will be used for feature/syntax compatibility warnings.
// Supported values: `'5.6'`, `'7.0'`, `'7.1'`, `'7.2'`, `'7.3'`, `'7.4'`,
// `'8.0'`, `'8.1'`, `null`.
// If this is set to `null`, Phan will first attempt to infer the value from
// the project's composer.json's `{"require": {"php": "version range"}}` if possible.
// If that could not be determined, then Phan assumes `target_php_version`.
//
// For analyzing Phan 3.x, this is determined to be `'7.2'` from `"version": "^7.2.0"`.
'minimum_target_php_version' => '7.2',
// Default: true. If this is set to true,
// and target_php_version is newer than the version used to run Phan,
// Phan will act as though functions added in newer PHP versions exist.
//
// NOTE: Currently, this only affects Closure::fromCallable
'pretend_newer_core_functions_exist' => true,
// If true, missing properties will be created when
// they are first seen. If false, we'll report an
// error message.
'allow_missing_properties' => false,
// Allow null to be cast as any type and for any
// type to be cast to null.
'null_casts_as_any_type' => false,
// Allow null to be cast as any array-like type
// This is an incremental step in migrating away from null_casts_as_any_type.
// If null_casts_as_any_type is true, this has no effect.
'null_casts_as_array' => false,
// Allow any array-like type to be cast to null.
// This is an incremental step in migrating away from null_casts_as_any_type.
// If null_casts_as_any_type is true, this has no effect.
'array_casts_as_null' => false,
// If enabled, Phan will warn if **any** type in a method invocation's object
// is definitely not an object,
// or if **any** type in an invoked expression is not a callable.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
'strict_method_checking' => true,
// If enabled, Phan will warn if **any** type in the argument's union type
// cannot be cast to a type in the parameter's expected union type.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
'strict_param_checking' => true,
// If enabled, Phan will warn if **any** type in a property assignment's union type
// cannot be cast to a type in the property's declared union type.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
// (For self-analysis, Phan has a large number of suppressions and file-level suppressions, due to \ast\Node being difficult to type check)
'strict_property_checking' => true,
// If enabled, Phan will warn if **any** type in a returned value's union type
// cannot be cast to the declared return type.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
// (For self-analysis, Phan has a large number of suppressions and file-level suppressions, due to \ast\Node being difficult to type check)
'strict_return_checking' => true,
// If enabled, Phan will warn if **any** type of the object expression for a property access
// does not contain that property.
'strict_object_checking' => true,
// If enabled, scalars (int, float, bool, string, null)
// are treated as if they can cast to each other.
// This does not affect checks of array keys. See `scalar_array_key_cast`.
'scalar_implicit_cast' => false,
// If enabled, any scalar array keys (int, string)
// are treated as if they can cast to each other.
// E.g. `array<int,stdClass>` can cast to `array<string,stdClass>` and vice versa.
// Normally, a scalar type such as int could only cast to/from int and mixed.
'scalar_array_key_cast' => false,
// If this has entries, scalars (int, float, bool, string, null)
// are allowed to perform the casts listed.
//
// E.g. `['int' => ['float', 'string'], 'float' => ['int'], 'string' => ['int'], 'null' => ['string']]`
// allows casting null to a string, but not vice versa.
// (subset of `scalar_implicit_cast`)
'scalar_implicit_partial' => [],
// If true, Phan will convert the type of a possibly undefined array offset to the nullable, defined equivalent.
// If false, Phan will convert the type of a possibly undefined array offset to the defined equivalent (without converting to nullable).
'convert_possibly_undefined_offset_to_nullable' => false,
// If true, seemingly undeclared variables in the global
// scope will be ignored.
//
// This is useful for projects with complicated cross-file
// globals that you have no hope of fixing.
'ignore_undeclared_variables_in_global_scope' => false,
// Backwards Compatibility Checking (This is very slow)
'backward_compatibility_checks' => false,
// If true, check to make sure the return type declared
// in the doc-block (if any) matches the return type
// declared in the method signature.
'check_docblock_signature_return_type_match' => true,
// If true, check to make sure the param types declared
// in the doc-block (if any) matches the param types
// declared in the method signature.
'check_docblock_signature_param_type_match' => true,
// If true, make narrowed types from phpdoc params override
// the real types from the signature, when real types exist.
// (E.g. allows specifying desired lists of subclasses,
// or to indicate a preference for non-nullable types over nullable types)
//
// Affects analysis of the body of the method and the param types passed in by callers.
//
// (*Requires `check_docblock_signature_param_type_match` to be true*)
'prefer_narrowed_phpdoc_param_type' => true,
// (*Requires `check_docblock_signature_return_type_match` to be true*)
//
// If true, make narrowed types from phpdoc returns override
// the real types from the signature, when real types exist.
//
// (E.g. allows specifying desired lists of subclasses,
// or to indicate a preference for non-nullable types over nullable types)
// Affects analysis of return statements in the body of the method and the return types passed in by callers.
'prefer_narrowed_phpdoc_return_type' => true,
// If enabled, check all methods that override a
// parent method to make sure its signature is
// compatible with the parent's. This check
// can add quite a bit of time to the analysis.
// This will also check if final methods are overridden, etc.
'analyze_signature_compatibility' => true,
// Set this to true to make Phan guess that undocumented parameter types
// (for optional parameters) have the same type as default values
// (Instead of combining that type with `mixed`).
// E.g. `function($x = 'val')` would make Phan infer that $x had a type of `string`, not `string|mixed`.
// Phan will not assume it knows specific types if the default value is false or null.
'guess_unknown_parameter_type_using_default' => false,
// Allow adding types to vague return types such as @return object, @return ?mixed in function/method/closure union types.
// Normally, Phan only adds inferred returned types when there is no `@return` type or real return type signature..
// This setting can be disabled on individual methods by adding `@phan-hardcode-return-type` to the doc comment.
//
// Disabled by default. This is more useful with `--analyze-twice`.
'allow_overriding_vague_return_types' => true,
// When enabled, infer that the types of the properties of `$this` are equal to their default values at the start of `__construct()`.
// This will have some false positives due to Phan not checking for setters and initializing helpers.
// This does not affect inherited properties.
'infer_default_properties_in_construct' => true,
// Set this to true to enable the plugins that Phan uses to infer more accurate return types of `implode`, `json_decode`, and many other functions.
//
// Phan is slightly faster when these are disabled.
'enable_extended_internal_return_type_plugins' => true,
// This setting maps case-insensitive strings to union types.
//
// This is useful if a project uses phpdoc that differs from the phpdoc2 standard.
//
// If the corresponding value is the empty string,
// then Phan will ignore that union type (E.g. can ignore 'the' in `@return the value`)
//
// If the corresponding value is not empty,
// then Phan will act as though it saw the corresponding UnionTypes(s)
// when the keys show up in a UnionType of `@param`, `@return`, `@var`, `@property`, etc.
//
// This matches the **entire string**, not parts of the string.
// (E.g. `@return the|null` will still look for a class with the name `the`, but `@return the` will be ignored with the below setting)
//
// (These are not aliases, this setting is ignored outside of doc comments).
// (Phan does not check if classes with these names exist)
//
// Example setting: `['unknown' => '', 'number' => 'int|float', 'char' => 'string', 'long' => 'int', 'the' => '']`
'phpdoc_type_mapping' => [ ],
// Set to true in order to attempt to detect dead
// (unreferenced) code. Keep in mind that the
// results will only be a guess given that classes,
// properties, constants and methods can be referenced
// as variables (like `$class->$property` or
// `$class->$method()`) in ways that we're unable
// to make sense of.
//
// To more aggressively detect dead code,
// you may want to set `dead_code_detection_prefer_false_negative` to `false`.
'dead_code_detection' => false,
// Set to true in order to attempt to detect unused variables.
// `dead_code_detection` will also enable unused variable detection.
//
// This has a few known false positives, e.g. for loops or branches.
'unused_variable_detection' => true,
// Set to true in order to force tracking references to elements
// (functions/methods/consts/protected).
// dead_code_detection is another option which also causes references
// to be tracked.
'force_tracking_references' => false,
// Set to true in order to attempt to detect redundant and impossible conditions.
//
// This has some false positives involving loops,
// variables set in branches of loops, and global variables.
'redundant_condition_detection' => true,
// Set to true in order to attempt to detect error-prone truthiness/falsiness checks.
//
// This is not suitable for all codebases.
'error_prone_truthy_condition_detection' => true,
// Enable this to warn about harmless redundant use for classes and namespaces such as `use Foo\bar` in namespace Foo.
//
// Note: This does not affect warnings about redundant uses in the global namespace.
'warn_about_redundant_use_namespaced_class' => true,
// If true, then run a quick version of checks that takes less time.
// False by default.
'quick_mode' => false,
// If true, then before analysis, try to simplify AST into a form
// which improves Phan's type inference in edge cases.
//
// This may conflict with 'dead_code_detection'.
// When this is true, this slows down analysis slightly.
//
// E.g. rewrites `if ($a = value() && $a > 0) {...}`
// into $a = value(); if ($a) { if ($a > 0) {...}}`
'simplify_ast' => true,
// If true, Phan will read `class_alias` calls in the global scope,
// then (1) create aliases from the *parsed* files if no class definition was found,
// and (2) emit issues in the global scope if the source or target class is invalid.
// (If there are multiple possible valid original classes for an aliased class name,
// the one which will be created is unspecified.)
// NOTE: THIS IS EXPERIMENTAL, and the implementation may change.
'enable_class_alias_support' => false,
// Enable or disable support for generic templated
// class types.
'generic_types_enabled' => true,
// If enabled, warn about throw statement where the exception types
// are not documented in the PHPDoc of functions, methods, and closures.
'warn_about_undocumented_throw_statements' => true,
// If enabled (and warn_about_undocumented_throw_statements is enabled),
// warn about function/closure/method calls that have (at)throws
// without the invoking method documenting that exception.
'warn_about_undocumented_exceptions_thrown_by_invoked_functions' => true,
// If this is a list, Phan will not warn about lack of documentation of (at)throws
// for any of the listed classes or their subclasses.
// This setting only matters when warn_about_undocumented_throw_statements is true.
// The default is the empty array (Warn about every kind of Throwable)
'exception_classes_with_optional_throws_phpdoc' => [
'RuntimeException',
'InvalidArgumentException',
'PHPUnit\Framework\ExpectationFailedException',
'SebastianBergmann\RecursionContext\InvalidArgumentException',
],
// Increase this to properly analyze require_once statements
'max_literal_string_type_length' => 1000,
// Setting this to true makes the process assignment for file analysis
// as predictable as possible, using consistent hashing.
// Even if files are added or removed, or process counts change,
// relatively few files will move to a different group.
// (use when the number of files is much larger than the process count)
// NOTE: If you rely on Phan parsing files/directories in the order
// that they were provided in this config, don't use this)
// See https://github.com/phan/phan/wiki/Different-Issue-Sets-On-Different-Numbers-of-CPUs
'consistent_hashing_file_order' => false,
// If enabled, Phan will act as though it's certain of real return types of a subset of internal functions,
// even if those return types aren't available in reflection (real types were taken from php 7.3 or 8.0-dev, depending on target_php_version).
//
// Note that with php 7 and earlier, php would return null or false for many internal functions if the argument types or counts were incorrect.
// As a result, enabling this setting with target_php_version 8.0 may result in false positives for `--redundant-condition-detection` when codebases also support php 7.x.
'assume_real_types_for_internal_functions' => true,
// Override to hardcode existence and types of (non-builtin) globals.
// Class names should be prefixed with '\\'.
// (E.g. ['_FOO' => '\\FooClass', 'page' => '\\PageClass', 'userId' => 'int'])
'globals_type_map' => [],
// The minimum severity level to report on. This can be
// set to Issue::SEVERITY_LOW, Issue::SEVERITY_NORMAL or
// Issue::SEVERITY_CRITICAL.
'minimum_severity' => Issue::SEVERITY_LOW,
// Add any issue types (such as `'PhanUndeclaredMethod'`)
// to this list to inhibit them from being reported.
'suppress_issue_types' => [
],
// If this list is empty, no filter against issues types will be applied.
// If this list is non-empty, only issues within the list
// will be emitted by Phan.
//
// See https://github.com/phan/phan/wiki/Issue-Types-Caught-by-Phan
// for the full list of issues that Phan detects.
//
// Phan is capable of detecting hundreds of types of issues.
// Projects should almost always use `suppress_issue_types` instead.
'whitelist_issue_types' => [
// 'PhanUndeclaredClass',
],
// A list of files to include in analysis
'file_list' => [
],
// A regular expression to match files to be excluded
// from parsing and analysis and will not be read at all.
//
// This is useful for excluding groups of test or example
// directories/files, unanalyzable files, or files that
// can't be removed for whatever reason.
// (e.g. '@Test\.php$@', or '@vendor/.*/(tests|Tests)/@')
'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@',
// Enable this to enable checks of require/include statements referring to valid paths.
'enable_include_path_checks' => true,
// A list of include paths to check when checking if `require_once`, `include`, etc. are valid.
//
// To refer to the directory of the file being analyzed, use `'.'`
// To refer to the project root directory, you must use \Phan\Config::getProjectRootDirectory()
//
// (E.g. `['.', \Phan\Config::getProjectRootDirectory() . '/src/folder-added-to-include_path']`)
'include_paths' => ['.'],
// Enable this to warn about the use of relative paths in `require_once`, `include`, etc.
// Relative paths are harder to reason about, and opcache may have issues with relative paths in edge cases.
'warn_about_relative_include_statement' => true,
// A list of files that will be excluded from parsing and analysis
// and will not be read at all.
//
// This is useful for excluding hopelessly unanalyzable
// files that can't be removed for whatever reason.
'exclude_file_list' => [
],
// The number of processes to fork off during the analysis
// phase.
'processes' => 1,
// A list of directories that should be parsed for class and
// method information. After excluding the directories
// defined in exclude_analysis_directory_list, the remaining
// files will be statically analyzed for errors.
//
// Thus, both first-party and third-party code being used by
// your application should be included in this list.
'directory_list' => [
'src',
//'tests/',
//'vendor/phpunit/phpunit/src',
],
// List of case-insensitive file extensions supported by Phan.
// (e.g. php, html, htm)
'analyzed_file_extensions' => ['php'],
// A directory list that defines files that will be excluded
// from static analysis, but whose class and method
// information should be included.
//
// Generally, you'll want to include the directories for
// third-party code (such as 'vendor/') in this list.
//
// n.b.: If you'd like to parse but not analyze 3rd
// party code, directories containing that code
// should be added to the `directory_list` as
// to `exclude_analysis_directory_list`.
'exclude_analysis_directory_list' => [
'vendor/'
],
// By default, Phan will log error messages to stdout if PHP is using options that slow the analysis.
// (e.g. PHP is compiled with `--enable-debug` or when using Xdebug)
'skip_slow_php_options_warning' => false,
// You can put paths to internal stubs in this config option.
// Phan will continue using its detailed type annotations, but load the constants, classes, functions, and classes (and their Reflection types) from these stub files (doubling as valid php files).
// Use a different extension from php to avoid accidentally loading these.
// The 'tool/mkstubs' script can be used to generate your own stubs (compatible with php 7.2+ right now)
//
// Also see `include_extension_subset` to configure Phan to analyze a codebase as if a certain extension is not available.
'autoload_internal_extension_signatures' => [
],
// This can be set to a list of extensions to limit Phan to using the reflection information of.
// If this is a list, then Phan will not use the reflection information of extensions outside of this list.
// The extensions loaded for a given php installation can be seen with `php -m` or `get_loaded_extensions(true)`.
//
// Note that this will only prevent Phan from loading reflection information for extensions outside of this set.
// If you want to add stubs, see `autoload_internal_extension_signatures`.
//
// If this is used, 'core', 'date', 'pcre', 'reflection', 'spl', and 'standard' will be automatically added.
//
// When this is an array, `ignore_undeclared_functions_with_known_signatures` will always be set to false.
// (because many of those functions will be outside of the configured list)
//
// Also see `ignore_undeclared_functions_with_known_signatures` to warn about using unknown functions.
// E.g. this is what Phan would use for self-analysis
/*
'included_extension_subset' => [
'core',
'standard',
'filter',
'json',
'tokenizer', // parsing php code
'ast', // parsing php code
'ctype', // misc uses, also polyfilled
'dom', // checkstyle output format
'iconv', // symfony mbstring polyfill
'igbinary', // serializing/unserializing polyfilled ASTs
'libxml', // internal tools for extracting stubs
'mbstring', // utf-8 support
'pcntl', // daemon/language server and parallel analysis
'phar', // packaging
'posix', // parallel analysis
'readline', // internal debugging utility, rarely used
'simplexml', // report generation
'sysvmsg', // parallelism
'sysvsem',
'sysvshm',
],
*/
// Set this to false to emit `PhanUndeclaredFunction` issues for internal functions that Phan has signatures for,
// but aren't available in the codebase, or from Reflection.
// (may lead to false positives if an extension isn't loaded)
//
// If this is true(default), then Phan will not warn.
//
// Even when this is false, Phan will still infer return values and check parameters of internal functions
// if Phan has the signatures.
'ignore_undeclared_functions_with_known_signatures' => false,
'plugin_config' => [
// A list of 1 or more PHP binaries (Absolute path or program name found in $PATH)
// to use to analyze your files with PHP's native `--syntax-check`.
//
// This can be used to simultaneously run PHP's syntax checks with multiple PHP versions.
// e.g. `'plugin_config' => ['php_native_syntax_check_binaries' => ['php72', 'php70', 'php56']]`
// if all of those programs can be found in $PATH
// 'php_native_syntax_check_binaries' => [PHP_BINARY],
// The maximum number of `php --syntax-check` processes to run at any point in time (Minimum: 1).
// This may be temporarily higher if php_native_syntax_check_binaries has more elements than this process count.
'php_native_syntax_check_max_processes' => 4,
// List of methods to suppress warnings about for HasPHPDocPlugin
'has_phpdoc_method_ignore_regex' => '@^Phan\\\\Tests\\\\.*::(test.*|.*Provider)$@',
// Warn about duplicate descriptions for methods and property groups within classes.
// (This skips over deprecated methods)
// This may not apply to all code bases,
// but is useful in avoiding copied and pasted descriptions that may be inapplicable or too vague.
'has_phpdoc_check_duplicates' => true,
// If true, then never allow empty statement lists, even if there is a TODO/FIXME/"deliberately empty" comment.
'empty_statement_list_ignore_todos' => true,
// Automatically infer which methods are pure (i.e. should have no side effects) in UseReturnValuePlugin.
'infer_pure_methods' => true,
// Warn if newline is allowed before end of string for `$` (the default unless the `D` modifier (`PCRE_DOLLAR_ENDONLY`) is passed in).
// This is specific to coding styles.
'regex_warn_if_newline_allowed_at_end' => true,
],
// A list of plugin files to execute
// NOTE: values can be the base name without the extension for plugins bundled with Phan (E.g. 'AlwaysReturnPlugin')
// or relative/absolute paths to the plugin (Relative to the project root).
'plugins' => [
'AlwaysReturnPlugin', // i.e. '.phan/plugin/AlwaysReturnPlugin.php' in phan itself
'DollarDollarPlugin',
'UnreachableCodePlugin',
'DuplicateArrayKeyPlugin',
'PregRegexCheckerPlugin',
'PrintfCheckerPlugin',
'PHPUnitAssertionPlugin', // analyze assertSame/assertInstanceof/assertTrue/assertFalse
'UseReturnValuePlugin',
// UnknownElementTypePlugin warns about unknown types in element signatures.
'UnknownElementTypePlugin',
'DuplicateExpressionPlugin',
// warns about carriage returns("\r"), trailing whitespace, and tabs in PHP files.
'WhitespacePlugin',
// Warn about inline HTML anywhere in the files.
'InlineHTMLPlugin',
////////////////////////////////////////////////////////////////////////
// Plugins for Phan's self-analysis
////////////////////////////////////////////////////////////////////////
// Warns about the usage of assert() for Phan's self-analysis. See https://github.com/phan/phan/issues/288
'NoAssertPlugin',
'PossiblyStaticMethodPlugin',
'HasPHPDocPlugin',
'PHPDocToRealTypesPlugin', // suggests replacing (at)return void with `: void` in the declaration, etc.
'PHPDocRedundantPlugin',
'PreferNamespaceUsePlugin',
'EmptyStatementListPlugin',
// Report empty (not overridden or overriding) methods and functions
// 'EmptyMethodAndFunctionPlugin',
// Warn about using the same loop variable name as a loop variable of an outer loop.
'LoopVariableReusePlugin',
// Warn about assigning the value the variable already had to that variable.
'RedundantAssignmentPlugin',
// These are specific to Phan's coding style
'StrictComparisonPlugin',
// Warn about `$var == SOME_INT_OR_STRING_CONST` due to unintuitive behavior such as `0 == 'a'`
'StrictLiteralComparisonPlugin',
'ShortArrayPlugin',
'SimplifyExpressionPlugin',
// 'UnknownClassElementAccessPlugin' is more useful with batch analysis than in an editor.
// It's used in tests/run_test __FakeSelfFallbackTest
// This checks that there are no accidental echos/printfs left inside Phan's code.
'RemoveDebugStatementPlugin',
'UnsafeCodePlugin',
'DeprecateAliasPlugin',
'NotFullyQualifiedUsagePlugin',
// Still have false positives to suppress
// '.phan/plugins/StaticVariableMisusePlugin.php',
////////////////////////////////////////////////////////////////////////
// End plugins for Phan's self-analysis
////////////////////////////////////////////////////////////////////////
// 'SleepCheckerPlugin' is useful for projects which heavily use the __sleep() method. Phan doesn't use __sleep().
// InvokePHPNativeSyntaxCheckPlugin invokes 'php --no-php-ini --syntax-check ${abs_path_to_analyzed_file}.php' and reports any error messages.
// Using this can cause phan's overall analysis time to more than double.
// 'InvokePHPNativeSyntaxCheckPlugin',
// 'PHPUnitNotDeadCodePlugin', // Marks PHPUnit test case subclasses and test cases as referenced code. This is only useful for runs when dead code detection is enabled.
// 'PHPDocInWrongCommentPlugin', // Useful to warn about using "/*" instead of ""/**" where phpdoc annotations are used. This is slow due to needing to tokenize files.
// NOTE: This plugin only produces correct results when
// Phan is run on a single core (-j1).
// 'UnusedSuppressionPlugin',
],
];
+21
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.
+36
View File
@@ -0,0 +1,36 @@
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)
[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 [var_representation](https://github.com/TysonAndre/var_representation) documentation 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"
```
@@ -0,0 +1,17 @@
ARG PHP_VERSION
FROM php:$PHP_VERSION
WORKDIR /code
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN apt-get update && \
apt-get install -y zlib1g-dev libzip-dev && \
docker-php-ext-configure zip && \
docker-php-ext-install -j$(nproc) zip
RUN pecl install ast && \
docker-php-ext-enable ast
ADD composer.json composer.lock ./
RUN composer install --prefer-dist
ADD src ./src
ADD .phan ./.phan
ADD tests ./tests
ADD phpunit.xml ./
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
if [ $# != 1 ]; then
echo "Usage: $0 PHP_VERSION" 1>&2
echo "e.g. $0 8.0" 1>&2
echo "The PHP_VERSION is the version of the php docker image to use" 1>&2
exit 1
fi
# -x Exit immediately if any command fails
# -e Echo all commands being executed.
# -u fail for undefined variables
set -xeu
PHP_VERSION=$1
DOCKER_IMAGE=igbinary-$PHP_VERSION-test-runner
docker build --build-arg="PHP_VERSION=$PHP_VERSION" --tag="$DOCKER_IMAGE" -f ci/Dockerfile .
docker run --rm $DOCKER_IMAGE vendor/bin/phpunit
docker run --rm $DOCKER_IMAGE vendor/bin/phan
@@ -0,0 +1,33 @@
{
"name": "tysonandre/var_representation_polyfill",
"description": "Polyfill for var_representation",
"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": "^4.0",
"phpunit/phpunit": "^8.5.0"
},
"autoload": {
"psr-4": {"VarRepresentation\\": "src/VarRepresentation"},
"files": ["src/var_representation.php"]
},
"autoload-dev": {
"psr-4": {"VarRepresentation\\Tests\\": "tests/"}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,303 @@
<?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;
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
{
$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_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-\xff]/', $unescaped_str)) {
// This does not have '"\0"', so it is already a single quoted string
return new Group([$prefix]);
}
return new Group([self::encodeRawStringDoubleQuoted($unescaped_str)]);
}
/**
* 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-\xff]/', $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-\xff\\\\"$]/',
/** @param array{0:string} $match */
static function (array $match): string {
$char = $match[0];
return self::CHAR_LOOKUP[$char] ?? \sprintf('\x%02x', \ord($char));
},
$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, ')');
}
}
@@ -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;
}
@@ -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);
}
}
@@ -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) . "]";
}
}
@@ -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);
}
}
@@ -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 = '\\' . $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;
}
}
@@ -0,0 +1,45 @@
<?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)
*/
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);
}