src/Phan/Config.php
<?php
declare(strict_types=1);
namespace Phan;
use Phan\Config\Initializer;
use Phan\Library\Paths;
use Phan\Library\StringUtil;
use function array_key_exists;
use function gettype;
use function in_array;
use function is_array;
use function is_bool;
use function is_float;
use function is_int;
use function is_null;
use function is_string;
use const PHP_EOL;
use const PHP_VERSION;
use const STDERR;
/**
* Program configuration.
*
* Many of the settings in this class can be overridden in .phan/config.php.
*
* Some configuration can be overridden on the command line.
* See `./phan -h` for command line usage, or take a
* look at \Phan\CLI.php for more details on CLI usage.
*
* For efficiency, all of these methods are static methods.
* Configuration is fetched frequently, and static methods were much faster than magic __get().
* @phan-file-suppress PhanPluginDescriptionlessCommentOnPublicMethod
*/
class Config
{
/**
* The version of the AST (defined in php-ast) that we're using.
* @see https://github.com/nikic/php-ast#ast-versioning
*/
public const AST_VERSION = 70;
/**
* The minimum AST extension version in the oldest php version supported by Phan.
*/
public const MINIMUM_AST_EXTENSION_VERSION = '1.0.1';
/**
* The version of the Phan plugin system.
* Plugin files that wish to be backwards compatible may check this and
* return different classes based on its existence and
* the results of version_compare.
* PluginV3 will correspond to 2.x.y, PluginV3 will correspond to 3.x.y, etc.
* New features increment minor versions, and bug fixes increment patch versions.
* @suppress PhanUnreferencedPublicClassConstant
*/
public const PHAN_PLUGIN_VERSION = '3.3.0';
/**
* @var string|null
* The root directory of the project. This is used to
* store canonical path names and find project resources
*/
private static $project_root_directory = null;
/**
* Configuration options
*/
private static $configuration = self::DEFAULT_CONFIGURATION;
// The most commonly accessed configs:
/** @var bool replicates Config::getValue('null_casts_as_any_type') */
private static $null_casts_as_any_type = false;
/** @var bool replicates Config::getValue('null_casts_as_array') */
private static $null_casts_as_array = false;
/** @var bool replicates Config::getValue('array_casts_as_null') */
private static $array_casts_as_null = false;
/** @var bool replicates Config::getValue('strict_method_checking') */
private static $strict_method_checking = false;
/** @var bool replicates Config::getValue('strict_param_checking') */
private static $strict_param_checking = false;
/** @var bool replicates Config::getValue('strict_property_checking') */
private static $strict_property_checking = false;
/** @var bool replicates Config::getValue('strict_return_checking') */
private static $strict_return_checking = false;
/** @var bool replicates Config::getValue('strict_object_checking') */
private static $strict_object_checking = false;
/** @var bool replicates Config::getValue('track_references') */
private static $track_references = false;
/** @var bool replicates Config::getValue('backward_compatibility_checks') */
private static $backward_compatibility_checks = false;
/** @var bool replicates Config::getValue('quick_mode') */
private static $quick_mode = false;
// End of the most commonly accessed configs.
/** @var int the 5-digit PHP version id which is closest to matching the PHP_VERSION_ID for the 'target_php_version' string */
private static $closest_target_php_version_id;
/** @var int the 5-digit PHP version id which is closest to matching the PHP_VERSION_ID for the 'minimum_target_php_version' string */
private static $closest_minimum_target_php_version_id;
/**
* This constant contains the default values for Phan's configuration settings.
*
* Both your project's `.phan/config.php` file and the CLI flags used to invoke Phan
* will override these defaults.
*
* NOTE: The line comments for individual configuration settings are written in markdown.
* They are extracted by `\Phan\Config\Initializer` and used in the following places:
*
* 1. The configuration automatically generated by `phan --init`.
* 2. The GitHub Wiki documentation generated by `internal/update_wiki_config_types.php`.
*/
public const DEFAULT_CONFIGURATION = [
// 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'`, `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'`, `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`.
'minimum_target_php_version' => null,
// 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_methods_exist' => true,
// Make the tolerant-php-parser polyfill generate doc comments
// for all types of elements, even if php-ast wouldn't (for an older PHP version)
'polyfill_parse_all_element_doc_comments' => true,
// A list of individual files to include in analysis
// with a path relative to the root directory of the
// project.
'file_list' => [],
// 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' => [],
// For internal use by Phan to quickly check for membership in directory_list.
'__directory_regex' => null,
// Whether to enable debugging output to stderr
'debug_output' => false,
// List of case-insensitive file extensions supported by Phan.
// (e.g. `['php', 'html', 'htm']`)
'analyzed_file_extensions' => ['php'],
// 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' => '',
// 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' => [],
// Enable this to enable checks of require/include statements referring to valid paths.
// The settings `include_paths` and `warn_about_relative_include_statement` affect the checks.
'enable_include_path_checks' => false,
// A list of [include paths](https://secure.php.net/manual/en/ini.core.php#ini.include-path) to check when checking if `require_once`, `include`, etc. are pointing to valid files.
//
// To refer to the directory of the file being analyzed, use `'.'`
// To refer to the project root directory, use \Phan\Config::getProjectRootDirectory()
//
// (E.g. `['.', \Phan\Config::getProjectRootDirectory() . '/src/folder-added-to-include_path']`)
//
// This is ignored if `enable_include_path_checks` is not `true`.
'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.
//
// This is ignored if `enable_include_path_checks` is not `true`.
'warn_about_relative_include_statement' => false,
// 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 well as
// to `exclude_analysis_directory_list`.
'exclude_analysis_directory_list' => [],
// This is set internally by Phan based on exclude_analysis_directory_list
'__exclude_analysis_regex' => null,
// A list of files that will be included in static analysis,
// **to the exclusion of others.**
//
// This typically should not get put in your Phan config file.
// It gets set by `--include-analysis-file-list`.
//
// Use `directory_list` and `file_list` instead to add files
// to be parsed and analyzed, and `exclude_*` to exclude files
// and folders from analysis.
'include_analysis_file_list' => [],
// Backwards Compatibility Checking. This is slow
// and expensive, but you should consider running
// it before upgrading your version of PHP to a
// new version that has backward compatibility
// breaks.
//
// If you are migrating from PHP 5 to PHP 7,
// you should also look into using
// [php7cc (no longer maintained)](https://github.com/sstalle/php7cc)
// and [php7mar](https://github.com/Alexia/php7mar),
// which have different backwards compatibility checks.
//
// If you are still using versions of php older than 5.6,
// `PHP53CompatibilityPlugin` may be worth looking into if you are not running
// syntax checks for php 5.3 through another method such as
// `InvokePHPNativeSyntaxCheckPlugin` (see .phan/plugins/README.md).
'backward_compatibility_checks' => true,
// A set of fully qualified class-names for which
// a call to `parent::__construct()` is required.
'parent_constructor_required' => [],
// If true, this runs a quick version of checks that takes less
// time at the cost of not running as thorough
// of an analysis. You should consider setting this
// to true only when you wish you had more **undiagnosed** issues
// to fix in your code base.
//
// In quick-mode the scanner doesn't rescan a function
// or a method's code block every time a call is seen.
// This means that the problem here won't be detected:
//
// ```php
// <?php
// function test($arg):int {
// return $arg;
// }
// test("abc");
// ```
//
// This would normally generate:
//
// ```
// test.php:3 PhanTypeMismatchReturn Returning type string but test() is declared to return int
// ```
//
// The initial scan of the function's code block has no
// type information for `$arg`. It isn't until we see
// the call and rescan `test()`'s code block that we can
// detect that it is actually returning the passed in
// `string` instead of an `int` as declared.
'quick_mode' => false,
// The maximum recursion depth that can be reached when analyzing the code.
// This setting only takes effect when quick_mode is disabled.
// A higher limit will make the analysis more accurate, but could possibly
// make it harder to track the code bit where a detected issue originates.
// As long as this is kept relatively low, performance is usually not affected
// by changing this setting.
'maximum_recursion_depth' => 2,
// 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 allow contravariance in real parameter types of method overrides
// (Users may enable this if analyzing projects that support only php 7.2+)
//
// See [this note about PHP 7.2's new features](https://secure.php.net/manual/en/migration72.new-features.php#migration72.new-features.param-type-widening).
// This is false by default. (By default, Phan will warn if real parameter types are omitted in an override)
//
// If this is null, this will be inferred from `target_php_version`.
'allow_method_param_type_widening' => null,
// 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 my_method($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' => false,
// 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.
//
// Set to true to enable.
'infer_default_properties_in_construct' => false,
// If enabled, inherit any missing phpdoc for types from
// the parent method if none is provided.
//
// NOTE: This step will only be performed if `analyze_signature_compatibility` is also enabled.
'inherit_phpdoc_types' => true,
// The minimum severity level to report on. This can be
// set to `Issue::SEVERITY_LOW`, `Issue::SEVERITY_NORMAL` or
// `Issue::SEVERITY_CRITICAL`. Setting it to only
// critical issues is a good place to start on a big
// sloppy mature code base.
'minimum_severity' => Issue::SEVERITY_LOW,
// If enabled, missing properties will be created when
// they are first seen. If false, we'll report an
// error message if there is an attempt to write
// to a class property that wasn't explicitly
// defined.
'allow_missing_properties' => false,
// If enabled, 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,
// If enabled, 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, null can be cast to any type and any
// type can be cast to null. Setting this to true
// will cut down on false positives.
'null_casts_as_any_type' => 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' => false,
// 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' => false,
// 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).
'strict_property_checking' => false,
// 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).
'strict_return_checking' => false,
// If enabled, Phan will warn if **any** type of the object expression for a property access
// does not contain that property.
'strict_object_checking' => 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' => false,
// 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,
// 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)
//
// This setting affects the analysis of return statements in the body of the method and the return types passed in by callers.
'prefer_narrowed_phpdoc_return_type' => true,
// 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' => 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' => false,
// 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' => false,
// Set to true in order to attempt to detect variables that could be replaced with constants or literals.
// (i.e. they are declared once (as a constant expression) and never modified)
// This is almost entirely false positives for most coding styles.
//
// This is intended to be used to check for bugs where a variable such as a boolean was declared but is no longer (or was never) modified.
'constant_variable_detection' => false,
// Set to true in order to emit issues such as `PhanUnusedPublicMethodParameter` instead of `PhanUnusedPublicNoOverrideMethodParameter`
// (i.e. assume any non-final non-private method can have overrides).
// This is useful in situations when parsing only a subset of the available files.
'unused_variable_detection_assume_override_exists' => false,
// Set this to true in order to aggressively assume class elements aren't overridden when analyzing uses of classes.
// This is useful for standalone applications which have all code analyzed by Phan.
//
// Currently, this just affects inferring that methods without return statements have type `void`
'assume_no_external_class_overrides' => false,
// 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,
// If true, the dead code detection rig will
// prefer false negatives (not report dead code) to
// false positives (report dead code that is not
// actually dead).
//
// In other words, the graph of references will have
// too many edges rather than too few edges when guesses
// have to be made about what references what.
'dead_code_detection_prefer_false_negative' => true,
// 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) {...}}`
//
// Defaults to true as of Phan 3.0.3.
// This still helps with some edge cases such as assignments in compound conditions.
'simplify_ast' => 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' => false,
// 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,
// If disabled, Phan will not read docblock type
// annotation comments for `@property`.
//
// - When enabled, in addition to inferring existence of magic properties,
// Phan will also warn when writing to `@property-read` and reading from `@property-read`.
// Phan will warn when writing to read-only properties and reading from write-only properties.
//
// Note: `read_type_annotations` must also be enabled.
'read_magic_property_annotations' => true,
// If disabled, Phan will not read docblock type
// annotation comments for `@method`.
//
// Note: `read_type_annotations` must also be enabled.
'read_magic_method_annotations' => true,
// If disabled, Phan will not read docblock type
// annotation comments for `@mixin`.
//
// Note: `read_type_annotations` must also be enabled.
'read_mixin_annotations' => true,
// If disabled, Phan will not read docblock type
// annotation comments (such as for `@return`, `@param`,
// `@var`, `@suppress`, `@deprecated`) and only rely on
// types expressed in code.
'read_type_annotations' => true,
// If enabled, Phan will cache ASTs generated by the polyfill/fallback to disk
// (except when running in the background as a language server/daemon)
//
// ASTs generated by the native AST library (php-ast) are never cached,
// because php-ast is faster than loading and unserializing data from the cache.
//
// Disabling this is faster when the cache won't be reused,
// e.g. if this would be run in a docker image without mounting the cache as a volume.
//
// The cache can be found at `sys_get_tmp_dir() . "/phan-$USERNAME"`.
'cache_polyfill_asts' => 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' => false,
// If enabled (and `warn_about_undocumented_throw_statements` is enabled),
// Phan will warn about function/closure/method invocations that have `@throws`
// that aren't caught or documented in the invoking method.
'warn_about_undocumented_exceptions_thrown_by_invoked_functions' => false,
// Phan will not warn about lack of documentation of `@throws` for any of the configured classes or their subclasses.
// This only matters when `warn_about_undocumented_throw_statements` is true.
// The default is the empty array (Don't suppress any warnings)
//
// (E.g. `['RuntimeException', 'AssertionError', 'TypeError']`)
'exception_classes_with_optional_throws_phpdoc' => [ ],
// 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 ignore issue suppression.
// This is useful for testing the state of your code, but
// unlikely to be useful outside of that.
'disable_suppression' => false,
// Set to true in order to ignore line-based issue suppressions.
// Disabling both line and file-based suppressions is mildly faster.
'disable_line_based_suppression' => false,
// Set to true in order to ignore file-based issue suppressions.
'disable_file_based_suppression' => false,
// If set to true, we'll dump the AST instead of
// analyzing files
'dump_ast' => false,
// If set to a string, we'll dump the fully qualified lowercase
// function and method signatures instead of analyzing files.
'dump_signatures_file' => null,
// If set to true, we'll dump the list of files to parse
// to stdout instead of parsing and analyzing files.
'dump_parsed_file_list' => false,
// Include a progress bar in the output.
'progress_bar' => false,
// When true, use a different version of the progress bar
// that's suitable for Continuous Integration logs.
'__long_progress_bar' => false,
// If this much time (in seconds) has passed since the last update,
// then update the progress bar.
'progress_bar_sample_interval' => 0.1,
// The number of processes to fork off during the analysis
// phase.
'processes' => 1,
// Set to true to emit profiling data on how long various
// parts of Phan took to run. You likely don't care to do
// this.
'profiler_enabled' => false,
// Phan will give up on suggesting a different name in issue messages
// if the number of candidates (for a given suggestion category) is greater than `suggestion_check_limit`.
//
// Set this to `0` to disable most suggestions for similar names, and only suggest identical names in other namespaces.
// Set this to `PHP_INT_MAX` (or other large value) to always suggest similar names and identical names in other namespaces.
//
// Phan will be a bit slower when this config setting is large.
// A lower value such as 50 works for suggesting misspelled classes/constants in namespaces,
// but won't give you suggestions for globally namespaced functions.
'suggestion_check_limit' => 1000,
// Set this to true to disable suggestions for what to use instead of undeclared variables/classes/etc.
'disable_suggestions' => false,
// Add any issue types (such as `'PhanUndeclaredMethod'`)
// to this list to inhibit them from being reported.
'suppress_issue_types' => [
// 'PhanUndeclaredMethod',
],
// 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 custom list of additional superglobals and their types. **Only needed by projects using runkit/runkit7.**
//
// (Corresponding keys are declared in `runkit.superglobal` ini directive)
//
// `globals_type_map` should be set for setting the types of these superglobals.
// E.g `['_FOO']`;
'runkit_superglobals' => [],
// Override to hardcode existence and types of (non-builtin) globals in the global scope.
// Class names should be prefixed with `\`.
//
// (E.g. `['_FOO' => '\FooClass', 'page' => '\PageClass', 'userId' => 'int']`)
'globals_type_map' => [],
// Enable this to emit issue messages with markdown formatting.
'markdown_issue_messages' => false,
// Enable this with `--absolute-path-issue-messages` to use absolute paths in issue messages
'absolute_path_issue_messages' => false,
// If true, then hide the issue's column in plaintext and pylint output printers.
// Note that phan only knows the column for a tiny subset of issues.
'hide_issue_column' => false,
// Enable this to automatically use colorized phan output for the 'text' output format if the terminal supports it.
// Alternately, set PHAN_ENABLE_COLOR_OUTPUT=1.
// This config setting can be overridden with NO_COLOR=1 or PHAN_DISABLE_COLOR_OUTPUT=1.
'color_issue_messages_if_supported' => false,
// Emit colorized issue messages for the 'text' output mode (false by default with the 'text' output mode to supported terminals).
// NOTE: it is strongly recommended to enable this via other methods,
// since this is incompatible with most output formatters.
//
// This can be enabled by setting PHAN_ENABLE_COLOR_OUTPUT=1 or passing `--color` or by setting `color_issue_messages_if_supported`
'color_issue_messages' => null,
// In `--output-mode=verbose`, refuse to print lines of context that exceed this limit.
'max_verbose_snippet_length' => 1000,
// Allow overriding color scheme in `.phan/config.php` for printing issues, for individual types.
//
// See the keys of `Phan\Output\Colorizing::STYLES` for valid color names,
// and the keys of `Phan\Output\Colorizing::DEFAULT_COLOR_FOR_TEMPLATE` for valid color names.
//
// E.g. to change the color for the file (of an issue instance) to red, set this to `['FILE' => 'red']`
//
// E.g. to use the terminal's default color for the line (of an issue instance), set this to `['LINE' => 'none']`
'color_scheme' => [],
// Enable or disable support for generic templated
// class types.
'generic_types_enabled' => true,
// Assign files to be analyzed on random processes
// in random order. You very likely don't want to
// set this to true. This is meant for debugging
// and fuzz testing purposes only.
'randomize_file_order' => false,
// 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 [this note in Phan's wiki](https://github.com/phan/phan/wiki/Different-Issue-Sets-On-Different-Numbers-of-CPUs).
'consistent_hashing_file_order' => false,
// Set by `--print-memory-usage-summary`. Prints a memory usage summary to stderr after analysis.
'print_memory_usage_summary' => false,
// 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,
// By default, Phan will warn if the 'tokenizer' module isn't installed and enabled.
'skip_missing_tokenizer_warning' => false,
// This is the maximum frame length for crash reports
'debug_max_frame_length' => 1000,
// You can put paths to stubs of internal extensions in this config option.
// If the corresponding extension is **not** loaded, then Phan will use the stubs instead.
// 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 `tools/make_stubs` script can be used to generate your own stubs (compatible with php 7.0+ right now)
//
// (e.g. `['xdebug' => '.phan/internal_stubs/xdebug.phan_php']`)
'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.
'included_extension_subset' => null,
// 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' => true,
// If a file to be analyzed can't be parsed,
// then use a slower PHP substitute for php-ast to try to parse the files.
// This setting is ignored if a file is excluded from analysis.
//
// NOTE: it is strongly recommended to enable this via the `--use-fallback-parser` CLI flag instead,
// since this may result in strange error messages for invalid files (e.g. if parsed but not analyzed).
'use_fallback_parser' => false,
// Use the polyfill parser based on tolerant-php-parser instead of the possibly missing native implementation
//
// NOTE: This makes parsing several times slower than the native implementation.
//
// NOTE: it is strongly recommended to enable this via the `--use-polyfill-parser` or `--force-polyfill-parser`
// since this may result in strange error messages for invalid files (e.g. if parsed but not analyzed).
'use_polyfill_parser' => false,
// Keep a reference to the original tolerant-php-parser node in the generated php-ast Node.
// This is extremely memory intensive, and only recommended if a Phan plugin is used for code reformatting, style checks, etc.
'__parser_keep_original_node' => false,
// Path to a Unix socket for a daemon to listen to files to analyze. Use command line option instead.
'daemonize_socket' => false,
// If a daemon should listen to files to analyze over TCP.
// This setting is mutually exclusive with `daemonize_socket`.
'daemonize_tcp' => false,
// TCP host for a daemon to listen to files to analyze.
'daemonize_tcp_host' => '127.0.0.1',
// TCP port (from 1024 to 65535) for a daemon to listen to files to analyze.
'daemonize_tcp_port' => 4846,
// If this is an array, it configures the way clients will communicate with the Phan language server.
// Possibilities: Exactly one of
//
// 1. `['stdin' => true]`
// 2. `['tcp-server' => string (address this server should listen on)]`
// 3. `['tcp' => string (address client is listening on)]`
'language_server_config' => false,
// Valid values: false, true. Should only be set via CLI (`--language-server-analyze-only-on-save`)
'language_server_analyze_only_on_save' => false,
// Valid values: null, 'info'. Used when developing or debugging a language server client of Phan.
'language_server_debug_level' => null,
// Set this to true to emit all issues detected from the language server (e.g. invalid phpdoc in parsed files),
// not just issues in files currently open in the editor/IDE.
// This can be very verbose and has more false positives.
'language_server_disable_output_filter' => false,
// This should only be set by CLI (`--language-server-force-missing-pcntl` or `language-server-require-pcntl`), which will set this to true for debugging.
// When true, this will manually back up the state of the PHP process and restore it.
'language_server_use_pcntl_fallback' => false,
// This should only be set via CLI (`--language-server-disable-go-to-definition` to disable)
// Affects "go to definition" and "go to type definition" of LSP.
'language_server_enable_go_to_definition' => true,
// This should only be set via CLI (`--language-server-disable-hover` to disable)
// Affects "hover" of LSP.
'language_server_enable_hover' => true,
// This should only be set via CLI (`--language-server-disable-completion` to disable)
// Affects "completion" of LSP.
'language_server_enable_completion' => true,
// Don't show the category name in issue messages.
// This makes error messages slightly shorter.
// Use `--language-server-hide-category` if you want to enable this.
'language_server_hide_category_of_issues' => false,
// Should be configured by --language-server-min-diagnostic-delay-ms.
// Use this for language clients that have race conditions processing diagnostics.
// Max value is 1000 ms.
'language_server_min_diagnostics_delay_ms' => 0,
// Set this to false to disable the plugins that Phan uses to infer more accurate return types of `array_map`, `array_filter`, and many other functions.
//
// Phan is slightly faster when these are disabled.
'enable_internal_return_type_plugins' => 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' => false,
// Set this to true to make Phan store a full Context inside variables, instead of a FileRef. This could provide more useful info to plugins,
// but will increase the memory usage by roughly 2.5%.
'record_variable_context_and_scope' => false,
// If a literal string type exceeds this length,
// then Phan converts it to a regular string type.
// This setting cannot be less than 50.
//
// This setting can be overridden if users wish to store strings that are even longer than 50 bytes.
'max_literal_string_type_length' => 200,
// internal
'dump_matching_functions' => false,
// This is the path to a file containing a list of pre-existing issues to ignore, on a per-file basis.
// It's recommended to set this with `--load-baseline=path/to/baseline.php`.
// A baseline file can be created or updated with `--save-baseline=path/to/baseline.php`.
'baseline_path' => null,
// For internal use only.
'__save_baseline_path' => null,
// This is the type of summary comment that will be generated when `--save-baseline=path/to/baseline.php` is used.
// Supported values: 'ordered_by_count' (default), 'ordered_by_type', 'none'.
// (The first type makes it easier to see uncommon issues when reading the code but is more prone to merge conflicts in version control)
// (Does not affect analysis)
'baseline_summary_type' => 'ordered_by_count',
// A list of plugin files to execute.
//
// Plugins which are bundled with Phan can be added here by providing their name (e.g. `'AlwaysReturnPlugin'`)
//
// Documentation about available bundled plugins can be found [here](https://github.com/phan/phan/tree/master/.phan/plugins).
//
// Alternately, you can pass in the full path to a PHP file with the plugin's implementation (e.g. `'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php'`)
'plugins' => [
],
// This can be used by third-party plugins that expect configuration.
//
// E.g. this is used by `InvokePHPNativeSyntaxCheckPlugin`
'plugin_config' => [
],
// This should only be set with `--analyze-twice`.
'__analyze_twice' => false,
// This should only be set with `--always-exit-successfully-after-analysis`
'__always_exit_successfully_after_analysis' => false,
];
public const COMPLETION_VSCODE = 'vscode';
/**
* Disallow the constructor.
*/
private function __construct()
{
}
/**
* @return string
* Get the root directory of the project that we're
* scanning
* @suppress PhanPossiblyFalseTypeReturn getcwd() can technically be false, but we should have checked earlier
*/
public static function getProjectRootDirectory(): string
{
return self::$project_root_directory ?? \getcwd();
}
/**
* @param string $project_root_directory
* Set the root directory of the project that we're
* scanning
*/
public static function setProjectRootDirectory(
string $project_root_directory
): void {
self::$project_root_directory = $project_root_directory;
}
/**
* Initializes the configuration used for analysis.
*
* This is automatically called with the defaults, to set any derived configuration and static properties as side effects.
*/
public static function init(): void
{
static $did_init = false;
if ($did_init) {
return;
}
$did_init = true;
self::initOnce();
}
private static function initOnce(): void
{
// Trigger magic setters
foreach (self::$configuration as $name => $v) {
self::setValue($name, $v);
}
}
/**
* @return array<string,mixed>
* A map of configuration keys and their values
*
* @suppress PhanUnreferencedPublicMethod useful for plugins, testing, etc.
*/
public static function toArray(): array
{
return self::$configuration;
}
// method naming is deliberate to make these getters easier to search.
// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
/**
* Allow null to be cast as any type and for any
* type to be cast to null.
*/
public static function get_null_casts_as_any_type(): bool
{
return self::$null_casts_as_any_type;
}
/**
* If enabled, Phan will warn if **any** type in a method's object expression
* is definitely not an object,
* or if **any** type in an invoked expression is not a callable.
*/
public static function get_strict_method_checking(): bool
{
return self::$strict_method_checking;
}
/**
* If enabled, Phan will warn if **any** type in the argument's type
* cannot be cast to a type in the parameter's expected type.
*/
public static function get_strict_param_checking(): bool
{
return self::$strict_param_checking;
}
/**
* If enabled, Phan will warn if **any** type in a property assignment's type
* cannot be cast to a type in the property's expected type.
*/
public static function get_strict_property_checking(): bool
{
return self::$strict_property_checking;
}
/**
* If enabled, Phan will warn if **any** type in the return statement's union type
* cannot be cast to a type in the method's declared return type.
*/
public static function get_strict_return_checking(): bool
{
return self::$strict_return_checking;
}
/**
* If enabled, Phan will warn if **any** type in the object expression for a property
* does not contain that property.
*/
public static function get_strict_object_checking(): bool
{
return self::$strict_object_checking;
}
/** If enabled, allow null to cast to any array-like type. */
public static function get_null_casts_as_array(): bool
{
return self::$null_casts_as_array;
}
/** If enabled, allow any array-like type to be cast to null. */
public static function get_array_casts_as_null(): bool
{
return self::$array_casts_as_null;
}
/** If true, then Phan tracks references to elements */
public static function get_track_references(): bool
{
return self::$track_references;
}
/** If true, then Phan enables backwards compatibility checking. */
public static function get_backward_compatibility_checks(): bool
{
return self::$backward_compatibility_checks;
}
/**
* If true, then Phan runs a quick version of checks that takes less
* time at the cost of not running as thorough
* of an analysis.
*/
public static function get_quick_mode(): bool
{
return self::$quick_mode;
}
/** @return int the 5-digit PHP version id which is closest to matching the PHP_VERSION_ID for the 'target_php_version' string */
public static function get_closest_target_php_version_id(): int
{
return self::$closest_target_php_version_id;
}
/** @return int the 5-digit PHP version id which is closest to matching the PHP_VERSION_ID for the 'minimum_target_php_version' string */
public static function get_closest_minimum_target_php_version_id(): int
{
return self::$closest_minimum_target_php_version_id;
}
// phpcs:enable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
/**
* @return mixed
* @phan-hardcode-return-type
*/
public static function getValue(string $name)
{
return self::$configuration[$name];
}
/**
* Resets the configuration to the initial state, prior to parsing config files and CLI arguments.
* @internal - this should only be used in unit tests.
*/
public static function reset(): void
{
self::$configuration = self::DEFAULT_CONFIGURATION;
// Trigger magic behavior
self::initOnce();
}
/**
* @param string $name
* @param mixed $value
*/
public static function setValue(string $name, $value): void
{
self::$configuration[$name] = $value;
switch ($name) {
case 'ignore_undeclared_functions_with_known_signatures':
case 'included_extension_subset':
if (is_array(self::$configuration['included_extension_subset'])) {
self::$configuration['ignore_undeclared_functions_with_known_signatures'] = false;
}
break;
case 'null_casts_as_any_type':
self::$null_casts_as_any_type = $value;
break;
case 'null_casts_as_array':
self::$null_casts_as_array = $value;
break;
case 'array_casts_as_null':
self::$array_casts_as_null = $value;
break;
case 'strict_method_checking':
self::$strict_method_checking = $value;
break;
case 'strict_param_checking':
self::$strict_param_checking = $value;
break;
case 'strict_property_checking':
self::$strict_property_checking = $value;
break;
case 'strict_return_checking':
self::$strict_return_checking = $value;
break;
case 'strict_object_checking':
self::$strict_object_checking = $value;
break;
case 'dead_code_detection':
case 'force_tracking_references':
self::$track_references = self::getValue('dead_code_detection') || self::getValue('force_tracking_references');
break;
case 'backward_compatibility_checks':
self::$backward_compatibility_checks = $value;
break;
case 'quick_mode':
self::$quick_mode = $value;
break;
case 'allow_method_param_type_widening':
self::$configuration['allow_method_param_type_widening_original'] = $value;
if ($value === null) {
// If this setting is set to null, infer it based on the closest php version id.
self::$configuration[$name] = self::$closest_minimum_target_php_version_id >= 70200;
}
break;
case 'target_php_version':
case 'minimum_target_php_version':
self::$configuration[$name] = $value;
self::updateClosestTargetPHPVersion();
break;
case 'exclude_analysis_directory_list':
self::$configuration['__exclude_analysis_regex'] = self::generateDirectoryListRegex($value);
break;
case 'directory_list':
self::$configuration['__directory_regex'] = self::generateDirectoryListRegex($value);
break;
case 'scalar_implicit_partial':
self::$configuration[$name] = self::normalizeScalarImplicitPartial($value);
break;
}
}
private const TRUTHY_SCALAR_EQUIVALENTS = [
'int' => 'non-zero-int',
'string' => 'non-empty-string',
];
private static function updateClosestTargetPHPVersion(): void
{
$value = self::$configuration['target_php_version'];
if (is_int($value) || is_float($value)) {
$value = \sprintf("%.1f", $value);
}
// @phan-suppress-next-line PhanSuspiciousTruthyString, PhanSuspiciousTruthyCondition
$value = (string) ($value ?: PHP_VERSION);
if (\strtolower($value) === 'native') {
$value = PHP_VERSION;
}
self::$closest_target_php_version_id = self::computeClosestTargetPHPVersionId($value);
$min_value = self::$configuration['minimum_target_php_version'];
// @phan-suppress-next-line PhanPartialTypeMismatchArgument
$min_value_id = StringUtil::isNonZeroLengthString($min_value) ? self::computeClosestTargetPHPVersionId($min_value) : null;
// @phan-suppress-next-line PhanSuspiciousTruthyString, PhanSuspiciousTruthyCondition
if (!$min_value_id) {
$min_value_id = self::determineMinimumPHPVersionFromComposer() ?? $min_value_id;
}
if (!$min_value_id) {
$min_value_id = self::computeClosestTargetPHPVersionId(PHP_VERSION);
}
self::$closest_minimum_target_php_version_id = (int) \min(self::$closest_target_php_version_id, $min_value_id);
if (!isset(self::$configuration['allow_method_param_type_widening_original'])) {
self::$configuration['allow_method_param_type_widening'] = self::$closest_minimum_target_php_version_id >= 70200;
}
}
/**
* Guess minimum_target_php_version based on composer.json supported versions
*/
private static function determineMinimumPHPVersionFromComposer(): ?int
{
$settings = self::readComposerSettings();
[$version, $_] = Initializer::determineTargetPHPVersion($settings);
if (is_string($version)) {
return self::computeClosestTargetPHPVersionId($version);
}
return null;
}
/**
* Read the composer settings if this phan project is a composer project.
*
* @return array<string,mixed>
*/
private static function readComposerSettings(): array
{
static $contents = null;
if (is_array($contents)) {
return $contents;
}
$path_to_composer_json = \getcwd() . "/composer.json";
if (!\file_exists($path_to_composer_json)) {
return $contents = [];
}
// @phan-suppress-next-line PhanPossiblyFalseTypeArgumentInternal
$composer_json_contents = @\file_get_contents($path_to_composer_json);
if (!is_string($composer_json_contents)) {
return $contents = [];
}
$library_composer_settings = @\json_decode($composer_json_contents, true);
if (!is_array($library_composer_settings)) {
CLI::printWarningToStderr("Saw invalid composer.json file contents when reading project settings: " . \json_last_error_msg() . "\n");
return $contents = [];
}
return $library_composer_settings;
}
/**
* If int can cast to/from T, where T is possibly not falsey,
* then allow non-zero-int to cast to/from T.
*
* @param array<string,list<string>> $value
* @return array<string,list<string>>
* @suppress PhanPluginCanUseParamType
*/
private static function normalizeScalarImplicitPartial($value): array
{
if (!is_array($value)) {
return [];
}
foreach (self::TRUTHY_SCALAR_EQUIVALENTS as $scalar => $non_falsey_scalar) {
if (isset($value[$scalar]) && !isset($value[$non_falsey_scalar])) {
$value[$non_falsey_scalar] = \array_values(\array_filter(
$value[$scalar],
static function (string $type): bool {
return !in_array($type, ['null', 'false'], true);
}
));
}
}
foreach ($value as $key => &$allowed_casts) {
if (in_array($key, ['null', 'false'], true)) {
continue;
}
foreach (self::TRUTHY_SCALAR_EQUIVALENTS as $scalar => $non_falsey_scalar) {
if (in_array($scalar, $allowed_casts, true) && !in_array($non_falsey_scalar, $allowed_casts, true)) {
$allowed_casts[] = $non_falsey_scalar;
}
}
}
return $value;
}
/**
* @param string[] $value
*/
private static function generateDirectoryListRegex(array $value): ?string
{
if (!$value) {
return null;
}
$parts = \array_map(static function (string $path): string {
$path = \str_replace('\\', '/', $path); // Normalize \\ to / in configs
$path = \rtrim($path, '\//'); // remove trailing / from directory
$path = \preg_replace('@^(\./)+@', '', $path); // Remove any number of leading ./ sections
return \preg_quote($path, '@'); // Quote this
}, $value);
return '@^(\./)*(' . \implode('|', $parts) . ')([/\\\\]|$)@';
}
private static function computeClosestTargetPHPVersionId(string $version): int
{
if (\version_compare($version, '6.0') < 0) {
return 50600;
} elseif (\version_compare($version, '7.1') < 0) {
return 70000;
} elseif (\version_compare($version, '7.2') < 0) {
return 70100;
} elseif (\version_compare($version, '7.3') < 0) {
return 70200;
} elseif (\version_compare($version, '7.4') < 0) {
return 70300;
} elseif (\version_compare($version, '8.0') < 0) {
return 70400;
} else {
return 80000;
}
}
/**
* @return string
* The relative path appended to the project root directory. (i.e. the absolute path)
*
* @suppress PhanUnreferencedPublicMethod
* @see FileRef::getProjectRelativePathForPath() for converting to relative paths
* NOTE: This deliberately does not support phar:// URLs, because those evaluate php code when the phar is first loaded.
*/
public static function projectPath(string $relative_path): string
{
return Paths::toAbsolutePath(self::getProjectRootDirectory(), $relative_path);
}
/**
* @param mixed $value
*/
private static function errSuffixGotType($value): string
{
return ", but got type '" . gettype($value) . "'";
}
/**
* @param array<string,mixed> $configuration
* @return list<string> a list of 0 or more error messages for invalid config settings
*/
public static function getConfigErrors(array $configuration): array
{
/**
* @param mixed $value
*/
$is_scalar = static function ($value): ?string {
if (is_null($value) || \is_scalar($value)) {
return null;
}
return 'Expected a scalar' . self::errSuffixGotType($value);
};
/**
* @param mixed $value
*/
$is_bool = static function ($value): ?string {
if (is_bool($value)) {
return null;
}
return 'Expected a boolean' . self::errSuffixGotType($value);
};
/**
* @param mixed $value
*/
$is_bool_or_null = static function ($value): ?string {
if (is_bool($value) || is_null($value)) {
return null;
}
return 'Expected a boolean' . self::errSuffixGotType($value);
};
/**
* @param mixed $value
*/
$is_string_or_null = static function ($value): ?string {
if (is_null($value) || is_string($value)) {
return null;
}
return 'Expected a string' . self::errSuffixGotType($value);
};
/**
* @param mixed $value
*/
$is_string = static function ($value): ?string {
if (is_string($value)) {
return null;
}
return 'Expected a string' . self::errSuffixGotType($value);
};
/**
* @param mixed $value
*/
$is_array = static function ($value): ?string {
if (is_array($value)) {
return null;
}
return 'Expected an array' . self::errSuffixGotType($value);
};
/**
* @param mixed $value
*/
$is_int_strict = static function ($value): ?string {
if (is_int($value)) {
return null;
}
return 'Expected an integer' . self::errSuffixGotType($value);
};
/**
* @param mixed $value
*/
$is_string_list = static function ($value): ?string {
if (!is_array($value)) {
return 'Expected a list of strings' . self::errSuffixGotType($value);
}
foreach ($value as $i => $element) {
if (!is_string($element)) {
return "Expected a list of strings: index $i is type '" . gettype($element) . "'";
}
}
return null;
};
/**
* @param mixed $value
*/
$is_string_list_or_null = static function ($value): ?string {
if (is_null($value)) {
return null;
}
if (!is_array($value)) {
return 'Expected null or a list of strings' . self::errSuffixGotType($value);
}
foreach ($value as $i => $element) {
if (!is_string($element)) {
return "Expected null or a list of strings: index $i is type '" . gettype($element) . "'";
}
}
return null;
};
/**
* @param mixed $value
*/
$is_associative_string_array = static function ($value): ?string {
if (!is_array($value)) {
return 'Expected an associative array mapping strings to strings' . self::errSuffixGotType($value);
}
foreach ($value as $i => $element) {
if (!is_string($element)) {
return "Expected an associative array mapping strings to strings: index $i is '" . gettype($element) . "'";
}
}
return null;
};
$config_checks = [
'absolute_path_issue_messages' => $is_bool,
'allow_method_param_type_widening' => $is_bool_or_null,
'allow_missing_properties' => $is_bool,
'analyzed_file_extensions' => $is_string_list,
'analyze_signature_compatibility' => $is_bool,
'array_casts_as_null' => $is_bool,
'autoload_internal_extension_signatures' => $is_associative_string_array,
'included_extension_subset' => $is_string_list_or_null,
'backward_compatibility_checks' => $is_bool,
'baseline_path' => $is_string_or_null,
'baseline_summary_type' => $is_string,
'cache_polyfill_asts' => $is_bool,
'check_docblock_signature_param_type_match' => $is_bool,
'check_docblock_signature_return_type_match' => $is_bool,
'color_issue_messages' => $is_bool_or_null,
'color_scheme' => $is_associative_string_array,
'consistent_hashing_file_order' => $is_bool,
'daemonize_socket' => $is_scalar,
'daemonize_tcp_host' => $is_string,
'daemonize_tcp' => $is_bool,
'daemonize_tcp_port' => $is_int_strict,
'dead_code_detection' => $is_bool,
'dead_code_detection_prefer_false_negative' => $is_bool,
'directory_list' => $is_string_list,
'disable_line_based_suppression' => $is_bool,
'disable_suggestions' => $is_bool,
'disable_suppression' => $is_bool,
'dump_ast' => $is_bool,
'dump_matching_functions' => $is_bool,
'dump_parsed_file_list' => $is_bool,
'dump_signatures_file' => $is_string_or_null,
'enable_class_alias_support' => $is_bool,
'enable_include_path_checks' => $is_bool,
'enable_internal_return_type_plugins' => $is_bool,
'exception_classes_with_optional_throws_phpdoc' => $is_string_list,
'exclude_analysis_directory_list' => $is_string_list,
'exclude_file_list' => $is_string_list,
'exclude_file_regex' => $is_string_or_null,
'file_list' => $is_string_list,
'force_tracking_references' => $is_bool,
'generic_types_enabled' => $is_bool,
'globals_type_map' => $is_associative_string_array,
'guess_unknown_parameter_type_using_default' => $is_bool,
'hide_issue_column' => $is_bool,
'infer_default_properties_in_construct' => $is_bool,
'ignore_undeclared_functions_with_known_signatures' => $is_bool,
'ignore_undeclared_variables_in_global_scope' => $is_bool,
'include_analysis_file_list' => $is_string_list,
'include_paths' => $is_string_list,
'inherit_phpdoc_types' => $is_bool,
'language_server_analyze_only_on_save' => $is_bool,
// 'language_server_config' => array|false, // should not be set directly
'language_server_debug_level' => $is_string_or_null,
'language_server_disable_output_filter' => $is_bool,
'language_server_enable_completion' => $is_scalar,
'language_server_enable_go_to_definition' => $is_bool,
'language_server_enable_hover' => $is_bool,
'language_server_hide_category_of_issues' => $is_bool,
'language_server_use_pcntl_fallback' => $is_bool,
'long_progress_bar' => $is_bool,
'markdown_issue_messages' => $is_bool,
'max_literal_string_type_length' => $is_int_strict,
'max_verbose_snippet_length' => $is_int_strict,
'minimum_severity' => $is_int_strict,
'null_casts_as_any_type' => $is_bool,
'null_casts_as_array' => $is_bool,
'parent_constructor_required' => $is_string_list,
'phpdoc_type_mapping' => $is_associative_string_array,
'plugin_config' => $is_array,
'plugins' => $is_string_list,
'polyfill_parse_all_element_doc_comments' => $is_bool,
'prefer_narrowed_phpdoc_param_type' => $is_bool,
'prefer_narrowed_phpdoc_return_type' => $is_bool,
'pretend_newer_core_methods_exist' => $is_bool,
'print_memory_usage_summary' => $is_bool,
'processes' => $is_int_strict,
'profiler_enabled' => $is_bool,
'progress_bar' => $is_bool,
'progress_bar_sample_interval' => $is_scalar,
'quick_mode' => $is_bool,
'randomize_file_order' => $is_bool,
'read_magic_method_annotations' => $is_bool,
'read_magic_property_annotations' => $is_bool,
'read_type_annotations' => $is_bool,
'runkit_superglobals' => $is_string_list,
'scalar_array_key_cast' => $is_bool,
'scalar_implicit_cast' => $is_bool,
'scalar_implicit_partial' => $is_array,
'simplify_ast' => $is_bool,
'skip_missing_tokenizer_warning' => $is_bool,
'skip_slow_php_options_warning' => $is_bool,
'strict_method_checking' => $is_bool,
'strict_param_checking' => $is_bool,
'strict_property_checking' => $is_bool,
'strict_return_checking' => $is_bool,
'strict_object_checking' => $is_bool,
'suggestion_check_limit' => $is_int_strict,
'suppress_issue_types' => $is_string_list,
'target_php_version' => $is_scalar,
'unused_variable_detection' => $is_bool,
'redundant_condition_detection' => $is_bool,
'assume_real_types_for_internal_functions' => $is_bool,
'use_fallback_parser' => $is_bool,
'use_polyfill_parser' => $is_bool,
'warn_about_redundant_use_namespaced_class' => $is_bool,
'warn_about_relative_include_statement' => $is_bool,
'warn_about_undocumented_exceptions_thrown_by_invoked_functions' => $is_bool,
'warn_about_undocumented_throw_statements' => $is_bool,
'whitelist_issue_types' => $is_string_list,
];
$result = [];
foreach ($config_checks as $config_name => $check_closure) {
if (!array_key_exists($config_name, $configuration)) {
continue;
}
$value = $configuration[$config_name];
$error = $check_closure($value);
if (StringUtil::isNonZeroLengthString($error)) {
$result[] = "Invalid config value for '$config_name': $error";
}
}
return $result;
}
/**
* Prints errors to stderr if any config options are definitely invalid.
*/
public static function warnIfInvalid(): void
{
$errors = self::getConfigErrors(self::$configuration);
foreach ($errors as $error) {
// @phan-suppress-next-line PhanPluginRemoveDebugCall
\fwrite(STDERR, $error . PHP_EOL);
}
}
/**
* Check if the issue fixing plugin (from --automatic-fix) is enabled.
*/
public static function isIssueFixingPluginEnabled(): bool
{
return \in_array(__DIR__ . '/Plugin/Internal/IssueFixingPlugin.php', Config::getValue('plugins'), true);
}
/**
* Fetches the value of language_server_min_diagnostics_delay_ms, constrained to 0..1000ms
*/
public static function getMinDiagnosticsDelayMs(): float
{
$delay = Config::getValue('language_server_min_diagnostics_delay_ms');
if (\is_numeric($delay) && $delay > 0) {
return \min((float)$delay, 1000);
}
return 0;
}
}
// Call init() to trigger the magic setters.
Config::init();